用递归方法实现穷举搜索(Exhaustive search)的有关内容笔记①

本文介绍了如何使用递归实现穷举搜索,强调了递归的自我相似性和基准情况在解决复杂问题如阶乘、二进制数生成和字符串排列中的应用。通过实例演示了递归调用的选择过程和basecase的特殊含义。
摘要由CSDN通过智能技术生成

       实现穷举搜索的方法有很多,有时可以用循环就能简单实现,比如:从0 -- 100中找到所有的质数并输出,你可以用for循环从0 -- 100一个个数字的判断当前数字是否为质数。但在这篇文章中我想记录基于递归的穷举搜索实现方法。

        递归(Recursion)主要有两个重要的特点。

        第一:自我相似性(self-similar)

        第二:有基准情况(base case)

        递归中的自我相似性(self-similar)意味着一个大问题可以被分成很多相似的小问题来解决,有基准情况(base case)意味着你要解决的问题的一定会有最简单的一种情况。

        到底什么意思呢,我们来打个比方:完成从1--n的阶乘。

int factorial(int n){
    if(n < 2){
        return 1;//基准情况(base case)
    }else{
        return n * factorial(n - 1);//自我相似性(self-similar)
    }
}

        如果你要完成一个1 -- n的阶乘,那么这个问题的自我相似性就是当前数字与之后所有数字的乘积相乘,也就是:return  n * factorial(n - 1);。并且这个问题最简单的情况就是只算从1 到 1的情况,所以它的基准情况(base case)就是if(n < 2){return 1;}。

        在用递归实现穷举搜索的问题中,我们也会用很多次递归调用来解决一个大问题,所以自我相似性(self-similar)在这里也同样适用。并且,我们可以将这里的递归调用作为一种“做选择”的行为。一次调用就是做一次选择,而后续的调用会做出连续的选择,我们通过一系列的递归调所做出的选择来构建一个选择序列(sequence)(来自cs106x)。而这里的base case就有所不同了,我们刚刚提到过在一般的递归问题中,base case 是一个问题的最简单的情况,但在这,base case并不意味着根本没有选项可供选择,而是我已经做出了所有需要做的选择,我已经构建了一个足够高的stack,不需要再增加高度,此时我呈现出我所构建的内容,这就是我的base case(来自cs106x)。

下图为穷举搜索伪代码(截图自cs106x):

1f46fd25fbcb498d80f1d2c39d705796.png

 我们再来打个比方:打印所有n位二进制数的程序(用递归*)。

void printBinary(int digits, string prefix){ // prefix默认值为“”
    if (digits == 0) {
        cout << prefix << endl; //base case
    }else{
        //digits >= 2
        printBinary(digits - 1, prefix + "0");//每次递归调用的意义为做出选择
        printBinary(digits - 1, prefix + "1");
    }
}

        在这段代码中else分支里的递归调用就是在做出选择,我们用树状图会更好理解(截图自cs106x,其中soFar对应代码中的prefix):

                dd6c5ae248b744d38655691f5f245106.png

        当然,我们在使用递归解决问题时也可以适当使用循环语句。比如当我们要生成所有n位十进制数时:

如果这样写,也不是不行,但是会“有点”麻烦:

void printDecimal(int digits, string prefix){
    if (digits == 0) {
        cout << prefix << endl;
    }else{
        printDecimal(digits - 1, prefix + “0”);
        printDecimal(digits - 1, prefix + “1”);
        printDecimal(digits - 1, prefix + “2”);
        printDecimal(digits - 1, prefix + “3”);
        printDecimal(digits - 1, prefix + “4”);
        printDecimal(digits - 1, prefix + “5”);
        printDecimal(digits - 1, prefix + “6”);
        printDecimal(digits - 1, prefix + “7”);
        printDecimal(digits - 1, prefix + “8”);
        printDecimal(digits - 1, prefix + “9”);
    }
}

我们可以通过循环代替重复的选择工作:

void printDecimal(int digits, string prefix){
    if (digits == 0) {
        cout << prefix << endl;
    }else{
        for(int i = 0; i <= 9; i++){
            printDecimal(digits - 1, prefix + to_string(i));
        }
    }
}

        接下来,我们来增加一点难度。让我们来实现排列函数permute(),permute()可以将给定的字符串中所有字母重新排列,以得到所有排列可能(例题来自CS106X)。

        以MARTY为例,在每个递归中,我们要选择下一个应该放在递归中的字母,在选定后,我们继续选择跟随该字母的四个字母的组合,所以我们可以通过循环来实现。

void permute(string s,string prefix){
    if(s.length() == 0){
        cout << prefix << endl; //base case
    }else{
        for(int i = 0 ;i < s.length();i++){//选择一个字母
            char ch = s[i];
            string s2 = s.strsub(0,i) + s.strsub(i + 1);
            permute(s2,prefix + ch);
        }
    }
}

        我们还是用base case和自我相似性(self-similar)来对这个代码进行分析,我们在上面提到过,穷举搜索中的base case和一般递归的理解方式不一样,这里我们解释为,当已经没有字母可供选择(也就是说,已经用所有字母形成一个排列可能时),我们将这个结果输出。那么在else中的内容,我们可以解释为选择一个字母,并继续选择跟随该字母的四个字母的组合。在代码中:

①char ch = s[i]; 为选择一个字母

②string s2 = s.strsub(0,i) + s.strsub(i + 1);为整合剩下的字母

③permute(s2,prefix + ch);为继续选择跟随该字母的字母组合

        我们通过将字符一个个加到prefix后面来组成一个排列可能,并将其输出,这种传递方式在递归中很常见,在上面的printBinary()函数中我们用的也是这个方法。

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值