符号表是一种存储键值对的数据结构,支持两种操作:插入(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次数组.