N皇后问题(递归、非递归回溯法;位运算求解N皇后)

目录

N皇后问题

数据范围

分析

C++代码(递归回溯法)

非递归回溯法

C++代码(非递归回溯法)

使用位运算求解N皇后问题

位运算求解N皇后代码


N皇后问题

n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后之间不能相互攻击到彼此,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数 n,请你输出所有的满足条件的棋子摆法,以及一共有多少种能够满足条件的棋子摆法。

数据范围

1<=n<=20

分析

B站上关于N皇后问题的求解视频:

C++代码(递归回溯法)

#include<iostream>

using namespace std;

#define Max 20  //定义棋盘的最大值 
#define MaxCount 1000  //定义最大的结果个数 

int N;

int direction_x[8] = {0, 1, 1, 1, 0, -1, -1, -1}, direction_y[8] = {1, 1, 0, -1, -1, -1, 0, 1};  //控制8个方向 
 
char queen[Max][Max];  //皇后位置棋盘 
int attack[Max][Max];  //皇后可攻击状态棋盘 

char solve[MaxCount][Max][Max];  //存储N皇后问题的全部解法
int solve_count = 0;  //solve数组的第一维 

/* 定义函数put_queen(),实现在(x,y)处放置皇后,对attack数组进行更新 */
void update_attack(int attack[][Max], int x, int y) 
{
	attack[x][y] = 1; //先将皇后所在位置(x,y)处置为1,表示不可再放置皇后 
	int nx,ny;
	//通过两层循环,将该皇后可能攻击到的位置进行标记 
	for(int l=1; l<N; l++)   //从皇后位置向1~N-1个距离延伸 
	{
		for(int k=0; k<8; k++)  //8个方向
		{
			nx = x + l * direction_x[k];
			ny = y + l * direction_y[k];
			if(nx>=0 && nx<N && ny>=0 && ny<N)  //不越界
			{
				attack[nx][ny] = 1;  //可以被皇后攻击 
			}	
		} 	
	}
} 

/*
回溯法求解N皇后的函数
k表示当前处理的行
n表示N皇后问题
queen存储皇后的位置
attack标记皇后的攻击位置
solve存储N皇后问题的全部解法*/
void backtrack(int k, int n, char queen[][Max], int attack[][Max]) 
{
	if(k == n)  //找到一个解 
	{
		for(int i=0; i<n; i++)
		{
			for(int j=0; j<n; j++)
			{
				solve[solve_count][i][j] = queen[i][j];  //将结果queen存储至solve 
			}
		}
		solve_count++;
		return;	
	}
	 //遍历0~n-1列,在循环中,回溯试探皇后可以放置的位置 
	for(int i=0; i<n; i++)
	{
		if(attack[k][i] == 0)  //判断当前第k行第i列是否可以放置皇后 
		{
			int temp[Max][Max];
			
			for(int a=0; a<n; a++)  //备份attack数组
			{
				for(int b=0; b<n; b++)
				{
					temp[a][b] = attack[a][b];
				}
			} 
			
			queen[k][i] = '*';  //放置皇后在该处 
			update_attack(attack, k, i);  //更新attack数组 
			
			backtrack(k+1, n, queen, attack);  //递归试探第k+1行的皇后放置情况(借助递归,进行回溯) 
			
			for(int a=0; a<n; a++)  //恢复attack数组
			{
				for(int b=0; b<n; b++)
				{
					attack[a][b] = temp[a][b];
				}
			} 
			
			queen[k][i] = 'o';  //恢复queen数组 
		}
	}
} 

int main()
{
	cout<<"请输入皇后个数:"<<endl;
	cin>>N;  //皇后个数为N
	 
	//初始化棋盘和皇后可攻击状态棋盘(*代表皇后所在棋盘位置、o代表普通棋盘位置;0表示不可被攻击,1表示可以被攻击) 
	for(int i=0; i<N; i++)
	{
		for(int j=0; j<N; j++)
		{
			queen[i][j] = 'o';
			attack[i][j] = 0;
		}
	}
	
	backtrack(0, N, queen, attack); 
	
	cout<<N<<"皇后问题的解的个数为:"<<solve_count<<endl;
	
	cout<<"具体的解为:"<<endl;
	for(int i=0; i<solve_count; i++)
	{
		for(int m=0; m<N; m++)
		{
			for(int n=0; n<N; n++)
			{
				cout<<solve[i][m][n]<<" ";
			}
			cout<<endl;
		}
		cout<<endl;
	}
	
	return 0;
}

非递归回溯法

非递归方法的一个重要问题是,何时回溯及如何回溯的问题。程序首先对N行中的每一行进行探测,寻找该行中可以放置皇后的位置,具体方法是对该行的每一列进行探测,看是否可以放置皇后,如果可以,则在该列放置一个皇后,然后继续探测下一行的皇后位置。如果在某一行已经探测完所有的列都没有找到可以放置皇后的列,此时就应该回溯,把上一行皇后的位置往右移一列,如果上一行皇后移动后也找不到位置,则继续回溯直至前面的某一行找到皇后的位置或回溯到第一行,如果第一行皇后也无法找到可以放置皇后的位置,则说明已经找到所有的解,程序终止。如果该行已经是最后一行,则探测完该行后,如果找到放置皇后的位置,则说明找到一个结果,打印出来。但是此时并不能在此处结束程序,因为我们要找的是所有N皇后问题所有的解,此时应该清除该行的皇后,从当前放置皇后列数的下一列继续探测。

