快速推广(可能建模/提取/总结)思维模式的递归部分

时间:2014-12-03 21:33:51

标签: algorithm recursion

感谢您的帮助。我是算法新手。目前我正在准备大多数来自LeetCode或Careercup等网站的问题。我认为有用的一个重要领域是递归编程,我已经非常努力但仍然无法得到它。

例如,生成具有重复字符的字符串的所有排列的问题。我可以考虑将每个步骤作为人类递归地进行,但是当我需要弄清楚如何组织递归部分的结构时,问题就出现了。

假设我们绘制了整个程序图表。之后,任何人都可以给我一些详细解释,说明如何快速识别哪个部分可以被提取到递归单元中(规则来提取出那个模式部分)或解释如何识别哪个部分应该放在当前的递归函数体中,哪个部分应该用作参数传递给下一轮递归函数调用。

我可能无法完全表达我的问题,所以如果有人回答这个问题,你可以给出如何建模这个问题的方式(或每个细节步骤或思维模式或任何技能)以及为什么以这种方式思考在递归函数方面。

我不相信这只是我个人的问题,必须有很多初学者都有同样的事情,我希望我们可以分享我们关于如何巧妙而快速地学习算法的想法,而不是在没有有效进展的情况下编写很多问题。我甚至认为我们可以设置帖子集(如食谱),以便如何构建思维模式和技巧来解决这些算法问题。

1 个答案:

答案 0 :(得分:0)

很好地生成长度为N

的字符串的所有排列
  • 你需要N嵌套for循环,它将通过字母
  • 生成所有可能性
  • 您可以将其视为数字的增量,但不是数字,而是字符
  • 所以3个没有交互的小写字母小写如下:

    const int N=3; // target word size
    char w[N+1];   // generated word
    int i;
    w[N]=0;        // end of word
    i=0; for (w[i]='a';w[i]<='z';w[i]++){
    i++; for (w[i]='a';w[i]<='z';w[i]++){
    i++; for (w[i]='a';w[i]<='z';w[i]++){
    // here w[] contains generated word so do something with it
    }i--;
    }i--;
    }
    
  • 您可以看到代码简单明了

  • 但硬编码匹配N = 3且无法在运行时更改
  • 除非您使用自行更改代码...这在当前更高级别的语言中不受欢迎且更适合仅用于汇编
  • 但是如果你把它重写为递归,那么所有问题都会消失
  • 粗略的新的
  • 将像堆/堆栈垃圾一样......

从迭代移植到递归

  • 递归参数就像for循环中的索引
  • 它必须降低其功能幅度,以便在有限的时间内停止
  • 并且递归必须包含某种停止条件
  • N字符生成可以重写为
  • genere_word(N)=genere_word(N-1)+generate_char();
  • 所以你可以编写生成所有1个字符的函数
  • 并且通过递归,您可以实现N个字符
  • 递归尾部(操作数)可以是实际生成的单词w
  • 和递归层(实际生成的字符位置i
  • 粗略i字符生成的
  • 不会更改i-1字符或任何先前的
  • 所以我们不需要在堆栈上抛出w,可以将其作为指针或全局变量
  • 所以我们真的需要i作为尾巴
  • 这样的事情:

    char w[1024]={0};           // big enough buffer
    void _generate(int i)       // recursion call
        {
        // stop conditions
        if (i==0) for (w[i]='a';w[i]<='z';w[i]++)   // if first character then the word is complete so no recursion
            {
            // here w[] contains generated word so do something with it
            }
        else      for (w[i]='a';w[i]<='z';w[i]++)   // else
            {
            // not all characters are genered yet so recursively generete next one
            _generate(i-1);
            }
        }
    void generate(int N)        // main call
        {
        // here you can create w[N+1] dynamically instead
        if (N<    0) N=   0;    // avoid access violations on invalid N
        if (N>=1024) N=1023;    // avoid access violations on invalid N
        w[N]=0;                 // end of word
        if (N) _generate(N-1);  // generate only if at least one char words ...
        }
    

[注释]

  • 所有代码都在C ++中
  • 迭代通常更快,因为如果编码正确,没有堆垃圾
  • 例如N = 3上的这两个例子,迭代时间为2.2秒,并且在矿山设置上递归2.3s
  • 花了这么长时间,因为我逐一向VCL TMemo添加了每一个字。
  • 如果我自己在内存中创建列表并复制整个内容会更快(68ms代替)
  • 我可以从头到尾生成字符,但为此您还需要知道N
  • 内部递归所以你需要i,N作为尾部,这会慢很多
  • 总是尝试尽可能小的递归尾巴...
  • 一些语言/解释器/编译器针对递归进行了优化,并且递归比迭代更快
  • 在这种情况下,上述说明不适用
  • 顺便说一句,如果你足够聪明,那么你甚至可以在尾部摆脱i并将其用作全局变量,如w
  • 在这种情况下,您必须确保在从_generate返回之前将i设置为其入口点的值
  • 这样的事情:

    //---------------------------------------------------------------------------
    int i;
    char w[1024]={0};
    void _generate()
        {
        if (i==0) for (w[i]='a';w[i]<='z';w[i]++)
            {
            // here w[] contains generated word so do something with it
            }
        else      for (w[i]='a';w[i]<='z';w[i]++)
            {
            i--;
            _generate();
            i++;
            }
        }
    void generate(int N)
        {
        if (N<    0) N=   0;
        if (N>=1024) N=1023;
        w[N]=0;
        i=N-1;
        if (N) _generate();
        }
    //---------------------------------------------------------------------------
    
  • 要注意这是O(26^N)的复杂性,这是巨大的

  • 所以如果N>4编码不好,你可以等很长时间直到结束......