zoukankan      html  css  js  c++  java
  • 折半查找

    所谓折半查找,又称二分查找,是一种在有序数组中查找某一特定元素的搜索算法。优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
    复杂度:
    时间复杂度:由于折半搜索每次把搜索区域减少一半,时间复杂度为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
    ");
      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, addr);
      else
        printf("There is no %d in array A
    ", 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!
    ");
       else 
         printf("the index of the element is %d
    ", 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次,依次类推。
  • 相关阅读:
    BZOJ 1221 [HNOI2001] 软件开发 费用流_建模
    BZOJ 1180 / 2843 LCT模板题 + 双倍经验
    bzoj 4372: 烁烁的游戏 动态点分治+树链剖分+线段树
    bzoj 3730: 震波 动态点分治+树链剖分+线段树
    luogu P2634 [国家集训队]聪聪可可 点分治
    bzoj 1468: Tree 点分治
    bzoj 1296: [SCOI2009]粉刷匠 动态规划
    bzoj 1293: [SCOI2009]生日礼物 问题转化 + 性质分析 + 滚动数组优化
    BZOJ 1042: [HAOI2008]硬币购物 容斥原理+背包
    matplotlib模块
  • 原文地址:https://www.cnblogs.com/xpjiang/p/3979784.html
Copyright © 2011-2022 走看看