主頁 >  其他 > 視頻解說:簡易版二分查找

視頻解說:簡易版二分查找

2021-10-20 08:29:17 其他

<iframe id="PYvaPUcJ-1634574396207" src="https://player.bilibili.com/player.html?aid=718733501" allowfullscreen="true" data-mediaembed="bilibili"></iframe>

點擊我跳轉末尾 獲取 粉絲專屬 《演算法和資料結構》原始碼,以及獲取博主的聯系方式,

前言

??二分查找,又叫二分列舉是在一個單調有序的陣列中查找某個元素的搜索演算法,原理比較簡單,基本說一遍就知道是怎么一回事,然而,實際程序中,很容易寫錯,比如:
??1)左區間是加一還是不加?
??2)右區間是減一還是不減?
??3)迭代的終止條件怎么寫?
??4)為什么有時候會死回圈?
??帶著以上幾個疑問,這篇文章將對二分查找的所有寫法進行一個歸納總結,

文章目錄

  • 前言
  • 一、線性列舉
    • 1、線性列舉定義
    • 2、舉例說明
    • 3、演算法分析
      • 1)前提
      • 2)條件
      • 3)回傳結果
    • 4、畫解列舉
      • 1)劃分
      • 2)游標
      • 3)遍歷
      • 4)詳解
    • 5、舉一反三
      • 1)大于 x x x 的最小數的下標
      • 2)大于等于 x x x 的最小數的下標
      • 3)小于 x x x 的最大數的下標
      • 4)小于等于 x x x 的最大數的下標
    • 6、時間復雜度
  • 二、二分列舉
    • 1、二分列舉定義
    • 2、舉例說明
    • 3、演算法分析
      • 1)目標
      • 2)游標
      • 3)二分
      • 4)時間復雜度
    • 4、原始碼詳解
      • 1)條件判定
      • 2)二分列舉模板
    • 5、細節決議
      • 1)迭代的程序
      • 2)結束條件
      • 3)游標初始值
      • 4)中點位置
      • 5)死回圈
  • 三、二分列舉的應用
    • 1、陣列精確查找
      • 1)題目描述
      • 2)演算法分析
      • 3)原始碼示例
    • 2、線性列舉 + 陣列精確查找
      • 1)題目描述
      • 2)演算法分析
      • 3)原始碼示例
    • 3、陣列的模糊查找
      • 1)大于等于 x x x 的最小值
      • 2)大于 x x x 的最小值
      • 3)小于等于 x x x 的最大值
      • 4)小于 x x x 的最大值
    • 4、單調函數的查找
      • 1)題目描述
      • 2)演算法分析
      • 3)原始碼示例
  • 四、二分列舉的通解
  • 粉絲專屬福利

一、線性列舉

1、線性列舉定義

??線性列舉指的就是遍歷某個一維陣列(順序表)的所有元素,找到滿足條件的那個元素并且回傳,回傳值可以是下標,也可以是元素本身,
??由于是遍歷的,窮舉了所有情況,所以一定是可以找到解的,一些資料上也稱之為 暴力演算法 (Brute Force),接下來,我們通過一個例子來理解 線性列舉,

2、舉例說明

??【例題1】給定一個單調不降的有序陣列 a r r arr arr 和 一個值 x x x,要求找到大于 x x x 的最小數的下標,

3、演算法分析

?我們從這個問題中提取幾個關鍵字并分類如下:
??1)前提:單調不降、有序;
??2)條件:大于 x x x、最小數;
??3)回傳結果:下標;

1)前提

??前提就是問題給定時的初始陣列需要滿足的先天性條件,保證資料是能夠符合這個前提的,這里的前提是 陣列一定是有序的,且是單調不降的,即 陣列下標大的數 不會比 陣列下標小的數 更小,

2)條件

??這個問題中的條件有兩個:
??1)大于 x x x
??2)值最小;
??我們如果仔細分析一下這個問題,就可以發現,正因為這里的陣列是單調不降的,所以,一旦滿足 某個數大于 x x x,之后的所有數必然都滿足 大于 x x x 這個條件,所以我們必然可以把陣列分成兩部分,一部分是 大于 x x x 的,另一部分是 不大于 x x x 的,

3)回傳結果

??這里的回傳結果要求是下標,而我們遍歷操作也是通過遍歷陣列的下標進行的,所以找到滿足條件的,回傳下標即可,

4、畫解列舉