C++代码(非递归回溯法)

#include<iostream>
#include<ctime>
#include<cmath>

using namespace std;

#define Max 20      //定义棋盘的最大值

int a[Max];

int show(int S)    //定义输出函数
{
	int i,p,q;
  	int b[Max][Max]={0};     //定义并初始化b[][]输出数组
  	for(i=1; i<=S; i++)    //按横列i顺序输出a[i]数组坐标
  	{
		b[i][a[i]] = 1;
        printf("(%d,%d)\t",i,a[i]);
  	}
  	printf("\n");
  	for(p=1;p<=S;p++)     //按棋盘的横列p顺序标明皇后的位置
  	{
		for(q=1;q<=S;q++)
        {
            if(b[p][q]==1)     //在第p行第q列放置一个皇后棋子
                printf("●");
            else
                printf("○");
        }
        printf("\n");
  	}
  	return 0;
}
 
int check(int k)    //定义check函数
{
  	int i;
  	for(i=1; i<k; i++)    //将第k行与前面的第1~~k-1行进行判断
  	{
		if((a[i]==a[k]) || (a[i]-a[k]==k-i) || (a[i]-a[k]==i-k)) //检查是否有多个皇后在同一条直线上
        {
            return 0;
        }
  	}
  	return 1;
}
 
void check_m(int num)   //定义函数
{
  	int k = 1, count = 0;
  	printf("The possible configuration of N queens are:\n");
  	a[k] = 1;
  	while(k > 0)
  	{
    	if(k<=num && a[k]<=num)//从第k行第一列的位置开始,为后续棋子选择合适位子
        {
            if(check(k) == 0)    //第k行的a[k]列不能放置皇后
            {
                a[k]++;        //继续探测当前行的下一列:a[k]+1
            }
            else
            {
                k++;        //第K行的位置已经确定了,继续寻找第k+1行皇后的位置
                a[k] = 1;      //从第一列开始查找
            }
        }
        else
        {
            if(k>num)     //若满足输出数组的要求则输出该数组
            {
                count++;
                printf("[%d]:  ",count);
                show(num);    //调用输出函数show()
            }
            //如果k>num会执行下面两行代码,因为虽然找到了N皇后问题的一个解,但是要找的是所有解,需要回溯,从当前放置皇后的下一列继续探测
            //如果a[k]>num也会执行下面两行代码,就是说在当前行没有找到可以放置皇后的位置,于是回溯,从上一行皇后位置的下一列继续探测
            k--;      //棋子位置不符合要求,则退回前一步
            a[k]++;   //继续试探下一列位置
        }
  	}
  	printf("The count is: %d \n",count);
}
 
int main()
{
  	clock_t start,finish;
  	int n;
  	printf("请输入皇后个数:");
  	scanf("%d",&n);
  	start = clock();
  	printf("\n使用非递归回溯法解决 %d 皇后问题时的运行情况:\n",n); 
  	check_m(n);
  	finish = clock();
  	printf("计算时间 %.2f ms\n", (double) (finish - start));
  	system("pause");
  	return 0;
}

使用位运算求解N皇后问题

位运算求解N皇后代码

#include<iostream>
#include<list>

using namespace std;

//方案链表
list<int> sol;

//棋盘大小
int n;

//棋盘摆满时的数字
int uplimit;

void print()
{
	cout<<endl;
	list<int>::iterator i;
	for(i = sol.begin(); i != sol.end(); i++)
	{
		int temp = *i, count = 0;
		while(temp != 1)
		{
			count++;
			temp = temp >> 1;
		}
		for(int j = 0; j < count; j++)
			cout<<"o"<<" ";
		cout<<"*"<<" ";
		for(int j = count+1; j < n; j++)
			cout<<"o"<<" ";
		cout<<endl;
	}
	cout<<endl;
} 

//r:皇后对行占有情况,
//ld:从左上角到右下角对角线皇后占有情况,
//rd:右上角到左下角对角线皇后占有情况
void doAns(int r, int ld, int rd)
{
	if(r != uplimit) //当r==uplimit时,表示每一行都有皇后
	{
		//还没摆完,取能放的位置
        //r|ld|rd进行的是或运算,求出第r行哪个位置是可利用的(可利用位的值是0),
        //然后取反(可利用位的值是1)与uplimit进行与运算,是为了保证pos的位数
		int pos = uplimit & ~(r | ld | rd);
		
		//若还有位置,如00001011表示能放在5、7、8位
		while(pos != 0)
		{
			//取低位
			int p = pos & (~pos + 1);  //即 p = pos & -pos
			
			//更新位置,将这个可利用位去除
			pos -= p;
			
			//答案入
			sol.push_back(p);
			
			//更新禁手,并进行下一层更新
            //两个对角线方向的禁位对下一行的影响需要平移一位
			doAns(r | p, (ld | p)<<1, (rd | p)>>1);  //(ld | p)<<1和(rd | p)>>1是将对角线左/右移
			 
			//答案出
			sol.pop_back(); 
		} 
	}
	else
		print();
}

int main()
{
	while(cin>>n)
	{
		//如果n为8,则uplimit为11111111,表示每一行都有皇后了
		uplimit = (1 << n) - 1;
		doAns(0, 0, 0); 
	}
	return 0;
}


 

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值