C++ / Python 关于 lower_bound & upper_bound 的算法实现与应用

文章目录

  • Part.I Introduction
    • Chap.I 无序→有序
    • Chap.II 背后原理:二分查找
  • Part.II 应用示例
    • Chap.I C++
    • Chap.II Python

Part.I Introduction

C++ / Python 关于 lower_bound & upper_bound 的算法实现与应用_第1张图片

在很多情况下,我们要在一个有序数组中找满足一定条件的数据或索引。比如,经常会用到,『找元素值大于等于某个数的最小值的索引』(lower_bound)和『找元素值小于等于某个数的最大值的索引 + 1』(upper_bound,其实也可以说成是『找元素值大于某个数的最小值的索引』)。用图像表示如下:

C++ / Python 关于 lower_bound & upper_bound 的算法实现与应用_第2张图片

下面是一些预备工作。

Chap.I 无序→有序

上面提到的是『有序数组』,但是很多情况下我们拿到的数组是无序的,所以我们首先要将数组进行排序。

C++ 排序函数:

sort(a.begin(),a.end(), cmp);

a根据规则cmp进行升序排列,执行完成之后a就变成升序的了;当要逆序时,用rbegin()即可。


Python 排序函数:

lst.sort(cmp=None, key=None, reverse=False)

一般不需给任何参数,换言之给个空括号即可;执行完之后lst就变成升序的了;如果要降序,只需将reverse赋值为true即可。


C++ 中的sort()函数用的是改进的快速排序算法,时间复杂度为O(logN);Python 中的sort()函数用的是Timsort算法, Timsort 是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,它在实际使用中有很好的效率。

Chap.II 背后原理:二分查找

实现这种功能所用到的算法是『二分查找』。关于二分查找,网上有很多大佬写的总结,笔者也整理了自己的版本,戳我!!!其中关于二分查找的模板,笔者分为了三种:

  • 基础版:适用于无重复元素的数组
  • 寻找左侧边界版
  • 寻找右侧边界版

口诀如下:

  • 待查数组需有序,搜索区间两端闭;
  • while 条件带等号,否则补丁打不及。
  • if 相等便返回,其他杂事可不提;
  • mid 必须加减一,因为区间两端闭。
  • while 结束就GG,凄凄惨惨返-1;
  • 如果元素有重复,相等返回需注意。

注:最后一句『相等』指的是『if 相等』;『返回』指的是 while 结束之后的操作。


下面以Python 为例,自己实现这种功能。

lower_bound 实际上就是一个『寻找左侧边界版的二分查找』,其实现如下:

def lower_bound(nums, target):
    low, high = 0, len(nums) - 1
    pos = len(nums)
    while low <= high:
        mid = low + (high - low) // 2
        if nums[mid] < target:
            low = mid + 1
        else:
            high = mid - 1      # 和模版有所区别的是,这里对两种情况进行了一个合并
    return low

upper_bound 实际上就是一个『寻找右侧边界版的二分查找』,其实现如下:

def upper_bound(nums, target):
    low, high = 0, len(nums) - 1
    pos = len(nums)
    while low <= high:
        mid = low + (high - low) // 2
        if nums[mid] > target:
            high = mid - 1
        else:
            low = mid + 1       # 和模版有所区别的是,这里对两种情况进行了一个合并
    return high + 1             # 要大于才行,所以要加一

Part.II 应用示例

Chap.I C++

C++ 中已经有现成的函数,位于#include 中,其用法如下:

  • auto itr = lower_bound(v.begin(), v.end(), tar);:在已排序的 v[begin,end)范围内查找值小于tar的元素,返回最后一个比tar小的元素的迭代器+1(第一个大于等于tar的元素的迭代器);
  • auto itr = upper_bound(v.begin(), v.end(), tar);:在已排序的 v[begin,end)范围内查找值小于等于tar的元素,返回第一个大于tar的迭代器。

可以看出来,返回的是一个迭代器,接着可通过下面的操作来得到我们所需:

cout << *itr << endl;	// 得到它的值
distance(v.begin(),itr)	// 得到它的索引下标,需要 #include

下面是一个简单的用法实例:

#include 
#include 
#include 
#include 

using namespace std;

int main()
{
    vector<int> v({1,2,2,2,3,6,8});
    int tar = 2;
    auto itr = lower_bound(v.begin(),v.end(),tar);
    cout << distance(v.begin(),itr) << endl;
    itr = upper_bound(v.begin(),v.end(),tar);
    cout << distance(v.begin(),itr) << endl;
    getchar();
    return 0;
}

输出:

1
4

Chap.II Python

Python 中也有现成的库函数,位于bisect中,需要导入相应的函数from bisect import bisect_right, bisect_left,其用法如下:

  • bisect_left(v, tar):在数组v中『找元素值大于等于某个数的最小值的索引』
  • bisect_right(v, tar):在数组v中『找元素值大于某个数的最小值的索引』

下面是一个简单的用法实例:

from bisect import bisect_left, bisect_right

v1=[1,2,2,2,3,6,8]
print(bisect_right(v1,2))
print(bisect_left(v1,2))

输出:

1
4

你可能感兴趣的:(#,C++,#,Python,实用,算法,python,c++)