查找(顺序,有序)总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014186096/article/details/48971529

一、代码示例

<span style="font-size:18px;">#include <stdio.h>

int Binary_Search_Recursion(int *a,int low,int high,int key);
int F(int i);
/*顺序查找,a为数组,len为要查找的数组个数,key为要查找的关键字*/  //代码1
int Sequential_Search(int *a,int len,int key)
{
	int i;
	for(i=1;i<len;i++)
	{
		if(a[i]==key)
			return i;
	}
	return -1;  /*返回-1则查找失败*/
}
/*有哨兵顺序查找*/   //代码2
int Sequential_Search2(int *a,int len,int key)
{
	int i;
	a[0]=key;
	i=len-1;                /*循环从队尾开始*/
	while(a[i]!=key)
	{
		i--;
	}
	return i;   /*返回0则说明查找失败*/
}

/*折半查找(二分法)的常规方法*/    //代码3
int Binary_Search(int *a,int len,int key)
{
	if(a==0||len<=0)  /*防御性编程*/
		return -1;
	int low=1,high=len-1,mid; /*初始化*/
	while(low<=high)
	{
		mid = low+(high-low)/2;   /*防止变量溢出*/
		if(key<a[mid])
			high=mid-1;
		else if(key>a[mid])
			low=mid+1;
		else 
			return mid;

	}
	return -1;

}

/*折半查找--递归法*/     //代码4
int Binary_Search_Recursion(int *a,int len,int key)
{
	if(a==0||len<=0)
		return -1;
	return Binary_Search_Recursion(a,0,len-1,key);
}
int Binary_Search_Recursion(int *a,int low,int high,int key)
{
	if (low<=high)
	{
		int mid = low + (high-low)/2;
		if(key<a[mid])
			return Binary_Search_Recursion(a,low,mid-1,key);
		else if(key>a[mid])
			return Binary_Search_Recursion(a,mid+1,high,key);
		else 
			return mid;
	}
	return -1;
}

/*插值查找*/    //代码5
int Interpolation_Search(int *a,int len,int key)
{
	if(a==0||len<=0)  /*防御性编程*/
		return -1;
	int low=1,high=len-1,mid; /*初始化*/
	while(low<=high)
	{
		mid = low+(high-low)*(key-a[low])/(a[high]-a[low]);   /*插值,折半中的1/2换成(key-a[low])/(a[high]-a[low])*/
		if(key<a[mid])
			high=mid-1;
		else if(key>a[mid])
			low=mid+1;
		else 
			return mid;

	}
	return -1;
}

/*斐波那契查找*/    //代码6
int F(int i)
{
	if(i<2)
		return i==0? 0:1;
	return F(i-1)+F(i-2);
}
int Fibonacci_Search(int *a,int n,int key)
{
	int low=1,high=n,mid,i,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;
			k=k-1;
		}
		else if (key>a[mid])
		{
			low=mid+1;
			k=k-2;
		}
		else
		{
			if(mid<=n)
				return mid;
			else
				return n;
		}
	}
	return -1;
}
int main(void)
{
	int a[11]={0,1,16,24,35,47,59,62,73,88,99};
	int len=sizeof(a)/sizeof(a[0]);
	int sequential_i=Sequential_Search(a,len,0);
	int sequential_j=Sequential_Search2(a,len,62);
	int binary_i=Binary_Search(a,len,62);
	int binary_j=Binary_Search_Recursion(a,len,62);
	int interpolation_i=Interpolation_Search(a,len,62);
	int fibonacci_i=Fibonacci_Search(a,len-1,59);
	printf("sequential_i=%d,sequential_j=%d,binary_i=%d,binary_j=%d,interpolation_i=%d,fibonacci_i=%d\n",sequential_i,sequential_j,binary_i,binary_j,interpolation_i,fibonacci_i);
	return 0;
}</span>

运行结果:


二、查找概论

查找表是由同一类型是数据元素(或记录)构成的集合。关键字是数据元素中某个数据项的值,又称为键值。若此关键字可以唯一标识一个记录,则称此关键字为主关键字。查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。

查找分为两类:静态查找表和动态查找表
静态查找表:只作查找操作的查找表。主要操作:

(1)查询某个“特定的”数据元素是否在查找表中。

(2)检索某个“特定的”数据元素和各种属性。

动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经已经存在的某个数据元素。 主要操作:

(1)查找时插入数据元素。

(2)查找时删除数据元素。

两者的区别: 静态查找表只负责查找任务,返回查找结果。而动态查找表不仅仅负责查找,而且当它发现查找不存在时会在表中插入元素(那也就意味着第二次肯定可以查找成功)。

