算法竞赛入门经典---函数部分2

本文深入解析了递归与回溯算法的基本原理,并通过具体实例(如生成全排列、可重集全排列、八皇后问题、素数环等)详细展示了算法的应用。同时,文章还探讨了如何利用回溯法优化算法性能,减少不必要的枚举量,提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第七章:(暴力求解法)

 枚举全排列:(包括可重集)

/*
//枚举排列:(递归)
int f[101];
void Print1(int n,int *A,int cur){
    //生成全排列:
    int i,j;
    if(cur==n){
        for(i=0;i<n;i++) printf("%d ",A[i]);
        printf("\n");
    }
    else{
        for(i=0;i<n;i++){
            int ok=1;
            for(j=0;j<cur;j++)
                if(A[j]==f[i]){ok=0;break;}
            if(ok){
                A[cur]=f[i];
                Print1(n,A,cur+1);
            }
        }
    }
}
void Print2(int n,int *A,int cur){
    //生成可重集全排列:
    int i,j;
    if(n==cur){
        for(i=0;i<n;i++) printf("%d ",A[i]);printf("\n");
    }
    else{
        int c1,c2;
        for(i=0;i<n;i++){
            if(!i || f[i]!=f[i-1]){
                c1=c2=0;
                for(j=0;j<cur;j++) if(f[i]==A[j]) c1++;
                for(j=0;j<n;j++) if(f[i]==f[j]) c2++;
                if(c1<c2){
                    A[cur]=f[i];
                    Print2(n,A,cur+1);
                }
            }
        }
    }
}
void text(){
    int i,j,k,n;int A[101];
    while(scanf("%d",&n)!=EOF){
        for(i=0;i<n;i++) scanf("%d",&f[i]);
        sort(f,f+n);//排列
        for(i=0;i<n;i++) printf("%d ",f[i]);
        //Print1(n,A,0);
        Print2(n,A,0);
    }

}
*/
View Code

下一个排列:

while(next_permutation(p,p+n)){

  printf(...) //当不存在下一个排列的时候,跳出while循环。

}

枚举子集:

 

int f[1010];int A[1010];
void Print1(int n,int *A,int cur){
    //法一:;增量构造法!  需要排序!依次从集合中取出元素! 注意解答树情况!
    int i,j,s;
    if(cur){
        for(i=0;i<cur;i++) printf("%d ",A[i]); printf("\n");
    }
    s = cur ? A[cur-1] : 0 ;//注意这里要加1 !
                            //s这里代表的是 A[cur-1]在原数组中第几位!
    for(i=s;i<n;i++){
        A[cur]=i+1;
        Print1(n,A,cur+1);
    }
}
void Print2(int n,int *B,int cur){
    //法二:位向量法! 思路: 设定一个数组B,其中 B[i] 为1,代表第i个元素被选中。
    int i,j;
    if(cur==n){
        for(i=0;i<n;i++)
            if(B[i]) printf("%d ",f[i]);//特点之一,不需要按照顺序来计算了!
        printf("%\n");
    }
    else{
        B[cur]=1;
        Print2(n,B,cur+1);
        B[cur]=0;
        Print2(n,B,cur+1);
    }
}
void PP(int n,int s){
    for(int i=0;i<n;i++)
        if(s&1<<i) printf("%d ",f[i]);
        //else printf("0 ");
    printf("\n");
}
void Print3(int n){
    //法三:二进制法! 使用二进制代表子集。
    //化成二进制形式,其中从右往左第i位表示元素i是否在集合中。(1表示在,0表示不在)
    int i,j;
    for(i=0;i<(1<<n);i++){
        PP(n,i);
    }

}
void text(){
    int i,j,k;
    int n;
    while(scanf("%d",&n)!=EOF){
        int A[1010];
        //Print1(n,A,0); //输出1-7的全排列;
        int B[101];memset(B,0,sizeof(B));
        for(i=0;i<n;i++) scanf("%d",&f[i]);
        //Print2(n,B,0);
        Print3(n);
    }
}
View Code

 

这两者的解答树是稍有不同的。但由于特性使然:最后一层节点数目 约等于  其上所有节点总和, 两者解答树节点数目相差2倍。

 

回溯:也即递归枚举算法! + 适当地减少枚举量。

   进一步减少解答树节点数目,减少时间空间开支,剪枝,加快算法。

  核心:生成和检查有机地结合起来! 从而减少不必要的枚举量

 

