C++数据结构与算法分析——二分查找

  • Post author:
  • Post category:其他




二分查找



介绍

二分查找,也叫折半搜索、对数搜索。是用来在一个

有序

数组中查找一个数的算法。



例题




题目描述

给定一个

n

个元素有序的

升序

整型数组

nums

和一个目标值

target

,写一个函数

搜索 nums 中的 target



如果目标值存在返回下标,否则返回 -1



示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4


示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1


提示:
  1. 你可以假设



    n

    u

    m

    s

    nums






    n


    u


    m


    s





    中的所有元素是

    不重复

    的。




  2. n

    n






    n





    将在



    [

    1

    ,

    10000

    ]

    [1, 10000]






    [


    1


    ,




    1


    0


    0


    0


    0


    ]





    之间。




  3. n

    u

    m

    s

    nums






    n


    u


    m


    s





    的每个元素都将在



    [

    9999

    ,

    9999

    ]

    [-9999, 9999]






    [





    9


    9


    9


    9


    ,




    9


    9


    9


    9


    ]





    之间。



解题思路



思路1:暴力



(

O

(

n

)

)

(O(n))






(


O


(


n


)


)





遍历数组,如果出现了

target

值则

返回其下标

,如果遍历完还没有出现

target

值则

返回-1

,因为整个数组需要遍历一遍,因此时间复杂度为



O

(

n

)

O(n)






O


(


n


)







代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        for(int i = 0; i < nums.size(); i ++) // 遍历数组
            if(nums[i] == target) return i; // 如果出现了target值,返回下标
        return -1; // 遍历完还没出现target值,返回-1
    }
};


思路2:二分



(

O

(

l

o

g

n

)

)

(O(logn))






(


O


(


l


o


g


n


)


)





从题目中我们发现数组是

有序

的,符合二分查找的使用条件。假设数组长度为

n

,我们可以先看

nums[n / 2]

,假如

nums[n / 2] > target

,那么就意味着

nums[n / 2]

之后的所有数都大于

target

,我们只需要从



0

n

/

2

0 \sim n / 2






0













n


/


2





中去找

target

即可。否则我们就从



n

/

2

n

1

n / 2 \sim n – 1






n


/


2













n













1





中去找,当只剩下最后一个元素时,我们

判断它与target是否相等

,如果

相等就返回下标,否则返回-1

.因为我们每次都只会用到前一半或者后一半的数组,最后数组长度从

n

变成

1

,因此总共操作次数

k

符合



2

k

=

n

2^k = n







2










k











=








n





,因此

k = logn

,时间复杂度为



O

(

l

o

g

n

)

O(logn)






O


(


l


o


g


n


)







代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0,r = nums.size() - 1; // 设l为左边界,r为右边界,从nums[]l~r]中查找target
        while(l < r){ // 如果还剩下不止一个元素,则继续查找
            int mid = l + r >> 1; // 求出l~r的中点
            if(nums[mid] < target) l = mid + 1; // 假如中点值小于target,,就从nums[mid + 1,r]中查找target
            else r = mid; // 否则从nums[l,mid]中查找target
        }

        if(nums[l] == target) return l; // 如果剩下的数 == target,返回下标
        return -1;// 否则返回-1
    }
};



二分思想

前面的例题应该已经让你对二分有个初步的概念。就是将一个

有序数组

分成两半,其中一半的元素都



<

=

m

i

d

<=mid






<






=








m


i


d





值,另外一半反之,因此只有一半是我们可能需要的,另一半我们可以直接舍弃。从广义上看,不仅是排序好的数组可以用二分,只要是左右两边具有不同的性质都可以用二分,排序好的数组中,

mid左边的数都<=mid



mid右边的数都>=mid

,这便是左右两边的不同性质,可以让我们对数组进行折半拆分。,例如

搜索旋转排序数组

,这个题目的数组虽然没有严格排序,但它依然可以用二分解答。



边界问题

整数二分有一个边界问题,那就是在

if(nums[mid] < target) l = mid + 1;

中能不能改成

l = mid

,答案是不可以,假如我们有一个从



1

100

1 \sim 100






1













1


0


0





内猜数字

target = 7

的程序代码如下:

#include <iostream>
using namespace std;

int main(){
    int n =100;
    int target = 7;
    int l = 1,r = 100;
    while(l < r){
        cout << l << ' ' << r << endl; // 输出l 和 r
        int mid = l + r >> 1;
        if(mid < target) l = mid;
        else r = mid;
    }

    cout << l;

    return 0;
}

在这里插入图片描述

我们很容易发现程序陷入了死循环,因为当

l = 6,r = 7

时,,

mid = l + r >> 1 = 6



6 < 7

,因此

l = mid = 6

,因此陷入了死循环,归根结底是因为

mid = l + r >> 1



向下取整

的,因此当还剩2个元素的时候

l和mid有可能相等

,因此令

l = mid

会引发死循环,所以

l = mid + 1

在这份代码中是无法修改为

l = mid

的,其它情况也可以自由探索。



版权声明:本文为l_red_forest原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。