算法_符号表

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

  实现的原则:

  每个键只对应一个值,表中不允许存在重复的键

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

  而对于符号表来说,如果保持键的有序性,可以大大的扩展它的API,根据键的相对位置作出更多有用的操作.这种抽象的数据结构又被称为有序符号表.

  可以通过链表来实现无序符号表,代码如下:

  

public class SequentialSearchST<Key,Value> {
    private Node first;     //链表首节点
    private class 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) {
        for(Node x=first;x!=null;x=x.next) {
            if(key.equals(x.key)) {
                return x.val;
            }

        }
        return null;
    }
    public void put(Key key,Value val) {
        for (Node x=first;x!=null;x=x.next) {
            if(key.equals(x.key)) {
                x.val=val;
                return;
            }
            first=new Node(key,val,first);
        }
    }
}

  在这种实现中,未命中(不存在指定的键)的查找和插入操作都需要N次比较.命中的查找在最坏的情况下需要N次比较.特别的,向一个空表中,插入N个不同的键需要N2/2次比较.

  接下来通过一对平行的数组实现有序符号表.实现的核心是rank方法.它返回表中小于给定键的数量.由于使用二分查找可以大大减少每次查找的时候所需要的比较次数,因此采用二分查找来作为rank方法的基本实现.下面是代码:

public class BinarySearchST<Key extends Comparable<Key>,Value> {
    private static final int INIT_CAPACITY = 2;
    private Key[] keys;
    private Value[] vals;
    private int N = 0;

    // create an empty symbol table with default initial capacity
    public BinarySearchST() { this(INIT_CAPACITY); }

    // create an empty symbol table with given initial capacity
    public BinarySearchST(int capacity) {
        keys = (Key[]) new Comparable[capacity];
        vals = (Value[]) new Object[capacity];
    }


    private void resize(int capacity) /*动态的调整数组大小*/{
        assert capacity >= N;
        Key[]   tempk = (Key[])   new Comparable[capacity];
        Value[] tempv = (Value[]) new Object[capacity];
        for (int i = 0; i < N; i++) {
            tempk[i] = keys[i];
            tempv[i] = vals[i];
        }
        vals = tempv;
        keys = tempk;
    }


    // 是否包含指定的键
    public boolean contains(Key key) {
        return get(key) != null;
    }

    // 符号表所有的键的数量
    public int size() {
        return N;
    }

    // 符号表是否为空
    public boolean isEmpty() {
        return size() == 0;
    }

    //返回键对应的值,如果没有则返回null
    public Value get(Key key) {
        if (isEmpty()) return null;
        int i = rank(key);
        if (i < N && keys[i].compareTo(key) == 0) return vals[i];
        return null;
    }

    // 采用二分查找法,获取key的位置
    public int rank(Key key) {
        int lo = 0, hi = N-1;
        while (lo <= hi) {
            int m = lo + (hi - lo) / 2;
            int cmp = key.compareTo(keys[m]);
            if      (cmp < 0) hi = m - 1;
            else if (cmp > 0) lo = m + 1;
            else return m;
        }
        return lo;
    }


    //寻找键,找到修改值,没有找到则新创建一个键值对.
    public void put(Key key, Value val)  {
        if (val == null) { delete(key); return; }

        int i = rank(key);

        // 键以及存在于符号表
        if (i < N && keys[i].compareTo(key) == 0) {
            vals[i] = val;
            return;
        }

        // 插入键值对
        if (N == keys.length) resize(2*keys.length);

        for (int j = N; j > i; j--)  {
            keys[j] = keys[j-1];
            vals[j] = vals[j-1];
        }
        keys[i] = key;
        vals[i] = val;
        N++;

        assert check();
    }


    // 移除键
    public void delete(Key key)  {
        if (isEmpty()) return;

        // 获取位置
        int i = rank(key);

        // 不在符号表中
        if (i == N || keys[i].compareTo(key) != 0) {
            return;
        }

        for (int j = i; j < N-1; j++)  {
            keys[j] = keys[j+1];
            vals[j] = vals[j+1];
        }

        N--;
        keys[N] = null;
        vals[N] = null;

        // resize if 1/4 full
        if (N > 0 && N == keys.length/4) resize(keys.length/2);

        assert check();
    }

    // 删除最小的键对应的值
    public void deleteMin() {
        if (isEmpty()) throw new RuntimeException("Symbol table underflow error");
        delete(min());
    }

    // 删除最大的键对应的值
    public void deleteMax() {
        if (isEmpty()) throw new RuntimeException("Symbol table underflow error");
        delete(max());
    }

    //最小的键
    public Key min() {
        if (isEmpty()) return null;
        return keys[0];
    }
    //最大的键
    public Key max() {
        if (isEmpty()) return null;
        return keys[N-1];
    }
}

  对于N个键的有序数组中进行二分查找最多需要(lgN+1)次比较,向大小为N的有序数组中插入一个新的元素在最坏的情况下,需要访问~2N次数组,因此一个空符号表中插入N个元素在最坏情况下需要访问~N2次数组.

posted @ 2016-07-07 12:12  hlhdidi  阅读(463)  评论(0编辑  收藏  举报