回溯法——N皇后问题

N皇后问题即给一个n*n方格,将n个皇后放进去,要求皇后不能出现在同一列,同一行,也不能出现在对角线上,换句话说,只有折线才可能连接两个皇后。我们拿四皇后来举例:
在这里插入图片描述
如图1,第三行的皇后放第一个各格子的话,和第一行的皇后处于同一列,所以不可以;放在第二个格子或者第四个的话,和第二行的皇后处于同一个对角线,所以不可以 ;放在第三个格子的话,和第二行的皇后处于同一列 ,所以不可以。
图2和图3是正确的放法。
在这里用一维数组就可以表示 皇后位置,比如n皇后,我们申请一个n+1空间大小的数组(申请n+1空间大小是为了下标和皇后好对应,0下标我们不用),下标对应的是第几行的皇后,对应的值是皇后放在这一行的第几个格子。
分析:
怎么表示两个皇后的位置是合理的呢?
(1)不在同一列:即数组里的值都各不相等
(2)不在对角线上:abs(x[i]-x[j]) != abs(i-j)
只要满足这两个条件就合理,不满足就不合理 。
注意:每次放完皇后,都要将这个皇后和前面所有的皇后判断是否触发这两个条件。

先呈递递归代码

// 递归
//一维数组,下标代表行,值代表列
class Queen 
{
	int n;//n皇后
	int* x;//数组
	int sum;//放法种数
public:
	Queen(int sz) :n(sz), sum(0)
	{
		x = new int[n + 1];
		memset(x, 0, sizeof(int)*(n+1));//多申请了一个空间,0下标不放东西
	}
	~Queen()
	{
		delete[] x;
		x = NULL;
	}

	bool Place(int k)//k为下标,剪枝函数,返回真就说明皇后没有碰撞,否则返回假
	{
		for (int i = 1; i < k; i++)
		{
			if (x[i] == x[k] || abs(i - k) == abs(x[i] - x[k]))//判断放的皇后位置是否合理
			{
				return false;
			}
		}
		return true;
	}

	void Print()
	{
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; ++j)
			{
				if (x[i] == j)//打印皇后,用“Q”代表
				{
					printf("Q");
				}
				else//不是皇后的格子,用“#”代表
				{
					printf("#");
				}
			}
			printf("\n");
		}
		printf("\n");
	}

	void BackTrack(int t)
	{
		if (t > n)//如果t>n就说明第n个皇后放置好了
		{
			sum += 1;
			Print();//打印 ,然后退回到上一个皇后放置问题,寻找其他正确路径
		}
		else
		{
			for (int i = 1; i <= n; i++)
			{
				x[t] = i;
				if (Place(t))//如果第t个皇后放置合理 ,就放置下一个,不合理就第t个皇后的位置向后面格子放,如果所有位置都不合理,会退回到上一层函数,即上一个皇后位置放置问题,对上一个皇后位置向后移,...递归
				{
					BackTrack(t + 1);
				}
			}
		}
	}

	int nQueen()
	{
		BackTrack(1);
		return sum;
	}
};

int main()
{
	Queen qu(4);
	int sum = qu.nQueen();
	cout << sum << endl;

	return 0;
}

运行结果:
在这里插入图片描述
下面呈递非递归回溯代码:

//N皇后问题
//非递归回溯
class Queen
{
	int n;
	int* x;//数组
	int sum;//放法种数
public:
	Queen(int sz) :n(sz), sum(0)
	{
		x = new int[n + 1];
		memset(x, 0, sizeof(int) * (n + 1));//多申请了一个空间,0下标不放东西
	}
	~Queen()
	{
		delete[] x;
		x = NULL;
	}

	bool Place(int k)//k为下标,剪枝函数,返回真就说明皇后没有碰撞,否则返回假
	{
		for (int i = 1; i < k; i++)
		{
			if (x[i] == x[k] || abs(i - k) == abs(x[i] - x[k]))//触发这两个条件,皇后有碰撞返回false
			{
				return false;
			}
		}
		return true;
	}

	void Print()
	{
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; ++j)
			{
				if (x[i] == j)
				{
					printf("Q");
				}
				else
				{
					printf("#");
				}
			}
			printf("\n");
		}
		printf("\n");
	}

	int nQueen()
	{
		int t = 1;
		x[t] = 1;
		while ( x[1] <= n )//第一个皇后位置超了即结束
		{
			if (t > n)//t>n,说明最后一个皇后找到合适位置了
			{
				sum++;
				Print();
				t--;//回溯,调整前一个皇后的位置,找其他成功方案
				x[t]++;//如果x[t]超过n,会继续回溯到上一个皇后
			}

			if (!Place(t) && x[t] <= n)//如果x[t]值不合适,且没尝试到最后,就让第t个皇后向后放
			{
				x[t]++;
			}
			else if(x[t] <= n )//第t个皇后找到合适位置,放下一个皇后
			{
				t++;
				if (t <= n)//注意可能是最后一个皇后放成功了,t++就会大于n,所以放置越界,需要加if语句判断
				{
					x[t] = 1;
				}
			}
			else//第t个皇后没有找到适合的位置,回溯 
			{
				x[t] = 1;//注意先把该值置为1再回溯,因为下次放这个皇后肯定要从1继续向后试探合适位置
				t--;//回溯到上一个皇后
				x[t]++;//如果皇后位置++后大于n,会继续回溯到上一个皇后
			}
		}
		return sum;
	}


};

int main()
{
	Queen qu(6);
	int sum = qu.nQueen();
	cout << sum << endl;

	return 0;
}

这一次我们用6皇后作为测试用例,运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟小胖_H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值