子串递归算法不起作用

Posted

技术标签:

【中文标题】子串递归算法不起作用【英文标题】:Substring recursive algorithm not working 【发布时间】:2011-01-19 12:05:57 【问题描述】:

我是第一个 C++ 课程的编程学生,最近有人鼓励我们编写一个简单的递归函数来查找给定字符串中第一次出现的子字符串。如果找到,则返回索引。如果未找到子字符串,index_of() 函数应返回 -1。我们鼓励使用将索引作为其参数之一的辅助函数,这就是我尝试过的方法。

例如:

int index_of("Mississippi", "sip"); // this would return a 6

这应该是一个简单的练习来帮助我们理解递归,不会上交。我的教授说我们实际的递归作业会涉及更多,这就是为什么我很想了解这个简单的用法的递归。

我已经使用 C 风格的字符串和指针成功地完成了这项工作,但没有使用 C++ std::string 对象。我在我的程序中做错了什么?我的教授说我们应该很容易在 5 分钟内写完这篇文章,但我已经为此苦苦挣扎了两个小时。这是我到目前为止所做的:

int index_of(string s, string t)

    int index = 0;

    if (s[index] == NULL)
        return -1;
    else if (starts_with(s, t, ++index))
    
        return index;
    
    else 
        return index;


bool starts_with(string s, string t, int index)

    if (t[index] == NULL)
        return true;
    if ( s[index] == NULL || t[0] != s[index])
        return false;
    return starts_with(s, t, ++index);

正如所写,此函数始终返回 1 的 index

【问题讨论】:

在将问题发布到此处之前付出了很多努力自己尝试解决这个问题,做得很好。很清爽! index 在 index_of 中增加 ++ 后永远不会更新。考虑从starts_with返回索引而不是标志 对于那些在家玩的人,这继续从***.com/questions/2307146 要明确一点,这不是上交作业,我知道我之前在那里问过一个问题,但我真的一直在努力理解这一点。我真的很挣扎,我一直在画画,我作为最后的手段在这里问。 【参考方案1】:
int index_of(string s, string t)

    int index = 0;

    if (s[index] == NULL)

句号。这不是 C++ 字符串的工作方式,如果你想使用它们,你必须解决这个问题。即使是 C 风格的字符串,也不要使用 NULL 来表示 ASCII 空字符。它们共享一个名称但用途不同,您不应使用 NULL 来表示整数零(字符是整数类型,空字符是它们的零值)。使用'\0' 或仅使用if (s[index])

但是,除非您知道索引有效,否则不允许您索引 std::string。为此,请将索引与 s.size() 进行比较(并确保它大于或等于 0)。即便如此,您在这里真正测试的是 s 是否为空,并且它有一个特殊的方法可以做到这一点:

    if (s.empty())

继续:

    else if (starts_with(s, t, ++index))

递增和递减内部表达式,尤其是在这里,可能会让初学者感到困惑而没有任何优势。它们的主要优点是代码简洁明了,但您必须先了解代码的主要部分,即使这样,有经验的程序员有时也会受益于稍微冗长一点。

有趣的是,同样参与早期 C 历史的 Go 的创造者甚至将增量从表达式变成了 语句,我相信清晰性是很大一部分原因。


从头开始

你想用这个签名实现一个函数:

int index_of(string haystack, string needle);
// returns the index of needle in haystack, if found
// otherwise returns -1

我故意将那些带有签名的 cmets 包括在内:它们是此函数的公共接口的一部分。更好的参数名称也会增加清晰度。

确定您需要考虑的情况:

针是空的(您可以通过多种方式处理) 干草堆是空的:返回-1 此时我们知道 haystack 和 needle 都不是空的 剩下的两种情况是算法的关键: haystack 的第一个字符与 needle 的第一个字符不匹配 第一个字符匹配

当第一个字符匹配时,你有两个子情况:

needle 中没有更多字符:找到匹配项 还有更多字符:继续检查

我把这些写成一个递归算法,它接收每个字符串(和子字符串)的“新副本”,而不是使用索引。但是,您可以通过将“第一个字符”更改为“当前字符”来转换为使用索引,对于“空”条件也是如此。在这种情况下,您将需要使用 两个 索引(到目前为止,尝试只使用一个可能是您的主要绊脚石),除非您有一个比较子字符串的帮助功能(尽管我我不确定您的教授是否有单独的意图发表此评论)。

将上述散文直接翻译成代码:

int index_of(string haystack, string needle) 
  if (needle.empty()) return 0;
  // this implementation considers empty substrings to occur at the start of any
  // string, even an empty haystack; you could also make it an error to call
  // index_of when needle is empty, or just return -1

  if (haystack.empty()) return -1;

  assert(!needle.empty() && !haystack.empty()); // I wouldn't normally include
  // this, since we just checked these conditions, but this is the "at this
  // point we know both haystack and needle are not empty" that I mentioned

  if (haystack[0] != needle[0]) 
    // mark A, see below
    int index = index_of(haystack.substr(1), needle);
    return index != -1 ? index + 1 : index;
  

  if (needle.length() == 1) return 0; // found complete match
  // note the way I chose to handle needle.empty() above makes this unnecessary

  // mark B, see below    
  // partial match (of the first character), continue matching
  int index = index_of(haystack.substr(1), needle.substr(1)); // strip first
  return index == 0 ? 0 : -1;
  // must check index == 0 exactly, if -1 then we must return that, and if not 0
  // then we've found a "broken" needle, which isn't a real match

