資料結構-對映

小墨魚3發表於2020-01-31

對映(Map)

對映是什麼?

如果使用白話來說的話, 就是更具特定唯一的資訊來找到對應的實體。
比如說我們查字典, 要查詢的字能找到對應的解釋。 比如我們的有身份證id就能查到對應的人資訊。
比如我們有快遞id就能知道當前快遞在什麼位置
等等, 都是通過對映的關係來進行實現。

對映結構特點

  • 以K,V鍵值對儲存的資料
  • 根據K能快速的尋找到V
對映介面的設計

我們優先建立一個父類介面, 並設計兩個子類一個是連結串列來實現, 一個是二分搜尋樹來實現。


public interface Map<K, V> {
    // 新增資料
    void add(K key, V value);
    // 刪除資料
    V remove(K key);
    // 判斷K是否存在
    boolean contains(K key);
    // 更具K獲取value
    V get(K key);
    // 設定新的value
    void set(K key, V newValue);
    int getSize();
    boolean isEmpty();
}
複製程式碼

基於連結串列實現對映資料結構

學習集合的時候, 我們也使用了連結串列進行集合的設計, 但是在設計對映物件的時候可能就無法滿足了, 畢竟要K和V一對。 所以我們需要重新構建一下Node物件。並重新設計add和remove等方法。



public class LinkedListMap<K, V> implements Map<K, V> {

    // 之前寫的連結串列只有一個元素是無法滿足map的結構, 所以重新定義一下Node
    private class Node {
        public K key;
        public V value;
        public Node next;

        public Node() {
            this(null, null, null);
        }

        public Node(K key, V value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
            return key + " : " + value;
        }
    }


    private int size ;
    private Node dummyHead ; // 虛擬頭結點

    public LinkedListMap() {
        dummyHead = new Node();
        size = 0;
    }

    /**
     * 更具K獲取到具體的Node物件
     * @param key
     * @return
     */
    private Node getNode(K key) {
        Node cur = dummyHead.next; // 實際的頭節點
        while (cur != null) {
            if (cur.key.equals(key)) {
                return cur ;
            }

            cur = cur.next;
        }

        return null;
    }

    @Override
    public void add(K key, V value) {
        Node cur = getNode(key);
        if (cur == null) {
            dummyHead.next = new Node(key, value, dummyHead.next);
            size ++ ;
        } else {
            // 已經存在了, 這裡的話不丟擲異常但是給個提示吧
            System.out.println("新增的key="+key+"已存在, 給你更新啦!");
            cur.value = value;
        }
    }

    @Override
    public void set(K key, V newValue) {
        Node cur = getNode(key);
        if (cur == null) {
            throw new IllegalArgumentException("當前key="+key+"不存在, 請檢查是否拼寫錯誤");
        }

        cur.value = newValue;
    }

    @Override
    public V remove(K key) {

        Node prev = dummyHead.next;
        while (prev.next != null) {
            if (prev.next.key.equals(key)) {
                break;
            }

            prev = prev.next;
        }

        if (prev.next != null) {
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
            size -- ;
            return delNode.value;
        }

        return null;
    }

    @Override
    public boolean contains(K key) {
        return getNode(key) != null;
    }

    @Override
    public V get(K key) {
        Node cur = getNode(key);
        return cur == null ? null : cur.value;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }
}
複製程式碼

接下來測試一下我們基於連結串列的對映資料結構把。


public static void main(String[] args) {
        String[] words = {"A", "B", "C", "D", "E", "A", "A", "A", "B", "B", "C", "C", "F", "F", "F", "K",};
        Map<String, Integer> map = new LinkedListMap<>();
        for (String word : words) {
            if (map.contains(word)) {
                Integer v = map.get(word) + 1;
                map.set(word, v);
            } else {
                map.add(word, 1);
            }
        }

        System.out.println("總共: " + map.getSize());

        System.out.println(map.get("A"));
        System.out.println(map.get("B"));
        System.out.println(map.get("C"));
        System.out.println(map.get("D"));
        System.out.println(map.get("E"));
        System.out.println(map.get("F"));
        System.out.println(map.get("K"));
}
複製程式碼

