1. 程式人生 > >【學習筆記】二分查詢

【學習筆記】二分查詢

二分查詢的學習筆記

之前在bilibili看到一個有趣的視訊,關於二分查詢的。戳我看這個有趣的視訊

筆記參考:極客時間|資料結構與演算法之美

1. 二分思想

就如上面視訊中的栗子,猜數字遊戲,如果從頭開始一個一個的猜是非常低效的。

在實際的開發場景中,假設有1w條訂單資料,已經按照訂單的金額從小到大排序,每個定乾的金額不同,最小單位是元。

如果從第一個訂單開始,遍歷這1w條訂單,直到找到目標為止,這種方法在最壞情況下可能要便利完所有資料才能找到。

但是用二分法,每次都通過跟區間中間元素對比,將整個帶查詢的區間縮小為之前的一半,直到查詢的需要的資料或者區間縮小為0。

2. 二分查詢實現(遞迴和非遞迴)

// 非遞迴實現
int bsearch(int a[], int n, int value) {
  int low = 0;
  int high = n - 1;

  while (low <= high) 
  {
    int mid = (low + high) / 2;
    if (a[mid] == value) 
    {
      return mid;
    } 
    else if (a[mid] < value) 
    {
      low = mid + 1;
    }
    else 
    {
      high = mid -
1; } }/*while*/ return -1; }
  1. 迴圈退出條件

low<=high

  1. mid的取值

mid=(low+high)/2在low和high比較大的話兩者之和容易溢位。

改進:low+(high-low)/2

更高效:low+((high-low)>>1)

  1. low和high的更新

low=mid+1high=mid-1這裡的+1和-1,如果寫成low=mid或high=mid就可能發生死迴圈。

// 二分查詢的遞迴實現
int bsearch(int a[], int n, int val) 
{
  return b(a, 0, n -
1, val); } int b(int a[], int low, int high, int value) { if (low > high) return -1; int mid = low + ((high - low) >> 1); if (a[mid] == value) { return mid; } else if (a[mid] < value) { return bsearchInternally(a, mid+1, high, value); } else { return bsearchInternally(a, low, mid-1, value); } }

3. 時間複雜度

二分查詢的被查詢區間縮小速度是一個等比數列,公比q為1/2。

假設資料規模為n,每次查詢後資料規模都會縮小為原來的一半,最壞情況下,直到查詢區間被縮小為空停止。

n/2^k=1時,k就是總共縮小的次數,每次縮小隻涉及2個數據的比較,經過k次區間縮小操作,時間複雜度為O(k)。k等於以2為底n的對數,解得時間複雜度為O(logn)

O(logn)在某些情況下比O(1)時間複雜度的演算法高效。O(1)時忽略掉常熟、係數和低階。當資料規模夠大時,如O(10000),此時log(n)的演算法效率比O(1)的高。

4. 侷限性

  1. 二分查詢依賴順序結構
  2. 二分查詢針對的是有序資料
  3. 資料量太小不適合二分查詢

比如在10個元素的陣列中查詢一個元素,遍歷會來的更方便一些。而且查詢速度也差不多。

  1. 資料量太大也不適合二分查詢

為了支援隨機訪問,記憶體空間需要連續。

5. 二分查詢的變體問題

  1. 查詢第一個值等於給定值的元素

當區間中間值不等於要查詢的元素時,需要繼續查詢。

當區間中間值等於要查詢的元素時,看看中間值的前一個值是否等於要查詢的元素,如果等則更新high=mid-1,否則現在這個元素就是要查詢的元素。

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == 0) || (a[mid - 1] != value)) return mid;
      else high = mid - 1;
    }
  }
  return -1;
}
  1. 查詢最後一個值等於給定值的元素

跟第一種一樣,不過這次要和mid後面的一個元素進行比較。更新low=mid+1。

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}
  1. 查詢第一個大於等於給定值的元素

如果mid的值小於要查詢的值,則目標值在[mid+1,high]之間,更新low。

如果mid的值大於要查詢的值,目標值在[low,mid-1]之間,更新high。

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] >= value) {
      if ((mid == 0) || (a[mid - 1] < value)) return mid;
      else high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  return -1;
}
  1. 查詢最後一個小於等於給定值的元素

要查詢的值大於當前mid中的值,目標值在[low,mid-1]之間,更新high=mid-1。

要查詢的值小於mid+1的值,則要查詢的值在[mid+1,high]之間,更新low=mid+1。

public int bsearch7(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}