首页 > 代码库 > 折半查找

折半查找

所谓折半查找,又称二分查找,是一种在有序数组中查找某一特定元素的搜索算法。优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
复杂度:
时间复杂度:由于折半搜索每次把搜索区域减少一半,时间复杂度为O(log n),其时间复杂度使得在数据量很大的时候,使用折半查找显得异常高效(就像"汉诺塔"和"数麦粒"的故事一样,增速迅猛,麦粒总数和金片移动次数均为2^64-1一样,那时人们面对海量数据毫无概念,根本想象不到数据有多大),直观点假设有40亿个有序序列,我们要查找其中的一个时最多需要32次比较,其高效性在这里就体现的淋漓尽致了。
空间复杂度:O(1)。

要求:

数据必须采用顺序存储结构,必须按关键字大小有序排列。

下面举一例子:

问题描述:
有一个数组A[10], 里面存放了10个整数,顺序递增。
A[10] = {2, 3, 5, 7, 8, 10, 12, 15, 19, 21}
任意输入一个数字n,用折半查找法找到n位于数组中的位置。如果n不属于数组A,显示错误提示。
 
【分析】
这是一个折半查找法的简单应用。折半查找不仅可以通过查找关键字对文件记录进行查找,也可应用于简单的数组,顺序表结构等查找。在这里同样要求数组中的元素有序。
 
程序清单 
# include <stdio.h>bin_search(int A[], int n, int key){   int low, high, mid;   low = 0;   high = n-1;    while(low <= high)   {     mid = (low + high) / 2;      if(A[mid] == key)       return mid; 			//查找成功,返回mid     if(A[mid] < key)      low = mid + 1; 		//在后半序列中查找     if(A[mid] > key)    	 high = mid - 1; 	//在前半序列中查找   }   return -1; 				//查找失败,返回-1}int main(void){  int A[10] = {2, 3, 5, 7, 8, 10, 12, 15, 19, 21};  int i, n, addr;  printf("The contents of the Array A[10] are\n");  for(i=0; i<10; i++)    printf("%d ", A[i]);	//显示数组A中的内容  scanf("%d", &n); 			//输入待查找的元素  addr = bin_search(A, 10, n);//折半查找,返回该元素在数组中的下标  if(-1 != addr)    printf("%d is at the %dth unit is array A\n", n, addr);  else    printf("There is no %d in array A\n", n);//查找失败     return 0;}

在C-Free 5.0中的输出结果是:

The contents of the Array A[10] are
2 3 5 7 8 10 12 15 19 21
10
10 is at the 5th unit is array A
Press any key to continue . . .
 
我的实现方式如下: (贴出自己的实现方式只是为了保存当时的思路,用于反思自己和做一些总结,因为我觉得没有自己的思路,一味记忆书上的代码片段,效果不大,记忆不深刻,且效率不高。所以我是在了解了基本的编码思想,做出正确答案后(至少表面上测试正确),又看的书上给出的答案。)
# include "stdio.h"int binarysearch(int a[], int length, int key){   int low = 0;   int high = length-1;   int mid = (low + high) / 2;    while(high >= low) //循环终结条件   {     if(a[mid] == key)     return mid;      if(key > a[mid])	//与程序清单不同,这里使用if-else来减少循环比较的次数。     {       low = mid + 1;       mid = (low + high) / 2;        continue; 		//与程序清单不同,因为mid的值可能在这里被改变,因此需要continue     }     else(key < a[mid])      {       high = mid - 1;       mid = (low + high)/ 2;       continue;     }  }  return -1;}int main(void){   int a[10] = {2, 3, 5, 7, 8, 10, 12, 15, 19, 21};   int n;   scanf("%d", &n);   int value = http://www.mamicode.com/binarysearch(a, 10, n);"ERROR, the element what you want to search is not found!\n");   else      printf("the index of the element is %d\n", value);    return 0;}

在C-Free 5.0中的输出结果是:

12

the index of the element is 6
Press any key to continue . . .
 
推荐使用方式:(取自:程序员编程艺术一书)
//二分查找V0.1 实现版//copyright@2011 July//随时欢迎读者找bug,email:zhoulei0907@yahoo.cn。//首先要把握下面几个要点://right=n-1 => while(left <= right) => right=middle-1;//right=n => while(left < right) => right=middle;//middle 的计算不能写在while 循环外,否则无法得到更新。int binary_search(int array[],int n,int value){  int left=0;  int right=n-1;  //如果这里是int right = n 的话,那么下面有两处地方需要修改,以保证一一对应:  //1、下面循环的条件则是while(left < right)  //2、循环内当array[middle]>value 的时候,right = mid  while (left<=right) //循环条件,适时而变  {    int middle=left + ((right-left)>>1); //防止溢出,移位也更高效。同时,每次循环都需要更新。    if (array[middle]>value)    {      right =middle-1; //right 赋值,适时而变    }    else if(array[middle]<value)    {      left=middle+1;    }    else    return middle;    //可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多,如果每次循环都判断一下是否相等,将耗费时间    }  return -1;}

一点补充:

1.这里对为什么用middle=left + ((right-left)>>1)(注意:加法的优先级大于移位)而不是middle=(right+left) >>1做一个说明,引用我先前的一条评论,用后者:"mid可能越界,比如说,初始状态下low取值0,high取值2^31-1,那么第一次求值mid为2^30-1,假设所要查找的值value在中值mid右侧,这时候求和(2^31-1)+(2^30-1)为负值,再除以2依然为负,这时候a[mid]就出现越界了,如此看来,出现越界的情况蛮多的,比如任意两个大于2^30的low和high求和都会得负数"。因此最好用mid = low + ((high-low) >> 1) 就不会出现上述情况了
2.比较次数:假设有n个元素,采用折半查找是,最多需要比较的次数是:(log n + 1)向下取整,比方说n取8,则最多比较4次,n取9,最多比较次数依然为4次,依次类推。

折半查找