二分查找

由一道题目引出的:

题目形貌

给定一个有序的数组,查找某个数是不是在数组中,请编程完成。

剖析与解法

一看到数组本身已有序,我想你能够回响反映出了要用二分查找,毕竟二分查找的实用前提就是有序的。那什么是二分查找呢?

二分查找能够处理(预排序数组的查找)题目:只需数组中包括T(即要查找的值),那末经由历程不停减少包括T的局限,终究就能够找到它。其算法流程以下:

一开始,局限掩盖全部数组。
将数组的中心项与T举行比较,如果T比数组的中心项要小,则到数组的前半部份继承查找,反之,则到数组的后半部份继承查找。
云云,每次查找能够消除一半元素,局限减少一半。就这样反复比较,反复减少局限,终究就会在数组中找到T,或许肯定原以为T地点的局限现实为空。
关于包括N个元素的表,全部查找历程约莫要经由log(2)N次比较。

此时,能够有不少读者内心嘀咕,不就二分查找么,太简朴了。

然《编程珠玑》的作者Jon Bentley曾在贝尔试验室做过一个试验,即给一些专业的递次员几个小时的时候,用任何一种言语编写二分查找递次(写出高等伪代码也能够),效果介入编写的一百多人中:90%的递次员写的递次中有bug(我并不认为没有bug的代码就准确)。

也就是说:在充足的时候内,只要约莫10%的专业递次员能够把这个小递次写对。但写不对这个小递次的还不止这些人:而且高德纳在《计算机递次设计的艺术 第3卷 排序和查找》第6.2.1节的“汗青与参考文献”部份指出,虽然早在1946年就有人将二分查找的要领公诸于世,但直到1962年才有人写出没有bug的二分查找递次。

你能准确无误的写出二分查找代码么?不妨一试,封闭一切网页,窗口,翻开记事本,或许编辑器,或许直接在本文批评下,不参考上面我写的或其他任何人的递次,给本身十分钟到N个小时不等的时候,马上编写一个二分查找递次。

正文:二分查找

关于二分查找法

二分查找法主如果处理在“一堆数中找出指定的数”这类题目。

而想要运用二分查找法,这“一堆数”必须有一下特性:(1)存储在数组中 (2) 有序分列
所以如果是用链表存储的,就没法在其上运用二分查找法了。

至因而递次递增分列照样递减分列,数组中是不是存在雷同的元素都不要紧。不过平常状况,我们照样愿望并假定数组是递增分列,数组中的元素互不雷同。

二分查找法的基础完成

二分查找法在算法家属大类中属于“分治法”,分治法基础都能够用递归来完成的,二分查找法的递归JS完成以下:

function bsearch(array,low,high,target)
{
    if (low > high) return -1;
    var mid = Math.floor((low + high)/2);
    if (array[mid]> target){
        return  bsearch(array, low, mid -1, target);
    } else if (array[mid]< target){
        return  bsearch(array, mid+1, high, target);
    }ese{return mid;}
        
}

不过一切的递归都能够自行定义stack来解递归,所以二分查找法也能够不必递归完成,而且它的非递归完成以至能够不必栈,由于二分的递归现实上是尾递归,它不体贴递归前的一切信息。

function bsearchWithoutRecursion(array,low,high,target)
{
    while(low <= high)
    {
        var mid = Math.floor((low + high)/2);
        if (array[mid] > target){
            high = mid - 1;
        }else if (array[mid] < target){
            low = mid + 1;
        }else{
            return mid;
        }
    }
    return -1;
}

用二分查找法找寻边境值

之前的都是在数组中找到一个数要与目的相称,如果不存在则返回-1。我们也能够用二分查找法找寻边境值,也就是说在有序数组中找到“恰好大于(小于)目的数”的那个数。
用数学的表述体式格局就是:
在鸠合中找到一个大于(小于)目的数t的数x,使得鸠合中的恣意数要么大于(小于)即是x,要么小于(大于)即是t。

举例来说:
赋予数组和目的数
var array = {2, 3, 5, 7, 11, 13, 17};
var target = 7;
那末上界值应该是11,由于它“刚刚好”大于7;下届值则是5,由于它“刚刚好”小于7。

用二分查找法找寻上界

function BSearchUpperBound(array,low,high,target)
{
    if(low > high || target >= array[high]) return -1;
    
    var mid = (low + high) / 2;
    while (high > low)
    {
        if (array[mid] > target){
            high = mid;
       } else{
            low = mid + 1; 
       }
        mid = (low + high) / 2;
    }

    return mid;
}

与准确查找不同之处在于,准确查找分红三类:大于,小于,即是(目的数)。而界线查找则分红了两类:大于和不大于。
如果当前找到的数大于目的数时,它能够就是我们要找的数,所以须要保存这个索引,也因而if (array[mid] > target)时 high=mid; 而没有减1。

用二分查找法找寻下界

function BSearchLowerBound(array,low,high,target)
{
    if(high < low  || target <= array[low]) return -1;
    
    var mid = (low + high + 1) / 2; //make mid lean to large side
    while (low < high)
    {
        if (array[mid] < target){
            low = mid;
        }else{
            high = mid - 1;
        }
        mid = (low + high + 1) / 2;
    }

    return mid;
}

下届寻觅基础与上届雷同,须要注重的是在取中心索引时,运用了向上取整。若同之前一样运用向下取整,那末当low == high-1,而array[low] 又小于 target时就会构成死循环。由于low没法往上爬凌驾high。
这两个完成都是找严厉界线,也就是要大于或许小于。如果要找松懈界线,也就是找到大于即是或许小于即是的值(即包括本身),只需对代码稍作修正就好了:

去掉推断数组边境的等号:
target >= array[high]改成 target > array[high]
在与中心值的比较中加上等号:
array[mid] > target改成array[mid] >= target

用二分查找法找寻地区

之前我们运用二分查找法时,都是基于数组中的元素各不雷同。如果存在反复数据,而数组依旧有序,那末我们照样能够用二分查找法鉴别目的数是不是存在。不过,返回的index就只能是随机的反复数据中的某一个。

此时,我们会愿望晓得有多少个目的数存在。或许说我们愿望数组的地区。
连系前面的界线查找,我们只需找到目的数的严厉上届和严厉下届,那末界线之间(不包括界线)的数据就是目的数的地区了。

//return type: pair<int, int>
//the fisrt value indicate the begining of range,
//the second value indicate the end of range.
//If target is not find, (-1,-1) will be returned
pair<int, int> SearchRange(int A[], int n, int target) 
{
    pair<int, int> r(-1, -1);
    if (n <= 0) return r;
    
    int lower = BSearchLowerBound(A, 0, n-1, target);
    lower = lower + 1; //move to next element
    
    if(A[lower] == target)
        r.first = lower;
    else //target is not in the array
        return r;
    
    int upper = BSearchUpperBound(A, 0, n-1, target);
    upper = upper < 0? (n-1):(upper - 1); //move to previous element
    
    //since in previous search we had check whether the target is
    //in the array or not, we do not need to check it here again
    r.second = upper;
    
    return r;
}

它的时候复杂度是两次二分查找所用时候的和,也就是O(log n) + O(log n),末了照样O(log n)。

    原文作者:specialCoder
    原文地址: https://segmentfault.com/a/1190000008584438
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