二分查找原理和实现

Posted banananana

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找原理和实现相关的知识,希望对你有一定的参考价值。

前言

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

区间

需要注意的是

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

左闭右闭区间

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

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

以上是关于二分查找原理和实现的主要内容,如果未能解决你的问题,请参考以下文章

二分查找法

阶乘二分查找法及原理--分支循环的简单应用

二分查找

二分查找和快速排序(理论+代码)

减而治之 - 二分查找 - lower_bound实现原理

leetcode查找算法(顺序查找,二分法,斐波那契查找,插值查找,分块查找)