第6篇 符号表

符号表

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

1 API

为了保证代码的一致性,简要说明具体实现中的几个设计决策:

1)每个键只对应着一个值。

2)当用例代码向表中存入键值对和表中已有的键(及关联的值)冲突时,新的值会替代旧的值。

这些规则定义了关联数组的抽象形式。

3)键不能为空。

4)键不能关联着空值。这样做可以有两个结果:①可以用get()方法是否返回空来测试给定键是否存在于符号表中。②可以将空值作为put()方法的第二个参数存入表中来实现删除。

5)删除操作:①延时删除:将键对应的值置为空,然后在某个时候删除所有值为空的键。put(key.null)是delete(key)的一种简单的(延时型)实现。②实时删除:delete()。

6)便捷方法

7)迭代:使用key()方法来返回一个Interable对象以便用例遍历所有的键。

2 有序符号表

3 用例举例

符号表的典型问题:

1)混合使用查找和插入的操作。

2)大量的不同键。

3)查找操作比插入操作多的多。

4)虽然不可预测,但是查找和插入操作使用的模式并非随机。

4 无序链表的顺序查找

符号表的一种简单实现为使用链表:put()和get()方法都是使用equals()方法来一个一个地顺序遍历符号表中所有键。

其中符合欧标实现使用了一个私有内部Node类来在链表中保存键和值。get()的实现会顺序地搜索链表查找给定的键(找到则返回相关联的值)。put()的实现也会顺序地搜索链表查找给定的键,如果找到则更新相关联的值,否则用给定的键值对创建一个新的结点,插入到链表的开头。

public class SequentialSearchST<Key, Value>
{
    private Node first; // first node in the linked list
    private class Node
    { // linked-list node
        Key key;
        Value val;
        Node next;
        public Node(Key key, Value val, Node next)
        {
            this.key = key;
            this.val = val;
            this.next = next;
        }
    }
    public Value get(Key key)
    { // Search for key, return associated value.
        for (Node x = first; x != null; x = x.next)
            if (key.equals(x.key))
                return x.val; // search hit
        return null; // search miss
    }
    public void put(Key key, Value val)
    { // Search for key. Update value if found; grow table if new.
        for (Node x = first; x != null; x = x.next)
            if (key.equals(x.key))
            { x.val = val; return; } // Search hit: update val.
        first = new Node(key, val, first); // Search miss: add new node.
    }
}

在一个(无序的)链表符号表中,有N个键值对的查找和插入,这两个都需要N个比较,而搜索结果在最坏的情况下进行N次比较。特别地,在一空表插入N个不同的键需要使用~N2/2比较。

5 有序数组的二分查找

有序符号表API的数据结构是一对平行的数组,一个存储键和一个存储值。

下面给出二分查找(基于有序数组)。

public class BinarySearchST<Key extends Comparable<Key>, Value>
{
    private Key[] keys;
    private Value[] vals;
    private int N;
    public BinarySearchST(int capacity)
    { // See Algorithm 1.1 for standard array-resizing code.
        keys = (Key[]) new Comparable[capacity];
        vals = (Value[]) new Object[capacity];
    }
    public int size()
    { return N; }
    public Value get(Key key)
    {
        if (isEmpty()) return null;
            int i = rank(key);
        if (i < N && keys[i].compareTo(key) == 0) return vals[i];
        else return null;
    }
    public int rank(Key key)
    {
        int lo = 0, hi = N-1;
        while (lo <= hi)
        {
            int mid = lo + (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;
    }
    public void put(Key key, Value val)
    { // Search for key. Update value if found; grow table if new.
        int i = rank(key);
        if (i < N && keys[i].compareTo(key) == 0)
        { vals[i] = val; return; }
        for (int j = N; j > i; j--)
        { keys[j] = keys[j-1]; vals[j] = vals[j-1]; }
        keys[i] = key; vals[i] = val;
        N++;
    }
    public void delete(Key key)
    // See Exercise 3.1.16 for this method.
    //基于二分查找的有序符号表的其他操作
    public Key min()
    { return keys[0]; }
    public Key max()
    { return keys[N-1]; }
    public Key select(int k)
    { return keys[k]; }
    public Key ceiling(Key key)
    {
        int i = rank(key);
        return keys[i];
    }
    public Key floor(Key key)
    // See Exercise 3.1.17.
    public Key delete(Key key)
    // See Exercise 3.1.16.
    public Iterable<Key> keys(Key lo, Key hi)
    {
        Queue<Key> q = new Queue<Key>();
        for (int i = rank(lo); i < rank(hi); i++)
            q.enqueue(keys[i]);
        if (contains(hi))
            q.enqueue(keys[rank(hi)]);
        return q;
    }
}

//其中,rank()算法的递归实现为:
public int rank(Key key, int lo, int hi)
{
    if (hi < lo) return lo;
    int mid = lo + (hi - lo) / 2;
    int cmp = key.compareTo(keys[mid]);
    if (cmp < 0)
        return rank(key, lo, mid-1);
    else if (cmp > 0)
        return rank(key, mid+1, hi);
    else return mid;
}

有序数组的符号表索引轨迹:

命题B1:在N个键的有序数组中,进行二分查找最多需要进行lgN+1次比较。

命题B2:向大小为N的有序数组中插入一个新的元素在最坏的情况下需要访问~2N次数组,因此向一个空符号表中插入N个元素最坏需要访问~N2次数组。

6 预览

接下来学习6种符号表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值