简体   繁体   English

递归二进制搜索“超出范围”

[英]Recursive Binary Search 'Out of Range'

just wondering if someone could help me out with my attempt at writing a recursive binary search. 我只是想知道是否有人可以帮助我编写递归二进制搜索。 It is currently throwing up a 'out of range' error: 当前抛出“超出范围”错误:

terminate called after throwing an instance of 'std::out_of_range'
what():  vector::_M_range_check: __n (which is 0) >= this->size() (which is 0)
Aborted (core dumped)

I'm sure it's to do with my failed attempt at writing correct recursion (which I am still new at). 我确信这与我编写正确的递归的失败尝试有关(我仍然是新手)。 If anyone could give me a hint as to where the issue lies I would be greatly appreciative. 如果有人能给我提示问题的根源,我将不胜感激。 Here is my code: 这是我的代码:

RecursiveBinarySearch.cpp RecursiveBinarySearch.cpp

    // RecursiveBinarySearch class constructor.
RecursiveBinarySearch::RecursiveBinarySearch()
{

}

// Sets the object that is being searched for. 
// In this case, we are always looking for the integer '1'.
int obj = 1;

// Searching the vector given for obj. if obj is found the function returns true, otherwise it returns false.
bool RecursiveBinarySearch::binarySearch(std::vector<int> vec, int mid)
{
    int start = 0, end = vec.size() - 1;
    std::cout << "mid : " << mid << "\n";

    while (start + 1 < end)
    {

        if (vec.at(mid) == obj)
            return true;

        else if (vec.at(mid) > obj)
            //end = mid - 1;
            return binarySearch(vec, mid - 1);

        else
            //start = mid + 1;
            return binarySearch(vec, mid + 1);

    }

    if ((vec.at(start) == obj) || (vec.at(end) == obj))
        return true;
    else
    {
        return false;
    }
}

// RecursiveBinarySearch class destructor.
RecursiveBinarySearch::~RecursiveBinarySearch()
{

}

main.cpp: main.cpp中:

int main()
{

    // The user inputs a string of numbers (e.g. "6 4 -2 88 ..etc") and those integers are then put into a vector named 'vec'.
    std::vector<int> vec;
    int vecSize = vec.size();
    int mid = (vec.at(0) + vec.at(vecSize - 1)) / 2;

    std::string line;
    if (getline(std::cin, line))
    {
        std::istringstream str(line);

        int value;
        str >> value;
        vec.push_back(value);
        while (str >> value)
        {
            vec.push_back(value);
        }
    }

    // Creating RecursiveBinarySearch object.
    RecursiveBinarySearch bSearch;
    RecursiveBinarySearch *ptrBSearch = &bSearch;
    bool bS = ptrBSearch->binarySearch(vec, mid);

    // Print out inputted integers.
    std::cout << "Binary Search Result: \n";
    std::cout << bS << "\n";

    return 0;
}

Thanks! 谢谢!

Out-of-range simply means you're indexing beyond your range limits (either low or high) for your given sequence container. 超出范围只是意味着您索引超出了给定序列容器的范围限制(低或高)。 And props to you for using at() to catch the issue. 并建议您使用at()来解决问题。

Your posted code has several issues. 您发布的代码有几个问题。 Among them, the most devastating is improper midpoint calculation. 其中,最具破坏性的是不正确的中点计算。 You're finding value averages; 您正在寻找价值平均值; not mid point, then using those as indexes, which is clearly wrong. 不是中点,然后将其用作索引,这显然是错误的。 Your initial mid value is also wrong, as it is taken prior to any elements being in your container. 您的初始mid值也是错误的,因为它是在将任何元素放入容器之前获取的。

An important point also, you should be using a const reference to your container. 还有一点很重要,您应该使用对容器的const引用。 Otherwise, you making copies of the entire container with each recursive call. 否则,您将在每次递归调用时复制整个容器 It may not seem such a big deal, but do this with a hundred-million items and I assure you it will be a very expensive. 看起来似乎没什么大不了的,但是用亿万个项目来做到这一点,我向您保证这将是非常昂贵的。

That said, your setup is simply wrong . 就是说,您的设置完全错误 Recursive binary search is about divide-and-conquer (I expect you know that). 递归二进制搜索是关于分而治之的(我希望您知道这一点)。 As a requirement for logarithmic search, the sequence must be sorted . 作为对数搜索的要求, 必须对序列进行排序 Beyond that, the most straight-forward approach to accomplish this with a sequence container requires you know three things: 除此之外,使用序列容器完成此操作的最简单方法要求您了解三件事:

  • The value you seek (duh) 您寻求的价值(duh)
  • The index (or iterator) of the item you're starting with. 您开始的项目的索引(或迭代器)。
  • The one-past index (or end-iterator) denoting the end-of-sequence position of the current partition. 表示当前分区的序列结束位置的过去一索引(或结束迭代器)。

