算法-13-符号表(HashMap的前世一)

目录

1、定义

2、用途

3、特点

4、方案一:基于无序链表

5、方案二:基于有序数组的二分查找

6、符号表的各种实现的优缺点


1、定义

符号表是一种存储键值对的数据结构,支持两种操作:插入(put),即将一组新的键值 对存入表中;查找(get),即根据给定的键得到相应的值。

2、用途

符号表主要的目的就是将一组键和值对应起来,并将多组键值对存储起来,方便后期查找的时候我们可以根据相应的键找到对应的值。

3、特点

我们的符号表需要遵循以下的规则:

扫描二维码关注公众号,回复: 11195127 查看本文章
  1. 没有重复的key。
  2. key不能为null。
  3. value也不能为null,这样设计的目的为了可以通过get()方法返回值是否为null来判断key是否存在,也可以通过put(key,null)来删除key这个键值对。

4、方案一:基于无序链表

我们对于符号表键值对的存储方式的数据结构的简单选择就是:链表。用一个结点来存储键和值。{如果对链表不太熟悉可以看算法-3-链表栈(最优设计方案)}

插入(put):需要从头结点遍历下去,直到找到相同的key,更新key对应的value;如果没有就利用该键值对创建新的头结点。

查找(get):也需要从头结点遍历下去,直到找到对应的key返回对应的value,如果没有则返回null。

public class NodeMap<Key, Value> {

    private Node first;//头结点

    class Node {
        Key key;
        Value value;
        Node next;

        public Node(Key key, Value value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public void put(Key key, Value value) {

        for (Node x = first; x != null; x = x.next) {
            if (key.equals(x.key)) { //如果链表中含有相同的key更新对应的value即可
                x.value = value;
                return;
            }
        }
        first = new Node(key, value, first);//链表中没有该key的话,利用该键值对创建新的头结点
    }

    public Value get(Key key) {
        
        for (Node x = first; x != null; x = x.next) {
            if (key.equals(x.key)) { 
               return x.value;
            }
        }
        return null;
    }

}

基于链表实现符号表首先它是无序的,然后它的每次插入和查找的最差时间复杂度都是N,平均情况下查找的时间复杂度为N/2,插入还是N,这显然是非常低效的设计方案。

对于无序列表低效的插入查找方式,我们可以利用有序表来进行改进,对于有序表的查找,我们可以利用二分查找法来将时间复杂度提升到lgN级别。同时它还支持有序相关的大部分操作,比如获取最大最小值等。

5、方案二:基于有序数组的二分查找

对于有序符号表的实现,我们采用的数据结构是 一对平行的数组,一个存储key,一个存储value。

插入(put):首先我们利用二分查找法,查找数组keys中是否有该key值,如果有则返回在keys数组中的下标,如果没有的话则返回大于等于该key的最小的key的下标k,然后将该键值对插入到下标k的位置,并将原来数组下标k以及后面的元素后移一位。

查找(get):我们利用二分查找法,在lgN的时间复杂度内就能得到我们想要的value。

该方案在最坏情况下,查找一个元素需要lgN+1 次比较;向大小为N的有序数组插入一个新的元素,最坏情况下需要访问数组~2N次(如果插入的位置为0,那么key和value数组都需要后移N次)。

public class BinarySearchArray<Key extends Comparable<Key>, Value> {
    private Key[] keys;
    private Value[] values;
    private int N;//存储键值对的数量

    public BinarySearchArray(int size) {
        keys = (Key[]) new Comparable[size];
        values = (Value[]) new Object[size];
    }

    public void put(Key key, Value value) {
        /*
         * 先查找keys数组中是否用相同的key如果有更新它对应的value就行

         */
        int k=rank(key);
        if (k<N&&key.compareTo(keys[k])==0){
            values[k]=value;
            return;
        }
        /*
         * 如果keys数组没有对应key,就需要将这个键值对插入到下标为k的位置,
         * 然后将原来数组下标为k以及k后面的元素平移到后面。
         */
        for (int i=N;i>k;i--){
            keys[i]=keys[i-1];
            values[i]=values[i-1];
        }
        keys[k]=key;
        values[k]=value;
        N++;
    }

    public Value get(Key key) {

        if (N == 0) return null;
        int k = rank(key);
        if (k < N && key.compareTo(keys[k]) == 0) {
            return values[k];
        }
        return null;
    }
    /**
     * 返回大于等于该key的最小key的下标
     * 就比如数组keys{1,3,4,7,9},我要利用二分查找法寻找5这个key,并返回大于等于5的最小的key的下标。
     * 第一步:mid=0+(4-0)/2=2;          keys[2]<5;      lo=2+1=3;
     * 第二步:mid=3+(4-3)/2=3;          keys[3]>5;      hi=4-1=3;
     * 第三步:mid=3+(3-3)/2=3;          keys[3]>5;      hi=3-1=2;lo=3;
     * 第四步:hi<lo跳出while循环,返回lo=3, 7就是大于等于5的最小key
     */
    private int rank(Key key) {
        int lo = 0;
        int hi = N;

        while (lo <= hi) {
            int mid = (hi - lo) / 2;
            int cmp = key.compareTo(keys[mid]);
            if (cmp < 0) {
                hi = mid - 1;
            } else if (cmp > 0) {
                lo = mid + 1;
            } else {
                return mid;
            }
        }
        return lo;
    }
}

6、符号表的各种实现的优缺点

显然上面的两种方案都没有达到理想的时间复杂度,后面我们会将表格中更优化的实现方案。

 

 

 

 

 

 

 

 

 

原创文章 120 获赞 34 访问量 28万+

猜你喜欢

转载自blog.csdn.net/qq_34589749/article/details/104182891