基于链表实现的Map与二叉搜索树的Map对比

Map61B定义Map需要实现的方法

import java.util.Set;


public interface Map61B<K, V> extends Iterable<K> {

    /** Removes all of the mappings from this map. */
    void clear();

    /* Returns true if this map contains a mapping for the specified key. */
    boolean containsKey(K key);

    /* Returns the value to which the specified key is mapped, or null if this
     * map contains no mapping for the key.
     */
    V get(K key);

    /* Returns the number of key-value mappings in this map. */
    int size();

    /* Associates the specified value with the specified key in this map. */
    void put(K key, V value);

    /* Returns a Set view of the keys contained in this map. Not required for Lab 7.
     * If you don't implement this, throw an UnsupportedOperationException. */
    Set<K> keySet();

    /* Removes the mapping for the specified key from this map if present. */
    V remove(K key);

    /* Removes the entry for the specified key only if it is currently mapped to
     * the specified value. */
    V remove(K key, V value);

}

比较 ULLMap 和 BSTMap 实现的优劣

在实现映射(Map)数据结构时,选择合适的底层数据结构会显著影响操作的性能和复杂性。本文将比较两种不同的映射实现:ULLMap,其底层使用未排序的链表;BSTMap,其底层使用二叉搜索树(BST)。这两种实现都用于处理键值对,但在效率和复杂性方面提供了不同的权衡。

ULLMap:基于未排序链表的 Map

ULLMap 实现使用未排序的链表来存储键值对。该结构具有以下特点:

关键操作:

  1. 获取(get):由于需要遍历链表查找键,其时间复杂度为线性时间 (O(n))。
  2. 插入(put):如果键已经存在,更新值的时间复杂度为线性时间 (O(n)),否则在链表头插入的时间复杂度为常数时间 (O(1))。
  3. 包含键(containsKey):由于需要遍历链表以检查键是否存在,其时间复杂度为线性时间 (O(n))。
  4. 删除(remove):遍历链表找到与给定键匹配的条目并将其删除,时间复杂度为线性时间 (O(n))。
  5. 获取大小(size):返回存储的键值对数量,时间复杂度为常数时间 (O(1))。
  6. 清空(clear):将链表头指针设为 null,清空链表,时间复杂度为常数时间 (O(1))。

优点:

  • 实现简单,适用于键值对数量较少的情况。
  • 内存开销较小,因为不需要额外的结构来维持排序。
  • 插入新键值对时效率高。

缺点:

  • 对于大规模数据,操作效率较低。
  • 查找、插入和删除的时间复杂度较高。

UULMap实现:

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/** A data structure that uses a linked list to store pairs of keys and values.
 *  Any key must appear at most once in the dictionary, but values may appear multiple
 *  times. Key operations are get(key), put(key, value), and contains(key) methods. The value
 *  associated to a key is the value in the last call to put with that key. */
public class ULLMap<K, V>  implements Map61B<K, V> {

    int size = 0;

    /** Returns the value corresponding to KEY or null if no such value exists. */
    public V get(K key) {
        if (list == null) {
            return null;
        }
        Entry lookup = list.get(key);
        if (lookup == null) {
            return null;
        }
        return lookup.val;
    }

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

    /** Removes all of the mappings from this map. */
    @Override
    public void clear() {
        size = 0;
        list = null;
    }

    /** Inserts the key-value pair of KEY and VALUE into this dictionary,
     *  replacing the previous value associated to KEY, if any. */
    public void put(K key, V val) {
        if (list != null) {
            Entry lookup = list.get(key);
            if (lookup == null) {
                list = new Entry(key, val, list);
            } else {
                lookup.val = val;
            }
        } else {
            list = new Entry(key, val, list);
            size = size + 1;
        }
    }

    /** Returns true if and only if this dictionary contains KEY as the
     *  key of some key-value pair. */
    public boolean containsKey(K key) {
        if (list == null) {
            return false;
        }
        return list.get(key) != null;
    }

    @Override
    public Iterator<K> iterator() {
        return new ULLMapIter();
    }

    /** Keys and values are stored in a linked list of Entry objects.
     *  This variable stores the first pair in this linked list. */
    private Entry list;

    /** Represents one node in the linked list that stores the key-value pairs
     *  in the dictionary. */
    private class Entry {

