我试图分析以下功能的时间复杂度。此函数用于检查字符串是否由其他字符串组成。
set<string> s; // s has been initialized and stores all the strings
bool fun(string word) {
int len = word.size();
// something else that can also return true or false with O(1) complexity
for (int i=1; i<=len; ++i) {
string prefix = word.substr(0,i);
string suffix = word.substr(i);
if (prefix in s && fun(suffix))
return true;
else
return false;
}
}
我认为时间复杂度为O(n)
,其中n是单词的长度(我是对的吗?)。但由于递归是在循环内部,我不知道如何证明它。
编辑:
此代码不是正确的C++
代码(例如prefix in s
)。我只是展示了这个功能的想法,并想知道如何分析它的时间复杂度
答案 0 :(得分:4)
分析这种方法的方法是根据输入的长度和前缀在s
中的(未知)概率开发递归关系。假设前缀在s
中的概率由前缀长度L的某个函数pr(L)给出。让复杂性(操作次数)由T(len)给出。
如果len == 0(word
是空字符串),则T = 1.(该函数在循环后缺少最后的return
语句,但我们假设实际代码只是概念的草图,而不是实际执行的内容。
对于每个循环迭代,用T(len; i)表示循环体复杂度。如果前缀不在s
中,那么正文具有恒定的复杂度(T(len; i)= 1)。此事件的概率为1 - pr(i)。
如果前缀在s
中,则该函数根据对true
的递归调用返回false
或fun(suffix)
,其具有复杂度T(len - i) 。该事件具有概率pr(i)。
因此,对于i
的每个值,循环体复杂度为:
T(len; i)= 1 *(1 - pr(i))+ T(len - i)* pr(i)
最后(这取决于预期的逻辑,而不是发布的代码),我们有
T(len)= sum i = 1 ... len (T(len; i))
为简单起见,我们将pr(i)视为值为0.5的常数函数。那么T(len)的递归关系是(直到一个常数因子,这对O()计算来说并不重要):
T(len)= sum i = 1 ... len (1 + T(len - i))= len + sum i = 0 ... len-1 < /子>(T(i))的
如上所述,边界条件是T(0)= 1.这可以通过标准递归函数方法来解决。让我们看看前几个术语:
len T(len)
0 1
1 1 + 1 = 2
2 2 + 2 + 1 = 5
3 3 + (4 + 2 + 1) = 11
4 4 + (11 + 5 + 2 + 1) = 23
5 5 + (23 + 11 + 5 + 2 + 1) = 47
模式显然是T(len)= 2 * T(len - 1)+ 1.这对应于指数复杂性:
T(n)= O(2 n )
当然,这个结果取决于我们对pr(i)的假设。 (例如,如果对于所有i,pr(i)= 0,则T(n)= O(1)。如果pr(i)具有最大前缀长度-p(i)=,则也将存在非指数增长0为所有i> M为某些M.)pr(i)独立于i的假设可能是不现实的,但这实际上取决于s
的填充方式。
答案 1 :(得分:0)
假设你已经修复了其他人注意到的错误,那么i
值就是字符串被拆分的位置(每个i
是最左边的拆分点,然后你就可以解决所有错误i
)的右侧。这意味着如果要展开递归,您将查看最多n-1
个不同的分割点,并询问每个子字符串是否为有效字。如果word
的开头没有来自你的集合中的很多元素,那么事情就可以了,因为那时你可以跳过递归。但在最坏的情况下,prefix in s
始终为真,您尝试n-1
分裂点的每个可能子集。这给出了2^{n-1}
个不同的分裂集,乘以每个这样的集合的长度。