??接下來,我們通過一組實際的資料來解釋這個問題,
a r r = [ 1 , 3 , 4 , 6 , 6 , 6 , 7 , 8 , 9 ] arr = [1, 3, 4, 6, 6, 6, 7, 8, 9] arr=[1,3,4,6,6,6,7,8,9]

1)劃分

??對于這個陣列,當 x = 6 x = 6 x=6 時,我們將陣列分成兩部分,大于 6 的部分用 綠色表示,不大于 6 的部分用紅色表示,
??這么表示的目的,主要是為了方便記憶,聯想一下 紅綠燈,綠色代表可以通行,即 “大于6” 這個條件滿足;紅色代表禁止通行,即條件不滿足,

2)游標

??設定一個游標,初始時指向陣列的第 0 個元素(C語言中陣列下標從 0 開始計數),
??游標,顧名思義,就是游動的下標,你也可以叫指標,我之所以沒有稱之為指標,是不想它和C語言中的指標概念混淆,

3)遍歷

??遍歷就是判斷當前游標指向的元素是否是綠色的,如果是綠色的直接回傳,因為它一定是大于 x x x 且值最小的;如果不是,則增加游標的值,繼續下一次判斷,直到陣列遍歷完畢,如下圖所示:

在這里插入圖片描述
??數字 7 就是我們要找到 大于 6 的最小數,它的下標為 6,

4)詳解

int isGreen(int val, int x) {               // (1)
    return val > x;
}
int findFirstBiggerThan(int *arr, int arrSize, int x) {
    int i;
    for(i = 0; i < arrSize; ++i) {          // (2)
        if( isGreen(arr[i], x) ) {          // (3)
            return i;
        }
    }
    return arrSize;                         // (4)
}
  • ( 1 ) (1) (1) int isGreen(int val, int x)這個函式代表條件是否滿足,滿足回傳 1,否則回傳 0;這里的條件便是 v a l > x val > x val>x
  • ( 2 ) (2) (2) 下標從小到大,從 0 開始遍歷陣列 a r r arr arr
  • ( 3 ) (3) (3) 一旦遇到大于 x x x 的數,則回傳它的下標,因為是下標從小往大遍歷的,所以第一個找到滿足條件的數一定是值最小的;
  • ( 4 ) (4) (4) 如果找不到,說明所有的數都是小于等于 x x x 的,直接回傳陣列長度;

5、舉一反三

??接下來,我們來看看線性列舉的其它幾種問法,

??【例題2】給定一個單調不降的有序陣列如下: [ 1 , 3 , 4 , 6 , 6 , 6 , 7 , 8 , 9 ] [1, 3, 4, 6, 6, 6, 7, 8, 9] [1,3,4,6,6,6,7,8,9],要求找到以下元素:
???? ( 1 ) (1) (1) > 6 \gt 6 >6 的 最小數 的下標位置;
???? ( 2 ) (2) (2) ≥ 6 \ge 6 6 的 最小數 的下標位置;
???? ( 3 ) (3) (3) < 6 \lt 6 <6 的 最大數 的下標位置;
???? ( 4 ) (4) (4) ≤ 6 \le 6 6 的 最大數 的下標位置;

?? 對于這四個問題,我們可以發現它們的答案如下所示:

012345678
134666789
( 3 ) (3) (3) ( 2 ) (2) (2) ( 4 ) (4) (4) ( 1 ) (1) (1)

1)大于 x x x 的最小數的下標

??將陣列按照條件進行劃分,然后利用上文提到的findFirstBiggerThan函式求解即可,

2)大于等于 x x x 的最小數的下標

??我們把問題做個變形,將問題變成找 大于等于 x x x 的最小數的下標(比之前的問題多了一個等于),按照條件劃分的結果應該是包含 6 本身的,所以如下圖所示:


??遍歷陣列的部分不變,只不過條件變成了 大于等于,C語言實作如下:

int isGreen(int val, int x) {
    return val >= x;              // (1)
}
int findFirstBiggerEqualThan(int *arr, int arrSize, int x) {
    int i;
    for(i = 0; i < arrSize; ++i) {
        if( isGreen(arr[i], x) ) {
            return i;
        }
    }
    return arrSize;
}
  • ( 1 ) (1) (1) 將原先的>號改成>=即可;

3)小于 x x x 的最大數的下標

