【算法笔记4.3小节-递归】概念,递归求全排列

分治(divide and conquer)


全称“分而治之”, 分治法作为一种算法思想,既可以使用递归的手段去实现,也可以通过非递归的手段去实现。

递归

递归边界递归式构成。

//求n的阶乘
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int f(int n)
{
    if(n==0) return 1;          //当到达递归边界f(0)时,返回f(0)==1
    else return f(n-1) * n;
}
int main()
{
    int n;
    scanf("%d",&n);
    printf("%d\n",f(n));

    return 0;
}

//递归实现斐波那契数列
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int f(int n)
{
    if(n==0 || n==1) return 1;    //递归边界
    else return f(n-1) + f(n-2);       //递归式
}
int main()
{
    int n;
    scanf("%d",&n);
    printf("%d\n",f(n));

    return 0;
}

 1. 递归求全排列

之前一直纠结程序是如何执行的,堆栈是怎么变化的。今天借助debug手写模拟一遍,差不多弄懂了这个全排列计算机是如何执行的。附上手写执行过程。

//递归实现求1~n的全排列
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 11;
int n ;              //n为全局变量
int p[maxn];         //p为当前排列,
int hashTable[maxn] = {false};      //hashTable记录整数x是否已经在p中

//当前处理排列的第index号位
void generateP(int index){
     if(index == n + 1){     //递归边界,已经处理完排列的1~n位
        for(int i=1; i<=n; i++){
            printf("%d", p[i]);     //输出当前排列
        }
        printf("\n");
        return;
    }

    for(int x=1; x<=n; x++){    //枚举1~n,试图将x填入p[index]
        if(hashTable[x] == false){  //如果x不在p[0]~p[index-1]中
            p[index] = x;           //把x加入当前排列
            hashTable[x] = true;    //记x已在p中
            generateP(index+1);     //处理排列的第index+1号位
            hashTable[x] = false;   //已处理完p[index]为x的子问题,还原状态
        }
    }
}


int main()
{
    n = 3;  //欲输出1~3的全排列
    generateP(1);   //从P[1]开始填

    return 0;
}

递归执行调用过程图:

标题

2. n皇后问题

(1)暴力法

/*
n皇后问题(枚举---暴力法)
指在一个n*n的国际象棋棋盘上放置n个皇后,使得这n个皇后两两均不在同一行,
同一列,同一条对角线上,求合法的方案数。
全排列的思路:只看对应的列号1~n的全排列(显然不在同一行和同一列),
递归边界判断在其内部是否为合法的方案,即遍历每两个皇后,判断他们是否在同一条对角线上;
*/
#include<stdio.h>
#include<math.h>
#include<algorithm>
int count = 0;
int visit[100010] = {false};    //标记数组
int p[100010] ;
int n;
void generateP(int index){
    if(index == n+1){               //递归边界,生成一个排列
        bool flag = true;           //flag为true表示当前位一个合法方案
        for(int i=1; i<=n; i++)     //遍历任意两个皇后
        {
            for(int j=i+1; j<=n; j++){
                if(abs(i-j) == abs(p[i] - p[j])){   //如果在一条对角线上
                    flag = false; //不合法
                }
            }
        }
        if(flag == true) count++;    //若当前方案合法,令count加1
        return;
    }

    for(int x=1; x<=n; x++){
        if(visit[x] == false){
            p[index] = x;
            visit[x] = true;
            generateP(index + 1);
            visit[x] = false;
        }
    }
}
int main(){
    n = 8;
    generateP(1);
    printf("%d\n",count);

    return 0;
}


//运行结果 92

(2)回溯优化

/*
回溯法:
一般来说,如果在到达递归边界前的某层,由于一些事实导致已经不需要
往任何一个子问题递归,就可以直接返回上一层。一般这种做法称为递归。
*/
#include<stdio.h>
#include<math.h>
#include<algorithm>
int count = 0;
int visit[100010] = {false};    //标记数组
int p[100010] ;
int n;
void generateP(int index){
    if(index == n+1){               //递归边界,生成一个排列
        count++;                    //能到达这里的一定是合法的
        return;
    }

    for(int x=1; x<=n; x++){        //第x行
        if(visit[x] == false){      //第x行还没有皇后
            bool flag = true;       //flag为true表示当前皇后不会和之前的皇后冲突
            for(int pre=1; pre<index; pre++){//遍历之前的皇后
            //前一个皇后的坐标 (pre, p[pre]
            //当前皇后的坐标(index, x)
            if(abs(pre-index) == abs(p[pre]-x)) {   //与之前的皇后冲突
                flag = false;
                break;
             }
            }

            //与全排列代码相同
            if(flag==true){         //如果可以把皇后放在第x行
                p[index] = x;
                visit[x] = true;
                generateP(index+1);
                visit[x] = false;
            }
        }
    }
}
int main(){
    n = 8;
    generateP(1);
    printf("%d\n",count);

    return 0;
}

//运行结果 92

 

 

 

 

 

 

 


 

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值