        /** Stores KEY as the key in this key-value pair, VAL as the value, and
         *  NEXT as the next node in the linked list. */
        Entry(K k, V v, Entry n) {
            key = k;
            val = v;
            next = n;
        }

        /** Returns the Entry in this linked list of key-value pairs whose key
         *  is equal to KEY, or null if no such Entry exists. */
        Entry get(K k) {
            if (k != null && k.equals(key)) {
                return this;
            }
            if (next == null) {
                return null;
            }
            return next.get(key);
        }

        /** Stores the key of the key-value pair of this node in the list. */
        K key;
        /** Stores the value of the key-value pair of this node in the list. */
        V val;
        /** Stores the next Entry in the linked list. */
        Entry next;

    }

    /** An iterator that iterates over the keys of the dictionary. */
    private class ULLMapIter implements Iterator<K> {

        /** Create a new ULLMapIter by setting cur to the first node in the
         *  linked list that stores the key-value pairs. */
        public ULLMapIter() {
            cur = list;
        }

        @Override
        public boolean hasNext() {
            return cur != null;
        }

        @Override
        public K next() {
            K ret = cur.key;
            cur = cur.next;
            return ret;
        }

        /** Stores the current key-value pair. */
        private Entry cur;

    }

    @Override
    public V remove(K key) {
        if (list == null) {
            return null;
        }

        if (list.key.equals(key)) {
            V val = list.val;
            list = list.next;
            size -= 1;
            return val;
        }

        Entry prev = list;
        Entry curr = list.next;

        while (curr != null) {
            if (curr.key.equals(key)) {
                V val = curr.val;
                prev.next = curr.next;
                size -= 1;
                return val;
            }
            prev = curr;
            curr = curr.next;
        }

        return null;
    }

    @Override
    public V remove(K key, V value) {
        if (list == null) {
            return null;
        }

        if (list.key.equals(key) && list.val.equals(value)) {
            V val = list.val;
            list = list.next;
            size -= 1;
            return val;
        }

        Entry prev = list;
        Entry curr = list.next;

        while (curr != null) {
            if (curr.key.equals(key) && curr.val.equals(value)) {
                V val = curr.val;
                prev.next = curr.next;
                size -= 1;
                return val;
            }
            prev = curr;
            curr = curr.next;
        }

        return null;
    }

    @Override
    public Set<K> keySet() {
        Set<K> keys = new HashSet<>();
        Entry curr = list;
        while (curr != null) {
            keys.add(curr.key);
            curr = curr.next;
        }
        return keys;
    }

}

BSTMap:基于二叉搜索树的 Map

BSTMap 实现使用二叉搜索树来存储键值对。该结构具有以下特点:

关键操作:

  1. 获取(get):使用二叉搜索树的性质,根据键值进行有序查找,平均时间复杂度为对数时间 (O(\log n)),最坏情况下(树不平衡)为线性时间 (O(n))。
  2. 插入(put):根据键值进行有序插入或更新,平均时间复杂度为对数时间 (O(\log n)),最坏情况下为线性时间 (O(n))。
  3. 包含键(containsKey):根据键值在二叉搜索树中进行有序查找,平均时间复杂度为对数时间 (O(\log n))。
  4. 删除(remove):在二叉搜索树中找到与给定键匹配的节点并删除,平均时间复杂度为对数时间 (O(\log n)),最坏情况下为线性时间 (O(n))。
  5. 获取大小(size):返回二叉搜索树中的节点数量,时间复杂度为常数时间 (O(1))。
  6. 清空(clear):将树的根节点设为 null,清空二叉搜索树,时间复杂度为常数时间 (O(1))。

优点:

  • 对于大规模数据,操作效率较高。
  • 在理想情况下,查找、插入和删除的时间复杂度较低。

缺点:

  • 需要维护树的平衡,可能引入额外的复杂度(如使用自平衡树结构)。
  • 实现复杂度较高,相较于链表,内存开销略大。

BSTMap实现:

