我用Java学数据结构之映射Map底层实现 ^_^

从一个值对应一个值的这种关系就叫映射

首先关于映射,我们主要关心的是一一对应的关系,就比如初高中所学的函数,在生活中也比较常见

一般都是一对一对出现的,为什么会出现这种数据结构呢,以为很多时候我们要根据键(key)来查找值(Value)

首先定义Map的接口(老规矩,依旧采用泛型做为底层实现)

public interface Map<K, V> {

    void add(K key, V value);

    /**
     * 通过键删除这个元素,并返回值Value
     *
     * @param key
     * @return
     */
    V remove(K key);

    boolean contains(K key);

    V get(K key);

    /**
     * 给定一个键与新的值修改键在map中对应的值
     * @param key
     * @param value
     */
    void set(K key, V value);

    int getSize();

    boolean isEmpty();
}

 

 Map可以基于多种底层实现,第一种基于二分搜索树实现一个Map

那第一步肯定是定义一个内部类来存放键与值

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

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

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

 要注意的是在这里要对‘K’做一个限制,因为要存储在二分搜索树中,所以必须有一个比较的标准

所以类的定义这么写即可>

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

 与之前实现二分搜索树中的逻辑相似,只是做出了很小的修改,当用户添加一个新键值对的时候,如果key已经存在,用

新的value覆盖掉之前的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 (key.compareTo(node.key) < 0) {
            node.left = add(node.left, key, value);
        } else if (key.compareTo(node.key) > 0) {
            node.right = add(node.right, key, value);
        } else /*key.compareTo(node.key) == 0*/ {       //如果添加一样的键,就对它的值重新赋值
            node.value = value;
        }
        return node;
    }

接下来实现一个辅助函数,可以在编写下面的方法中更容易实现>

    /**
     * 返回以node为根的树中,key所在的结点
     *
     * @param node
     * @param key
     * @return
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.equals(node.key)) {
            return node;
        } else if (key.compareTo(node.key) < 0) {
            return getNode(node.left, key);
        } else  /*key.compareTo(node.key)*/ {
            return getNode(node.right, key);
        }
    }

 接下来就可以很容易的实现contains ,get 和 set 方法

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

    /**
     * 通过key获取相应的value
     * @param key
     * @return
     */
    @Override
    public V get(K key) {
        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    /**
     * 置存在的key对应的value为新的value
     * @param key
     * @param newValue
     */
    @Override
    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null) {
            throw new IllegalArgumentException(key + " doesn't exist ");
        } else {
            node.value = newValue;
        }
    }

 最后当然还要实现删除任意键值对的函数啦