三、顺序表

顺序表查找又称为线性查找,是最基本的查找技术。 它的查找思路是:
逐个遍历记录,用记录的关键字和给定的值比较;若相等,则查找成功,找到所查记录; 反之,则查找不成功。
代码如"代码1、代码2",对于这种查找算法,查找成功最好就是第一个位置找到,时间复杂度为O(1)。最坏情况是最后一个位置才找到,需要n次比较,时间复杂度为O(n) 显然,n越大,效率越低下。

四、有序表

所谓有序表,是指线性表的数据有序排列。

1.折半查找

代码如“代码3、代码4”,其中代码4用了递归,使代码更简单。折半查找的时间复杂度为O(logn)。

2.插值查找

如代码5:

:

3.斐波那契

代码如6,

如果一个有序表的元素个数为n,并且n正好是某个斐波那契数-1,即n == F[k]-1时,才能用斐波那契查找法。

(1). 如果有序表的元素个数n不等于某个斐波那契数-1,即n != F[k]-1,如何处理呢?

 这时必须要将有序表的元素个数扩展到比n大的第一个斐波那契数-1的个数才符合算法的查找条件。通俗点讲,也就是为了使用斐波那契查找法,那么要求所查找顺序表的元素个数n必须满足n == F[k]-1这样的条件才可以。因为查找表为从小到大的顺序表,所以如果数据元素个数不满足要求,只有在表末用顺序表的最大值补满。

(2). 对于二分查找,分割点是从mid= (low+high)/2开始。

 而对于斐波那契查找,分割是从mid = low + F[k-1] - 1开始的。 为什么如此计算?

 用实例验证,比如本例中: 第一次进入查找循环时,数组元素个数准确说应该是12(包括随后补满的元素),而黄金分割点比例为0.618,那么12*0.618=7.416,此值对应12个元素应该为a[8],观察程序运行第一次mid=1+F[7-1]-1=8,正是此原理所体现。

 key=59,a[8]=73,显然key<a[8],可知low=1,high=7,k=7-1=6

 注意此条件意思即为7个数据元素,正好满足F[6]-1=7的再次查找客观要求。而同理,黄金分割点比例为0.618,那么7*0.618=4.326,此值对应7个元素应该为a[5],再看第二次进入循环mid=1+F[6-1]-1=5,正是此原理所体现。

 key=59,a[5]=47,显然key>a[5],可知low=6,high=7,k=6-2=4。

 注意此条件意思即为2个数据元素,正好满足F[4]-1=2的再次查找客观要求,而同理黄金分割点比例为0.618,那么2*0.618=1.236,此值对应2个元素中的第二个即为a[7]。key=59,a[7]=62,显然key<a[7],可知low=6,high=6,k=4-1=3。

 同理mid=6+F[3-1]-1=6。此时a[6]=59=key。 即查找成功。

(3). 注意紧接着下面一句代码可以改写为:return  (mid <= n) ? mid : n;

 当然这样写也没有功能错误,但是细细琢磨还是有逻辑问题:mid == n时,返回为n; mid > n时返回也是n。

 那么到底n属于那种情况下的返回值呢?是否有违背if的本质!窃以为写成if(mid < n)会合理些。

 另外,许多资料对于这步判断描述如下:

 return  (mid <= high) ? mid : n;

 其实分析至此,我认为这种写法从代码逻辑而言更为合理。

(4). 通过上面知道:数组a现在的元素个数为F[k]-1个,即数组长为F[k]-1。

 mid把数组分成了左右两部分,左边的长度为:F[k-1]-1

 那么右边的长度就为(数组长-左边的长度-1): (F[k]-1)-(F[k-1]-1)= F[k]-F[k-1]-1 = F[k-2] - 1

(5.) 斐波那契查找的核心是:

a: 当key == a[mid]时,查找成功;

b: 当key<a[mid]时,新的查找范围是第low个到第mid-1个,此时范围个数为F[k-1] - 1个,

 即数组左边的长度,所以要在[low, F[k - 1] - 1]范围内查找;

c: 当key>a[mid]时,新的查找范围是第mid+1个到第high个,此时范围个数为F[k-2] - 1个,

 即数组右边的长度,所以要在[F[k - 2] - 1]范围内查找。

关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去。

对处于中间的大部分数据,其工作效率要高一些。

所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。

可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找。   

还有关键一点:折半查找是进行加法与除法运算的(mid=(low+high)/2),插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low]))),而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1]-1)。

在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。


猜你喜欢

转载自blog.csdn.net/u014186096/article/details/48971529