2种二分查找及2种优化方式

来源:互联网 发布:机器人编程实战 编辑:程序博客网 时间:2024/05/21 10:27

题目1:经典算法—二分查找
折半查找的基本思想:减小查找序列的长度,分而治之的进行关键字的查找。该序列必须是有序的
查找过程是:在有序表中,取中间的记录作为比较关键字,若给定值与中间记录的关键字相等,则查找成功;若给定的值小于中间记录的关键字,则在中间记录的左半区间继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区间继续查找;不断重复这个过程,直到查找成功。否则查找失败

实现:通常设置3个指针:low、high、mid。假设序列(数组)是从小到大排序。如果数组是从大到小排序,只需要改一些代码即可,思想不变。

Code:

int BinSearch(int* array, int n, int key){    int low = 0, high = n-1, mid;    while (low <= high)    {        mid = (low + high) / 2;        if (array[mid] == key)        {            return mid;        }        else if (key > array[mid])        {            low = mid +1;        }        else        {            high = mid - 1;        }    }    return -1;}

实际上,二分查找的过程可以绘制成一棵二叉树,每次二分查找的过程就相当于把原来的树划分为两棵子树,所以每次二分之后下次就只需要查找其中一半的数据就可以了。那么二分查找算法的时间复杂度是多少呢?在最好的情况下,只需要查找一次就可以了,因为这时候中间记录的关键字与要查找的key是相等,自然一次就够了。在最坏的情况下是从根节点查找到最下面的叶子结点,这个过程需要的时间复杂度是O(logn)

需要注意的是,虽然二分查找算法的效率很高(这也是二分查找算法被广泛应用的原因),但是仍然是有使用条件的:有序。就是说在需要频繁进行插入或者删除操作的数据记录中使用二分查找算法不太划算,因为要维持数据的有序还需要额外的排序开销

题目2:
升序数组a经过循环右移后,二分查找给定元素x。
如a={1,2,3,4,5,6,7},循环移动后a={5,6,7,1,2,3,4}。

思路:
(1)类似正常的二分查找,都是不断移动左右边界,不过判断条件更加复杂一点。
(2)每次计算中间那个元素mid,和左边界的元素left比较,总能确定有一边的区间是升序的;
(3)然后对升序那边进行分析,若这个区间不可能包含x,则不再考虑这个区间;若可能包含x,则将查找范围限制在这个区间。即每次都可以排除一半区间。

int BinSearch(int* arr, int n, int key) {    int low=0, high=n-1;    int mid = 0;    while (low <= high)    {        mid = (low+high)/2;         if (arr[mid] == key)            return mid;        if (arr[mid] > arr[low])    //左边升序        {            if (arr[low] > key)                 low = mid+1;            else if (arr[mid] > key)                high = mid-1;            else                low = mid+1;         }        else                        //右边升序        {            if (arr[high] < key)                high = mid-1;            else if (arr[mid] > key)                high = mid-1;            else                low = mid+1;         }    }    return -1;}

优化一:插值查找算法

可以发现二分查找每次都是选取中间的那个记录关键字作为划分依据的,那为什么不可以是其他位置的关键字呢?在有些情况下,使用二分查找算法并不是最合适的。举个例子:在1-1000中,一共有1000个关键字,如果要查找关键字10,按照二分查找算法,需要从500开始划分,这样的话效率就比较低了,所以有人提出了插值查找算法。说白了就是改变划分的比例,比如三分或者四分。

插值查找算法对二分查找算法的改进主要体现在mid的计算上,其计算公式如下:

这里写图片描述

而原来的二分查找公式是这样的:

这里写图片描述

所以我们发现主要变化的地方是1/2这个系数。其思想可以总结如下:插值查找是根据要查找的关键字的key与查找表中最大最小记录的关键字比较之后的查找算法,其核心是上述计算mid的计算公式。由于大体框架与二分查找算法是一致的,所以时间复杂度仍然是O(logn)。

优化二:斐波那契查找算法

从前面的分析中可以看到,无论划分的关键字太大或者太小都不合适,所以又有人提出了斐波那契查找算法,其利用了黄金分割比原理来实现的。
一个数列如果满足F(n)=F(n-1)+F(n-2),则称这个数列为斐波那契数列。在斐波那契查找算法中计算mid的公式如下:

这里写图片描述

斐波那契查找的前提是待查找的查找表必须顺序存储并且有序。波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的

要求开始表中记录的个数为某个斐波那契数小1,及n=Fk-1;
开始将key值与第F(k-1)位置的记录进行比较(即mid=low+F(k-1)-1),比较结果也分为三种

1、key == arr[mid],mid位置的元素即为所求
2、key > arr[mid],low=mid+1,k-=2
low=mid+1:说明待查找的元素在[mid+1,high]范围内
k-=2:说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找
3、key < arr[mid],high=mid-1,k-=1
low=mid+1:说明待查找的元素在[low,mid-1]范围内
k-=1:说明范围[low,mid-1]内的元素个数为F(k-1)-1 个,所以可以递归 的应用斐波那契查找

Code:

const int max_size=20;//斐波那契数组的长度  //构造一个斐波那契数组 void Fibonacci(int* F)  {      F[0]=0;      F[1]=1;      for(int i=2;i<max_size;++i)          F[i]=F[i-1]+F[i-2];  }  //斐波那契查找//arr为要查找的数组,n为要查找的数组长度,key为要查找的关键字  int FibonacciSearch(int* arr, int n, int key)  {    int low=0, high=n-1;    int mid = 0;  //构造一个斐波那契数组F   int F[max_size];    Fibonacci(F);                //计算n位于斐波那契数列的位置  int k=0;    while(n>F[k]-1)                  ++k;    //将数组arr扩展到F[k]-1的长度   int* temp;                temp=new int [F[k]-1];    memcpy(temp,arr,n*sizeof(int));    for(int i=n;i<F[k]-1;++i)        temp[i]=arr[n-1];    while(low<=high)    {        mid=low+F[k-1]-1;        if(key < temp[mid])        {            high = mid-1;            k -= 1;        }        else if(key > temp[mid])        {            low = mid+1;            k -= 2;        }        else        {            if(mid<n)                return mid; //若相等则说明mid即为查找到的位置            else                return n-1; //若mid>=n则说明是扩展的数值,返回n-1        }    }      delete[] temp;    return -1;  } 

斐波那契查找的核心是:
1、当key = arr[mid]时,查找成功;
2、当key < arr[mid]时,新的查找范围是第low个到第mid-1个,此时范围个数为F[k-1] - 1个,即数组左边的长度,所以要在[low, F[k - 1] - 1]范围内查找;
3、当key > arr[mid]时,新的查找范围是第mid+1个到第high个,此时范围个数为F[k-2] - 1个,即数组右边的长度,所以要在[F[k - 2] - 1]范围内查找。

关键点1:
关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当众的大部分数据,其工作效率要高一些。所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找

关键点2:
1、折半查找是进行加法与除法运算的(mid=(low+high)/2)
2、插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low])))
3、而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1] - 1),在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。

0 0
原创粉丝点击