【问题标题】:Binary search for finding the lowest and largest element in a sorted array than a given value?二进制搜索查找排序数组中比给定值的最小和最大元素?
【发布时间】:2016-02-01 00:30:47
【问题描述】:

所以,我试图实现二进制搜索算法(尽可能通用,可以适应不同的情况)。我在互联网上搜索了这个,有些使用while (low != high),有些使用while (low <= high),以及其他一些非常令人困惑的不同条件。

因此,我开始编写代码来查找大于给定元素的第一个元素。我想知道是否有比这更优雅的解决方案?

主要代码:

#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
#include <stack>
#include <queue>
#include <climits>
#include <set>
#include <cstring>

using namespace std;
int arr1[2000];
int n;
int main (void)
{
    int val1,val2;
    cin>>n;
    for (int i = 0; i < n; i++)
        cin>>arr1[i];
    sort(arr1,arr1+n); 
    cout<<"Enter the value for which next greater element than this value is to be found";   
    cin>>val1;
    cout<<"Enter the value for which the first element smaller than this value is to be found";
    cin>>val2;

    int ans1 = binarysearch1(val1);
    int ans2 = binarysearch2(val2);

    cout<<ans1<<"\n"<<ans2<<"\n";
    return 0;
}
int binarysearch1(int val)
{
    while (start <= end)
    {
        int mid = start + (end-start)/2;
        if (arr[mid] <= val && arr[mid+1] > val)
            return mid+1;
        else if (arr[mid] > val)
            end = mid-1;
        else
            start = mid+1;
    }
}

类似地,为了找到小于给定元素的第一个元素,

int binarysearch2(int val)
{

    while (start <= end)
    {
        int mid = start + (end-start)/2;
        if (arr[mid] >= val && arr[mid] < val)
            return mid+1;
        else if (arr[mid] > val)
            end = mid-1;
        else
            start = mid+1;
    }
}

当我必须修改二进制搜索以实现这种抽象时,我经常会感到非常困惑。请让我知道是否有更简单的方法?谢谢!

【问题讨论】:

  • 请告诉我您已经为这些任务创建了一个函数...发布完整代码。
  • 如果没有这样的元素,预期的行为是什么?
  • 如果没有这样的元素,它应该返回 -1。我在编写这种二进制搜索时感到困惑,因此我不知道在哪里写它。分开怎么办?
  • @KarolyHorvath,添加了完整的代码。
  • 搜索lower_bound()upper_bound()

标签: c++ algorithm binary-search


【解决方案1】:

正如你所说,有不同的方式来表达二分搜索的结束条件,这完全取决于你的两个限制意味着什么。让我解释一下我的,我认为它很容易理解,它可以让你在其他情况下修改它而不用想太多。

让我把这两个限制称为firstlast。我们希望找到大于某个 x 的第一个元素。以下不变量将一直保持:

last 之后的每个元素都大于 x 并且之前的每个元素 first 小于或等于(相反的情况)。

请注意,不变量没有说明区间 [first, last]。在不进一步了解向量的情况下,唯一有效的限制初始化是 first = 0 和 last = 向量的最后位置。这满足条件,因为在last之后什么都没有,first之前什么都没有,所以一切都是对的。

由于区间 [first, last] 是未知的,我们将不得不继续直到它为空,从而更新限制。

int get_first_greater(const std::vector<int>& v, int x)
{
  int first = 0, last = int(v.size()) - 1;
  while (first <= last)
  {
    int mid = (first + last) / 2;
    if (v[mid] > x)
      last = mid - 1;
    else
      first = mid + 1;
  }
  return last + 1 == v.size() ? -1 : last + 1;
}

如您所见,我们只需要两种情况,因此代码非常简单。在每次检查时,我们都会更新限制以始终保持我们的不变量为真。

当循环结束时,使用不变量我们知道 last + 1 大于 x 如果它存在,所以我们只需要检查我们是否还在是否在我们的向量中。

考虑到这一点,您可以根据需要修改二进制搜索。让我们更改它以找到小于 x 的最后一个。我们改变不变量:

first 之前的每个元素都小于 x 并且每个元素 last 之后大于或等于 x

这样,修改代码真的很容易:

int get_last_smaller(const std::vector<int>& v, int x)
{
  int first = 0, last = int(v.size()) - 1;
  while (first <= last)
  {
    int mid = (first + last) / 2;
    if (v[mid] >= x)
      last = mid - 1;
    else
      first = mid + 1;
  }
  return first - 1 < 0 ? -1 : first - 1;
}

检查我们是否只更改了运算符(>= 而不是 >)和返回,使用与以前相同的参数。

【讨论】:

  • 这很容易。你让它变得如此简单。荣誉。这么容易就明白了。非常感谢。 :)
  • 在同一行中,如果我想在包含重复值的排序数组中查找元素的第一次和最后一次出现,您能否也发布代码?我可以这样做,但是我的代码涉及递归,我想按照你的方法来做。
  • 您可以使用前面的两个代码。对于第一次出现,你取“get_last_smaller”位置+1,因为它是唯一的候选位置(当然你需要检查它是否是正确的值,否则说它不存在)。对于最后一次出现,只需取“get_first_greater” - 1,再次检查数字是否正确。在这两种情况下,不要忘记检查您是否仍在向量中。
  • 我有一个疑问。就像您所做的那样,您永远不会真正检查a[mid] == something 的情况,您只需要两个情况,小于或大于。我想知道,您的方法是否适用于所有情况,或者我们是否也必须检查中间值?
  • 我编写了许多二进制搜索变体,我总是使用这种方法。但我不能保证它在所有情况下都有效,因为可能有一个问题我到目前为止还没有考虑过。