基於二分搜尋樹實現對映資料結構

利用二分搜尋樹的話, 之前寫的也是不能使用的。所以我們需要重新構建一個Node。



public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {


    private class Node {
        public K key ;
        public V value ;
        public Node left ;
        public Node right ;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.left = null;
            this.right = null;
        }
    }

    private Node root ;
    private int size ;

    public BSTMap() {
        this.root = null;
        this.size = 0;
    }

    private Node getNode(Node node , K key) {
        if (node == null)
            return null;

        if (node.key.compareTo(key) == 0)
            return node;
        else if (node.key.compareTo(key) < 0)
            return getNode(node.left, key);
        else
            return getNode(node.right, key);
    }

    @Override
    public void add(K key, V value) {
        root = add(root, key, value);
    }

    private Node add(Node node, K key, V value) {
        if (node == null) {
            Node n = new Node(key, value);
            size ++ ;
            return n;
        }

        if (node.key.compareTo(key) > 0) {
            node.right = add(node.right, key, value);
        } else if (node.key.compareTo(key) < 0) {
            node.left = add(node.left, key, value);
        } else {
            // 如果存在key則更新資料
            node.value = value;
        }

        return node;
    }


    private Node minimum(Node node) {
        if (node.left == null)
            return node ;
        return minimum(node.left);
    }

    private Node removeMin(Node node) {
        if (node.left == null) {
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    private Node remove(Node node, K key) {

        if (node.key.compareTo(key) == 0) {
            if(node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 第二種情況: 刪除只有右孩子的節點(簡單)
            if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            /***
             * 待刪除節點左右子樹都不為空的情況
             */
            // 1. 找到比待刪除節點大的最小節點, 即待刪除節點右子樹中最小的節點(找到後繼節點)
            // 2. 用後繼節點頂替待刪除節點的位置
            Node succeed = minimum(node.right);
            // 返回刪除最小值後的一個新樹, 最小值已經被我們記錄住了, 然後設定左右子樹
            succeed.right = removeMin(node.right);
            succeed.left = node.left;
            // 這裡之所以沒有size--, 是因為removeMin方法已經做了
            node.left = node.right = null;
            return succeed;
        } else if (node.key.compareTo(key) > 0) {
            node.right = remove(node.right, key);
            return node;
        } else {
            node.left = remove(node.left, key);
            return node;
        }
    }

    @Override
    public V remove(K key) {

        Node node = getNode(root, key);
        if (node != null) {
            root = remove(root, key);
            return node.value;
        }

        return null;
    }

    @Override
    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null)
            throw new IllegalArgumentException("key=" + key + " 不存在, 更新失敗!");
        node.value = newValue;
    }

    @Override
    public boolean contains(K key) {
        return getNode(root, key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }
}
複製程式碼

測試一下


public static void main(String[] args) {
        String[] words = {"A", "B", "C", "D", "E", "A", "A", "A", "B", "B", "C", "C", "F", "F", "F", "K",};
        Map<String, Integer> map = new BSTMap<>();

        for (String word : words) {
            if (map.contains(word)) {
                Integer v = map.get(word) + 1;
                map.set(word, v);
            } else {
                map.add(word, 1);
            }
        }

        System.out.println("總共: " + map.getSize());

        System.out.println(map.get("A"));
        System.out.println(map.get("B"));
        System.out.println(map.get("C"));
        System.out.println(map.get("D"));
        System.out.println(map.get("E"));
        System.out.println(map.get("F"));
        System.out.println(map.get("K"));

        Integer v = map.remove("A");
        System.out.println("A " + v);
        System.out.println(map.get("A"));
        System.out.println(map.get("K"));
}
複製程式碼

avatar

相關文章