That last one always throws people new to the algorithm for a loop, but it makes sense when you begin doing the math. 最后一个总是让算法的新手陷入困境,但是当您开始进行数学运算时才有意义。 In short, think of it as the first index where you're not searching, not an inclusive index where you are searching. 总之,把它想成,你不是在搜索的第一个索引,而不是一个包容性的指标,你正在寻找。 It also makes coding the algorithm, either iteratively or recursively, straightforward. 这也使得对算法进行迭代或递归编码变得简单。

Only when you have all of the above can you produce a recursive algorithm with an escape condition , which is critical. 只有具备以上所有条件 ,才能生成具有转义条件的递归算法,这是至关重要的。 You must have a way to stop doing what you're doing. 必须有一种方法来停止做您正在做的事情。

Using your parameter list and providing the missing pieces, a recursive version looks like this: 使用参数列表并提供缺少的部分,递归版本如下所示:

bool binarySearchR(std::vector<int> const& v, size_t beg, size_t end, int val)
{
    // when these are equal it means there are no elements
    //  left to search, and that means no match was found.
    if (beg == end)
        return false;

    // find midpoint
    size_t mid = beg + (end-beg)/2;

    // if the test value is less, recurse to upper partition
    //  important: we just checked 'mid', so the lower point
    //  is one *past* that; therefore ++mid is the recursed
    //  'beg' index.
    if (v[mid] < val)
        return binarySearchR(v, ++mid, end, val);

    // if the test value is greater, recurse to lower partition
    //  important: we don't check the 'end' index, it's the
    //  stopping point so just pass it as the recursed 'end' index;
    //  'mid' is therefore not modified here.
    if (val < v[mid])
        return binarySearchR(v, beg, mid, val);

    // not lesser, not greater, thus equal
    return true;
}

You can further simplify this by overloading the function to simply take a vector by const-reference and a value, then invoke the recursive function: 您可以通过重载该函数以通过const-reference和一个值简单地获取向量,然后调用递归函数来进一步简化此过程:

bool binarySearchR(std::vector<int> const& v, int val)
{
    return binarySearchR(v, 0, v.size(), val);
}

This allows you to invoke it like this: 这使您可以像这样调用它:

int main()
{
    std::vector<int> vec { 1,2,3,4,6,9,10 };
    std::cout << std::boolalpha;

    for (int i=-1; i<=11; ++i)
        std::cout << std::setw(2) << i << ':' << binarySearchR(vec, i) << '\n';
}

Output 产量

-1:false
 0:false
 1:true
 2:true
 3:true
 4:true
 5:false
 6:true
 7:false
 8:false
 9:true
10:true
11:false

The output is as-expected, and test values and edge cases work correctly. 输出结果符合预期,测试值和边沿情况正常运行。


Iterator-based Recursive Binary Search 基于迭代器的递归二进制搜索

An iterator-based approach is much more inline with how modern C++ works, and as a bonus extends the operation to other sequence containers such as std::deque . 基于迭代器的方法更加符合现代C ++的工作原理,此外,该操作还扩展了对其他序列容器的操作,例如std::deque It follows the same overall design as described above, but uses a template-based Iter type: 它遵循与上述相同的总体设计,但是使用基于模板的Iter类型:

template<class Iter>
bool binarySearchR(Iter beg, Iter end, typename std::iterator_traits<Iter>::value_type const& arg)
{
    if (beg == end)
        return false;

    Iter mid = std::next(beg, std::distance(beg,end)/2);
    if (*mid < arg)
        return binarySearchR(++mid, end, arg);

    if (arg < *mid)
        return binarySearchR(beg, mid, arg);

    return true;
}

We could likewise overload this to take just a vector (which we assume is sorted) and a test value, but why stop there. 同样,我们可以重载它以仅获取一个向量(我们假设它已排序)和一个测试值,但是为什么要停在那里。 We can craft a template that takes a template-type as one of the arguments, and thanks to C++11 and variadic template arguments, it brings an elegant solution: 我们可以制作一个以template-type作为参数之一的模板,并且由于C ++ 11和可变参数模板参数,它带来了一个优雅的解决方案:

template<class T, template<class, class...> class C, class... Args>
bool binarySearchR(C<T,Args...> const& seq, T const& val)
{
    return binarySearchR(std::begin(seq), std::end(seq), val);
}

The same test program from the prior section will then work, and produce the same results. 然后,与上一部分相同的测试程序将起作用,并产生相同的结果。


