查找
查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
查找表分类
:静态查找表和动态查找表。
动态查找表
:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
平均查找长度
(Average Search Length,ASL)
需和指定key进行比较的关键字的个数的期望值,成为查找算法在查找成功时的平均查找长度。
对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。
对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。
1.顺序查找 :
查找
成功时的平均查找长度为:(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
当查找 不成功时,需要n+1次比较,时间复杂度为O(n);
当查找 不成功时,需要n+1次比较,时间复杂度为O(n);
所以,顺序查找的时间复杂度为O(n)。
/* 顺序查找,a为数组,n为要查找的数组元素个数,key为要查找的关键字*/
int Sequential_Search(int *a, int n, int key)
{
int i = 0;
for(; i < n; i++)
{
if(a[i] == key)
return i;
}
return 0;
}
/*有哨兵顺序查找-优化最简单的顺序查找*/
/*优化部分:a[0]存放要查找的关键字key,减少了数组越界的比较,如果查找表长度很大,还是比最简单的顺序查找快很多的。a[0] = key的目的就是让上述的简单顺序查找的两次判断修正为一次a[i]与a[0]是否相等的一次判断。
// 利用了算法设计中一个重要的“加速原理”:当算法的一个内循环要测试两个或多个条件时,应力图将其减少成一个条件
int Sequential_Search2(int *a, int n, int key)
{
int i;
a[0] = key;
for(i = n; a[i] != a[0]; i--);
return i;
}
2.有序表查找
2.1 折半查找
前提:
线性表中的记录必须是关键字有序(通常从小到大),线性表必须采用
顺序存储。
基本思想:取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录左半区继续查找;否则,在右半区查找。不断重复,知道查找成功或者查找失败为止
。
/*折半查找,非递归*/
int Binary_Search(int *a, int n, int key)
{
int low, high;
int mid;
low = 1;
high = n;
while(low < high)<span style="white-space:pre"> </span>//可以有等号 low < = high ,不影响判断。
{
mid = (low + high) / 2; <span style="white-space:pre"> </span>//可以修正为 mid = (low + high) >> 1;
if(a[mid] == key)
return mid;
if(a[mid] > key)
high = mid - 1;
if(a[mid] < key)
low = mid + 1;<span style="white-space:pre"> </span>//对于if else语句,可以考虑条件表达式,减少代码的行数。
}
return 0;
}
/*折半查找,递归实现*/
int Binary_Search2(int *a, int low, int high, int key)
{
int mid = (low + high) / 2;
if(a[mid] == key)
return mid;
if(a[mid] > key)
return Binary_Search2(a, low, mid-1, key); //有没有return都可以。
else
return Binary_Search2(a, mid+1, high, key); //有没有return都可以。
}
注:
(1)对半查找二叉树T(s,e)的高度为[log2(s-e+2)];
(2)最坏情况下,关键词比较次数为[log2(N+1)],且期望复杂性等于O(log2N);
(3)折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要
频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
2.3斐波那契查找
《斐波那契查找》 http://blog.csdn.net/shenbo2030/article/details/44559937
最坏情况下,时间复杂度为O(log2N),且其期望复杂度也为O(log2N)。
2.2 插值查找(利用线性插值来决定K的期望地址)
首先考虑一个新问题,为什么一定要是折半,而不是折四分之一或者折更多呢?
打个比方,在英文字典里面查“apple”,你下意识翻开字典是翻前面的书页还是后面的书页呢?如果再让你查“zoo”,你又怎么查?很显然,这里你绝对不会是从中间开始查起,而是有一定目的的往前或往后翻。
同样的,比如要在取值范围1 ~ 10000 之间 100 个元素从小到大均匀分布的数组中查找5, 我们自然会考虑从数组下标较小的开始查找。
经过以上分析,折半查找这种查找方式,还是有改进空间的,并不一定是折半的!
mid = (low+high)/ 2, 即 mid = low + 1/2 * (high - low);
改进为下面的计算机方案(不知道具体过程):mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low),也就是将上述的比例参数1/2改进了,根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。
注:
对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么差值查找未必是很合适的选择;
查找成功或者失败的时间复杂度均为O(log2(log2N))。