/*
//八皇后:回溯法的实例应用
int tot=0;
void Print(int n,int cur,int *C){
    int i,j;
    if(n==cur) tot++;
    else{
        for(i=0;i<n;i++){
            int ok=1;
            for(j=0;j<cur;j++)
                if(i==C[j] || cur-i==j-C[j] || cur+i==j+C[j]) { ok=0; break;}
            if(ok){C[cur]=i;Print(n,cur+1,C);}
        }
    }
}
void text(){
    //回溯法:核心:生成和检查有机地结合起来! 从而减少不必要的枚举量
    //7.4.1八皇后问题
    //初步判定: 皇后的排列问题,相当于一个组合选择全排列问题: (8行8列,每列选择位置不同,可以看成一个全排列。)
    //但经过枚举过程中进一步的将: 生成和检查  结合起来,可以进一步减少枚举量!!---> 回溯
    int i,j,k;
    int n;
    while(scanf("%d",&n)!=EOF){
        tot=0;int C[15];memset(C,0,sizeof(C));
        Print(n,0,C);
        printf("共有%d种放法。\n",tot);
    }
}
//注意: 一般地,如果在回溯递归过程中,改变了函数的局部变量,尤其是函数参数,可能需要将他们恢复原状!!
*/
View Code

注意恢复原状的问题,一般也是在“出口”处回复原状,也即:函数调用结束的地方。

素数环:

/*
//7.4.2素数环:回溯
int vis[101];
int isp(int x){
    if(x==2|| x== 3|| x==5||x==7||x==11||x==13||x==17||x==19||x==23) return 1;
    else return 0;
}
void Print(int n,int cur,int *A){
    int i,j;
    if(n==cur){
        if(isp(A[cur-1]+A[0])) {
            for(i=0;i<n;i++) printf("%d ",A[i]);
            printf("\n");
        }
    }
    else{
        for(i=2;i<=n;i++){
            if(!vis[i] && isp(i+A[cur-1])){ //回溯的分支判断;
                A[cur]=i;vis[i]=1;
                Print(n,cur+1,A);
                //!!! 记得还原!
                vis[i]=0;
            }
        }
    }
}
void text(){
    int i,j,k,n;int A[101];
    while(scanf("%d",&n)!=EOF){
        memset(vis,0,sizeof(vis));
        A[0]=1;vis[1]=1; //放置第一个数字;
        Print(n,1,A);
    }
}
//总结:记得初始化放置第一个数字的时候,一定要对A和vis数组进行第一步的处理。
 * 还有一个还原vis数组的问题!!  同时,因为cur(在同一个函数中,cur的值不变)的存在,所以A数组不需要进行还原处理!
 * 这也是vis数组与构造的A数组的不同之处。
*/
View Code

 

困难的串:

/*
//6.4.3困难的串
int cnt;
int Print(int n,int L,int cur,int *A){
    int i,j,k;
    if(cnt==n){
        for(i=0;i<cur;i++) printf("%c ",A[i]+'A');
        printf("\n");
        return 0;
    }
    else{

        for(i=0;i<L;i++){
            int ok=1;
            int flag;
            A[cur]=i;//为数组赋值。
            for(j=1;2*j<=cur+1;j++){ //字符串长度从2j开始枚举
                flag=1;
                for(k=0;k<j;k++)
                    if(A[cur-k]!=A[cur-j-k]){flag=0;break;}
                if(flag){ok=0;break;} //判断当前i已经不行了!
            }
            if(ok){
                cnt++;if(!Print(n,L,cur+1,A)) return 0;
            }
        }
    }
    return 1;
}
void text(){
    int i,j,n,L;
    while(scanf("%d %d",&n,&L)!=EOF){
        cnt=0;//计数器,第n个困难的串;  //按照字典序的顺序!
        int A[101];memset(A,0,sizeof(A));
        Print(n,L,0,A);
    }
}
//总结: if(!Print(n,L,cur+1,A)) return 0; 可以很好地在得到一组解之后就跳出循环!!
//--->bjfu_oj上木棒那题可以应用

*/
View Code

//注意最后一点总结注释!可以跳出整个递归!

带宽:

 /*

与八皇后问题不同之处:

  八皇后问题,在得到完整解之前,可以避免拓展一些不可行的节点!

  但是此题每种情况都是可行解。那么就没有可以优化的方式了么?

当然不是! 我们可以采用一个在平常最常使用的方法:维护最大值! 只不过这里我们是在递归的过程中维护,用来剪枝!

通俗讲:我们可以记录下目前已经找到的最小带宽k,在枚举其它路子树结构时,如果发现已经有某两个节点的距离大于等于k,那么我们考虑将这一路“剪掉”!那么可以优化很多!(就像一个数组,维护最大值最小值等问题,思想类似,只不过应用在了不同的地方。)

*/ 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值