算法设计与分析hw(3)回溯法

1、N皇后

【问题描述】在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上)。
你的任务是,对于给定的N(N是正整数且6<=N<15),求出有多少种合法的放置方法。并输出前4个解。比如当N=6时,检查一个如下的6 x 6的棋盘,有六个皇后被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个皇后。

上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个皇后,如下:

行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是跳棋放置的一个解。
【输入形式】
输入皇后的个数

【输出形式】

前四行为前四个解,每个解的两个数字之间用一个空格隔开。第五行只有一个数字,表示解的总数。

【样例输入】

6

【样例输出】

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
5 3 1 6 4 2
4

【样例说明】解按字典顺序排列。请输出前4个解。最后一行是6皇后的解的总个数。

【参考代码】

//#include <bits/stdc++.h>
#include<stdio.h>
#include <stdlib.h>
using namespace std;
int n,sum;    //n表示输入的数,sum表示方案总数
//a[i]表示第i个皇后所占的列、b[i]表示第i列有没有被占
//c[i]表示左下到右上的对角线有没有被占、d[i]表示左上到右下的对角线有没有被占
int a[15],b[15],c[32],d[32];
void dfs(int m)    //深搜,当前正在放置第m个皇后
{
    if(m==n+1)    //如果放置的皇后超出了n个,说明已放完了n个,方案数要+1
    {
        sum++;    //sum既是总数也是前三个排列的判断
        if(sum<=4)    //只输出前三个解,如果解超出三个就不再输出
        {
            for(int i=1;i<=n;i++)
                printf("%d ",a[i]);
            printf("\n");
        }
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(!b[i]&&!c[m+i]&&!d[m-i+n])    //如果该列和两条对角线都没有被占领,放置皇后
        {
            a[m]=i;            //第x个皇后占了第i列
            b[i]=1;            //标记占领了第i列
            c[m+i]=1;        //标记占领左下--右上对角线
            d[m-i+n]=1;        //标记占领左上--右下对角线
            dfs(m+1);        //深搜放置下一个皇后
            b[i]=0;            //回溯,清除标记
            c[m+i]=0;
            d[m-i+n]=0;
        }
    }
}
int main()
{
    scanf("%d",&n);
    dfs(1);    //从第一个皇后开始放
    printf("%d\n",sum);    //输出总方案数
    return 0;
}

2、子集和问题

【问题描述】子集和问题的一个实例为〈S,c〉。其中,S={  x1 , x2 ,…,xn }是一个正整数的集合,c是一个正整数。子集和问题判定是否存在S的一个子集S1,使得:


试设计一个解子集和问题的回溯法。
对于给定的正整数的集合S={  x1 , x2 ,…,xn }和正整数c,计算S 的一个子集S1,使得:

【输入形式】

输入数据的第1 行有2 个正整数n 和c(n≤10000,c≤10000000),n 表示S 的大小,c是子集和的目标值。接下来的1 行中,有n个正整数,表示集合S中的元素。

【输出形式】

将子集和问题的解输出。当问题无解时,输出“No Solution!”。

【样例输入】

5 10 2 2 6 5 4

【样例输出】

2 2 6

【参考代码】

#include<bits/stdc++.h>
 
using namespace std;
 
int a[100];//存储S的正整数集合
bool x[100];//记录各分支的结果情况  
int w=0;//记录此时的子集和
bool flag=false;//记录是否有解
int n,c;
 
void backtrack(int i) {
	if(i>n||flag==true)//到了最后一层或者已找到子集 
		return ;
	//取出该层的数字
	x[i]=true;
	w=w+a[i];
	//判断
	if(w==c) {
		for(int j=0; j<=i; j++) {
			if(x[j]==true)
				cout<<a[j]<<" ";
		}
		flag=true;
		return ;
	} else if(w<c) {
		//接着向下取数
		backtrack(i+1);
	}
	//回溯
	x[i]=false;
	w=w-a[i];
	backtrack(i+1);
	return ;
}
 
int main() {
	cin>>n>>c;
	for(int i=0; i<n; i++)
		cin>>a[i];
	backtrack(0);
	if(flag==false)
		cout<<"No Solution!\n";
	return 0;
}


3、走迷宫

【问题描述】有一个m*n格的迷宫(表示有m行、n列),其中有可走的也有不可走的,如果用1表示可以走,0表示不可以走,输入这m*n个数据和起始点、结束点(起始点和结束点都是用两个数据来描述的,分别表示这个点的行号和列号)。现在要你编程找出所有可行的道路,要求所走的路中没有重复的点,走时只能是上下左右四个方向。如果一条路都不可行,则输出相应信息(用-1表示无路)。

【输入形式】

第一行是两个数m,n(1< m, n< 15),接下来是m行n列由1和0组成的数据,最后两行是起始点和结束点。

【输出形式】

所有可行的路径,输出时按照左上右下的顺序。描述一个点时用(x,y)的形式,除开始点外,其他的都要用“->”表示。如果没有一条可行的路则输出-1。

【样例输入】

5 4 1 1 0 0 1 1 1 1 0 1 1 0 1 1 0 1 1 1 1 1 1 1 5 4

【样例输出】

(1,1)->(1,2)->(2,2)->(2,3)->(3,3)->(3,2)->(4,2)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4) (1,1)->(1,2)->(2,2)->(2,3)->(3,3)->(3,2)->(4,2)->(5,2)->(5,3)->(5,4) (1,1)->(1,2)->(2,2)->(3,2)->(4,2)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4) (1,1)->(1,2)->(2,2)->(3,2)->(4,2)->(5,2)->(5,3)->(5,4) (1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,2)->(4,2)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4) (1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,2)->(4,2)->(5,2)->(5,3)->(5,4) (1,1)->(2,1)->(2,2)->(3,2)->(4,2)->(4,1)->(5,1)->(5,2)->(5,3)->(5,4) (1,1)->(2,1)->(2,2)->(3,2)->(4,2)->(5,2)->(5,3)->(5,4)

【参考代码】

#include <stdio.h>
#include <string.h>
int a[30][30];
int b[100];
int dir[4][2]={{0,-1},{-1,0},{0,1},{1,0}};
int c,d,f;
void s(int g,int h,int k)
{
    int i;
    if(g==c&&h==d)
    {
        f=1;
        for(i=0;i<k;i+=2)
        {
            printf("(%d,%d)",b[i],b[i+1]);
            if(i<k)
            printf("->");
        }
        printf("(%d,%d)\n",c,d);
        return;
    }
    if(!a[g][h])
    return;
    for(i=0;i<4;i++)
    {
        a[g][h]=0;
        b[k]=g;
        b[k+1]=h;
        s(g+dir[i][0],h+dir[i][1],k+2);
        a[g][h]=1;
    }
}
int main()
{
    int n,m,g,h,i,j,k;
    while(~scanf("%d %d",&n,&m))
    {
        k=f=0;
        memset(b,0,sizeof(b));
        memset(a,0,sizeof(a));
        for(i=1;i<=n;i++)
        {
           for(j=1;j<=m;j++)
            {
              scanf("%d",&a[i][j]);
            }
        }
        scanf("%d %d %d %d",&g,&h,&c,&d);
        s(g,h,0);
        if(!f)
        printf("-1\n");
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值