在这个之前。同样需要几个辅助函数,这里与之前实现二分搜索树中的删除任意节点逻辑相同

    /**
     * 返回以node为结点的树下的最小值
     *
     * @param node
     * @return
     */
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    /**
     * 删除以node为根的树下的最小结点,并返回根节点
     *
     * @param node
     * @return
     */
    private Node removeMIn(Node node) {
        //node的左孩子为空的时候就代表着node就是此树中最小的节点
        //所以在这里只需要将他的右孩子作为新的根返回至上一层即可
        if (node.left == null) {
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        //最小的节点肯定是沿着左孩子一直到底
        node.left = removeMIn(node.left);
        return node;
    }

 有了这两个辅助函数,实现删除任意值就很容易了>

    /**
     * 从二分搜索树中删除key对应的节点
     *
     * @param key
     * @return
     */
    @Override
    public V remove(K key) {
        Node node = getNode(root, key);
        if (node != null) {
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    /**
     * 删除掉以node为根的二分搜索树中键为key对应的节点
     * 并返回删除节点后新的二分搜索树的根
     *
     * @param node
     * @param key
     * @return
     */
    private Node remove(Node node, K key) {
        if (node == null) {
            return null;
        }

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

            return successor;
        }
    }

 这里有一个最典型的应用就是统计词频>

    @Test
    public void testBSTMap() {
        double startTime = System.nanoTime();
        System.out.println("Pride and Perjudice");
        //list的作用就是将这本书的所有词汇全部放置到集合中
        ArrayList<String> words = new ArrayList<>();
        //如果读取文件正常,返回true,接着进行下面的统计工作
        if (FileOperation.readFile("src/pride-and-prejudice.txt", words)) {
            System.out.println("Total word : " + words.size());
            BSTMap<String, Integer> map = new BSTMap<String, Integer>();
            for (String word : words) {
                //如果map中存在这个单词,将键对应的值进行+1操作
                if (map.contains(word)) {
                    map.set(word, map.get(word) + 1);
                } else /*如果不存在这个单词,就将这个单词添加进去*/{
                    map.add(word, 1);
                }
            }
            System.out.println("The different words : " + map.getSize());

            System.out.println(map.toString());
            System.out.println("Frequency of pride : " + map.get("pride"));
            System.out.println("Frequency of prejudice : " + map.get("prejudice"));

            double endTime = System.nanoTime();
            System.out.println((endTime - startTime) / 1000000000.0 + "s");
        }
    }

 统计结果如下所示,在十二万数据的情况下,二分搜索树的性能表现得还是相当优异的

 二分搜索树的底层实现可以参照:二分搜索树底层实现

整个Map的代码也放在这里

import java.util.LinkedList;
import java.util.Queue;

/**
 * 以二分搜索树为底层
 * 实现映射
 *
 * @param <K>
 * @param <V>
 */
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {

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

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

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

    private Node root;
    private int size;

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

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


    @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 (key.compareTo(node.key) < 0) {
            node.left = add(node.left, key, value);
        } else if (key.compareTo(node.key) > 0) {
            node.right = add(node.right, key, value);
        } else /*key.compareTo(node.key) == 0*/ {       //如果添加一样的键,就对它的值重新赋值
            node.value = value;
        }
        return node;
    }

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

    /**
     * 通过key获取相应的value
     *
     * @param key
     * @return
     */
    @Override
    public V get(K key) {
        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    /**
     * 置存在的key对应的value为新的value
     *
     * @param key
     * @param newValue
     */
    @Override
    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null) {
            throw new IllegalArgumentException(key + " doesn't exist ");
        } else {
            node.value = newValue;
        }
    }

    /**
     * 从二分搜索树中删除key对应的节点
     *
     * @param key
     * @return
     */
    @Override
    public V remove(K key) {
        Node node = getNode(root, key);
        if (node != null) {
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    /**
     * 删除掉以node为根的二分搜索树中键为key对应的节点
     * 并返回删除节点后新的二分搜索树的根
     *
     * @param node
     * @param key
     * @return
     */
    private Node remove(Node node, K key) {
        if (node == null) {
            return null;
        }

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

            return successor;
        }
    }

    /**
     * 返回以node为结点的树下的最小值
     *
     * @param node
     * @return
     */
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    /**
     * 删除以node为根的树下的最小结点,并返回根节点
     *
     * @param node
     * @return
     */
    private Node removeMIn(Node node) {
        if (node.left == null) {
            /*
            node的左孩子为空的时候就代表着node就是此树中最小的节点
            所以在这里只需要将他的右孩子作为新的根返回至上一层即可
            */
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        //最小的节点肯定是沿着左孩子一直到底
        node.left = removeMIn(node.left);
        return node;
    }


    /**
     * 返回以node为根的树中,key所在的结点
     *
     * @param node
     * @param key
     * @return
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.equals(node.key)) {
            return node;
        } else if (key.compareTo(node.key) < 0) {
            return getNode(node.left, key);
        } else  /*key.compareTo(node.key)*/ {
            return getNode(node.right, key);
        }
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            res.append(node + "\n");
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        return res.toString();
    }
}

在这里还有基于链表的Map的实现>    他的性能要远差于基于二分搜索树的Map

public class LinkedListMap<K, V> implements Map<K, V> {
    private class Node {
        public K key;
        public V value;
        public Node next;

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

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

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

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

    private Node dummyHead;
    private int size;

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

    @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 prev = dummyHead;
        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 node = getNode(key);
        return node == null ? null : node.value;
    }

    @Override
    public void set(K key, V newValue) {
        Node node = getNode(key);
        if (node == null) {
            throw new IllegalArgumentException(key + " doesn't  exist ! ");
        } else {
            node.value = newValue;
        }
    }

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

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

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        Node cur = dummyHead.next;
        while (cur != null) {
            res.append(cur + "\n");
            cur = cur.next;
        }
        return res.toString();
    }

    private Node getNode(K key) {
        Node cur = dummyHead.next;
        while (cur != null) {
            if (cur.key.equals(key)) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值