Binary Search

二分搜尋法

有序資料

Ordered

有序資料

  • 如果有一些陣列,滿足前一個元素與後一個元素,都滿足小於的關係,那這一個陣列就是一個有序資料
1 2 3 5 6
20 30 87 99 256

有序資料

  • 我們可以定義一個符號 \(<_+\) 來表示順序的關係
  • 如果一個序列 \(\{a_i\}\) 的任兩個元素 \(a_i, a_j\)滿足
    • \(i<j\)
    • \(a_i<_+a_j\)

序列 \(\{a_i\}\) 就是一個有序資料

有序資料 Example

  • 排序完的期中考成績
  • 排序完的學號
  • 排序完的菜單價錢
  • 排序完的XXX

搜尋問題

Search

搜尋問題

  • 給大家的成績,問:
    • 有多少人考100
    • 有沒有人考87分
    • 有多少人不及格

搜尋問題

有數列\(a_1,a_2,a_3,\cdots,a_n\),想要做一些事

  • 問有沒有一個數字 \(a_i\) 等於 \(x\)

Liner Search

  • 把所有可能的資料都看過一次

Liner Search

  • 把所有可能的資料都看過一次
int a[5] = {55, 60, 70, 75, 99};
int x = 100;
int index = -1;

for(int i=0; i<5; ++i)
{
    if( a[i] == x )
        index = i;
}

printf("data at %d", index);

Liner Search

  • 把所有可能的資料都看過一次
  • 故如果有 \(N\) 筆資料,那就必須看完所有的資料
  • 需要花費 \(O(N)\) 的時間

好像有點浪費時間?如果資料有神奇的特性呢?

有序資料的搜尋

有序資料的搜尋

  • 如果今天資料是有序的,我們想要在資料中找 \(x\),那我們可以把資料分成三種
小於x 等於x 大於x

\(<_+\)

\(<_+\)

有序資料的搜尋

  • 如果在資料中,隨意地戳一個元素來看,因為資料是已序的,我們就可以知道是戳到哪一團

\(<_+\)

\(<_+\)

1 3 5 6 7 8 10 12 13

小於6

大於6

有序資料的搜尋

  • 如果在資料中,隨意地戳一個元素來看,因為資料是已序的,我們就可以知道是戳到哪一團

\(<_+\)

\(<_+\)

1 3 5 6 7 8 10 12 13

小於6

大於6

有序資料的搜尋

  • 如果在資料中,隨意地戳一個元素來看,因為資料是已序的,我們就可以知道是戳到哪一團

\(<_+\)

\(<_+\)

1 3 5 6 7 8 10 12 13

小於6

大於6

有序資料的搜尋

  • 要怎麼戳效率最好呢?

二分搜尋法

  • 每一次都戳最中間的元素,這樣每一次一定會少掉一半的資料。

二分搜尋法

  • 每一次都戳最中間的元素,這樣每一次一定會少掉一半的資料。
1 3 5 6 7 8 10 12 13

二分搜尋法

  • 每一次都戳最中間的元素,這樣每一次一定會少掉一半的資料。
1 3 5 6 7 8 10 12 13

二分搜尋法

  • 每一次都戳最中間的元素,這樣每一次一定會少掉一半的資料。
1 3 5 6 7 8 10 12 13

二分搜尋法

  • 每一次都戳最中間的元素,這樣每一次一定會少掉一半的資料。
1 3 5 6 7 8 10 12 13

二分搜尋法

int a[100], x;
int L = 0; // 資料的最左邊
int R = N-1; // 資料的最右邊
int index = -1; // 找到的位置

二分搜尋法

int a[100], x;
int L = 0;      // 資料的最左邊
int R = N-1;    // 資料的最右邊
int index = -1; // 找到的位置

while( L<=R )   // 在範圍有東西的狀況下
{




}

二分搜尋法

int a[100], x;
int L = 0;      // 資料的最左邊
int R = N-1;    // 資料的最右邊
int index = -1; // 找到的位置

while( L<=R )   // 在範圍有東西的狀況下
{
    int M = (L+R)/2; //中間點



}

二分搜尋法

int a[100], x;
int L = 0;      // 資料的最左邊
int R = N-1;    // 資料的最右邊
int index = -1; // 找到的位置

while( L<=R )   // 在範圍有東西的狀況下
{
    int M = (L+R)/2;      //中間點
    if( a[M] < x ) L=M+1; //戳到左邊,比 M 小的都不是答案


}

二分搜尋法

int a[100], x;
int L = 0;      // 資料的最左邊
int R = N-1;    // 資料的最右邊
int index = -1; // 找到的位置

while( L<=R )   // 在範圍有東西的狀況下
{
    int M = (L+R)/2;           //中間點
    if( a[M] < x )      L=M+1; //戳到左邊,比 M 小的都不是答案
    else if( x < a[M] ) R=M-1; //戳到右邊,比 M 大的都不是答案


}

二分搜尋法

int a[100], x;
int L = 0;      // 資料的最左邊
int R = N-1;    // 資料的最右邊
int index = -1; // 找到的位置

while( L<=R )   // 在範圍有東西的狀況下
{
    int M = (L+R)/2;           //中間點
    if( a[M] < x )      L=M+1; //戳到左邊,比 M 小的都不是答案
    else if( x < a[M] ) R=M-1; //戳到右邊,比 M 大的都不是答案
    else { index = M; break; } //找到答案!

}

二分搜尋法

  • 因為每一次的搜尋,都會刪掉一半的資料
  • 二分搜尋法可以在\(O(\log{N})\)的時間查詢已序資料

基礎程式設計與競技程式設計的差異

int a[100], x;
int L = 0;      // 資料的最左邊
int R = N-1;    // 資料的最右邊
int index = -1; // 找到的位置

while( L<=R )   // 在範圍有東西的狀況下
{
    int M = (L+R)/2;           //中間點
    if( a[M] < x )      L=M+1; //戳到左邊,比 M 小的都不是答案
    else if( x < a[M] ) R=M-1; //戳到右邊,比 M 大的都不是答案
    else { index = M; break; } //找到答案!

}

AC

WA / TLE

這份 Code 有一個 bug (while迴圈內)

注意overflow

int M = (L+R)/2;

L = 2147483646, R = 2147483647

可改寫為

int M = L+(R-L)/2;

binary_search-1

By sylveon

binary_search-1

  • 613