資料結構與演算法-二分查詢

yew0發表於2020-10-26

什麼是二分查詢

  二分查詢又稱作折半搜尋演算法,是一種在有序陣列中查詢某一個特定元素的搜尋演算法;二分查詢的平局時間複雜度為O(logn)。


根據實際問題來講解二分查詢

問題1:查詢有序遞增陣列中的特定元素

  1. 查詢有序遞增陣列中的某一個數;

  2. 從有序遞增陣列的中間開始,如果中間的數值剛好是要查詢的數,搜尋就結束;

  3.  否則,將中間的數值跟搜尋的數作對比,如果中間的數比搜尋的數要大,那下一次查詢的範圍在左邊,如果中間的數比搜尋的數要小,那下一次查詢的範圍在右邊;

  4. 進入下一個範圍後,再次進行上面的2,3步驟操作,直到搜尋到相等的數值或者陣列為空。

列如:有序遞增陣列a=[1,4,4,5,6],特定元素是5。L為最左邊位置,R為最右邊位置,M為[L,R]區間內的中間位置。

第一次比較為:L=0,R=4,M=2,a[M]位元定值5小,區間往右邊搜尋,所以L=M+1,最左邊的位置往中間值下一位;L=3,R=4,M=3,a[M]與特定值5相等,結束搜尋。

如下圖所示:

上述是最基礎的二分查詢問題,核心思想。

//查詢有序遞增陣列中的特定元素
int BinarySearch(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查詢區域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中間值比搜尋值大,取左半邊區域
        if(pArrayNum[nMid] > nValue) {
            nRight = nMid - 1;
        }
        //中間值比搜尋值小,取右半邊區域
        else if(pArrayNum[nMid] < nValue) {
            nLeft = nMid + 1;
        }
        //找到相等值結束搜尋
        else {
            return nMid;
        }
    }
    //沒有找到對應值,返回-1
    return -1;
}

問題2:查詢在有序遞增陣列中第一個等於特定元素的位置

    判斷中間值位元定值大或者小跟上面問題1的思路是一樣的,只要問題是當相等的時候,當陣列內有多個數值跟特定元素相等,怎麼判斷為第一個?那我們就在相等處多加判斷條件,當中間位置為0,就是第一個時或者中間位置的前一個數值不等於特定數,那麼中間位置就是第一個等於特定值的;否則,我們應該往左邊區域搜尋,直到滿足中間位置為0或者前一個數值不等於特定值。

//查詢在有序遞增陣列中第一個等於特定元素的位置
int BinarySearch1(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查詢區域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中間值比搜尋值大,取左半邊區域
        if(pArrayNum[nMid] > nValue) {
            nRight = nMid - 1;
        }
        //中間值比搜尋值小,取右半邊區域
        else if(pArrayNum[nMid] < nValue) {
            nLeft = nMid + 1;
        }
        else {
            //判斷為相等時
            //如果nMid為第一個數或者上一個數不等於nValue,就能判斷出nMid就是要找的元素
            if((nMid == 0) || (pArrayNum[nMid-1] != nValue )) {
                return nMid;
            }
            //否則,再次進行左區域的搜尋,查詢的是第一個相等的數
            else {
                nRight = nMid - 1;
            }
        }
    }
    //沒有找到對應值,返回-1
    return -1;
}

問題3:查詢在有序遞增陣列中最後一個等於特定元素的位置

    思路基本跟問題2一樣,只是相等處的判斷條件稍微更改一下,因為我們要找最後一個等於特定值,所以我們更改判斷條件中間位置是最後一位或者下一個值是不等於特定值;否則,取右邊區域搜尋,直到滿足中間位置是最後一位或者下一個值不等於特定值。

//查詢在有序遞增陣列中最後一個等於特定元素的位置
int BinarySearch2(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查詢區域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中間值比搜尋值大,取左半邊區域
        if(pArrayNum[nMid] > nValue) {
            nRight = nMid - 1;
        }
        //中間值比搜尋值小,取右半邊區域
        else if(pArrayNum[nMid] < nValue) {
            nLeft = nMid + 1;
        }
        else {
            //判斷為相等時
            //如果nMid為最後一個數或者下一個數不等於nValue,就能判斷出nMid就是要找的元素
            if((nMid == (nCount - 1)) || (pArrayNum[nMid+1] != nValue )) {
                return nMid;
            }
            //否則,再次進行右區域的搜尋,查詢的是最後一個相等的數
            else {
                nLeft = nMid + 1;
            }
        }
    }
    //沒有找到對應值,返回-1
    return -1;
}

問題4:查詢在有序遞增陣列中第一個大於特定元素的位置

    根據問題1的思路,判斷條件只需要兩個,一個是大於特定值另外一個是小於等於特定值;當小於等於特定值時,取右邊區域搜尋;當大於特定值時,判斷中間位是否為第一位或者上一個值是否小於等於特定值,如果是,就是第一個大於特定值,否則,取右邊區域搜尋。

//查詢在有序遞增陣列中第一個大於特定元素的位置
int BinarySearch3(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查詢區域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中間值比搜尋值大
        if(pArrayNum[nMid] > nValue) {
            //如果中間值是第一個數或者特定值大於等於上一個值,那中間值就是第一個大於特定值
            if((nMid == 0) || (pArrayNum[nMid-1] <= nValue)) {
                return nMid;
            }
            //否則,再次進行左區域的搜尋,查詢的是第一個大於特定值
            else {
                nRight = nMid - 1;
            }
        }
        //中間值比搜尋值小或者等於,取右半邊區域
        else {
            nLeft = nMid + 1;
        }
    }
    //沒有找到對應值,返回-1
    return -1;
}

問題5:查詢在有序遞增陣列中最後一個小於特定元素的位置

    問題5的判斷條件基本跟問題4是相反的,當大於等於特定值時,取左邊區域搜尋;當小於特定值時,判斷中間位是否為最後一位或者下一個值是否大於等於特定值,如果是,就是最後一個小於特定值,否則,取左邊區域搜尋。

//查詢在有序遞增陣列中最後一個小於特定元素的位置
int BinarySearch4(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查詢區域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中間值比搜尋值大或者,取左半邊區域
        if(pArrayNum[nMid] >= nValue) {
            nRight = nMid - 1;
        }
        //中間值比搜尋值小
        else if(pArrayNum[nMid] < nValue) {
            //如果中間值是最後一個或者上一個值大於等於特定值,那中間數就是最後一個小於特定值
            if((nMid == (nCount - 1)) || (pArrayNum[nMid+1] >= nValue)) {
                return nMid;
            }
            //否則,再次進行右區域的搜尋,查詢的是最後一個小於特定值
            else {
                nLeft = nMid + 1;
            }
        }
    }
    //沒有找到對應值,返回-1
    return -1;
}

問題2,3,4,5都是從問題1變體而來,其實只要根據查詢的條件去更改每一個if條件,就可以得到相應的答案,最主要是弄懂二分查詢的原理,那相應的問題就能迎刃而解。

喜歡的可以關注公眾號檢視更多的文章

相關文章