符号表
定义:
- 符号表最主要的目的就是将一个键和一个值联系起来,符号表能够将存储的数据元素是一个键和一个值共同组成的键值对数据,我们可以根据键来查找对应的值。
符号表中,键具有唯一性。

使用场景:
符号表在实际生活中的使用场景是非常广泛的,见下表: 
链表实现
符号表API设计:
结点类:  符号表: 
代码实现:
public class SymbolTable<Key,Value> {
private Node head;
private int N;
public SymbolTable() {
this.head = new Node(null,null,null);
this.N=0;
}
public int size(){
return N;
}
public void put(Key key,Value value){
Node n = head;
while(n.next!=null){
n = n.next;
if (n.key.equals(key)){
n.value = value;
return;
}
}
Node newNode = new Node(key, value, null);
Node oldFirst = head.next;
newNode.next = oldFirst;
head.next = newNode;
N++;
}
public void delete(Key key){
Node n = head;
while(n.next!=null){
if (n.next.key.equals(key)){
n.next = n.next.next;
N--;
return;
}
n = n.next;
}
}
public Value get(Key key){
Node n = head;
while(n.next!=null){
n = n.next;
if (n.key.equals(key)){
return n.value;
}
}
return null;
}
private class Node{
public Key key;
public Value value;
public Node next;
public Node(Key key, Value value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
}
测试:
public class SymbolTableTest {
public static void main(String[] args) {
SymbolTable<Integer, String> symbolTable = new SymbolTable<>();
symbolTable.put(1,"乔峰");
symbolTable.put(2,"虚竹");
symbolTable.put(3,"段誉");
System.out.println("插入完毕后,元素的个数为:"+symbolTable.size());
symbolTable.put(2, "慕容复");
System.out.println("替换完毕后的元素的个数为:"+symbolTable.size());
System.out.println("替换完毕后,键2对应的值为:"+symbolTable.get(2));
symbolTable.delete(2);
System.out.println("删除完毕后,元素的个数:"+symbolTable.size());
}
}

有序符号表
- 刚才实现的符号表,我们可以称之为无序符号表,因为在插入的时候,并没有考虑键值对的顺序,而在实际生活中,有时候我们需要根据键的大小进行排序,插入数据时要考虑顺序,那么接下来我们就实现一下有序符号表。

有序链表实现:
public class OrderSymbolTable<Key extends Comparable<Key>,Value> {
private Node head;
private int N;
private class Node{
public Key key;
public Value value;
public Node next;
public Node(Key key, Value value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
public OrderSymbolTable() {
this.head = new Node(null,null,null);
this.N=0;
}
public int size(){
return N;
}
public void put(Key key,Value value){
Node curr = head.next;
Node pre = head;
while(curr!=null && key.compareTo(curr.key)>0){
pre = curr;
curr = curr.next;
}
if (curr!=null && key.compareTo(curr.key)==0){
curr.value = value;
return;
}
Node newNode = new Node(key, value, curr);
pre.next = newNode;
N++;
}
public void delete(Key key){
Node n = head;
while(n.next!=null){
if (n.next.key.equals(key)){
n.next = n.next.next;
N--;
return;
}
n = n.next;
}
}
public Value get(Key key){
Node n = head;
while(n.next!=null){
n = n.next;
if (n.key.equals(key)){
return n.value;
}
}
return null;
}
}
debug测试:
 
数组二分查找实现:
使用一对平行数组,一个存储键一个存储值。
- 二分查找的思想是在内部维护一个按照key排好序的二维数组,每一次查找的时候,跟中间元素进行比较,如果该元素小,则继续左半部分递归查找,否则继续右半部分递归查找。整个实现代码如下:
- 二分查找的 rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。

public class SymbolTable<K extends Comparable<K>,V> {
private K[] keys;
private V[] values;
public int size;
private static final int initSize = 10;
public SymbolTable(){
this(initSize);
}
public SymbolTable(int capacity) {
keys = (K[]) new Comparable[capacity];
values = (V[]) new Object[capacity];
}
public V get(K k){
if(isEmpty()){
return null;
}
int i = rank(k);
if(i < size && keys[i].compareTo(k) == 0){
return values[i];
}
return null;
}
public void put(K k,V v){
int i = rank(k);
if(i < size && keys[i].compareTo(k)==0){
values[i] = v;
return;
}
for(int j = size ;j>i ; j--){
keys[j] = keys[j - 1];
values[j] = values[j - 1];
}
keys[i] = k;
values[i] = v;
size ++;
}
public boolean isEmpty(){
return size == 0;
}
public int rank(K k){
int low = 0;
int high = size-1;
while(low <= high){
int mid = low + (high - low) /2;
int cmd = k.compareTo(keys[mid]);
if(cmd == 0){
return mid;
}else if(cmd < 0){
high = mid - 1;
}else {
low = mid + 1;
}
}
return low;
}
}
debug测试:
 
总结:
本文介绍了符号表这一抽象数据结构,然后介绍了两种基本实现:基于无序链表的实现和基于有序数组的实现,两种实现的时间复杂度如下:

无序链表实现:
- 插入的时候先要查找,如果存在则更新value,查找的时候需要从链表头进行查找,所以插入和查找的平均时间复杂度均为O(n)
数组二分查找:
- 采用二分查找只需要最多 logN+1次的比较即可找到对应元素,所以查找效率比较高。
- 但是对于插入元素来说,每一次插入不存在的元素,需要将该元素放到指定的位置,然后,将他后面的元素依次后移,所以平均时间复杂度O(n),对于插入来说效率仍然比较低。
使用有序数组的二分查找法提高了符号表的查找速度,但是插入效率仍旧没有得到提高,而且在要维护数组有序,还需要进行排序操作。这两种实现方式简单直观,但是无法同时达到较高查找和插入效率。
本文只是一个引子,后面的系列文章将会介绍二叉查找树,平衡查找树以及哈希表。
|