import java.util.*;

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

    int size = 0;
    private BST tree;

    private class BST {
        private Node root;
        private class Node {
            private K key;           // sorted by key
            private V val;         // associated data
            private Node left, right;  // left and right subtrees
            private int size;          // number of nodes in subtree

            public Node(K key, V val, int size) {
                this.key = key;
                this.val = val;
                this.size = size;
            }
        }
        public BST() {
        }

        /**
         * Returns the value associated with the given key.
         *
         * @param  key the key
         * @return the value associated with the given key if the key is in the symbol table
         *         and {@code null} if the key is not in the symbol table
         * @throws IllegalArgumentException if {@code key} is {@code null}
         */
        public V get(K key) {
            return get(root, key);
        }

        private V get(Node x, K key) {
            if (key == null) throw new IllegalArgumentException("calls get() with a null key");
            if (x == null) return null;
            int cmp = key.compareTo(x.key);
            if      (cmp < 0) return get(x.left, key);
            else if (cmp > 0) return get(x.right, key);
            else              return x.val;
        }

        public void put(K key, V val) {
            if (key == null) throw new IllegalArgumentException("calls put() with a null key");
            // if val not contain null
//            if (val == null) {
//                delete(key);
//                return;
//            }
            root = put(root, key, val);
        }
        private Node put(Node x, K key, V val) {
            if (x == null) return new Node(key, val, 1);
            int cmp = key.compareTo(x.key);
            if      (cmp < 0) x.left  = put(x.left,  key, val);
            else if (cmp > 0) x.right = put(x.right, key, val);
            else              x.val   = val;
            x.size = 1 + size(x.left) + size(x.right);
            return x;
        }
        /**
         * Returns the number of key-value pairs in this symbol table.
         * @return the number of key-value pairs in this symbol table
         */
        public int size() {
            return size(root);
        }

        /** return number of key-value pairs in BST rooted at x**/
        private int size(Node x) {
            if (x == null) return 0;
            else return x.size;
        }

        /**
         * Removes the specified key and its associated value from this symbol table
         * (if the key is in this symbol table).
         *
         * @param  key the key
         * @throws IllegalArgumentException if {@code key} is {@code null}
         */
        public void delete(K key) {
            if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
            root = delete(root, key);
        }

        private Node delete(Node x, K key) {
            if (x == null) return null;

            int cmp = key.compareTo(x.key);
            if      (cmp < 0) x.left  = delete(x.left,  key);
            else if (cmp > 0) x.right = delete(x.right, key);
            else {
                if (x.right == null) return x.left;
                if (x.left  == null) return x.right;
                Node t = x;
                x = min(t.right);
                x.right = deleteMin(t.right);
                x.left = t.left;
            }
            x.size = size(x.left) + size(x.right) + 1;
            return x;
        }

        /**
         * Removes the smallest key and associated value from the symbol table.
         *
         * @throws NoSuchElementException if the symbol table is empty
         */
        public void deleteMin() {
            if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
            root = deleteMin(root);
        }

        private Node deleteMin(Node x) {
            if (x.left == null) return x.right;
            x.left = deleteMin(x.left);
            x.size = size(x.left) + size(x.right) + 1;
            return x;
        }


        /**
         * Returns true if this symbol table is empty.
         * @return {@code true} if this symbol table is empty; {@code false} otherwise
         */
        public boolean isEmpty() {
            return size() == 0;
        }

        /**
         * Returns the smallest key in the symbol table.
         *
         * @return the smallest key in the symbol table
         * @throws NoSuchElementException if the symbol table is empty
         */
        public K min() {
            if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
            return min(root).key;
        }

        private Node min(Node x) {
            if (x.left == null) return x;
            else                return min(x.left);
        }

    }

    @Override
    public Iterator<K> iterator() {
        return new BSTIterator();
    }

    /** Removes all of the mappings from this map. */
    @Override
    public void clear() {
        size = 0;
        tree = null;
    }

    /** Returns true if this map contains a mapping for the specified key. */
    @Override
    public boolean containsKey(K key) {
        if (tree == null) return false;
        return get(key) != null;
    }

    /** Returns the value corresponding to KEY or null if no such value exists. */
    @Override
    public V get(K key) {
        if (tree == null) {
            return null;
        }
        return tree.get(key);
    }

    /** Returns the number of key-value mappings in this map. */
    @Override
    public int size() {
        return size;
    }

    /** Associates the specified value with the specified key in this map. */
    @Override
    public void put(K key, V value) {
        if (tree != null) {
            if (containsKey(key)) {
                tree.put(key, value);
            } else {
                tree.put(key, value);
                size = size + 1;
            }
        }
        else {
            tree = new BST();
            tree.put(key, value);
            size = size + 1;
        }
    }



    /** Removes the mapping for the specified key from this map if present.*/
    @Override
    public V remove(K key) {
        if (containsKey(key)) {
            V v = get(key);
            tree.delete(key);
            size = size - 1;
            return v;
        }
        return null;
    }

    /* Removes the entry for the specified key only if it is currently mapped to
     * the specified value.*/
    @Override
    public V remove(K key, V value) {
        if (containsKey(key)) {
            V v = get(key);
            if (v.equals(value)) {
                tree.delete(key);
                size = size - 1;
                return v;
            }
        }
        return null;
    }

    @Override
    public Set<K> keySet() {
        Set set = new HashSet<K>();
        for (K node: this) {
            set.add(node);
        }
        return set;
    }

    void printInOrder() {
        if (tree == null) throw new IllegalArgumentException("calls printInOrder() with a null Map");
        System.out.println("{");
        printInOrder(tree.root);
        System.out.println("}");
    }

    /**Print out BSTMap in increasing order of Key*/
    void printInOrder(BST.Node node) {
        if (node == null) return;
        printInOrder(node.left);
        System.out.println("key"+node.key+":"+"value"+node.val);
        printInOrder(node.right);
    }

    private class BSTIterator implements Iterator<K> {
        private Stack<BST.Node> stack = new Stack<>();

        public BSTIterator() {
            pushLeft(tree.root);
        }

        private void pushLeft(BST.Node x) {
            while (x != null) {
                stack.push(x);
                x = x.left;
            }
        }

        @Override
        public boolean hasNext() {
            return !stack.isEmpty();
        }

        @Override
        public K next() {
            if (!hasNext()) throw new NoSuchElementException();
            BST.Node node = stack.pop();
            K key = node.key;
            if (node.right != null) {
                pushLeft(node.right);
            }
            return key;
        }
    }
}