??上面兩個問題能理解的話,我們再來看一個問題,如何找到 小于 x x x 的最大數的下標 ,要求下標最大,那么我們在列舉的程序中,如果發現一個大于等于 x x x 的數,那么后續都不用列舉了,并且需要回傳這個數的前一個位置,條件劃分如下圖所示:

??我們要做的是回傳紅色中的最大下標,C語言實作如下:

int isGreen(int val, int x) {
    return val >= x;                  // (1)
} 

int findLastSmallThan(int *arr, int arrSize, int x) {
    int i;
    for(i = 0; i < arrSize; ++i) {
        if( isGreen(arr[i], x) ) {
            return i - 1;              
        }
    }
    return arrSize - 1;
}
  • ( 1 ) (1) (1) 大于等于 x x x 時,isGreen成立;
  • ( 2 ) (2) (2) 由于我們要做的是回傳紅色中的最大下標,所以一旦遇到大于等于 x x x 的數(即綠色的情況),則回傳它的前一個下標;
  • ( 3 ) (3) (3) 如果找不到,則回傳 arrSize - 1,即所有數都是紅色的,則最大下標就是陣列的最后一個元素的下標;

4)小于等于 x x x 的最大數的下標

??我們把問題繼續做變形,將問題變成找 小于等于 x x x 的最大數的下標(比之前的問題多了一個等于),劃分如下圖所示:

??遍歷陣列的部分不變,只不過條件變成了 大于,我們要做的是回傳紅色中的最大下標,C語言實作如下:

int isGreen(int val, int x) {
    return val > x;             // (1)
} 

int findLastSmallEqualThan(int *arr, int arrSize, int x) {
    int i;
    for(i = 0; i < arrSize; ++i) {
        if( isGreen(arr[i], x) ) {
            return i - 1;              
        }
    }
    return arrSize - 1;
}
  • ( 1 ) (1) (1) 將原先的>=號改成>即可;

6、時間復雜度

??以上的內容就是線性列舉的幾種常見情況,也就是無腦遍歷所有情況,并且在滿足條件的第一時間退出回圈,當陣列長度為 n n n 時,演算法的時間復雜度為 O ( n ) O(n) O(n),比較低效,有沒有更加高效的演算法呢?
??接下來出場的,就是本文的主角 —— 二分列舉,

二、二分列舉

1、二分列舉定義

??二分列舉,也叫二分查找,指的就是給定一個區間,每次選擇區間的中點,并且判斷區間中點是否滿足某個條件,從而選擇左區間繼續求解還是右區間繼續求解,直到區間長度不能再切分為止,
??由于每次都是把區間折半,又叫折半查找,時間復雜度為 O ( l o g 2 n ) O(log_2n) O(log2?n),和線性列舉的求解結果一直,但是高效許多,回傳值可以是下標,也可以是元素本身,

2、舉例說明

??【例題3】只有兩種顏色的陣列 a r r arr arr ,左邊部分為紅色用 0 表示,右邊部分為綠色用 1 表示,要求找到下標最小的綠色元素的下標,

如圖所示,下標最小的綠色元素的下標為 3,所以應該回傳 3,

3、演算法分析

1)目標

??對于這個問題,當我們拿到這個陣列的時候,第一個綠色位置在哪里,我們是不知道的,所以,現在的目標就是要通過二分列舉找到紅色區域和綠色區域的邊界,

2)游標

??利用線性列舉的思路,我們引入游標的概念,只不過需要兩個游標,左邊一個紅色游標,右邊一個綠色游標,并且游標初始位置都在陣列以外,對于一個 n n n 個元素的陣列,紅色游標初始位置在 ? 1 -1 ?1,綠色游標初始位置在 n n n

3)二分

??我們將兩個游標相加,并且除 2,從而得到游標的中點,并且判斷中點所在位置的顏色,發現是綠色的,這說明從 中點游標綠色游標 的元素都是綠色的,如下圖所示:

??于是,我們可以把 綠色游標 替換成 中點游標,如下圖所示:

??這樣就完成了一次二分,區間相比之前,縮小了一半,注意,我們要求的解,一定永遠在 紅色游標綠色游標 之間,
??然后,我們繼續將兩個游標相加,并且除 2,從而得到游標的中點,并且判斷中點所在位置的顏色,發現是紅色的,這說明從 紅色游標中點游標 的元素都是紅色的,如下圖所示:

??于是,我們可以把 紅色游標 替換成 中點游標,如下圖所示:

