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

二分查找

介绍

二分查找,也叫折半搜索、对数搜索。是用来在一个有序数组中查找一个数的算法。

例题

题目描述

给定一个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 nums中的所有元素是不重复的。
  2. n n n将在 [ 1 , 10000 ] [1, 10000] [1,10000]之间。
  3. n u m s nums nums的每个元素都将在 [ − 9999 , 9999 ] [-9999, 9999] [9999,9999]之间。

解题思路

思路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(logn))

从题目中我们发现数组是有序的,符合二分查找的使用条件。假设数组长度为n,我们可以先看nums[n / 2],假如nums[n / 2] > target,那么就意味着nums[n / 2]之后的所有数都大于target,我们只需要从 0 ∼ n / 2 0 \sim n / 2 0n/2中去找target即可。否则我们就从 n / 2 ∼ n − 1 n / 2 \sim n - 1 n/2n1中去找,当只剩下最后一个元素时,我们判断它与target是否相等,如果相等就返回下标,否则返回-1.因为我们每次都只会用到前一半或者后一半的数组,最后数组长度从n变成1,因此总共操作次数k符合 2 k = n 2^k = n 2k=n,因此k = logn,时间复杂度为 O ( l o g n ) O(logn) O(logn)

代码
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 <=mid值,另外一半反之,因此只有一半是我们可能需要的,另一半我们可以直接舍弃。从广义上看,不仅是排序好的数组可以用二分,只要是左右两边具有不同的性质都可以用二分,排序好的数组中,mid左边的数都<=midmid右边的数都>=mid,这便是左右两边的不同性质,可以让我们对数组进行折半拆分。,例如搜索旋转排序数组,这个题目的数组虽然没有严格排序,但它依然可以用二分解答。

边界问题

整数二分有一个边界问题,那就是在if(nums[mid] < target) l = mid + 1;中能不能改成l = mid,答案是不可以,假如我们有一个从 1 ∼ 100 1 \sim 100 1100内猜数字target = 7的程序代码如下:

#include 
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;
}

C++数据结构与算法分析——二分查找_第1张图片
我们很容易发现程序陷入了死循环,因为当l = 6,r = 7时,,mid = l + r >> 1 = 66 < 7,因此l = mid = 6,因此陷入了死循环,归根结底是因为mid = l + r >> 1向下取整的,因此当还剩2个元素的时候l和mid有可能相等,因此令l = mid会引发死循环,所以l = mid + 1在这份代码中是无法修改为l = mid的,其它情况也可以自由探索。

你可能感兴趣的:(笔记,题解,二分,算法,c++,二分法)