入门练习二:典型的回溯法题型(素数环、八皇后)

本文介绍使用回溯法解决素数环和八皇后问题的算法实现,详细解析了递归过程中的边界条件与冲突检测,并提供完整的C++代码示例。

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

典型回溯法题型

1. 素数环

问题描述:把若干正整数填到一个环中,要求每个整数只填写一次,且相邻两个整数之和为素数。
应用回溯法思想(也就是继续DFS递归),注意递归边界条件和冲突条件是什么。
素数环问题还需特别注意对A[0]的处理。因为第一次PrimeCircle(1)要比较i和A[index-1]加和是否为素数,所以A[0]初始值不应赋为0。
(我太蠢了 数组下标和递归算法的参数和小循环的i值(也就是放的数字)老容易搞混,记住:永远不会出现A[i]的写法。

#include <iostream>
#include <math.h>


using namespace std;

int Prime(int x){
    //判断x是否为素数
    int i,n;
    n=(int)sqrt(x);
    for(i=2;i<=n;i++){
        if(x%i==0) return 0;
    }
    return 1;
}

//n=6的素数环问题
int n=6;
int A[6]={1,0,0,0,0,0};//用来放素数的素数环数组

/*这里的A[0]不能为0,否则对PrimeCircle(1)会有越界错误*/

bool vis[4]={0};//访问标记数组

//递归关键弄清index和i的含义。index是大环上的指向标记,i是小循环的
void PrimeCircle(int index){
    //递归边界条件,判断第一个数和最后一个数加和
    if(index==n+1 && Prime(A[1]+A[n])){
        for(int i=1;i<=n;i++)
            cout << A[i] << " ";
        cout << "\n";
    }
    for(int i=1;i<=n;i++){
        if(vis[i]==false && Prime(i+A[index-1])){
                //冲突条件:i没有访问过且与前面一个数加和为素数
                A[index]=i;
                vis[i]=true;
                PrimeCircle(index+1);
                vis[i]=false;
        }
    }
}
int main()
{
    PrimeCircle(1);
    return 0;
}

代码运行结果(n值为6的素数环)
结果1

2. 八皇后

问题描述:在8×8棋盘上放8枚棋子,使其互相不在一条线上(不同行,不同列,不同斜线)。
同样的DFS思想,与素数环不同的是八枚棋子是相同的,不需要设置vis数组记录位置是否访问过。可以用“把棋子放上去”、“取下来的思路”作为标记访问。也就是说,再递归调用后需要重新把棋子放下——对应数组元素设为0。
我的思路是用二维数组表示棋盘,放棋子用1,没有放棋子用0,输出打印所有解法。
另外check(line,list) 函数也需要注意。斜线比较时,(line(和 li)- i)(line(和 li)+ i) 范围应在0~9之间,也就是要控制一下斜线下标的范围,如果没有这个限制很多时候返回false了(比如第一行第一个棋子的左上方,==1才会false,不为1自然就true了)。我一开始这里没有注意界限,还是找大佬debug被点明后才意识到这里竟然对结果有影响。
八皇后看着容易,实际短短几十行代码调了许久,期间遇到无数不知道什么问题,非常感谢大佬帮忙,终于搞出来了。卑微的跨考生真的代码这一块太需要练习了。

#include <iostream>
#include <cmath>

using namespace std;

int n=8;
int sum;//解的个数
int Queens[9][9]={0};//怕越界数组定的大一点
//bool vis[9]={0}; 无须标记访问,放棋子的过程就是标记

bool check(int line,int li){
    //line为行,list为列
    //遍历该行之前的所有行
    for (int i=0; i<line; i++) {
        //如果在同一列,该位置不能放
        if (Queens[i][li]==1) return false;
    }
    for(int i=0;i<li;i++){
        //如果在同一行,该位置不能放
        if(Queens[line][i]==1) return false;
    }
    for(int i=0;i<line;i++){
        //斜线上(左上)不能放
        //这里要注意:line-i与li-i的范围应在0~9之间。
        if((line-i)>0 && (li-i)>0 && Queens[line-i][li-i]==1) return false;
    }
    for(int i=0;i<line;i++){
        //斜线上(右上)不能放
        if((line-i)>0 && (li+i)<9 && Queens[line-i][li+i]==1) return false;
    }
    //如果以上情况都不是,当前位置就可以放皇后
    return true;
}

void Queen(int line){
    if(line == n+1){
        sum++;//解个数增加
        //打印输出此解
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                cout << Queens[i][j]<<" ";
            }
            cout <<"\n";
        }
        cout <<"\n";
    }
    for(int li=1;li<=n;li++){
        if(check(line,li)==true){
            Queens[line][li]=1;
            //vis[li]=true;
            Queen(line+1);
            //vis[li]=false;

            //拿掉棋子
            /*这里拿掉棋子和取消标记是类似的意思,
            八皇后放下一枚棋子的过程只是“试“,而不是真正的放上去。
            试了之后访问标记要置回否,放的棋子要取下来*/
            Queens[line][li]=0;
        }
    }
}

int main()
{
    Queen(1);
    cout << "摆放方式有:" << sum << "种";
    return 0;
}

运行结果:
结果2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值