【解决方案2】:

很难编写正确的程序。一旦一个程序被证实是正确的,它就应该很少被修改并被更多地重用。在该行中,鉴于您使用的是 C++ 而不是 C,我建议您尽可能使用标准 C++ 库。您正在寻找的两个功能都在算法中提供给您。

http://en.cppreference.com/w/cpp/algorithm/lower_bound http://en.cppreference.com/w/cpp/algorithm/upper_bound 为您创造了奇迹,并且鉴于模板的强大功能,您应该能够通过添加其他实现排序的方法来使用这些方法。

HTH。

【讨论】:

    【解决方案3】:

    要部分回答这个问题,可以根据是要搜索大于元素的第一个元素还是要搜索的第一个元素来排除实际比较(使用回调函数或类似函数)。更小。但是,在第一个代码块中,您使用

    arr[mid] <= val && arr[mid+1] > val
    

    在第二个块中,第二个条件下的索引移位

    if (arr[mid] >= val && arr[mid] < val)
    

    省略了,好像不一致。

    【讨论】:

      【解决方案4】:

      您的搜索程序有一些错误 [其中一个被彻底破坏了]。我已经清理了它们,但我从你的代码开始。注意:不能保证——这里已经很晚了,但这应该给你一个起点。请注意,“lo/hi”是标准命名法(例如,lo 是您的开始,hi 是您的结束)。另外,请注意 hi/lo 设置为 mid 而 not mid+1 或 mid-1

      有一些边缘情况需要处理。 while 循环必须是 "

      int
      binarysearch_larger(const int *arr,int cnt,int val)
      // arr -- array to search
      // cnt -- number of elements in array
      // val -- desired value to be searched for
      {
          int mid;
          int lo;
          int hi;
          int match;
      
          lo = 0;
          hi = cnt - 1;
      
          match = -1;
      
          while (lo < hi) {
              mid = (hi + lo) / 2;
      
              if (arr[mid] <= val) && (arr[mid+1] > val)) {
                  if ((mid + 1) < cnt)
                      match = mid + 1;
                  break;
              }
      
              if (arr[mid] > val)
                  hi = mid;
              else
                  lo = mid;
          }
      
          return match;
      }
      
      int
      binarysearch_smaller(const int *arr,int cnt,int val)
      // arr -- array to search
      // cnt -- number of elements in array
      // val -- desired value to be searched for
      {
          int mid;
          int lo;
          int hi;
          int match;
      
          lo = 0;
          hi = cnt - 1;
      
          match = -1;
      
          while (lo < hi) {
              mid = (hi + lo) / 2;
      
              if (arr[mid] <= val) && (arr[mid+1] > val)) {
                  match = mid;
                  break;
              }
      
              if (arr[mid] > val)
                  hi = mid;
              else
                  lo = mid;
          }
      
          // the condition here could be "<=" or "<" as you prefer
          if ((match < 0) && (arr[cnt - 1] <= val))
              match = cnt - 1;
      
          return match;
      }
      

      【讨论】:

        【解决方案5】:

        下面是一个通用算法,它给定一个排序范围的元素和一个值,它返回一对迭代器,其中第一个迭代器的值是排序范围中比较小于输入值的第一个元素,并且第二个迭代器的值是该范围内比较大于输入值的第一个元素。

        如果返回的迭代器对指向范围的末尾,则表示输入的范围为空。

        我已尽我所能使其通用,它还可以处理边缘情况和重复。

        template<typename BidirectionalIterator>
        std::pair<BidirectionalIterator, BidirectionalIterator>
        lowhigh(BidirectionalIterator first, BidirectionalIterator last,
                typename std::iterator_traits<BidirectionalIterator>::value_type const &val) {
          if(first != last) {
            auto low = std::lower_bound(first, last, val);
            if(low == last) {
              --last;
              return std::make_pair(last, last);
            } else if(low == first) {
             if(first != last - 1) {
                return std::make_pair(first, std::upper_bound(low, last - 1, val) + 1);   
              } else {
                return std::make_pair(first, first);  
              }
            } else {
              auto up = std::upper_bound(low, last, val);
              return (up == last)? std::make_pair(low - 1, up - 1) : std::make_pair(low - 1, up);
            }
          }
          return std::make_pair(last, last);
        }
        

        LIVE DEMO

        【讨论】:

        • 此代码假定数组中没有重复元素。您断言“它肯定比您的版本更快,因为它处理边缘情况而不进行二进制搜索”假设搜索的项目通常位于或超出数组内容的范围。这可能是真的,也可能不是。如果它总是正确的,那么您的通用二进制搜索库函数很可能会做同样的事情。也许您刚刚添加了几个比较,可以更好地划分数组。
        • @MichaelBurr 非常感谢您的评论,所有的 cmets 都非常准确,并且发现了错误。我更正了语言和算法,现在它也可以处理重复项。再次感谢您花时间发现我的愚蠢错误。
        • 没什么愚蠢的,如果我觉得我粗鲁,对不起。
        猜你喜欢
        • 2015-05-10
        • 2020-11-01
        • 2018-08-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-17
        • 2014-07-21
        • 2013-12-15
        相关资源
        最近更新 更多