断针注释暗示了该代码的效率低下,因为它将递归调用分为两类:必须在标记 B 处匹配 1(分割成子字符串后为 0),并且可以在标记处匹配任何位置答:我们可以使用辅助函数来改进这一点,为此我将使用 std::string 的 operator== 重载(对 haystack 的子字符串进行操作)。这产生了经典“naive strstr”的递归等价物:

int index_of(string haystack, string needle) 
  if (needle.empty()) return 0;
  if (haystack.empty()) return -1;
  if (haystack.substr(0, needle.length()) == needle()) 
    return 0;
  
  int index = index_of(haystack.substr(1), needle);
  if (index != -1) index++;
  return index;

当使用带有 string::compare 作为帮助器的 haystack 索引时,不需要针索引:

// might not be exposed publicly, but could be
int index_of(string const& haystack, int haystack_pos, string const& needle) 
  // would normally use string const& for all the string parameters in this
  // answer, but I've mostly stuck to the prototype you already have

  // shorter local name, keep parameter name the same for interface clarity
  int& h = haystack_pos;

  // preconditions:
  assert(0 <= h && h <= haystack.length());

  if (needle.empty()) return h;
  if (h == haystack.length()) return -1;
  if (haystack.compare(h, needle.length(), needle) == 0) 
    return h;
  
  return index_of(haystack, h+1, needle);


int index_of(string haystack, string needle) 
  // sets up initial values or the "context" for the common case
  return index_of(haystack, 0, needle);

注意这个版本是尾递归的,但这仍然是一个简单的算法和更高级的算法exist。


如果我有更多时间,我会写一封更短的信。 ——西塞罗

您已经说过这对您有很大帮助,但是,即使我刚刚包含了其他示例,我似乎也缺乏它。在我看来,子字符串搜索不是一个好的递归练习,这可能就是原因。

【讨论】:

哇,对我来说真的很棒。非常感谢你,我的朋友!您帮助我解决了问题,同时了解了递归。如果我能给你更多的代表...... @Alex:很高兴我能帮上忙。这与代表无关,但无论如何谢谢。 :)【参考方案2】:

(注 - 为简单起见进行了编辑)

不确定这是否会有所帮助,因为我不确定您的老师在多大程度上向您解释了递归,但是这里......

这就是您需要考虑的方式 - 递归函数包含两个主要组件:

1) 基本案例和

2) 递归案例

关键是在每次调用时,您要确定这两种情况中的哪一种对于函数的这次运行是正确的(基于输入参数)。

基本情况是函数不再被调用,递归情况总是调用该函数,其逻辑通常比基本情况复杂.


所以我建议重新开始。考虑一下输入应该是什么,这样在使用该输入的函数调用中,函数不会调用自身 ---> 这是你的基本情况

如果在函数调用中它不是基本情况,则它是递归情况。所以你的函数需要看起来像这样:

function index_of(params...)
    if base case
        do something simple and return
    else
        do something and call index_of again

提示:在你的函数中,没有递归的情况。

【讨论】:

【参考方案3】:

我认为 5 分钟对于入门班的学生来说根本不是一个合理的估计。为了帮助你平衡帮助你理解问题,我写了我认为是最终答案的内容...

#include <string>
#include <iostream>
using namespace std;

int starts_with(string s, string sub, unsigned int i) 
  if (i >= s.length())
    return -1;
  if (s.compare(i, sub.length(), sub) == 0)
    return i;
  return starts_with(s, sub, i + 1);

int index_of(string s, string sub)  return starts_with(s, sub, 0U); 
int main(void)  cout << index_of("Mississippi", "sip") << "\n"; 

【讨论】:

叹息,一个没有评论的开车投票 ...我不需要积分,但这总是让我想知道为什么... 哦,U后缀的意思是无符号【参考方案4】:

您的代码归结为这一点

int index_of(string s, string t)

    int index = 0;

    //if (s[index] == NULL)
    //    return -1;
    ++index // from this: else if (starts_with(s, t, ++index))
    //
    //    return index;
   // 
   //else 
        return index;

所以,是的,它总是返回 1

index 在递归 starts_with 函数内继续递增,但更改后的值永远不会使其成为您的返回值。

【讨论】:

【参考方案5】:

您没有更新index_of() 函数中的index 变量。当您将它传递给starts_with() 时,它会在堆栈上创建一个副本。您需要以某种方式返回更新的值 - 以太通过引用/指针获取它或返回它而不是 bool

【讨论】:

以上是关于子串递归算法不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个“数独求解器”算法不起作用

指针分配在递归函数中不起作用

为啥这个 PHP 递归函数不起作用

Laravel 一对多关系不起作用 - 返回递归

为啥我的修剪二叉树的递归解决方案不起作用?

XSLT 2.0 中的尾递归函数不起作用