??同樣上述演算法,再經過兩次二分以后,我們得到了如下結果:

??這個時候,這個時候 紅色游標綠色游標 的位置一定相差 1,并且 綠色游標 的位置就是我們這個問題要求的解,

4)時間復雜度

??由于每次操作都是將區間減小一半,所以時間復雜度為 O ( l o g 2 n ) O(log_2n) O(log2?n)

4、原始碼詳解

??那么接下來,我們來看下,如何用 C語言來 實作這個問題,

1)條件判定

??判斷一個元素是綠色還是紅色,我們可以單獨用一個函式來實作,根據題意,當值為 1 時代表綠色,值為 0 時代表紅色,C語言實作如下:

int isGreen(int val) {
    return val == 1;
}

2)二分列舉模板

??接下來的二分列舉模板可以解決大部分二分列舉的問題,請妥善保管,

int binarySearch(int *arr, int arrSize, int x) {
    int l = -1, r = arrSize;         // (1)
    int mid;
    while(r - l > 1) {               // (2)
        mid = l + (r - l) / 2;       // (3)
        if( isGreen(arr[mid], x) )   // (4)
            r = mid;                 // (5)
        else
            l = mid;                 // (6)
    }
    return r;                        // (7)
}
  • ( 1 ) (1) (1) l l l 代表 紅色游標 r r r 代表 綠色游標
  • ( 2 ) (2) (2) 當區間長度大于 2 的時候,二分縮小區間,這一步被稱為 區間收斂;
  • ( 3 ) (3) (3) m i d mid mid 為計算出來的區間 [ l , r ] [l, r] [l,r] 的中點;
  • ( 4 ) (4) (4) 判斷區間中點對應的元素是 綠色 還是 紅色
  • ( 5 ) (5) (5) 如果 中點元素綠色,則從 中點 r r r 的值都為 綠色,用 中點 替換 綠色游標
  • ( 6 ) (6) (6) 如果 中點元素紅色,則從 l l l中點 的值都為 紅色,用 中點 替換 紅色游標
  • ( 7 ) (7) (7) 這個地方是模板需要變通的地方,如果需要回傳紅色邊界,那么應該回傳 l l l;反之,如果需要回傳綠色邊界,則應該回傳 r r r,這個問題中,是后者,

5、細節決議

1)迭代的程序

??整個二分的程序是一個不斷迭代區間的程序,并且 紅色游標 指向的元素始終是 紅色 的;綠色游標 指向的元素始終是 綠色 的,迭代的程序就是不斷向 紅綠邊界 逼近的程序,

2)結束條件

??迭代結束時,紅色游標綠色游標 剛好指向 紅綠邊界,且區間長度為 2,

3)游標初始值

??為什么 紅色游標 初始值為 ? 1 -1 ?1綠色游標 初始值為 n n n ?
??能否將 紅色游標 初始化為 0 0 0綠色游標 初始化為 n ? 1 n-1 n?1 ? 答案是否定的,試想一下,如果資料元素都是綠色,紅色游標 初始化為 0 就違背了 " 紅色游標 指向的元素始終是 紅色 的 " 這個條件;反之,如果元素都是紅色的,也有類似問題,

4)中點位置

??由于中點的位置是需要去訪問陣列來獲取值的,所以必須滿足始終在 [ 0 , n ) [0, n) [0,n) 區間范圍內,
??中點位置計算公式為: m i d = ? l + r 2 ? mid = \lfloor \frac {l +r} 2 \rfloor mid=?2l+r??
?? l l l 的最小值為 ? 1 -1 ?1 r r r 的最小值為 l + 2 l+2 l+2,所以 m i d mid mid 的最小值就是 ? l + r 2 ? = ? ? 1 + ( ? 1 + 2 ) 2 ? = 0 \lfloor \frac {l +r} 2 \rfloor = \lfloor \frac {-1 + (-1 + 2)} 2 \rfloor = 0 ?2l+r??=?2?1+(?1+2)??=0
?? r r r 的最大值為 n n n l l l 的最大值為 r ? 2 r-2 r?2,所以 m i d mid mid 的最大值就是 ? l + r 2 ? = ? n + ( n ? 2 ) 2 ? = n ? 1 \lfloor \frac {l + r} 2 \rfloor = \lfloor \frac {n + (n - 2)} 2 \rfloor = n-1 ?2l+r??=?2n+(n?2)??=n?1
??綜上所述,中點的下標位置始終在 [ 0 , n ) [0, n) [0,n) 區間范圍內,

