将同一类型的数据元素构成的集合称之为查找表,如果仅对查找表进行查找操作,则此类查找表称之为静态查找表;如果除了查找操作还会对表执行插入删除操作,则此类查找表称之为动态表。本文讨论静态表的查找算法。
顺序查找
顺序查找的思想非常简单,就是从表中的第一个元素开始,对表进行遍历,将每个元素与待查找值进行比较。如果某个元素与待查找值相等,则查找成功,反之查找失败。
顺序查找虽然简单,但是它的平均查找长度较大。在只考虑查找成功且每个元被查找的概率相等的情况下,算法的平均查找长度为
n
+
1
2
\frac{n+1}2
2n+1 。若考虑查找失败的情况,则算法的平均查找长度为
3
(
n
+
1
)
4
\frac{3(n+1)}4
43(n+1) 。
折半查找
如果查找表有序,则可用折半查找。
折半查找又称为二分查找,它先确定待查元素所在的范围,然后通过折半的方式不断缩小这个范围,直到找到元素为止。
下面举一个例子,在11个元素中查找值为21的元素。
第一轮:
第二轮:
第三轮:
上面的过程可以用一个二叉树来描述,通常称这个二叉树为判定树。
C++代码
//对整数数组进行折半查找
int BinarySearch(vector<int> &list,int value) {
int low = 0, high = list.size(), mid = 0;
while (low < high)
{
mid = (low + high) / 2;
if (list[mid] > value) {
high = mid;
}
else if (list[mid] < value) {
low = mid + 1;
}
else {
return mid;
}
}
return -1;
}
算法性能
折半查找的效率比顺序查找高,算法的平均查找长度近似为 l o g 2 ( n + 1 ) − 1 log_2{(n+1)}-1 log2(n+1)−1 。
当查找表有序时,还可以使用斐波那契查找。与折半查找类似,它先确定待查元素所在的范围,但是它按照斐波那契序列的规则不断缩小这个范围(具体步骤不再赘述)。斐波那契查找的平均性能比折半查找好,但是最坏性能比折半查找差。
静态最优查找树
上面两种算法都是在所有元素被等概率查找的前提下进行的,当元素被查找的概率不等时上面的算法未必是一个好的选择。
只考虑查找成功的情况下,使查找性能达最佳的判定树是其带权内路径长度之和取最小的二叉树,称此二叉树为静态最优查找树。
带权内路径长度定义为
P
H
PH
PH:
P
H
=
∑
i
=
1
n
ω
i
h
i
PH = \sum_{i=1}^n\omega_ih_i
PH=i=1∑nωihi
其中
ω
\omega
ω为结点的权,
h
h
h为节点在二叉树的层次数。
一般求取静态最优查找树的算法时间代价很高,所以我们一般求次优查找树,它的带权内路径长度之和在具有相同权值的二叉树中近似最小。
构造次优查找树的方法
定义
△
P
i
=
∣
∑
j
=
i
+
1
h
ω
j
−
∑
j
=
l
i
−
1
ω
j
∣
\bigtriangleup P_i = |\sum_{j=i+1}^h\omega_j-\sum_{j=l}^{i-1}\omega_j|
△Pi=∣j=i+1∑hωj−j=l∑i−1ωj∣
首先取表中第
i
i
i个元素作为根节点,使得
△
P
i
\bigtriangleup P_i
△Pi最小,然后分别对子序列
{
r
l
,
r
l
+
1
,
.
.
.
,
r
i
−
1
}
\{r_l,r_{l+1},...,r_{i-1}\}
{rl,rl+1,...,ri−1}和
{
r
i
+
1
,
r
i
+
2
,
.
.
.
,
r
h
}
\{r_{i+1},r_{i+2},...,r_h\}
{ri+1,ri+2,...,rh}构造次优查找树。重复上述步骤,直到查找树构造完毕。
为了方便计算和实现,引入累计加权和
s
i
=
∑
j
=
l
i
ω
j
s_i = \sum_{j=l}^i\omega_j
si=j=l∑iωj则有
∑
j
=
i
+
1
h
ω
j
=
s
h
−
s
i
∑
j
=
l
i
−
1
ω
j
=
s
i
−
1
−
s
l
−
1
△
P
i
=
∣
s
h
+
s
l
−
1
−
s
i
−
s
i
−
1
∣
\sum_{j=i+1}^h\omega_j=s_h-s_i\\ \sum_{j=l}^{i-1}\omega_j=s_{i-1}-s_{l-1}\\ \bigtriangleup P_i =|s_h+s_{l-1}-s_i-s_{i-1}|
j=i+1∑hωj=sh−sij=l∑i−1ωj=si−1−sl−1△Pi=∣sh+sl−1−si−si−1∣
C++代码
//对整数数组构建次优查找树
struct BiTree {
int data;
BiTree* left;
BiTree* right;
BiTree() :data(0), left(NULL), right(NULL) {};
~BiTree() {
if (left != NULL)delete left;
if (right != NULL)delete right;
}
};
BiTree* SecondOptimal_R(vector<int> &list, vector<int> &s, int low, int hight);
BiTree* CreateSecondOptimal(vector<int> &list) {
vector<int> s;
int accumulation = 0;
for (auto item : list) {//假设元素的值就是它的权重
accumulation += item;
s.push_back(accumulation);
}
int hight = list.size()-1, low = 1,min = abs(s[hight]-s[low]),min_ind = low;
for (int i = low; i <= hight; i++) {
if (abs(s[hight] + s[low - 1] - s[i] - s[i - 1]) < min) {
min_ind = i;
min = abs(s[hight] + s[low - 1] - s[i] - s[i - 1]);
}
}
BiTree* bitree = new BiTree();
bitree->data = list[min_ind];
if (min_ind != low)bitree->left = SecondOptimal_R(list, s, low, min_ind - 1);
if (min_ind != hight)bitree->right = SecondOptimal_R(list, s, min_ind + 1, hight);
return bitree;
}
BiTree* SecondOptimal_R(vector<int> &list, vector<int> &s,int low,int hight) {
int min = abs(s[hight] - s[low]), min_ind = low;
for (int i = low + 1; i <= hight; i++) {
if (abs(s[hight] + s[low - 1] - s[i] - s[i - 1]) < min) {
min_ind = i;
min = abs(s[hight] + s[low - 1] - s[i] - s[i - 1]);
}
}
BiTree* bitree = new BiTree();
bitree->data = list[min_ind];
if (min_ind != low)bitree->left = SecondOptimal_R(list, s, low, min_ind - 1);
if (min_ind != hight)bitree->right = SecondOptimal_R(list, s, min_ind + 1, hight);
return bitree;
}
算法性能
大量实验证明,最优查找树和次优查找树的查找性能仅差1%~2%,而构造次优查找树的时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。次优查找树的查找过程可以看做是从树根到待查元素所在的节点的一条路径,进行比较的次数不超过树的深度,所以次优查找树的平均查找长度和
l
o
g
n
logn
logn称正比。
分块查找
分块查找又称索引顺序查找,是顺序查找的一种改进算法。该算法需要对原查找表进行分块,并且针对每个块建立一个索引表。其具体要求如下:
- 将原查找表进行分块,分成若干个子表。
- 对于每个子表,将子表中第一个记录的位置和子表中最大值存入索引表。
- 确保索引表有序,则原查找表需要有序或分块有序。
按照上述规则,可以构造出如下结构:
接下来分块查找分两步进行:
- 首先经待查找元素的值与索引表中存储的各个分块的的最大值进行比较,找到待查找元素所在的分块。
- 然后在块中查找元素的具体位置。
由于索引表有序,所以第一步可以使用折半查找,而块中元素不一定有序,所以第二步只能使用顺序查找。
算法性能
分块查找的平均查找长度为索引表中查找元素所在块的平均查找长度与块中查找元素的平均查找长度之和。
设分块的大小为
s
s
s,若使用顺序查找确定所在块,则平均查找长度为:
1
2
(
n
s
+
s
)
+
1
\frac12(\frac{n}s+s)+1
21(sn+s)+1
若使用折半查找确定所在块,则平均查找长度为:
l
o
g
(
n
s
+
1
)
+
n
s
log(\frac{n}s+1)+\frac{n}s
log(sn+1)+sn