Symbol Table(符号表)
一、定义
符号表是一种存储键值对的数据结构并且支持两种操作:将新的键值对插入符号表中(insert);根据给定的键值查找对应的值(search)。
二、API
1、无序符号表
几个设计决策:
A、泛型
在设计方法时没有指定处理对象,而是使用了泛型。
并且将键(Key)和值(Value)区分开来。
B、重复键的处理
规则:
每个值(Value)都只对应一个键(Key)(即不允许存在重复的键)。
当用例代码向表中存入的键值对和表中的已有的键(以及关联的值)冲突时,新的值替代旧的值。
C、Null 键
键不能为空,否则会抛出空指针异常。
D、Null 值
同样规定值不能为Null,因为API中get函数,如果键不存在会返回null,如果在表中的值可以为null的话,就会产生混淆。
这个规定产生两个结果:
可以用get()方法是否返回null来判断给定的key是否存在表中。
可以用put(key, null)来实现删除。
E、便捷方法
可以由其他函数来实现,如:
//shorthand methods
public boolean contains(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function contains"); return get(key) != null;
} //shorthand methods
public boolean isEmpty() {
return size() == 0;
}
F、键的等价性
对象的equals方法。
最好用不可变的数据类型作为键,否则表的一致性是不可保证的。
2、有序符号表
对于实现了Comparable的键,符号表可利用这一特性来保持键的有序性。
这就大大拓展了API,根据键的相对位置定义更多使用的操作。这种表就叫有序符号表。
只要符号表出现了泛型变量Key extends Comparable<Key>,那么这个符号表就实现了有序性。
A、最大键和最小键
在有序符号表中有获取最大(最小)键操作和删除最大(最小)键操作。
之前的Priority Queue也有类似的操作,主要区别在优先队列中可以存在重复的键而符号表不行。
B、向上取整和向下取整
向下取整:floor, find the largest key that is less than or equal to the given key.
向上取整:ceiling, find the smallest key that is greater than or equal to the given key.
C、排名和选择
rank(key), 小于key的键的个数。
select(k), 返回第k大的键,即符号表中有k个键小于返回的键(k的取值范围为0到st.size)。
i == rank(select(i)),key = select(rank(key))
上述两个等式可以更好的理解这两个操作。
D、范围查找
对于给定的范围有多少个键在这个范围,或者哪些键在这些范围?
public int size(Key lo, Key hi)
public Iterable<Key> keys(Key lo, Key hi)
这两个函数实现了上述操作。
E、异常情况
当方法需要返回一个键,但符号表没有符合的键时,抛出一个异常。
或者返回null。
F、便捷方法
可以由其他函数来实现,如:
//shorthand methods
public void deleteMin() {
if(isEmpty())
throw new NoSuchElementException("underflow error");
delete(min());
} //shorthand methods
public void deleteMax() {
if(isEmpty())
throw new NoSuchElementException("underflow error");
delete(max());
} //shorthand methods
public int size(Key lo, Key hi) { if (lo == null)
throw new IllegalArgumentException("first argument to size() is null");
if (hi == null)
throw new IllegalArgumentException("second argument to size() is null"); if(hi.compareTo(lo) < 0)
return 0;
else if(contains(hi))
return rank(hi) - rank(lo) + 1;
else
return rank(hi) - rank(lo);
} //shorthand methods
public Iterable<Key> keys() {
if(isEmpty())
return new LinkedList<>();
return keys(min(), max());
}
G、键的等价性
任何一个Comparable类型的两个值a和b都要保证(a.compareTo(b) == 0)和a.equals(b)的返回值相等。
为了避免二义性,在有序符号表中只使用compareTo()方法来比较两个键,即a.compareTo(b) == 0来表示a和b是否相等。
H、成本模型
无论是用a.compareTo(b) == 0还是用a.equals(b),都用比较来表示一个符号表条目和一个被查找的键的比较操作。
如果比较操作不在内循环,则统计数组的访问次数。
三、测试用例
两个用例:一个用来跟踪在小规模输入下的行为测试用例,一个用来寻找更高效实现的性能测试用例。
1、Test Client
public static void main(String[] args) { ST<String, Integer> st = new ST<String, Integer>(); for(int i = 0; !StdIn.isEmpty(); i++) { String key = StdIn.readString(); if(key.equals("-")) {
key = StdIn.readString();
st.delete(key);
StdOut.println("delete " + key);
} else {
st.put(key, i);
StdOut.println("put" + " " + key + " " + i);
} StdOut.print(st.size() + " key-value pairs:");
for(String s : st.keys())
StdOut.print(" " + s + " " + st.get(s));
StdOut.println(); } }
跟课本的有所不同,加上了delete操作,更好的观测行为。
2、Performance Client
public static void main(String[] args) { int minlen = Integer.parseInt(args[0]);
SequentialSearchST<String, Integer> st = new SequentialSearchST<>(); //build symbol table and count frequencies
while(!StdIn.isEmpty()) {
String word = StdIn.readString();
if(word.length() < minlen)
continue;//ignore short keys
Integer freq = st.get(word);
if(freq == null)
st.put(word, 1);
else
st.put(word, freq + 1);
} //find a key with the highest frequency count
String max = "";
st.put(max, 0);
for(String word : st.keys())
if(st.get(word) > st.get(max))
max = word;
StdOut.println(max + " " + st.get(max)); }
这个是对课本的用例进行改进后的,消除对contains的调用。
本章一直用这个用例来进行性能测试。用例特点:
查找(search)和插入(insert)操作交叉进行。
大量不同的键。
查找(search)比插入(insert)操作多的多。
虽然不可预测,查找和插入的模式并非随机。
四、初级实现
1、无序链表中的顺序查找
顺序查找用的是数据结构是链表,每个节点存储一个键值对。
get()的实现为遍历链表,用equals函数比较寻找的键和节点中的键。如果有匹配的键,则返回相应的值。
否则,返回null。
//search for key, return associated value
public Value get(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function get"); for(Node current = first; current != null; current = current.next) {
if(key.equals(current.key))
return current.val;//search hit
} return null;//search miss
}
put()的实现也是遍历链表,如果有匹配的键,则更新相应的值,否则,在链表头插入新的节点。
public void put(Key key, Value val) { if(key == null)
throw new IllegalArgumentException("key is null in function put"); if(val == null) {
delete(key);
return;
} for(Node current = first; current != null; current = current.next) {
if(key.equals(current.key)) {
current.val = val;//search hit: update val
return;
}
} first = new Node(key, val, first);//search miss: add new node.
n++; }
delete()实现:
public void delete(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function delete"); Node fakehead = new Node(null, null, first); for(Node prev = fakehead; prev.next != null; prev = prev.next) {
if(key.equals(prev.next.key)) {
prev.next = prev.next.next;
n--;
break;
}
} first = fakehead.next; }
整个代码:
package com.qiusongde; import java.util.LinkedList;
import java.util.Queue; import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut; public class SequentialSearchST<Key, Value> { private Node first;
private int n; public SequentialSearchST() {
first = null;
n = 0;
} public void put(Key key, Value val) { if(key == null)
throw new IllegalArgumentException("key is null in function put"); if(val == null) {
delete(key);
return;
} for(Node current = first; current != null; current = current.next) {
if(key.equals(current.key)) {
current.val = val;//search hit: update val
return;
}
} first = new Node(key, val, first);//search miss: add new node.
n++; } //search for key, return associated value
public Value get(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function get"); for(Node current = first; current != null; current = current.next) {
if(key.equals(current.key))
return current.val;//search hit
} return null;//search miss
} public void delete(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function delete"); Node fakehead = new Node(null, null, first); for(Node prev = fakehead; prev.next != null; prev = prev.next) {
if(key.equals(prev.next.key)) {
prev.next = prev.next.next;
n--;
break;
}
} first = fakehead.next; } public Iterable<Key> keys() { Queue<Key> queue = new LinkedList<>(); for(Node cur = first; cur != null; cur = cur.next) {
queue.add(cur.key);
} return queue; } //shorthand methods
public boolean contains(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function contains"); return get(key) != null;
} //shorthand methods
public boolean isEmpty() {
return size() == 0;
} public int size() {
return n;
} //inner class
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 static void main(String[] args) { SequentialSearchST<String, Integer> st = new SequentialSearchST<String, Integer>(); for(int i = 0; !StdIn.isEmpty(); i++) { String key = StdIn.readString(); if(key.equals("-")) {
key = StdIn.readString();
st.delete(key);
StdOut.println("delete " + key);
} else {
st.put(key, i);
StdOut.println("put" + " " + key + " " + i);
} StdOut.print(st.size() + " key-value pairs:");
for(String s : st.keys())
StdOut.print(" " + s + " " + st.get(s));
StdOut.println(); } } }
测试数据:
A
B
C
D
-
B
C
E
F
-
A
B
-
B
-
F
-
D
Test Client的输出结果:
put A 0
1 key-value pairs: A 0
put B 1
2 key-value pairs: B 1 A 0
put C 2
3 key-value pairs: C 2 B 1 A 0
put D 3
4 key-value pairs: D 3 C 2 B 1 A 0
delete B
3 key-value pairs: D 3 C 2 A 0
put C 5
3 key-value pairs: D 3 C 5 A 0
put E 6
4 key-value pairs: E 6 D 3 C 5 A 0
put F 7
5 key-value pairs: F 7 E 6 D 3 C 5 A 0
delete A
4 key-value pairs: F 7 E 6 D 3 C 5
put B 9
5 key-value pairs: B 9 F 7 E 6 D 3 C 5
delete B
4 key-value pairs: F 7 E 6 D 3 C 5
delete F
3 key-value pairs: E 6 D 3 C 5
delete D
2 key-value pairs: E 6 C 5
2、无序链表符号表性能分析
结论1:在含有N个键值对的基于无序链表的符号表中,未命中的查找(search miss)和插入(insert)操作都需要N次比较。命中的查找(search hit)在最坏的情况下需要N次比较。
推论:向一个空表中插入N个不同的键需要~N2/2次比较。
随机查找命中,即在符号表中查找每个键的可能性是相同的,所以平均比较次数是(1+2+……+N)/N = (N+1)/2 ~ N/2
3、有序数组中的二分查找
采用的数据结构是一对平行的数组,一个用于存储键,一个用于存储值。
算法需要保持数组中的Comparable类型的键有序,然后使用数组的索引来高效的实现其他操作。
这份实现的核心是rank方法:
//return the number of keys in the table that are smaller than key
//the heart method
public int rank(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function rank"); 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;
}
对于get方法,如果键在数组中,调用rank方法即可知道键在数组中的位置。否则不存在,返回null。
public Value get(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function get"); if(isEmpty())
return null; int k = rank(key);
if(k < n && keys[k].compareTo(key) == 0)
return vals[k];
else
return null; }
对于put方法,如果键在数组中,调用rank方法即可知道更新该键值的位置,或者插入的位置。
插入的时候,需要将后边的键都往后移动一个位置(对于大数组,移动的开销将会非常大)。
public void put(Key key, Value val) { if(key == null)
throw new IllegalArgumentException("key is null in function put"); if(val == null) {
delete(key);
return;
} //it works when ST is empty(n = 0)
int k = rank(key);
if(k < n && keys[k].compareTo(key) == 0) {
vals[k] = val;//key is already in symbol table
return;
} //move
for(int j = n; j > k; j--) {
keys[j] = keys[j-1];
vals[j] = vals[j-1];
} keys[k] = key;
vals[k] = val;
n++;
}
delete操作:其中对于大数组,移动的开销也会非常大。
public void delete(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function delete"); if(isEmpty())
return; int k = rank(key); if(k < n && keys[k].compareTo(key) == 0) {//key is in symbol table //move
for(int j = k; j < n - 1; j++) {
keys[j] = keys[j+1];
vals[j] = vals[j+1];
} n--; keys[n] = null;// to avoid loitering
vals[n] = null; } }
其余操作:
public Key min() {
if(isEmpty())
return null;
return keys[0];
} public Key max() {
if(isEmpty())
return null;
return keys[n -1];
} public Key select(int k) {
if(k < 0 || k >= n)
return null;
return keys[k];
} public Key ceiling(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function ceiling"); if(isEmpty())
return null; int k = rank(key);
if(k == n)
return null;
else
return keys[k]; } public Key floor(Key key) { if(key == null)
throw new IllegalArgumentException("key is null in function floor"); if(isEmpty())
return null; int k = rank(key);
if(k < n && keys[k].compareTo(key) == 0)
return keys[k];
else if(k == 0)
return null;
else
return keys[k-1]; } public Iterable<Key> keys(Key lo, Key hi) { if(lo == null || hi == null)
throw new IllegalArgumentException("one of the arguements is null in function keys"); Queue <Key> queue = new LinkedList<>(); if(lo.compareTo(hi) > 0 || isEmpty())
return queue;//special case for(int i = rank(lo); i < rank(hi); i++) {
queue.add(keys[i]);
}
if(contains(hi))
queue.add(keys[rank(hi)]); return queue; } //shorthand methods
public boolean isEmpty() {
return size() == 0;
} //shorthand methods
public boolean contains(Key key) {
return get(key) != null;
} public int size() {
return n;
} //shorthand methods
public void deleteMin() {
if(isEmpty())
throw new NoSuchElementException("underflow error");
delete(min());
} //shorthand methods
public void deleteMax() {
if(isEmpty())
throw new NoSuchElementException("underflow error");
delete(max());
} //shorthand methods
public int size(Key lo, Key hi) { if (lo == null)
throw new IllegalArgumentException("first argument to size() is null");
if (hi == null)
throw new IllegalArgumentException("second argument to size() is null"); if(hi.compareTo(lo) < 0)
return 0;
else if(contains(hi))
return rank(hi) - rank(lo) + 1;
else
return rank(hi) - rank(lo);
} //shorthand methods
public Iterable<Key> keys() {
if(isEmpty())
return new LinkedList<>();
return keys(min(), max());
}
Test Client测试结果(数据同上):
put A 0
1 key-value pairs: A 0
put B 1
2 key-value pairs: A 0 B 1
put C 2
3 key-value pairs: A 0 B 1 C 2
put D 3
4 key-value pairs: A 0 B 1 C 2 D 3
delete B
3 key-value pairs: A 0 C 2 D 3
put C 5
3 key-value pairs: A 0 C 5 D 3
put E 6
4 key-value pairs: A 0 C 5 D 3 E 6
put F 7
5 key-value pairs: A 0 C 5 D 3 E 6 F 7
delete A
4 key-value pairs: C 5 D 3 E 6 F 7
put B 9
5 key-value pairs: B 9 C 5 D 3 E 6 F 7
delete B
4 key-value pairs: C 5 D 3 E 6 F 7
delete F
3 key-value pairs: C 5 D 3 E 6
delete D
2 key-value pairs: C 5 E 6
4、二分查找性能分析
结论1:在N个键的有序数组中进行二分查找最多需要logN + 1次比较(无论是否成功)。
但是put和delete这两个方法太慢了。
结论2:向大小为N的数组中插入一个新元素,在最坏的情况下需要访问~2N次数组。因此向一个空的符号表中插入N个元素,在最坏的情况下需要访问~N2次数组。
五、总结
二分查找法适用于静态表(不允许插入),在初始化的时候就进行排序。
但是二分查找法不适用于查找和插入操作是混合进行的,而且符号表非常大的情况。
目前很多应用都需要同时支持高效的查找和插入两种操作。
如何保证查找和插入操作都是对数级别的算法和数据结构?
首先,要支持高效的插入操作,需要一种链式结构;但是单链接的链表是不能支持二分查找法的。
为了将二分查找法的效率和链表的灵活性结合起来,需要更加复杂的数据结构,二叉查找树。
Symbol Table(符号表)的更多相关文章
- 符号表 symbol table 符号 地址 互推
https://zh.wikipedia.org/wiki/符号表 https://en.wikipedia.org/wiki/Symbol_table 在计算机科学中,符号表是一种用于语言翻译器(例 ...
- 符号表实现(Symbol Table Implementations)
符号表的实现有很多方式,下面介绍其中的几种. 乱序(未排序)数组实现 这种情况,不需要改变数组,操作就在这个数组上执行.在最坏的情况下插入,搜索,删除时间复杂度为O(n). 有序(已排序)数组实现 这 ...
- 符号表(Symbol Tables)
小时候我们都翻过词典,现在接触过电脑的人大多数都会用文字处理软件(例如微软的word,附带拼写检查).拼写检查本身也是一个词典,只不过容量比较小.现实生活中有许多词典的应用: 拼写检查 数据库管理应用 ...
- 《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅳ
3.1.4 无序链表中的顺序查找 符号表中使用的数据结构的一个简单选择是链表,每个结点存储一个键值对,如以下代码所示.get()的实现即为遍历链表,用equals()方法比较需被查找的键和每个节点中的 ...
- 《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅲ
3.1.3 用例举例 在学习它的实现之前我们还是应该先看看如何使用它.相应的我们这里考察两个用例:一个用来跟踪算法在小规模输入下的行为测试用例和一个来寻找更高效的实现的性能测试用例. 3.1.3.1 ...
- 《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅱ
3.1.2 有序的符号表 典型的应用程序中,键都是Comparable的对象,因此可以使用a.compare(b)来比较a和b两个键.许多符号表的实现都利用Comparable接口带来的键的有序性来更 ...
- 《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅰ
3.1符号表 符号表最主要的目的就是将一个键和一个值联系起来.用例能够将一个键值对插入符号表并希望在之后能够从符号表的所有键值对中按照键值姐找到对应的值.要实现符号表,我们首先要定义其背后的数据结构, ...
- C/C++编译和链接过程详解 (重定向表,导出符号表,未解决符号表)
详解link 有 些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错 ...
- ELF Format 笔记(七)—— 符号表
最是那一低头的温柔,像一朵水莲花不胜凉风的娇羞,道一声珍重,道一声珍重,那一声珍重里有蜜甜的忧愁 —— 徐志摩 ilocker:关注 Android 安全(新手) QQ: 2597294287 符号表 ...
随机推荐
- MySQL四:表操作
阅读目录 表介绍 一 创建表 二 查看表结构 三 数据类型 四 表完整性约束 五 修改表ALTER TABLE 六 复制表 七 删除表 八 完整性约束 九 数据类型 表介绍 表相当于文件,表中的一条记 ...
- CentOS7如何使用U盘安装
前段时间给一台没有光驱的PC安装CentOS7(CentOS-7.0-1406-x86_64-DVD.iso),惯例直接用Universal-USB-Installer直接转换镜像至U盘,顺利启动,却 ...
- python学习 02 元组
元组和列表除了能不能修改外 定义单一元组还需要加逗号
- 在express项目中使用redis
在express项目中使用redis 准备工作 安装redis 安装redis桌面管理工具:Redis Desktop Manager 项目中安装redis:npm install redis 开始使 ...
- hdu 5881 Tea (2016 acm 青岛网络赛)
原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=5881 Tea Time Limit: 3000/1000 MS (Java/Others) Me ...
- BZOJ 1602 [Usaco2008 Oct]牧场行走 dfs
题意:id=1602">链接 方法:深搜暴力 解析: 这题刚看完还有点意思,没看范围前想了想树形DP,只是随便画个图看出来是没法DP的,所以去看范围. woc我没看错范围?果断n^2暴 ...
- Web客户端语言HTML、XHTML和XML相关知识介绍
HTML简介 HTML(Hyper Text Mark-up Language)即超文本标记语言或超文本链接标示语言,是目前网络上应用最为广泛的语言,也是构成网页文档的主要语言.HTML文本是由HTM ...
- 字符串HASH模板
//注意MAXN是最大不同的HASH个数,一般HASHN是MAXN的两倍左右,MAXLEN表示字符串的最大长度 //K表示正确率,越大正确率越高,当时也越费空间,费时间. //使用前注意初始化hash ...
- is assembler instruction and machine instuction atomic
1 assembler instruction depends,有的汇编指令会被assemble成多条机器指令. 2 机器指令 depends,有的机器指令也不是atomic的. 所以,不要希望在单条 ...
- images have the “stationarity” property, which implies that features that are useful in one region are also likely to be useful for other regions.
Convolutional networks may include local or global pooling layers[clarification needed], which combi ...