5)死回圈

??上面的程式模板是否會進入死回圈?
??我們可以這么來看,當區間為 2 時,回圈結束,當區間為 3 時,它一定可以變成區間為 2 的情況,當區間為4時,一定可以變成區間為 2 或者 3 的情況,也就是任何一種情況下,區間一定會減小,并且當等于 2 時,回圈結束,所以不會造成死回圈,

三、二分列舉的應用

??接下來,提供一個通用模板,利用這個模板,只需要修改isGreen函式,就能實作絕大部分的二分列舉問題,


/************** 二分查找 陣列 模板 **************/
/*

  1)傳參的陣列滿足:紅紅紅紅紅紅紅紅綠綠綠綠綠綠綠; 
  2)回傳值:綠色區段的左邊界; 
*/

int isGreen(int val, int x);

int binarySearch(int *arr, int arrSize, int x) {
    int l = -1, r = arrSize;
    int mid;
    while(l + 1 < r) {
        mid = l + (r - l) / 2;
        if( isGreen(arr[mid], x) )
            r = mid;
        else
            l = mid;
    }
    return r;
}
/************** 二分查找 陣列 模板 **************/

??其中,條件函式int isGreen(int val, int x)函式的實作需要根據具體問題具體分析,

1、陣列精確查找

1)題目描述

??【例題4】給定一個 n n n 個元素的升序整型陣列 n u m s nums nums 和一個目標值 x x x,寫一個函式search搜索 n u m s nums nums 中的 t a r g e t target target,如果目標值存在回傳下標,否則回傳 ? 1 -1 ?1
??原題鏈接:WhereIsHeroFrom/118445716

2)演算法分析


??對于查找 t a r g e t target target 是否存在,我們把陣列中 "大于等于 t a r g e t target target" 的劃分為 綠色,其它為紅色,利用模板得到回傳值,回傳值回傳的是 r r r,也就是圖中的綠色箭頭指向位置,需要分三種情況討論:
??1) r = n r = n r=n,所有數都小于 t a r g e t target target,回傳-1
??2) n u m s [ r ] ≠ t a r g e t nums[r] \neq target nums[r]?=target,代表這個值不存在,回傳-1
??3) n u m s [ r ] = t a r g e t nums[r] = target nums[r]=target,直接回傳 r r r
??時間復雜度為 O ( l o g 2 n ) O(log_2n) O(log2?n)

3)原始碼示例

int isGreen(int val, int x) {
    return val >= x;                   // (1)
}

int search(int* nums, int n, int x){
    int r = binarySearch(nums, n, x);  // (2)
    if(r == n || nums[r] != x)         // (3)
        return -1;
    return r;                          // (4)
}
  • ( 1 ) (1) (1) 劃分綠色邊界;
  • ( 2 ) (2) (2) 呼叫模板,回傳綠色邊界;
  • ( 3 ) (3) (3) 沒有找到解情況;
  • ( 4 ) (4) (4) 找到解的情況;

2、線性列舉 + 陣列精確查找

1)題目描述

??【例題5】輸入一個遞增排序的陣列和一個數字 s s s,在陣列中查找兩個數,使得它們的和正好是 s s s,如果有多對數字的和等于s,則輸出任意一對即可,
??原題鏈接:WhereIsHeroFrom/118507985

2)演算法分析

??我們已經知道在一個陣列中尋找一個數的演算法,那么,我們只需要列舉一個確定的數 x x x,然后再在陣列的剩余部分去查找 s ? x s-x s?x 是否存在,就可以確定兩個數的實際位置了,時間復雜度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2?n)
??即先做一次線性列舉,再做一次二分列舉,

3)原始碼示例


