代码改变世界

折半查找

2014-09-18 18:09  星星之火✨🔥  阅读(2055)  评论(0编辑  收藏  举报
所谓折半查找,又称二分查找,是一种在有序数组中查找某一特定元素的搜索算法。优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
复杂度:
时间复杂度:由于折半搜索每次把搜索区域减少一半,时间复杂度为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 = binarysearch(a, 10, n);

   if(value == -1)
     printf("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次,依次类推。