映射

映射

是一种可以存储(键、值)数据对的数据结构(key、value),键跟值一一对应,通过键来寻找或删除对应的值,分为有序映射 (基于搜索树的实现) 和无序映射 (基于哈希表和链表的实现) ,有序即映射中的键具有顺序性。通常无序映射是基于哈希表 (HashMap) 来实现的,因为基于链表实现的映射的性能太差。

映射代码实现

首先,我们先定义一个映射需要用到的接口方法,因为映射的底层实现可以是多样的,只要实现了接口方法的,都可以称为映射。

映射接口定义

public interface Map<K, V> {

    /**
     * 添加一个键值对,如果 key 存在,则直接更新值
     */
    void add(K key, V value);

    /**
     * 根据键来移除该键值对
     */
    V remove(K key);

    /**
     * 是否有包含该键的键值对
     */
    boolean contains(K key);

    /**
     * 获取 key 对应的 value
     */
    V get(K key);

    /**
     * 修该键值对
     */
    void set(K key, V newValue);

    /**
     * 返回键值对的数量
     */
    int getSize();

    /**
     * 映射是否为空
     */
    boolean isEmpty();
}

底层为二分搜索树的映射

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

    private Node root;
    private int size;

    // 向二分搜索树中添加新的元素(key, value)
    @Override
    public void add(K key, V value) {
        root = add(root, key, value);
    }

    /**
     * 向以node为根的二分搜索树中插入元素(key, value),递归算法
     * 返回插入新节点后二分搜索树的根
     */
    private Node add(Node node, K key, V value) {
        if (node == null) {
            size++;
            // 不存在,添加一个节点
            return new Node(key, value);
        }
        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 {
            // 存在,更新值
            node.value = value;
        }

        return node;
    }

    @Override
    public V remove(K key) {
        Node node = getNode(root, key);
        if (node == null) {
            // 节点不存在,返回 null
            return null;
        } else {
            root = remove(root, key);
            return node.value;
        }
    }

    private Node remove(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.key) < 0) {
            node.left = remove(node.left, key);
        } else if (key.compareTo(node.key) > 0) {
            node.right = remove(node.right, key);
        } else {
            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;
            }

            Node successor = minimum(node.right);
            successor.left = node.left;
            successor.right = removeMin(node.right);
            node.left = node.right = null;

            return successor;

        }
        return node;
    }

    /**
     * 向以 node 为根的树中删除最小元素
     * 返回删除节点后的树的根
     */
    private Node removeMin(Node node) {
        // 找到要删除的节点
        if (node.left == null) {
            // 找到该节点的右节点(不用管是否为null)
            Node retNode = node.right;
            // 将要删除的节点的右节点置为 null
            node.right = null;
            // 维护数量
            size--;
            // 返回右节点
            return retNode;
        }

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

    /**
     * 向以 node 为根的树中找最小元素并返回
     */
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    /**
     * 向以 node 为根的树中查询 key ,递归算法
     */
    private boolean contains(Node node, K key) {
        if (node == null) {
            return false;
        }

        if (key.compareTo(node.key) < 0) {
            return contains(node.left, key);
        } else if (key.compareTo(node.key) > 0) {
            return contains(node.right, key);
        } else {
            return true;
        }
    }

    @Override
    public boolean contains(K key) {
//        if (key == null) {
//            return false;
//        } else {
//            return contains(root, key);
//        }

        return getNode(root, key) != null;

    }

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

    /**
     * 向以 node 为根的树中查询 key 对应的节点,递归算法
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.compareTo(node.key) < 0) {
            return getNode(node.left, key);
        } else if (key.compareTo(node.key) > 0) {
            return getNode(node.right, key);
        } else {
            return node;
        }
    }

    @Override
    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null) {
            // 不存在该键值对,无法修改,抛出异常
            throw new IllegalArgumentException(key + " does not exist!");
        } else {
            // 存在键值对,直接更新值
            node.value = newValue;
        }
    }

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

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

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

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

}

底层为链表的映射

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

    // 虚拟头节点
    private Node dummyHead;

    private int size;

    public LinkedListMap() {
        dummyHead = new Node(null, null);
    }

    @Override
    public void add(K key, V value) {
        Node node = getNode(key);
        // 不存在该键值对
        if (node == null) {
            // 在链表头添加一个节点
            dummyHead.next = new Node(key, value, dummyHead.next);
            size++;
        } else {
            // 存在键值对,直接更新值
            node.value = value;
        }
    }

    @Override
    public V remove(K key) {
        Node prevNode = dummyHead;
        while (prevNode.next != null) {
            Node delNode = prevNode.next;
            if (delNode.key.equals(key)) {
                prevNode.next = delNode.next;
                delNode.next = null;
                size--;
                return delNode.value;
            } else {
                prevNode = prevNode.next;
            }
        }
        return null;
    }

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

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

    /**
     * 获取当前 KEY 对应的 Node
     */
    private Node getNode(K key) {
        Node curNode = dummyHead.next;
        while (curNode != null) {
            if (curNode.key.equals(key)) {
                return curNode;
            }
            curNode = curNode.next;
        }
        return null;
    }

    @Override
    public void set(K key, V newValue) {
        Node curNode = getNode(key);
        if (curNode == null) {
            // 不存在该键值对,无法修改,抛出异常
            throw new IllegalArgumentException(key + " does not exist!");
        } else {
            // 存在键值对,直接更新值
            curNode.value = newValue;
        }
    }

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

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("LinkedListMap: ");

        for (Node node = dummyHead.next; node != null; node = node.next) {
            res.append(node + "->");
        }

        res.append("NULL");
        return res.toString();
    }

    private class Node {
        public K key;
        public V value;
        public Node next;

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

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

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

    }
}