int* twoSum(int* nums, int numsSize, int target, int* returnSize){
    int i, pos, base;
    int *ret = (int *)malloc( sizeof(int) * 2 );  // (1)
    *returnSize = 0;                              // (2)
    for(i = 0; i < numsSize; ++i) {               
        base = i+1;                               // (3)
        pos = search(nums+base , numsSize-base, target - nums[i]); // (4)    
        if(pos != -1) {                           // (5)
            ret[0] = nums[i];
            ret[1] = nums[pos+base];
            *returnSize = 2;
            return ret;
        }
    }
    return ret;
}
  • ( 1 ) (1) (1) 利用malloc申請一塊記憶體空間,用于回傳值;
  • ( 2 ) (2) (2) 將回傳資料的長度設為 0,因為有可能無解;
  • ( 3 ) (3) (3) 升序陣列中的兩個數的和為target,所以我們可以列舉nums[i],并且在剩下的陣列中列舉,剩下陣列的偏移量為base = i + 1
  • ( 4 ) (4) (4) 呼叫【例題4】中的精確查找,查找是否存在解;
  • ( 5 ) (5) (5) 回傳值不等于 ? 1 -1 ?1 ,則代表nums[i]nums[pos+i+1]的和為target,回傳這兩個值組成的陣列即可;

3、陣列的模糊查找

1)大于等于 x x x 的最小值

??【例題6】給定一個排序陣列和一個目標值 t a r g e t target target,在陣列中找到目標值,并回傳其索引,如果目標值不存在于陣列中,回傳它將會被按順序插入的位置,
??原題鏈接:WhereIsHeroFrom/118452682

??首先,我們需要考慮一下,插入的這個位置,需要滿足什么條件?
??如果我們對陣列進行劃分,大于等于目標值的作為綠色元素,其余作為紅色元素,如果有目標值,那么綠色元素的左邊界就是我們要找的索引;如果沒有目標值,那么紅色的右邊界一定比我們要插入的數小,綠色元素的左邊界一定比我們要插入的元素大,所以綠色元素的左邊界也是我們要找的索引,

??綜上所述,我們就是要找到綠色元素的左邊界,直接套用上面的二分列舉的模板即可,其中isGreen函式實作如下:

int isGreen(int val, int x) {
    return val >= x;
}

2)大于 x x x 的最小值

??【例題7】給你一個排序后的字串列letters,串列中只包含小寫英文字母,另給出一個目標字母target,請你尋找在這一有序串列里比目標字母大的最小字母,
??原題鏈接:WhereIsHeroFrom/120754725

??如果我們對陣列進行劃分,大于目標值的作為綠色元素,其余作為紅色元素,那么顯而易見,我們只要找到這個綠色元素的左邊界,就找到了大于目標值的最小值,
??綜上所述,我們就是要找到綠色元素的左邊界,直接套用上面的二分列舉的模板即可,其中isGreen函式實作如下:

int isGreen(int val, int x) {
    return val > x;
}

3)小于等于 x x x 的最大值

??【例題8】給你一個排序后的遞增陣列 和 一個目標值 t a r g e t target target,要求找到小于等于 t a r g e t target target 的最大值的下標,

??如果我們對陣列進行劃分,大于目標值的作為綠色元素,其余作為紅色元素,那么顯而易見,我們只要找到這個紅色元素的右邊界,就找到了小于等于目標值的最大值,

??綜上所述,直接套用上面的二分列舉的模板,并且對回傳值減一即可,其中isGreen函式實作如下:

int isGreen(int val, int x) {
    return val > x;
}

4)小于 x x x 的最大值

??【例題9】給你一個排序后的遞增陣列 和 一個目標值 t a r g e t target target,要求找到小于 t a r g e t target target 的最大值的下標,

??如果我們對陣列進行劃分,大于等于目標值的作為綠色元素,其余作為紅色元素,那么顯而易見,我們只要找到這個紅色元素的右邊界,就找到了小于目標值的最大值,

??綜上所述,直接套用上面的二分列舉的模板,并且對回傳值減一即可,其中isGreen函式實作如下:

int isGreen(int val, int x) {
    return val >= x;
}

??有關陣列的模糊查找問題,列出表格如下:

模糊查找綠色部分條件回傳值
大于等于 x x x 的最小值 ≥ x \ge x x r r r
大于 x x x 的最小值 > x \gt x >x r r r
小于等于 x x x 的最大值 > x \gt x >x l l l
小于 x x x 的最大值 ≥ x \ge x x l l l

4、單調函式的查找

??二分查找除了能夠在陣列中找到可行解,也能夠在單調函式中找到可行解,同樣是將函式根據定義域劃分成兩部分,左邊為紅色,右邊為綠色,然后找到邊界紅綠邊界,根據實際情況選擇紅色邊界或者綠色邊界,
??相應的,二分列舉的模板需要做適當的修改,傳入的引數由原先的陣列,變成了一個區間,C語言實作如下:

/**************二分查找模板 回傳綠色邊界**************/
int isGreen(int val, int x);