Binary Search Without Recursion 没有递归的二进制搜索

Once you get the hang of the algorithm, you quickly discover it lends itself well to an iterative algorithm instead of recursive. 一旦掌握了算法的要点,就会很快发现它很适合迭代算法,而不是递归算法。 It honestly won't matter much in terms of call-stack space. 老实说,就调用栈空间而言,这无关紧要。 A binary search of 2.4 billion sorted items will only recurse at-most 31 times, but still, that's unnecessary calls and it would be nice if we can avoid them. 对24亿个排序项的二进制搜索最多只能重复执行31次,但是仍然是不必要的调用,如果我们可以避免它们,那将是很好的。 Further it may optimize better, and that's always something worth considering: 此外,它可能会优化得更好,这始终是值得考虑的事项:

template<class Iter>
bool binarySearchI(Iter beg, Iter end, typename std::iterator_traits<Iter>::value_type const& arg)
{
    while (beg != end)
    {
        Iter mid = std::next(beg, std::distance(beg,end)/2);
        if (*mid < arg)
            beg = ++mid;
        else if (arg < *mid)
            end = mid;
        else return true;
    }
    return false;
}

The same overload applies: 相同的重载适用:

template<class T, template<class, class...> class C, class... Args>
bool binarySearchI(C<T,Args...> const& seq, T const& val)
{
    return binarySearchI(std::begin(seq), std::end(seq), val);
}

And it produces the same results we are expecting. 它产生的结果与我们预期的相同。

Lets take a closer look at 让我们仔细看看

    std::vector<int> vec;
    int vecSize = vec.size();
    int mid = (vec.at(0) + vec.at(vecSize - 1)) / 2;

Breaking it down, we get 分解,我们得到

    std::vector<int> vec;

Creates an empty vector 创建一个空向量

    int vecSize = vec.size();

The size of an empty vector will be zero, so 空向量的大小将为零,因此

    int mid = (vec.at(0) + vec.at(vecSize - 1)) / 2;

always resolves to 总是决心

    int mid = (vec.at(0) + vec.at(0 - 1)) / 2;

and vec.at(0 - 1) is not a good place for vectors to go. vec.at(0 - 1)并不是矢量传播的好地方。

Solution: Compute mid later AFTER loading the vector. 解决方案:在加载向量之后的中途计算。

As an afterthought, consider providing binarySearch with the item to search for as a parameter. 事后,请考虑为binarySearch提供要搜索的项目作为参数。 The current implementation is not re-entrant. 当前的实现不是可重入的。

Taking a closer look at your algorithm. 仔细研究您的算法。 Few things to note: 几件事要注意:

  1. your binary search takes in 2 params vector and mid, binarySearch(std::vector vec, int mid) For any recursive algorithm to work you need to have 2 things, a stopping condition (so you don't end up in infinite loops) and a recursive condition ( which with each recursion takes you closer to your solution, in case of binary search it would be something that reduces your search space by half ) In you case your vector passed in is always the same, and the start and end calculated are also the same everything, resulting in the same search space everytime. 您的二进制搜索采用2个参数vector和mid,binarySearch(std :: vector vec,int mid)要使任何递归算法正常工作,您需要具备2种条件,即停止条件(因此您不会陷入无限循环中)和一个递归条件(每次递归都使您更接近解决方案,在二进制搜索的情况下,这会使您的搜索空间减少一半)。在这种情况下,传入的向量始终相同,并且开始和结束计算得出的所有内容也相同,每次都产生相同的搜索空间。 You need to either pass in a new start and end recursively or you need to modify your vector in each recursive pass. 您需要递归输入新的起点和终点,或者需要在每次递归传递中修改向量。

  2. You define you mid as follows: int mid = (vec.at(0) + vec.at(vecSize - 1)) / 2; 您对mid的定义如下:int mid =(vec.at(0)+ vec.at(vecSize-1))/ 2; by doing this you are defining your mid to be the average of the first and last elements of the vector and not their positions. 通过这样做,您将中间定义为向量的第一个和最后一个元素的平均值,而不是它们的位置。 For eg. 例如。 vector = [ 2, 5, 60, 75, 80], your mid would be (2+80)/2 = 41, and 41 is definitely not a valid position in your vector. vector = [2,5,60,75,80],您的中位数将是(2 + 80)/ 2 = 41,并且41绝对不是向量中的有效位置。 your mid should instead be (0 + 4) / 2 which is = 2; 您的中位数应该是(0 + 4)/ 2,即= 2; you can get this by using your start and end and should be recalculated each time inside your recursive function. 您可以通过使用开始和结束来获得此值,并且每次在递归函数中都应重新计算。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM