先上一张图
要求
1.一行只能放一个皇后,所以我们一旦确定此处可以放皇后,那么该行就只能放一个皇后,下面的就不要再搜了。
2.每一列只能放一个皇后,所以我们下次搜索就不要再搜已经放过的皇后了。
3.斜的45°线也只能放一个。
算法思路
首先我们分析一下问题的解,我们每取出一个皇后,放入一行,共有八种不同的放法,然后再放第二个皇后,同样如果不考虑规则,还是有八种放法。于是我们可以用一个八叉树来描述这个过程。从根节点开始,树每增加一层,便是多放一个皇后,直到第8层(根节点为0层),最后得到一个完全八叉树。
紧接着我们开始用深度优先遍历这个八叉树,在遍历的过程中,进行相应的条件的判断。以便去掉不合规则的子树。
那么具体用什么条件来进行子树的裁剪呢?
我们先对问题解的结构做一个约定。
用X[i]来表示,在第i行,皇后放在了X[i]这个位置。
于是我们考虑第一个条件,不能再同一行,同一列于是我们得到x[i]不能相同。剩下一个条件是不能位于对角线上,这个条件不是很明显,我们经过分析得到,设两个不同的皇后分别在j,k行上,x[j],x[k]分别表示在j,k行的那一列上。那么不在同一对角线的条件可以写为abs((j-k))!=abs(x[j]-x[k]),其中abs为求绝对值的函数。
于是下面我们便可以利用一个递归的调用来遍历八叉树。
private void button1_Click(object sender, EventArgs e)
{
int n = 4;
N_Queen (n);
}
public bool Place(int[] Column, int index)
{
int i;
for (i = 1; i < index; i++)
{
//求列的差值和行的差值
int Column_differ = System.Math.Abs(Column[index] - Column[i]);
int Row_differ = System.Math.Abs(index - i);
//检查列或对角线上是否有冲突
if (Column[i] == Column[index] || Column_differ == Row_differ)
return false ; //有冲突,位置放置的不正确
}
return true ; //没有与皇后同行、同列或者同对角线
}
void N_Queen(int n)
{
int i;
int answer_num = 0; //方案计数
int[] Column_Num = new int[n + 1];
for (i=1;i<=n;i++)
Column_Num [i]=0; //四皇后全放在第0列
int index = 1; //从第一行的第一个皇后开始
while (index > 0)
{
//第一个皇后安放在的第一行第一列
Column_Num[index]++;
//检查与第index个皇后是否相互攻击
while (Column_Num[index] <= n && !Place(Column_Num, index))
Column_Num[index]++; //有冲突,第index个皇后右移一列
//无冲突,找下一个皇后,或者最后一个皇后放置完毕
if (Column_Num[index] <= n)
{
if (index == n) //最后一个皇后放置成功
{
answer_num++;
textBox1 .Text =textBox1 .Text + "\r\n" + "方案" + answer_num;
for (i = 1; i <= n; i++)
{
textBox1 .Text = textBox1 .Text + "(" + i + "," + Column_Num[i] + ")";
}
for (i = 1; i <= n; i++)
Column_Num[index]++;
}
else //继续寻找下一个皇后的位置
{
index++;
Column_Num[index] = 0;
}
}
else //当前皇后无法安置,回溯
index--; //当前皇后回归0列,回溯到前一行皇后
}
}
结语
n皇后问题是回溯的经典例子,而回溯找到界限函数是重点,界限函数可以避免移动到不可能产生解的子空间。