时间复杂度分析

O(logn) 级别的复杂度比 O(n) 级别的要快很多。

LinkedListMap 的时间复杂度分析

void add(K key, V value) , O(n) 级别

V remove(K key) , O(n) 级别

boolean contains(K key) , O(n) 级别

BSTMap 的时间复杂度分析

void add(K key, V value) , O(logn) 级别

V remove(K key) , O(logn) 级别

boolean contains(K key) , O(logn) 级别

BSTMap 和 LinkedListMap 的性能比较

public class MapMain {

    public static void main(String[] args) {
        String fileName = "F:\\java_projects\\data_structure\\Map\\src\\pride-and-prejudice.txt";
        Map<String,Integer> linkedListMap = new LinkedListMap<>();
        BSTMap<String, Integer> bstMap = new BSTMap<>();
        System.out.println("linkedListMap need time = " + testMap(linkedListMap, fileName));
        System.out.println("pride show num of times = " + linkedListMap.get("pride"));

        System.out.println();

        System.out.println("bstMap need time = " + testMap(bstMap, fileName));
        System.out.println("pride show num of times = " + bstMap.get("pride"));

        System.out.println();


    /**
     * 测试映射的运行时间
     */
    private static double testMap(Map<String,Integer> map, String fileName) {
        long startTime = System.nanoTime();
        ArrayList<String> words = new ArrayList<>();
        boolean isSuccesss = FileOperation.readFile(fileName, words);
        if (isSuccesss) {
            System.out.println("Pride and Prejudice Total words : " + words.size());
            for (String word : words) {
                if (map.contains(word)) {
                    map.set(word, map.get(word) + 1);
                } else {
                    map.add(word,1);
                }
            }
            System.out.println("Pride and Prejudice Total different words : " + map.getSize());
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }
}

运行结果:

Pride and Prejudice Total words : 125901
Pride and Prejudice Total different words : 6530
linkedListMap need time = 19.359294327
pride show num of times = 53

Pride and Prejudice Total words : 125901
Pride and Prejudice Total different words : 6530
bstMap need time = 0.171941341
pride show num of times = 53

总结:BSTMap 的性能比 LinkedListMap 好很多,因些 BSTMapO(logn) 级别的,LinkedListMapO(n) 级别的。

LeetCode 的题目解答

350.两个数组的交集II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

解题代码如下:

public static int[] intersect(int[] nums1, int[] nums2) {
    TreeMap<Integer, Integer> treeMap = new TreeMap<>();

    // 将第一个数组的数据放到 Map 中
    for (int i : nums1) {
        if (treeMap.containsKey(i)) {
            // 存在,数量加1
            treeMap.put(i, treeMap.get(i) + 1);
        } else {
            // 不存在,数量设为1
            treeMap.put(i, 1);
        }
    }

    ArrayList<Integer> list = new ArrayList<>();
    for (int i : nums2) {
        if (treeMap.containsKey(i)) {
            list.add(i);
            // 如果包含了元素,移除元素
            treeMap.put(i, treeMap.get(i) - 1);
            // 元素的数量为0,移除键值对
            if (treeMap.get(i) == 0) {
                treeMap.remove(i);
            }
        }
    }

    // 把交集数据放到数组里面并返回
    int[] result = new int[list.size()];
    for (int i = 0; i < list.size(); i++) {
        result[i] = list.get(i);
    }

    return result;
}