演算法基礎---二分演算法

qzl_ecut發表於2021-01-25

一、定義

二分查詢也稱折半查詢(Binary Search),它是一種效率較高的查詢方法。但是,折半查詢要求線性表必須採用順序儲存結構,而且表中元素按關鍵字有序排列。

二、查詢過程

在一個有序序列中,取中間值把序列分成兩邊,先看中間值,如果中間值小於要查詢的值,那麼說明目標值一定在

右邊;如果中間值大於目標值,那麼目標值一定在左邊;如果中間值等於目標值,那麼查詢完成。

以猜數字遊戲為例,小明想一個0-1000的數,小紅來猜。

小明選的是623為例:

小紅:大於500嗎?

小明:yes

小紅:那麼一定在[501,1000]之間,大於750嗎?

小明:no

小紅:那麼一定在[501,750]之間,大於625嗎?

小明:no

小紅:那麼一定在[501,625]之間,大於563嗎?

小明:yes

小紅:那麼一定在[564,625]之間,大於594嗎?

小明:yes

小紅:那麼一定在[595,625]之間,大於610嗎?

小明:yes

小紅:那麼一定在[611,625]之間,大於618嗎?

小明:yes

小紅:那麼一定在[619,625]之間,大於622嗎?

小明:yes

小紅:那麼一定在[623,625]之間,大於624嗎?

小明:no

小紅:那麼一定在[623,623]之間,你想的數字是623

小明:yes。

對於1-1000之間的猜數字遊戲,最多隻需要10次就能找到。

二分查詢演算法的時間複雜度是O(logN)

三、框架

二分查詢的框架大致如下:

public int searchInsert(int[] nums, int target) {
        int l = 0;
        int r = nums.length-1;
        int mid = l + (r-l)/2;
        while(l < r){
            //能找到,直接返回元素下標
            if(nums[mid] == target) 
                return mid;
            else if(nums[mid] < target){
                l = mid + 1;
                mid = l + (r-l)/2;
            }
            else{
                r = mid-1;
                mid = l + (r-l)/2;
            }
        }
    	//未找到
    	return -1;
    }

注意:

1、中間值mid取法

應該用 mid = l + (r-l)/ 2 ,而不是 mid = (l + r) / 2,因為如果 l 和 r都很大,那麼(l + r)將會溢位整數範圍。

2、偶數個數的中間值

如果數字的總數是偶數,那麼中間值有兩個,按照 mid = l + (r-l)/ 2 算的話,中間值應該是左邊那個。例如對於1、2、3、4,l = 0,r = 3,

mid = 0 + (0+3)/ 2

​ =0 + 1

​ = 1。

因為mid、l、r都是int型別,如果有小數部分,會自動捨棄。

四、例題

1、搜尋插入位置

給定一個排序陣列和一個目標值,在陣列中找到目標值,並返回其索引。如果目標值不存在於陣列中,返回它將會被按順序插入的位置。

你可以假設陣列中無重複元素。

示例 1:

輸入: [1,3,5,6], 5
輸出: 2

示例 2:

輸入: [1,3,5,6], 2
輸出: 1

示例 3:

輸入: [1,3,5,6], 7
輸出: 4

示例 4:

輸入: [1,3,5,6], 0
輸出: 0

題目來源:力扣(LeetCode)

解題思路:先使用二分查詢,如果能找到,直接返回元素下標;

接下來討論不能找到的情況:

第一種情況,二分查詢結束後,目標值大於mid

以[1,3,5,6,9] 和目標值 4為例

初始狀態:

image-20210125171558598

第一遍二分查詢結束:

image-20210125171719166

第二遍二分查詢結束:

image-20210125171851758

兩遍查詢結束之後,發現mid小於目標值4,此時,4應該插入在3後面,所以返回 mid+1。

第二種情況,二分查詢結束後,目標值小於mid

以[1,3,5,6,9] 和目標值 7為例,

初始狀態:

image-20210125172150256

第一遍二分查詢後:

image-20210125172306442

第二遍二分查詢後:

image-20210125172346205

兩遍查詢結束之後,發現mid大於目標值7,此時,7應該插入在9前面,所以返回 mid。

題解程式碼:

public int searchInsert(int[] nums, int target) {
        int l = 0;
        int r = nums.length-1;
        int mid = l + (r-l)/2;
        while(l < r){
            //能找到,直接返回元素下標
            if(nums[mid] == target) 
                return mid;
            else if(nums[mid] < target){
                l = mid + 1;
                mid = l + (r-l)/2;
            }
            else{
                r = mid-1;
                mid = l + (r-l)/2;
            }
        }
    	//第一種情況,二分查詢結束後,目標值大於mid
        if(nums[mid] < target)
            return mid+1;
    	//第二種情況,二分查詢結束後,目標值小於mid
        else
            return mid;
    }

2、搜尋二維矩陣

image-20210125180220665

題目來源:力扣(LeetCode)

解題思路:

既然題目提到了要編寫一個高效的演算法,那麼用兩個for迴圈暴力列舉肯定是不行的。

再看一遍題目,該矩陣具有以下特性:同一行,右邊的數要大,同一列,下面的數要大。由此我們可以發現,如果我們取一個數作為右上角,那麼比該數小的一定在其左邊的列之中,比該數大的一定在該數的下面的行之中。

例如:

對於下面矩陣,我們取7作為右上角的數,那麼比7小的一定在第0、1、2列,比7大的一定在第1、2、3行。

image-20210125181457081

假設我們要找11,那麼首先取右上角7,發現7比11小,因為7是第一行最大的,所以第一行不可能再找到11,剔除第0行;從第1行開始找,同樣,從右上角20開始看,20大於11,所以第3列一定不可能找到11,因此我們剔除第3列;之後,16作為右上角,16大於11,剔除第2列;11作為右上角,找到目標數。

簡單來說就是:從右上角開始找,

右上角的數 = 目標,結束查詢,返回true;

右上角的數 < 目標,剔除該行;

右上角數 > 目標,剔除該列。

程式結束也未找到,返回false。

題解程式碼:

public boolean searchMatrix(int[][] matrix, int target) {
        for(int i = 0;i < matrix.length;i++){
            for(int j = matrix[0].length-1;j >=0;j--){
                //從右上角開始搜尋
                //如果右上角數字為目標數字,返回true
                if(matrix[i][j] == target)
                    return true;
                //如果右上角數字小於目標數字
                //說明該行都小於目標數字,剔除該行
                else if(matrix[i][j] < target)
                    break;
                //如果右上角數字大於目標數字
                //說明該列都大於目標數字,剔除該列
                else
                    continue;
            }
        }
        //沒有找到
        return false;
    }

原文連結:邱子林的部落格

相關文章