递归和返回布尔值

recursion & returning boolean values

本文关键字:布尔值 返回 递归      更新时间:2023-10-16
bool binsearch(string phrase, vector<string> words, int from, int to, int &test)
{
    while (tf == "y") //tf is a global variable
    {
        int mid = (to+from)/2;
        if (words[mid] == phrase) {tf = "t"; return true;}
        if (mid == test) {tf = "f"; return false;}
        if (words[mid] > phrase) {return binsearch(phrase, words, mid-1, to, mid);}
        else {return binsearch(phrase, words, from, mid+1, mid);}
     }
}

我正在努力使这个二进制搜索工作。我需要整个函数返回"真"或"假"。我理解递归是如何工作的,直到执行第6行或第7行并调用return命令。我做了研究,似乎没有办法退出这个函数,它必须"unwind"自己。全局变量tf是无意义的,所以它不会在unwind时再次执行该体…但我还是没有得到我想要的结果。

本质上,我只是想在调用return true或return false命令后尽快退出函数,并相应地将值返回给主函数

谢谢

我也不明白你的二进制搜索,除了递归之外使用全局变量会导致程序非常难以理解。最好是再次返回调用堆栈并正确地"展开"它。请看下面的例子(未经测试):

bool binsearch(const string& phrase, const vector<string> &words, int from, int to)
{
    if (from > to)
        return false; // word not found
    int mid = from + (to - from) / 2; // i think your calculation is wrong here
    int cmp = phrase.compare(words[mid]);
    if (cmp < 0)
        return binsearch(phrase, words, from, mid - 1);
    else if (cmp > 0)
        return binsearch(phrase, words, mid + 1, to);
    else
        return true; // word found
}

您可以使用STL的内置binary_search如下:

binary_search(words.begin(),words.end(), phrase)

如果你这样做是为了学习;有几件事……

  • 你不需要while循环。有三种情况需要考虑:单词出现在mid之前,mid之后或mid之后。这三种情况中的每一种都是return s -所以甚至不可能到达循环体的末端。
  • 恰好时使用test,您需要这个变量吗?
  • 您应该仔细考虑到底哪个索引范围仍然需要搜索。 fromto是包含的还是不包含的?你需要精确和一致。
  • 考虑正整数的除法向下舍入。无论它们有什么值,确保递归调用调用更小的范围,以避免无限循环。这将有助于避免需要test变量(参见下面David的评论)。
  • 使用全局变量不是好的做法;当然不是在其他纯函数中—我假设您这样做是为了调试目的?
  • tofrom可以有多大?在某些情况下,注意to+from可能超过2^31-1
  • 在c++中,用迭代器来表达这些概念是很典型的。当然,你不必这么做。在c++中,如果可能的话,通过const &传递大对象是很典型的——这样,递归调用就不需要复制整个向量。这对于正确性来说并不重要,但是对于高效的代码来说却非常重要。

传递vector<string> words作为binsearch()函数的引用。目前,每当调用该函数时,它都会创建vector<string>的副本,这是不需要的。此外,如果将来要更新vector<>,那么通过引用传递是最好的方法。

while循环之外应该有return语句。那将是最后的"回归"。

解决这个问题的一个经典方法是:不递归地重写。

例如,使用while循环,然后一旦找到结果,使用break退出。你可以看看下面的代码(没有编译,只是用你自己的代码快速编写的)

bool binsearch(const string& phrase, const vector<string> &words, int from, int to)
{
    bool retVal = false;
    int start = from;
    int end = to;

    while(start<end) {
       int mid = from + (to - from) / 2; // i think your calculation is wrong here
       int cmp = phrase.compare(words[mid]);
       if (cmp < 0) {
           end=mid-1;
       } else if (cmp > 0) {
           start=mid+1;
       } else {
           retVal = true;
           break;
       }
    }
    return retVal;
}

没有优雅的或可移植的方法来跳出一个完整的调用堆栈,它充其量是相当冒险的。而且,非化函数将会更快:它不需要把东西压入堆栈,也不需要调用a函数

编辑

  • 添加了丢失的返回
  • 关于性能的
  • :只是基准测试。在这种特殊情况下,代码复杂性(对于人类读者来说)几乎是相同的,但根据算法的不同,它可能会复杂得多(甚至不可能)。

您的代码有几处错误。首先,不清楚tofrom是什么意思:它们是包含的,还是排斥的。如果它们都是包容的(你的论点对于递归调用似乎建议),您如何检测结束。test是什么意思?你好像把它当作当你没有找到这个词,但我不知道如何结束条件。

如果我在写这个,我会使用一个简单的helper类来保持目标和单词列表(但您可以传播它们)Down显式地),以及一个包装器函数,以便客户端代码不需要指定tofrom参数。有不需要全局变量或任何额外的test。和我会使用c++中惯用的半开放间隔:更低界包容,上界不包容(所以top == bottom指定一个空范围,所以我没有找到元素):