方法优劣性对比

get 方法对比:

  • ULLMap:

    • 时间复杂度:(O(n))
    • 优点:实现简单
    • 缺点:效率低
  • BSTMap:

    • 时间复杂度:平均 (O(\log n)),最坏 (O(n))
    • 优点:在平衡树情况下效率高
    • 缺点:实现复杂

put 方法对比:

  • ULLMap:

    • 时间复杂度:键存在时 (O(n)),键不存在时 (O(1))
    • 优点:插入新键值对时效率高
    • 缺点:更新现有键值对时效率低
  • BSTMap:

    • 时间复杂度:平均 (O(\log n)),最坏 (O(n))
    • 优点:在平衡树情况下效率高
    • 缺点:实现复杂

containsKey 方法对比:

  • ULLMap:

    • 时间复杂度:(O(n))
    • 优点:实现简单
    • 缺点:效率低
  • BSTMap:

    • 时间复杂度:平均 (O(\log n)),最坏 (O(n))
    • 优点:在平衡树情况下效率高
    • 缺点:实现复杂

remove 方法对比:

  • ULLMap:

    • 时间复杂度:(O(n))
    • 优点:实现简单
    • 缺点:效率低
  • BSTMap:

    • 时间复杂度:平均 (O(\log n)),最坏 (O(n))
    • 优点:在平衡树情况下效率高
    • 缺点:实现复杂

size 和 clear 方法对比:

  • ULLMap:

    • 时间复杂度:(O(1))
    • 优点:实现简单,效率高
    • 缺点:无
  • BSTMap:

    • 时间复杂度:(O(1))
    • 优点:实现简单,效率高
    • 缺点:无

总结

总体而言,ULLMap 实现简单,适用于小规模数据集,操作效率在键值对数量较少时表现尚可。然而,随着数据规模增大,其操作性能会显著下降。而 BSTMap 尽管实现复杂,但在大规模数据集下操作效率更高,尤其在平衡树情况下,其查找、插入和删除操作的性能明显优于 ULLMap。因此,在需要处理较大数据集或对操作效率要求较高的情况下,BSTMap 是更合适的选择。

通过对这两种不同实现的对比,我们可以更好地理解不同数据结构在不同场景下的适用性,从而在实际应用中做出更为合适的选择。

  • 34
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

poison_Program

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值