天天看點

2021-05-16二分查找

二分查找

二分查找的大體模闆很簡單,但是上課的時候看到結束條件 下邊界和上邊界有時候相等有時候不等;mid有時候加一有時候不加一 ;本來感覺沒什麼太大的意義;後來問了百度才知道 ,細節是魔鬼!

二份查找的基本架構

int Search(int *num, int target) {
    int left = 0, right = ...;
    while(...) {
        int mid = (right + left) / 2;
        if (num[mid] == target) {
            ...
        } else if (num[mid] < target) {
            left = ...
        } else if (num[mid] > target) {
            right = ...
        }
    }
    return ...;
           

下面從兩個方面細說

尋找一個數、尋找邊界;

尋找一個數

問題大概是

搜尋一個數,如果存在,傳回其索引,否則傳回 -1。

int Search(int*num, int target) {
    int left = 0; 
    int right = num.length - 1; //
    while(left <= right) { // 注意
        int mid = (right + left) / 2;
        if(num[mid] == target)
            return mid; 
        else if (num[mid] < target)
            left = mid + 1; // 注意
        else if (num[mid] > target)
            right = mid - 1; // 注意
        }
    return -1;
}
           

有兩個值得注意的細節

1是結束條件 left <= right 因為我們right = num.length - 1;即r=最後一個元素 當然前提是從num【0】開始輸入的; left <= right 就是周遊的區間【l,r】是閉區間;

如果是 left < right就是開區間【l,r);顯然最後一個元素别遺漏了;但是如果我right = num.length;那麼就代表開區間【l,r)r是最後一個元素再後一個;

這樣子就可以把最後一個元素包括了

2 mid是否加一

本題因為 num[mid]有三種情況 是以當num[mid]=目标的時候 就輸出了;也就是說num[mid]這個值已經被周遊了不用再繼承了;

但是尋找邊界的時候情況又不同;

尋找邊界

左邊界

int left(int*nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意

    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}
           

需要注意的細節

1 while(left < right) 因為r是最後一個元素的下一個;是以用開區間就可以;

2 left = mid + 1,right = mid 因為我們的搜尋區間是 [left, right) 左閉右開,是以當 nums[mid] 被周遊之後,下一步的搜尋區間應該去掉 mid 分割成兩個區間,即 [left, mid) 或 [mid + 1, right)。剛好把mid給去掉

3

if (nums[mid] == target)

right = mid;

意思是把上邊界改成mid 因為 mid已經符合條件了 但不一定是最左邊的邊界;是以這樣從left到mid來周遊,看有沒有其他邊界;沒有或者有都輸出left 有統一性;因為有另一個邊界的時候和沒有 循環結束的條件都是left=right;這樣子就找到最左邊的邊界了

下面給出例題

是尋找邊界的變例 區間由整數集變成連續的小數集

HDU 1551 Cable master

将n根網線切成k段相同長度的網線,問可切成的最長長度是多少;

思路:利用二分發查找答案,每次偏右查找(因為要查找大的)也就是查找右邊界;

sum = sum/k;
        double l = 0,r = sum;
        while(fabs(l-r)>exp)
        {
            double mid = (l+r)/2;
            if(judge(mid))
                l = mid+1;
            else
                r = mid;
        }
        printf("%.2f\n",l-1);
    }

    return 0;
}
int judge(double s)
{
    int cnt = 0;
    for(int i = 0; i<n; i++)
    {
        cnt+=(int)(a[i]/s);
    }
    if(cnt>=k) return 1;
    return 0;
}
           

1周遊區間是有小數的形式 是以不能用大于或者大于等于的這種形式 這樣不夠精确

2因為我們初始化 right = sum;

是以決定了我們的「搜尋區間」是 [left, right)

是以決定了 while (left +10e-6< right)

同時也決定了 left = mid 和 right = mid

因為我們需找到 target 的最右側索引

是以當 cnt== target 時不要立即傳回

而要收左側邊界以鎖定右側邊界

又因為收左側邊界時必須 left = mid 而不是left = mid+1;這是因為區間是連續的 不是離散的整數;

3傳回 left

傳回right可能會出錯 可能恰好left+10e-6越界了;

細節決定ac!