bool 
binSearch( std::string const& target,
           std::vector<std::string> const& words );
namespace {
    class BinSearcher
    {
        std::vector<std::string> const& myWords;
        std::string const& myTarget;
        bool doSearch( int bottom, int top ) const
        {
            int mid = (top - bottom) / 2;
            return mid != top
                && (myTarget == myWords[mid]
                    || (myTarget > myWords[mid] && doSearch( mid + 1, top ))
                    || (myTarget < myWords[mid] && doSearch( bottom, mid ));
        }
        BinSearcher( std::string const& target,
                     std::vector<std::string> const& words )
            : myWords( words )
            , myTarget( target )
        {
        }
        friend bool binSearch( std::string const&,
                               std::vector<std::string> const& );
    };
}
bool 
binSearch( std::string const& target,
           std::vector<std::string> const& words )
{
    BinSearcher searcher( target, words );
    return searcher.doSearch( 0, words.size() );
}

注意,在测试之前不能进行比较范围不相等;这将导致越界访问

也:我认为你这样做是出于教学的原因。否则,您应该使用标准中的函数图书馆。我通常不会在这里使用递归,这里有一个直接的迭代解决方案:

namespace {
    bool
    doBinSearch( std::string const& target,
                 std::vector<std::string> const& words,
                 int bottom,
                 int top )
    {
        bool found = false;
        while ( bottom != top && ! found ) {
            int mid = (top - bottom) / 2;
            int cmp = target.compare( words[mid] );
            if ( cmp < 0 ) {
                top = mid ;
            } else if ( 0 < cmp ) {
                bottom = mid + 1;
            } else {
                found = true;
            }
        }
        return found;
    }
}
bool
binSearch( std::string const& target,
           std::vector<std::string> const& words )
{
    return doBinSearch( target, words, 0, words.size() );
}

(最后,您会注意到我已将参数转换为参考文献它不会改变逻辑中的任何东西但是如果words比较大,它会产生一个非常大的代码

您可以使用longjmp,也就是"非本地goto",来立即退出内部递归,但问题是这种微优化是否值得。

一个更好的选择是将递归改为循环。由于所有递归调用都是"在尾部位置"(是return的参数),因此可以用重置参数变量的代码替换它们。不幸的是,我不懂你的代码,所以我不能给你一个例子。

这是使用递归的经典练习——当然,也可以用非递归的方式做事,但是"让递归来管理记账"是非常优雅的。对于那些下意识的反应是"迭代地做"的人,我建议在归并排序或快速排序上做类似的练习。非常相似的递归结构,但是在递归上下文中,簿记工作大大简化了。而在现代的cpu上,递归代码——令人惊讶!-通常运行速度一样快或更快,启动

这是我的递归实现,使用OP的问题上下文。注意,不需要单独测试中点元素:在比较谓词的c++"小于"范式的上下文中(在给定<的情况下,可以通过.not.(a.lt.b)和.not.(b.lt.a)推断相等),额外的中点相等性测试没有什么意义,尽管在具有多值比较结果的字符串类的特殊情况下,为0返回结果添加特殊处理可能会产生适度的加速。我的示例版本只假设><(从某种意义上说,如果真的只有><,则需要稍微重新排列分治条件以使用它),这更容易泛化到数字和用户定义的数据类型:>

bool binsearch(const string&phrase, vector<string>&words, int from, int to)
{
    if(from > to) return false;    // range sanity check
    if(from == to) return (phrase.compare(words[to]) == 0);    // bottom of the recursion
    int mid = from + (to-from)/2;        // Range still has >= 2 elements; set up to recurse
    if(phrase.compare(words[mid]) <= 0)  // Recurse into the subinterval bracketing the target
        return binsearch(phrase,words, from,mid);
    else
        return binsearch(phrase,words, mid+1,to);
}

这是上面的一个非递归版本,它纠正了用户'Bruce'发布的示例代码中的几个问题,并且再次使用不单独的中点值测试:

bool binsearch_nr(const string& phrase, const vector<string> &words, int from, int to)
{
    if(from > to) return false;    // range sanity check
    while(from < to) {
        int mid = from + (to-from)/2;
        int cmp = phrase.compare(words[mid]);
        if (cmp <= 0)
            to = mid;
        else
            from = mid+1;
    }
    return (phrase.compare(words[to]) == 0);
}

我使用100万个准随机文本片段的向量对上述两种实现进行了比较计时,代码在MacOS上使用gcc 4.2构建…递归版本的运行速度要慢20%。不过,对于我手工编写的合并排序代码,递归更快。YMMV .