问题描述
我正在尝试实现一个二进制搜索版本,它是一个函数模板。它需要两个迭代器来定义范围,以及它正在搜索的值。如果在范围内找到该值,则函数模板将返回该值的迭代器。如果没有找到,则返回off-the-end迭代器。为简单起见,您必须能够使用提供的迭代器进行迭代器算术。
这是我目前所拥有的:
template <typename It,typename T> It binary(It beg,It end,T val) {
It mid = beg + (end - beg) / 2;
if (*mid == val)
return mid;
else if (*mid < val)
return mid = binary(mid,end,val);
else if (val < *mid)
return mid = binary(beg,mid,val);
return mid;
}
如果找到值,这个函数模板就可以正常工作。但是,如果找不到该值,我的程序就会崩溃。发生的情况是,如果在范围内找不到要搜索的值,则迭代器 mid
会“卡住”,因为它指向容器中的最后一个元素或指向容器中的第一个元素容器。当再次进行递归调用时,mid
将重新计算到其完全相同的位置,从而导致无限循环。
template <typename It,T val) {
//New piece of code
if (end - beg == 1 and *beg < val)
return end;
if (end == beg)
//*** WHAT TO RETURN HERE? ***
//old code
It mid = beg + (end - beg) / 2;
if (*mid == val)
return mid;
else if (*mid < val)
return mid = binary(mid,val);
return mid;
}
如果要搜索的值大于容器中的所有值,这解决了问题,但是如果要搜索的值小于容器中的所有值,那么我返回什么? (请参阅我在代码中的评论,询问“我要返回什么?”)。我想返回一个迭代器,它超过原始范围中的最后一个元素,但是当我达到 beg == end
的条件时,我可能已经对 binary
执行了多次递归调用,并且因此我无法访问原始的 end
迭代器。
我希望所有这些都有意义。我很感激任何帮助。另外,我想在仍然使用递归的同时解决这个问题。
解决方法
{
if (beg==end) return end;
正确处理空范围。从根本上说,这就是您提出的问题的正确答案。
下一步是备份,看看std库是如何处理的。 std 库公开了两个函数,下界和上界,再加上一个两者兼而有之——范围相等。
下限返回不小于参数的第一个元素,上限返回不小于或等于该元素的第一个元素。
它们一起构成了一系列元素,如果非空,则包含您想要的元素。
你的代码版本
if (beg==end) return end;
现在是 lower_bound
。您可以添加 upper_bound
并通过搜索 equal_range
到 answer
元素 end
(又名 >
)来生成 !<=
,其中 {{1} } 只是小于参数颠倒。
因为它使代码更简洁,所以我写了一个简单的范围类型:
>
捆绑一对迭代器。
template<class It>
struct range {
It b,e;
range(It s,It f):b(s),e(f) {}
range(It s,std::size_t n):range(s,s+n) {}
It begin()const{ return b; }
It end()const{ return e; }
bool empty()const{ return begin()==end(); }
std::size_t size()const{ return end()-begin(); }
range without_front(std::size_t n=1)const{
n = (std::min)(size(),n);
return {begin()+n,end()};
}
range without_back(std::size_t n=1)const{
n = (std::min)(size(),n);
return {begin(),end()-n};
}
range only_front(std::size_t n=1)const{
n = (std::min)(size(),begin()+n};
}
range only_back(std::size_t n=1)const{
n = (std::min)(size(),n);
return {end()-n,end()};
}
decltype(*std::declval<It const&>()) front()const
{ return *begin(); }
decltype(*std::declval<It const&>()) back()const
{ return *std::prev(end()); }
};
所以现在我们有一个有趣的函数来查找等于 template <class It,class T>
range<It> binary(range<It> toSearch,T val) {
if (toSearch.empty()) return toSearch;
// note,for a size 1 range,left can be empty
auto left = toSearch.only_front( toSearch.size()/2 );
// while right cannot.
auto right = toSearch.without_front( toSearch.size()/2 );
// is val only left or right? If so,recurse
if (right.front() < val) {
return binary( right.without_front(1),val );
} else if (val < right.front()) {
return binary( left,val );
}
// otherwise,right.front() == val at this point
// find entire range recursively:
auto lhs = binary(left,val);
auto rhs = binary(right,val);
return {lhs.begin(),rhs.end()};
}
的元素范围。
然后我们会解决您的问题:
val
完成。
您会注意到此代码仅使用 template <typename It,typename T>
It binary(It beg,It end,T val) {
auto r = binary( range<It>{beg,end},val );
if (r.empty()) return end;
return r.begin();
}
来比较值,它从不检查 <
。这与 std 库的工作方式相匹配。
一般来说,二分查找有两种特殊情况;空的一个,和 1 个元素的一个。以上明确处理空的; 1 元素由 ==
隐式处理,这也是对一般搜索的优化(我们已经排除了右边的前元素)。
另一个有趣的事情是这段代码:
.without_front(1)
我们真的想在这里进行 3 向比较。在 C++17 中,我们得到 if (right.front() < val) {
return binary( right.without_front(1),val );
} else if (val < right.front()) {
return binary( left,val );
}
正是这样做的。这不是巧合。