查找:
- 查找(Searching)就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
- 查找表(Search Table)是由同一类型的数据元素(或记录)构成的集合。
- 关键字(Key)是数据元素中某个数据项的值,又称为键值。
- 若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(Primary Key)。
- 对于那些可以识别多个数据元素(或记录)的关键字,我们称为次关键字(Secondary Key),次关键字也可以理解为是不以唯一标识一个数据元素(或记录)的关键字,它对应的数据项就是次关键码。
静态查找表(Static Search Table):只作查找操作的查找表。
- 查询某个“特定的”数据元素是否在查找表中。
- 检索某个”特定的“数据元素和各种属性。
动态查找表(Dynamic Search Table):在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
- 查找时插入数据元素。
- 查找时删除数据元素。
顺序表查找
顺序查找(Sequential Search)又叫线性查找,是最基本的查找技术,它的查找过程是:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功。
- 顺序表查找算法
/* 顺序查找,a为数组,n为要查找的数组个数,key为要查找的关键字 */
int Sequential_Search(int *a, int n, int key)
{
int i;
for (i=1; i<=n; i++)
{
if (a[i] == key)
return i;
}
return 0;
}
- 顺序表查找优化
/* 有哨兵顺序查找 */
int Sequential_Search2(int *a, int n, int key)
{
int i;
a[0] = key; /* 设置a[0]为关键字值,我们称之为“哨兵” */
i = n; /* 循环从数组尾部开始 */
while (a[i] != key)
{
i--;
}
return i; /* 返回0则说明查找失败 */
}
- 这种方法免去了在查找过程中每一次比较后都要判断查找位置是否越界的小技巧,在数据较多时,效率提高很大,是非常好的编码技巧。
- 算法时间复杂度为O(n)。查找效率极为低下,算法非常简单。
有序表查找
折半查找
折半查找(Binary Search)技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序,线性表必须采用顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。
/* 折半查找 */
int Binary_Search(int *a, int n, int key)
{
int low, high, mid;
low = 1; /* 定义最低下标为记录首位 */
high = n; /* 定义最高下标为记录末位 */
while (low <= high)
{
mid = (low+high) / 2; /* 折半 */
if (key < a[mid]) /* 若查找值比中值小 */
high = mid-1; /* 最高下标调整到中位下标小一位 */
else if (key > a[mid]) /* 若查找值比中值大 */
low = mid+1; /* 最低下标调整到中位下标大一位 */
else
return mid; /* 若相等则说明mid即为查找到的位置 */
}
return 0;
}
- 每次查找范围少了一半,查找效率很高,时间复杂度为O(logn)。
- 折半查找的前提条件是需要有序表顺序存储,对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。
插值查找
在折半查找算法代码的基础上更改一下
mid = low + (high-low) * (key-a[low]) / (a[high]-a[low]); /* 插值 */
就得到了另一种有序表查找算法,插值查找法。插值查找(Interpolation Search)是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式。
- 时间复杂度为O(logn)。
- 对于表长较大,而关键字分布比较均匀的查找表来说,插值查找算法的平均性能比折半查找好得多。反之,数组中如果分布极端不均匀的数据,用插值查找未必是很合适的选择。
斐波那契查找
- 斐波那契查找(Fibonacci Search),它是利用了黄金分割原理来实现的。
- 我们先需要有一个斐波那契数列的数组。
/* 斐波那契查找 */
int Fibonacci_Search(int *a, int n, int key)
{
int low, high, mid, i, k;
low = 1; /* 定义最低下标为记录首位 */
high = n; /* 定义最高下标为记录末位 */
k = 0;
while (n > F[k]-1) /* 计算n位于斐波那契数列的位置 */
k++;
for (i=n; i<F[k]-1; i++) /* 将不满的数值补全 */
a[i] = a[n];
while (low <= high)
{
mid = low+F[k-1]-1; /* 计算当前分隔的下标 */
if (key < a[mid]) /* 若查找记录小于当前分隔记录 */
{
high = mid-1; /* 最高下标调整到分隔下标mid-1处 */
k= k-1; /* 斐波那契数列下标减一位 */
}
else if (key > a[mid]) /* 若查找记录大于当前分隔记录 */
{
low = mid+1; /* 最低下标调整到分隔下标mid+1处 */
k = k-2; /* 斐波那契数列下标减两位 */
}
else
{
if (mid <= n)
return mid; /* 若相等则说明mid即为查找到的位置 */
else
return n; /* 若mid>n说明是补全数值,返回n */
}
}
return 0;
}
斐波那契查找算法的核心在于:
- 当key=a[mid]时,查找就成功;
- 当key<a[mid]时,新范围是第low个到第mid-1个,此时范围个数为F[k-1]-1个;
- 当key>a[mid]时,新范围是第m+1个到第high个,此时范围个数为F[k-2]-1个。
斐波那契查找的时间复杂度为O(logn),但平均性能来说,斐波那契查找要优于折半查找。
总结
- 折半查找是进行加法与除法运算(mid=(low+high)/2),插值查找进行复杂的四则运算(mid=low+(high-low)*(key-a[low])/(a[high]-a[low])),而斐波那契查找只是最简单加减法运算(mid=low+F[k-1]-1),在海量数据的查找过程中,这种细微的差别可能会影响最终的查找效率。
- 三种有序表的查找本质上是分隔点的选择不同,各有优劣,实际开发时可根据数据的特点综合考虑再做出选择。