插值查找-有序表查找
- 前言
有序表的查找一般分为二分查找(折半查找),斐波那契查找以及插值查找方法,前面我们讨论了斐波那契查找算法的具体实现,本文着重讨论插值查找算法。
插值查找只适用于关键字均匀分布的情况,在这种情况下,对于表厂较长的表,其查找的平均性能比折半查找要优异,如果关键字分布不均匀,其查找效率相对而言会很低,尤其是某个关键字很大,它对查找位置的影响就非常大,从而影响分割下标的移动速度。
插值查找也是利用分治的方法对表进行分割,与二分查找不同的地方在于,它不是对有序表进行均匀分割,而是按照关键字的大小进行比例分割。
- 具体算法
算法的核心思想是对分割查找点的计算,《数据结构》严蔚敏版本给出的定义是,
s
p
l
i
t
=
(
(
k
e
y
−
s
t
.
e
l
e
m
[
l
]
.
k
e
y
)
∗
(
h
−
l
+
1
)
)
/
(
s
t
.
e
l
e
m
[
h
]
.
k
e
y
−
s
t
.
e
l
e
m
[
l
]
.
k
e
y
)
split=((key - st.elem[l].key) * (h - l + 1))/ (st.elem[h].key - st.elem[l].key)
split=((key−st.elem[l].key)∗(h−l+1))/(st.elem[h].key−st.elem[l].key)
相当于把最大值和最小值进行等分,然后按照待查询关键字与最小关键字之间的间距,确认查询点距离low位置的长度。
由于有序表中可能存在相邻两个相等的元素或者low==high的情况,在这种情形之下,就需要特别处理,否则分母为零,计算会出现异常。
定义当st.elem[h].key== st.elem[l].key相等的时候,split=(l+m)/2的值取中间即可。通过定义函数,具体查找点的位置算法,
int find_interpolation_point(SSTable st, int key, int l, int h)
{
if(EQ(st.elem[l].key,st.elem[h].key))
{
return (l+h)/2;
}
else
{
return (((key - st.elem[l].key) * (h - l + 1))/ (st.elem[h].key - st.elem[l].key));
}
}
- 其它主要算法的实现
利用迭代或者递归的方式实现插值查找,首先采用迭代的方式进行查找,
int interpolation_search_iteration(SSTable st, KeyType key)
{
int low;
int high;
int split;
low=1;
high=st.len;
while(low<=high)
{
split = find_interpolation_point(st, key, low, high) + low;
if (EQ(key, st.elem[split].key))
{
return split;
}
else if (LT(key, st.elem[split].key))
{
high = split - 1;
}
else
{
low = split + 1;
}
}
return 0;
}
特别值得一提的细节是,正常情况下split的查找点为:
s
p
l
i
t
=
f
i
n
d
_
i
n
t
e
r
p
o
l
a
t
i
o
n
_
p
o
i
n
t
(
s
t
,
k
e
y
,
l
o
w
,
h
i
g
h
)
+
l
o
w
−
1
split = find \_\ interpolation\_point(st, key, low, high) + low-1
split=find_ interpolation_point(st,key,low,high)+low−1
由于计算分割点的函数做了舍去处理,所以这里需要采用的计算方式:
s
p
l
i
t
=
f
i
n
d
_
i
n
t
e
r
p
o
l
a
t
i
o
n
_
p
o
i
n
t
(
s
t
,
k
e
y
,
l
o
w
,
h
i
g
h
)
+
l
o
w
;
split = find\_interpolation\_point(st, key, low, high) + low;
split=find_interpolation_point(st,key,low,high)+low;
如果采用第一种方式,就会出现分割点小于最小值的情况,读者可以自行尝试。
我们也尝试采用递归的方式进行插值查找,其思路与迭代相同:
int interpolation_search_recursion(int low, int high, SSTable st, KeyType key)
{
int split;
int low_side;
int high_side;
if(low>high)
{
return 0;
}
else
{
split = find_interpolation_point(st, key, low, high) + low;
if (EQ(key, st.elem[split].key))
{
return split;
}
else if (LT(key, st.elem[split].key))
{
low_side = interpolation_search_recursion(low, split - 1, st, key);
return low_side;
}
else
{
high_side = interpolation_search_recursion(split + 1, high, st, key);
return high_side;
}
}
}
- 算法分析
如果数值分布不均一,那么split的值移动的速度会非常慢,导致查询效率比二叉树低,算法时间复杂度过高。读者可以分析有序数组arr[]={5,13,19,21,37,56,64,75,80,88,92,157,200,235,270}在利用插值查找88的时候出现的情况。
- 小结
通过本文分析,理解了插值查找的应用场景以及局限性,另外对于插值点查询函数做了些许优化,在遇到相邻数值相等的有序表中,采用中值替代插值点。
参考资料:
1.《数据结构(C语言版)》-严蔚敏,清华大学出版社