符号表表示一张抽象的表格,我们将信息存储到其中,然后按照指定的键来搜索并获取这些信息。键和值的具体意义取决于不同的应用。符号表中可能会保存很多键和很多信息,因此实现一张高效的符号表也是一项很有挑战性的任务。符号表也称为字典。
符号表最主要的目的就是将一个键和一个值联系起来。用例能够将一个键值对插入符号表中,并希望在之后能够从符号表的所有键值对中按照键直接找到相对应的值。
符号表是一种存储键值的数据结构,支持两种操作:插入( put ),即将一组新的键值对存入表中;查找( get ),即根据给定的键得到相应的值。
无序链表中的顺序查找
我们可以很容易想到使用链表实现,每个结点存储一个键值对,get() 的实现即为遍历链表,如果匹配成功则返回相应的值,put() 的实现也是遍历链表,如果匹配成功则用第二个参数指定的值更新该键相关联的值,否则就用给定的键值对创建一个新的结点并将其插入到链表。这种方法被称为顺序查找。
代码:
template <typename T, typename U>
class SequentialSearchST
{
private:
struct node
{
T key;
U val;
node* next = nullptr;
node(T key, U val, node* next) : key(key), val(val), next(next) {}
};
node* first = nullptr;
public:
void put(T key, U val) {
for (node* x = first; x != nullptr; x++)
if (x->key == key)
{
x->val = val;
return;
}
first = new node(key, val, first);
}
U get(T key) {
for (node* x = first; x != nullptr; x = x->next)
if (x->key == key)
return x->val;
return 0;
}
};
在含有 N 对键值对的基于链表中,未命中的查找和插入操作都需要 N 次比较。命中的查找在最坏情况下需要 N 次比较。向一个空表中插入 N 个不同的键需要 N 2 / 2 N^2 / 2 N2/2 次比较
基于链表的实现以及顺序查找是非常低效的。
有序数组中的二分查找
很多应用中,键都是可以比较的对象,因此很多符号表的实现都利用了键的有序性来更好的实现插入和查找方法,并且可以高效的实现更多功能。
该实现的核心是 rank() 方法,它返回表中小于给定键的键的数量。
代码:
#include <vector>
template <typename T, typename U>
class BinarySearchST
{
private:
std::vector<T> keys;
std::vector<U> vals;
int N = 0;
public:
int rank(T key) {
int lo = 0, hi = N - 1;
while (lo <= hi)
{
int mid = lo + (hi - lo) / 2;
if (key < keys[mid])
hi = mid - 1;
else if (key > keys[mid])
lo = mid + 1;
else
return mid;
}
return lo;
}
U get(T key) {
int i = rank(key);
if (i < N && keys[i] == key)
return vals[i];
return 0;
}
void put(T key, U val) {
int i = rank(key);
if (i < N && keys[i] == key)
{
vals[i] = val;
return;
}
keys.resize(N + 1);
vals.resize(N + 1);
for (int j = N; j > i; j--)
{
keys[j] = keys[j-1];
vals[j] = vals[j-1];
}
keys[i] = key;
vals[i] = val;
N++;
}
};
使用有序数组存储键的原因是使用二分查找能够根据数组的索引大大减少每次查找所需的比较次数。
在 N 个键的有序数组中进行二分查找最多需要 l n N + 1 lnN+1 lnN+1 次比较(无论是否成功)
二分查找减少了比较的次数,能够保证查找所需的时间是对数级别的,但是无法减少运行所需时间,因为 put() 方法太慢,构造一个基于有序数组的符号表所需要访问数组的次数是数组长度的平方级别。
向大小为 N 的有序数组中插入一个新的元素在最坏情况下需要访问 2N 次数组,因此向一个空符号表中插入 N 个元素在最坏的情况下需要访问 N 2 N^2 N2 次数组
所以,以上两种方法都不可行,下一节介绍二叉查找树。