1. 程式人生 > >八、二分查詢(Binary Search)

八、二分查詢(Binary Search)

一、概述

二分查詢(Binary Search,也稱折半查詢)——針對有序資料集合的查詢演算法

1、基本思想

類似分治思想,每次都通過跟區間的中間元素進行對比,將代查詢的區間縮小為之前的一半,直到找到要查詢的元素,或者區間被縮小為0(不存在該元素)。

2、時間複雜度分析——O(logn)

不妨假設資料量為n,每次查詢後資料量均為原來的1/2,最壞的情況下,直到查詢區間被縮小為空,才停止。設經過k次區間縮小操作區間縮小到1,可得 k=log2n 時間複雜度為O(k),也就是O(logn)。

O(1) 常量級時間複雜度的演算法可能還沒有O(logn)的演算法執行效率高。

因為:大O標記法表示時間複雜度時,會省略掉常數、係數和低階,O(1)可能表示一個非常大的常量值,eg: O(10000)。

二、簡單實現

二分查詢最簡單的情況:有序陣列中不存在重複元素

1、遞迴方法實現

#include<iostream>
using namespace std;

int BinarySearchRecursive(int *array, int low, int high, int key)
{
    if(low > high)
        return -1;

	// int mid = (low + high) / 2;  若low和high比較大,兩者之和就可能溢位
int mid = low + (high - low) / 2; // 若效能有要求,可將除以2操作轉化為位運算,以提升效率 // int mid = low + ((high - low)>>1) if(array[mid] == key) return mid; else if(array[mid] < key) return BinarySearchRecursive(array, mid+1, high, key); else return BinarySearchRecursive(array,
low, mid-1, key); } int main() { int array[10]; for(int i = 0; i < 10; i++) array[i] = i; cout<< "Recursive:"<<endl; cout<<"postion:"<<BinarySearchRecursive(array,0,9,4)<<endl; return 0; }

2、非遞迴方法

int BinarySearch(int *array, int aSzie, int key)
{
    if(array == NULL || aSize == 0)
        return -1;
    int low = 0;
    int high = aSize - 1;
    int mid = 0;

    while(low <= high)
    {
        mid = low + (high - low) / 2;

        if(array[mid] == key)
            return mid;
        else if(array[mid] < key)
            low = mid + 1;
        else
            high = mid - 1;
    }
    return -1;
}

3、應用場景的侷限性

適用於:插入、刪除操作都不頻繁,一次排序多次查詢且資料量較大的場景。

  • 二分查詢依賴的是順序表資料結構,也就是陣列(可以按照下標隨機訪問元素)

  • 二分查詢的資料是有序的:排序演算法時間複雜度最低為O(nlogn),若動態變化的資料集合,不再適用。

  • 適合資料量較大的場景,而非特別大。因為陣列為了支援隨機訪問的特性,要求記憶體空間連續。

特例:若資料之間的比較操作特別耗時,不管資料量大小,都推薦使用二分查詢。同on個過儘可能較少比較次數來提升效能。

三、應用例項

1、例項

假設我們有 1000 萬個整數資料,每個資料佔 8 個位元組,如何設計資料結構和演算法,快速判斷某個整數是否出現在這 1000 萬資料中? 我們希望這個功能不要佔用太多的記憶體空間,最多不要超過 100MB,你會怎麼做呢?

2、分析

由於每個資料佔 8 個位元組,記憶體佔用差不多為80M < 100M,符合記憶體限制。
==》先從小到大排序,然後利用二分查詢演算法找資料。

注意:散列表、二叉樹這些支援快速查詢的動態資料結構。但是,在該情況卻不行的。雖然在大部分情況下,用二分查詢可以解決的問題,用散列表、二叉樹都可以解決。但是都會需要比較多的額外的記憶體空間。所以,如果用散列表或者二叉樹來儲存這
1000 萬的資料,用 100MB 的記憶體肯定是存不下的。而二分查詢底層依賴的是陣列,除了資料本身之外,不需要額外儲存其他資訊,是最省記憶體空間的儲存方式,所以剛好能在限定的記憶體大小下解決這個問題。

四、二分查詢的進級(變形問題)

前提:資料都是從小到大排列的,且資料中存在重複的資料。

1、常見的變形問題

  • 查詢第一個值等於給定值的元素
  • 查詢最後一個值等於給定值的元素
  • 查詢第一個大於等於給定值的元素
  • 查詢最後一個小於等於給定值的元素

2、查詢第一個值等於給定值的元素

(1)方法一

int bsearch1(int *array, int aSize, int key)
{
    int low = 0;
    int high = aSize - 1;
    while (low <= high) {
        int mid = low + ((high - low)/2);
        if(array[mid] > key)
            high = mid - 1;
        else if(array[mid] < key)
            low = mid + 1;
        else
        // 當array[mid]=key時,
        // 需要確認一下這個 array[mid] 是不是第一個值等於給定值的元素
        {
            // 若mid等於0,說明該元素為第一個元素;
            // array[mid]的前一個元素array[mid-1]不等於key,說明該元素為第一個元素;
            if((mid == 0)||(array[mid-1]!=key))
                return mid;
            // 否則更新區間
            else
                high = mid - 1;
        }
    }
    return -1;
}

(2)方法二

int bsearch2(int *array, int aSize, int key)
{
    int low = 0;
    int high = aSize - 1;
    
    while (low <= high)
    {
        int mid = low + ((high - low) / 2);
        if(array[mid] >= key)
        {
            high = mid - 1;
        }
        else
        {
            low = mid + 1;
        }
    }
    
    if(array[low] == key) 
        return low;
    else 
        return -1;
}

3、查詢最後一個值等於給定值的元素

int bSearch(int *array,int aSize,int key)
{
    int low = 0;
    int high = aSize - 1;
    while(low <= high)
    {
        int mid = low + ((high - low) / 2);
        if (array[mid] >= key)
            high = mid - 1;
        else if (array[mid < key]) {
            low = mid + 1;
        }
        else
        {
            if((mid == aSize - 1)||(array[mid+1] != key))
                return mid;
            else
                low = mid + 1;
        }
    }
    return -1;
}

4、查詢第一個大於等於給定值的元素

int bSearch(int *array,int aSize,int key)
{
    int low = 0;
    int high = aSize - 1;
    while(low <= high)
    {
        int mid = low + ((high - low) / 2);
        if (array[mid] >= key)
        {
            if((mid == 0)||(array[mid-1] < key))
                return mid;
            else
                high = mid - 1;
        }
        else
        {
            low = mid + 1;
        }
    }
    return -1;
}

5、查詢最後一個小於等於給定值的元素

int bSearch(int *array,int aSize,int key)
{
    int low = 0;
    int high = aSize - 1;
    while(low <= high)
    {
        int mid = low + ((high - low) / 2);
        if (array[mid] > key)
        {
            high = mid - 1;
        }
        else
        {
            if((mid == aSize - 1)||(array[mid+1] > key))
                return mid;
            else
                low = mid + 1;
        }
    }
    return -1;
}