int binarySearch(int l, int r, int x) {
    int mid;
    while(l + 1 < r) {
        mid = l + (r - l) / 2;
        if( isGreen(mid, x) )
            r = mid;
        else
            l = mid;
    }
    return r;
}
/**************二分查找模板 回傳綠色邊界**************/

1)題目描述

??【例題10】給你一個非負整數 x x x ,計算并回傳 x x x 的 算術平方根 ,由于回傳型別是整數,結果只保留 整數部分,小數部分將被舍去,
??原題鏈接:WhereIsHeroFrom/119976200

2)演算法分析

??考慮 f ( x ) = x 2 f(x) = x^2 f(x)=x2 這個函式,當 x x x 遞增時,函式的值越來越大,是一個單調遞增函式,我們現在要做的就是,找到 f ( k ) f(k) f(k) 使得 f ( k ) f(k) f(k) 小于等于 x x x,且盡量大,并且回傳 k k k 的值,
??當 x = 0 x=0 x=0, x = 1 x=1 x=1 時,我們可以直接回傳 x x x;當 x > 1 x > 1 x>1時,我們構造紅綠邊界,所有 f ( k ) ≤ x f(k) \le x f(k)x 的情況為紅色,反之, f ( k ) > x f(k) \gt x f(k)>x 的情況為綠色,然后通過二分找到紅色邊界就是答案了,

3)原始碼示例

int isGreen(int val, int x) {
    return (long long)val * val > x;  // (1)    
}

int mySqrt(int x){
    int r;
    if(x == 0 || x == 1) {
        return x;
    }
    r = binarySearch(0, x, x);        // (2)
    return r - 1;                     // (3)
}

  • ( 1 ) (1) (1) 構造綠色部分為 k 2 > x k^2 \gt x k2>x,那么紅色部分就是 k 2 ≤ x k^2 \le x k2x
  • ( 2 ) (2) (2) 找到綠色邊界;
  • ( 3 ) (3) (3) 回傳紅色邊界;

四、二分列舉的通解

??任何可以用二分列舉來求解的問題,都可以抽象出一個單調函式,并且將單調函式劃分成 紅色 和 綠色 兩部分,通過二分列舉求出 紅綠邊界,然后再根據條件來決定是回傳 紅色的右邊界,還是綠色的左邊界,簡化為以下四步:

??1)抽象出單調函式;
??2)確定isGreen函式;
??3)二分列舉求出紅綠邊界;
??4)確定回傳 紅色邊界 還是 綠色邊界;



??關于 「 二分查找 」 的內容到這里就結束了,
??如果還有不懂的問題,可以通過 「 電腦版主頁 」找到作者的「 聯系方式 」 ,線上溝通交流,


??有關🌳《畫解資料結構》🌳 的原始碼均開源,鏈接如下:《畫解資料結構》



??相信看我文章的大多數都是「 大學生 」,能上大學的都是「 精英 」,那么我們自然要「 精益求精 」,如果你還是「 大一 」,那么太好了,你擁有大把時間,當然你可以選擇「 刷劇 」,然而,「 學好演算法 」,三年后的你自然「 不能同日而語 」
??那么這里,我整理了「 幾十個基礎演算法 」 的分類,點擊開啟:

🌌《演算法入門指引》🌌

??如果鏈接被屏蔽,或者有權限問題,可以私聊作者解決,




在這里插入圖片描述




🔥讓天下沒有難學的演算法🔥

C語言免費動漫教程,和我一起打卡!
🌞《光天化日學C語言》🌞

入門級C語言真題匯總
🧡《C語言入門100例》🧡

幾張動圖學會一種資料結構
🌳《畫解資料結構》🌳

組團學習,抱團生長
🌌《演算法入門指引》🌌

競賽選手金典圖文教程
💜《夜深人靜寫演算法》💜

粉絲專屬福利

語言入門:《光天化日學C語言》(示例代碼)
語言訓練:《C語言入門100例》試用版
資料結構:《畫解資料結構》原始碼
演算法入門:《演算法入門》指引
演算法進階:《夜深人靜寫演算法》演算法模板

??

👇🏻 關注公眾號 獲取更多資料👇🏻

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/325590.html

標籤:其他

上一篇:單鏈表基礎知識詳解——基本概念結構及函式實作(增刪查改)

下一篇:自然數的串列=>回傳布林值

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more