我是第一个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);
}
如上所述,此函数始终返回index
为1。
答案 0 :(得分:7)
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))
增加和减少内部表达式,特别是在这里,对于没有优势的初学者来说可能会让人感到困惑。它们的主要优点是代码简洁明了,但您必须先了解代码的主要部分,即使是经验丰富的程序员有时也会受益于更加冗长的代码。
有趣的是,Go的创作者也参与了早期的C历史,甚至将表达式的增量转换为语句,我相信清晰度是其中很大一部分原因。
您想要使用此签名实现一个函数:
int index_of(string haystack, string needle);
// returns the index of needle in haystack, if found
// otherwise returns -1
我故意将这些注释包含在签名中:它们是此功能的公共接口的一部分。更好的参数名称也可以提高清晰度。
确定您需要考虑的案例:
当第一个角色匹配时,你有两个子案例:
我把它们写成递归算法,它接收每个字符串(和子字符串)的“新副本”,而不是使用索引。但是,您可以通过将“第一个字符”更改为“当前字符”来转换为使用索引,对于“空”条件也是如此。在这种情况下你会想要使用两个索引(并且尝试只使用一个可能是你到目前为止的一个主要绊脚石),除非你有一个帮助功能来比较子串(虽然我'我不确定你的教授是否对这个评论有单独的意图。)
将上述散文直接翻译成代码:
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
}
断针评论暗示该代码效率低下,因为它将递归调用分为两类:必须在1处(在切入子字符串后为0),在标记B处匹配,并且可以在任何地方匹配,在标记处答:我们可以使用辅助函数来改进它,并且我将使用std :: string的operator == overload(在haystack的子字符串上运行)。这产生了经典的“天真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。
如果我有更多时间,我会写一封较短的字母 - 西塞罗
你说这对你有很大的帮助,但是,即使我刚才包含的其他例子,我似乎也缺乏。在我看来,子串搜索不是一个很好的递归练习,这可能就是原因。
答案 1 :(得分:5)
您的代码归结为此
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
索引在递归starts_with
函数内继续递增,但更改后的值永远不会使其返回为您的返回值。
答案 2 :(得分:0)
您没有更新index
函数中的index_of()
变量。当您将其传递给starts_with()
时,它会在堆栈上复制。您需要以某种方式返回更新的值 - ether通过引用/指针获取它或返回它而不是bool
。
答案 3 :(得分:0)
我认为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"; }
答案 4 :(得分:0)
(注意 - 为简单而编辑)
不确定这是否有帮助,因为我不确定你的老师向你解释了递归的程度,但是这里有...
这是你需要考虑的方法 - 递归函数包含两个主要组件:
1)基础案例和
2)递归案例
关键是每次调用时,你要确定这个函数运行的两种情况中的哪一种是真的(基于输入参数)。
基本情况是不再调用函数,并且递归情况总是调用函数,并且通常的逻辑比基本情况更复杂
所以我建议重新开始。考虑输入应该是什么,以便在具有该输入的函数调用中函数不会调用自身---&gt;这是你的基本案例。
如果在函数调用中它不是基本情况,那么它就是递归情况。所以你的功能需要看起来像这样:
function index_of(params...){
if base case
do something simple and return
else
do something and call index_of again
}
提示:在你的函数中,没有递归的情况。