• 二分查找原理和实现


    前言

    的确,我一开始的时候也认为二分查找挺简单的,但是在我对二分查找进行总结的时候,发现虽然思路很简单,但是代码要写的正确就不容易了。

    区间

    需要注意的是

    注意计算的区间是左闭右开区间[)还是左闭右闭区间[],两者的代码是不太一样的。

    左闭右闭区间

    如果说你使用的是左闭右闭区间

    int search3(int array[], int n, int target) {
        int left = 0;
        int right = n - 1; //比如说只有一个情况下
        while (left <= right ) { //需要等于号,不然会进不来
            int middle = left + (right - left) >> 1;
            if (target < array[middle]) {
                right = middle - 1;
            }
            else if (target > array[middle]) {
                left = middle + 1;
            }
            else {
                return middle;
            }
        }
        return -1;
    }
    

    左闭右开区间

    如果使用的是左闭右开区间:

    // 左闭右开区间
    int search4(int array[], int n, int target) {
        int left = 0;
        int right = n;
        while (left < right) {
            int middle = left + (right - left) >> 1;
            if (target < array[middle]) {
                right = middle;
            }
            else if (target > array[middle]) {
                left = middle + 1;
            }
            else {
                return middle;
            }
        }
        return -1;
    }
    

    lower_bound 和 upper_bound 和 binary_search 和 equal_range

    我们可以发现,它们的代码是有区别的

    左闭右闭区间,right = n - 1,left <= right,right = middle - 1

    左闭右开区间,right = n,left < right,right = middle

    为什么会这样呢?比如说n为1的时候,right此时为0,此时left也为0,需要等号才能进行while 循环。

    还有需要注意的是,计算middle的时候,先计算减法是为了避免溢出。

    STL的使用:

    lower_bound:找到第一个 >= target的位置upper_bound:找到第一个 > target的位置

    比如说给定的数组[1,2,2,2,3]

    使用lower_bound返回的是第一个值为2的迭代器;使用upper_bound返回的是最后一个值为2的下一个,也就是值为3的迭代器;

    使用lower_bound

    • 如果mid < val,显然mid本身及其左边的元素都不可能是答案了,则把first置为 mid + 1。
    • 如果mid >= val,答案可能是mid本身或者mid左边的元素,则把last置为mid。

    使用upper_bound

    • 如果mid <= val,显然mid其左边的元素都不可能是答案了,则把first置为 mid + 1。
    • 如果mid > val,答案可能是mid右边的元素,所以把last设置成mid。

    binary_search:查找某个元素是否出现

    equal_range:查找某个元素出现的起止位置(终止位置为最后一次出现的位置加1)

    实现

    二分查找元素key的下标,如无return -1

    有两个版本:

    // 二分查找找数组下标
    int search(int array[], int low, int high, int target) {
        if (low > high) {
            return -1;
        }
        int mid = (low + high) / 2;
        if (array[mid] < target) {
            return search(array, low, mid, target);
        }
        else if (array[mid] > target) {
            return search(array, mid + 1, high, target);
        }
        else {
            return mid;
        }
    }
    // 迭代版本
    int search2(int array[], int low, int high, int target) {
        while (low <= high) {
            int mid = (low + high) / 2;
            if (array[mid] > target) {
                high = mid-1;
            }
            else if (array[mid] < target) {
                low = mid+1;
            }
            else {
                return mid;
            }
        }
        return -1;
    }
    

    二分查找返回key(可能有重复)第一次出现的下标,如无return -1

    // 求最小的i,使得a[i] = key,其实就是求有重复数值的数组中第一次出现key的下标
    // 重点在于不能要等于,如果(l <= r)这样的话,会导致陷入死循环,因为题目要求计算的是最小下标
    // 并且(a[m] < key),所以导致r=m,l=r,会退出不了
    // 循环退出条件:left == right
    int binary_search_1(int a[], int n, int key) {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r - l) >> 1);//向下取整
            cout << ",m " << m;
            if (a[m] < key) l = m + 1;
            else r = m;
        }
        if (a[r] == key) return r;
        return -1;
    }
    // 同上
    int binary_search_1_1(int a[], int n, int key) {
        int res = (int)(lower_bound(a, a + n, key) - a);
        return (res == n || a[res] != key) ? -1 : res;
    }
    

    二分查找返回key(可能有重复)最后一次出现的下标,如无return -1

    // 求最大的i,使得a[i] = key,其实就是求有重复数值的数组中最后一次出现key的下标
    // 循环退出条件:left == right || left == right - 1
    int binary_search_2(int a[], int n, int key) {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r + 1 - l) >> 1);//向上取整
            cout << ",m " << m;
            if (a[m] <= key) l = m;
            else r = m - 1;
        }
        if (a[l] == key) return l;
        return -1;
    }
    // 同上
    int binary_search_2_2(int a[], int n, int key) {
        int res = (int)(upper_bound(a, a + n, key) - a);
        return (res == 0 || a[res - 1] != key) ? -1 : res;
    }
    

    二分查找返回刚好小于key的元素下标,如无return -1

    // 求最大的i,使得a[i] < key,如果key已经是最小的了,则返回-1
    // 其实就是求一个key前一个位置的下标
    int binary_search_4(int a[], int n, int key) {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r + 1 - l) >> 1);//向上取整
            cout << ",m " << m;
            if (a[m] < key) l = m;
            else r = m - 1;
        }
        if (a[l] < key) return l;
        return -1;
    }
    // 同上
    int binary_search_4_4(int a[], int n, int key) {
        int res = (int)(lower_bound(a, a + n, key) - a);
        return (res == 0) ? -1 : res;
    }
    

    二分查找返回刚好大于key的元素下标,如无return -1

    // 求最小的i,使得a[i] > key,如果key是最大的了,则返回-1
    // 其实就是求最后一个key的下一个位置的下标
    // 需要(a[m] <= key),这样才能把相等的数值去除掉
    int binary_search_3(int a[], int n, int key)
    {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r - l) >> 1);//向下取整
            cout << ",m " << m;
            if (a[m] <= key) l = m + 1;
            else r = m;
        }
        if (a[r] > key) return r;
        return -1;
    }
    // 同上
    int binary_search_3_3(int a[], int n, int key) {
        int res = (int)(upper_bound(a, a + n, key) - a);
        return (res == n) ? -1 : res;
    }
    

    参考文章

    全部的代码如下:

    #include <iostream>
    using namespace std;
    // 二分查找找数组下标
    int search(int array[], int low, int high, int target) {
        if (low > high) {
            return -1;
        }
        int mid = (low + high) / 2;
        if (array[mid] < target) {
            return search(array, low, mid, target);
        }
        else if (array[mid] > target) {
            return search(array, mid + 1, high, target);
        }
        else {
            return mid;
        }
    }
    // 迭代版本
    int search2(int array[], int low, int high, int target) {
        while (low <= high) {
            int mid = (low + high) / 2;
            if (array[mid] > target) {
                high = mid-1;
            }
            else if (array[mid] < target) {
                low = mid+1;
            }
            else {
                return mid;
            }
        }
        return -1;
    }
    // 左闭右闭区间
    int search3(int array[], int n, int target) {
        int left = 0;
        int right = n - 1; //比如说只有一个情况下
        while (left <= right ) { //需要等于号,不然会进不来
            int middle = left + (right - left) >> 1;
            if (target < array[middle]) {
                right = middle - 1;
            }
            else if (target > array[middle]) {
                left = middle + 1;
            }
            else {
                return middle;
            }
        }
        return -1;
    }
    // 左闭右开区间
    int search4(int array[], int n, int target) {
        int left = 0;
        int right = n;
        while (left < right) {
            int middle = left + (right - left) >> 1;
            if (target < array[middle]) {
                right = middle;
            }
            else if (target > array[middle]) {
                left = middle + 1;
            }
            else {
                return middle;
            }
        }
        return -1;
    }
    // 寻找包含某个值的区间[begin,end]
    void search5(int array[], int left, int right, int target, int& begin, int& end) {
        if (left >= right) {
            return ;
        }
        int mid = right - (right - begin)/2;
        if (array[mid] == target) {
            if (mid < begin || begin == -1) {
                begin = mid;
            }
            if (mid > end) {
                end = mid;
            }
            search5(array, left, mid-1, target, begin, end);
            search5(array, mid+1, right, target, begin, end);
        }
        else if (array[mid] > target) {
            search5(array, left, mid-1, target, begin, end);
        }
        else {
            search5(array, mid+1, right, target, begin, end);
        }
    }
    // 求最小的i,使得a[i] = key,其实就是求有重复数值的数组中第一次出现key的下标
    // 重点在于不能要等于,如果(l <= r)这样的话,会导致陷入死循环,因为题目要求计算的是最小下标
    // 并且(a[m] < key),所以导致r=m,l=r,会退出不了
    // 循环退出条件:left == right
    int binary_search_1(int a[], int n, int key) {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r - l) >> 1);//向下取整
            cout << ",m " << m;
            if (a[m] < key) l = m + 1;
            else r = m;
        }
        if (a[r] == key) return r;
        return -1;
    }
    // 同上
    int binary_search_1_1(int a[], int n, int key) {
        int res = (int)(lower_bound(a, a + n, key) - a);
        return (res == n || a[res] != key) ? -1 : res;
    }
    // 求最大的i,使得a[i] = key,其实就是求有重复数值的数组中最后一次出现key的下标
    // 循环退出条件:left == right || left == right - 1
    int binary_search_2(int a[], int n, int key) {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r + 1 - l) >> 1);//向上取整
            cout << ",m " << m;
            if (a[m] <= key) l = m;
            else r = m - 1;
        }
        if (a[l] == key) return l;
        return -1;
    }
    // 同上
    int binary_search_2_2(int a[], int n, int key) {
        int res = (int)(upper_bound(a, a + n, key) - a);
        return (res == 0 || a[res - 1] != key) ? -1 : res;
    }
    // 求最小的i,使得a[i] > key,如果key是最大的了,则返回-1
    // 其实就是求最后一个key的下一个位置的下标
    // 需要(a[m] <= key),这样才能把相等的数值去除掉
    int binary_search_3(int a[], int n, int key)
    {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r - l) >> 1);//向下取整
            cout << ",m " << m;
            if (a[m] <= key) l = m + 1;
            else r = m;
        }
        if (a[r] > key) return r;
        return -1;
    }
    // 同上
    int binary_search_3_3(int a[], int n, int key) {
        int res = (int)(upper_bound(a, a + n, key) - a);
        return (res == n) ? -1 : res;
    }
    // 求最大的i,使得a[i] < key,如果key已经是最小的了,则返回-1
    // 其实就是求一个key前一个位置的下标
    int binary_search_4(int a[], int n, int key) {
        int m, l = 0, r = n - 1;//闭区间[0, n - 1]
        while (l < r)
        {
            m = l + ((r + 1 - l) >> 1);//向上取整
            cout << ",m " << m;
            if (a[m] < key) l = m;
            else r = m - 1;
        }
        if (a[l] < key) return l;
        return -1;
    }
    // 同上
    int binary_search_4_4(int a[], int n, int key) {
        int res = (int)(lower_bound(a, a + n, key) - a);
        return (res == 0) ? -1 : res;
    }
    int main() {
        int arr[4] = {3, 4, 5, 6};
        int result1 = binary_search_1(arr, 4, 5);
        cout << " result1:" << result1 << endl;
        int result2 = binary_search_2(arr, 4, 5);
        cout << " result2:" << result2 << endl;
        int result3 = binary_search_3(arr, 4, 5);
        cout << " result3:" << result3 << endl;
        int result4 = binary_search_4(arr, 4, 5);
        cout << " result4:" << result4 << endl;
        return 0;
    }
    

    下面是Python实现

    import math
    # 第一个小于k的数
    def smallk(seq, k):
        lo, hi = 0, len(seq)
        while lo < hi:
            mid = math.ceil((lo + hi) / 2) # 注意需要向上取整,而//是向下取整
            if seq[mid] >= k:
                hi = mid - 1
            else:
                lo = mid
        return lo
    # 相同元素中第一个数
    def firstequalk(seq, k):
        lo, hi = 0, len(seq)
        while lo < hi:
            mid = (lo + hi) // 2
            if seq[mid] < k:
                lo = mid + 1
            else:
                hi = mid
        return lo
    # 第一个大于k的数
    def biggerk(seq, k):
        lo, hi = 0, len(seq)
        while lo < hi:
            mid = (lo + hi) // 2
            if seq[mid] <= k:
                lo = mid + 1
            else:
                hi = mid
        return lo
    # 相同元素中最后一个数
    def lastequalk(seq, k):
        lo, hi = 0, len(seq)
        while lo < hi:
            mid = (lo + hi) // 2
            if seq[mid] > k:
                hi = mid - 1
            else:
                lo = mid
        return lo
  • 相关阅读:
    Django基于form组件实现注册校验
    Django 框架概况
    【ARM】S5PV210芯片的启动流程
    【ARM】S3C6410芯片的启动流程
    Go's Declaration Syntax
    挺经
    软件系统最重要的一点
    Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)
    你大概走了假敏捷:认真说说敏捷的实现和问题(手绘版)
    我的书单
  • 原文地址:https://www.cnblogs.com/George1994/p/7532036.html
Copyright © 2020-2023  润新知