八皇后问题(回溯)
1.问题描述
在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
2.问题分析
历史
这是个老经典的问题了,在1848年由国际西洋棋棋手马克斯·贝瑟尔提出,是回溯算法的典型案例。
尔后陆续有不同的学者提出自己的见解。 大数学家高斯认为一共有76种摆法,1854年在柏林的象棋杂志上不同的作者发表了多达40种不同的见解,后来还有人利用图论的方法得出共有92种摆法。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。
而现在,学习了高级语言还有算法的我们可以轻轻松松 😄 搞定这个问题。
暴搜
我觉着第一次接触这个问题的人首先想到的就是暴搜,穷尽所有情况的话大概就是64个空里面选8个,口算一下就是4426165368,大概也就是44亿😦,不过让程序多跑一跑也不是不行。假如再做一些简单的剪枝的话也可以稍微降低一点复杂度,但是大多属于是把44亿变到40亿变到10亿这种。小了,但是没有完全小😆。那么有没有办法一刀砍狠一点呢?那就得请出我们的回溯算法了。
仔细思索一下暴搜计算量如此之大原因,你会发现存在大量重复性计算。
如上图所示,很明显摆到这一步已经不符合规则了,但是暴搜算法依旧会继续遍历完包含这个组合的所有可能摆法,盲目地按既定顺序枚举所有的可能方案,而非哪里有冲突就调整哪里,这样就导致了其效率非常之低😱。
回溯算法
但是回溯算法就不一样了,它的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试,直到无路可走。
详细一点的话,回溯算法解决问题的一般步骤:
1、 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
2 、确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
3 、以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
在确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止😟。
八皇后问题就是回溯算法的一个典例😃,第一步按照顺序放一个皇后,然后第二步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第一个皇后的位置,重新放第2个皇后的位置,直到找到符合条件的位置就可以了。
其实,回溯算法说白了也是暴搜(穷举法)。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成,也就大大减少了计算量和时间消耗。
讲了这么多,情况就是这样,懂得都懂,不懂的我也不多说,上代码😏。
3.完整代码
using System;//使其包含Console等类
public class eight_queen//创建一个包含所需函数的类
{
int N = 0;//计数
int[] queen = { -1, -1, -1, -1, -1, -1, -1, -1, -1 };//开辟数组用于存放每一行皇后的位置
public bool judge(int n, int t)//判断第n行的皇后是否违规
{
for (int i = 1; i < n; i++)
if (queen[i] == t || queen[i] - t == i - n || queen[i] - t == n - i) return false;
//判断依据分别为是否同一列,是否处于主副对角线上
return true;
}
public void print()//输出结果 +代表皇后
{
Console.Write("Answer:");
Console.WriteLine("{0,2:d}",N);
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < queen[i]; j++)
Console.Write(".");
Console.Write("+");
for (int k = queen[i]; k < 8; k++)
Console.Write(".");
Console.WriteLine();
}
}
public void put_queen(int n)//深搜摆放皇后
{
for (int i = 1; i < 9; i++)//尝试每一行的八种可能摆法
{
if (judge(n, i))//如果该位置可行
{
queen[n] = i;//先存起来
if (n == 8)//若true此时已经产生了一种可行的摆法
{
N++;
print();
return;//触底反弹
}
put_queen(n + 1);//假如还没摆到第八行就继续摆下一行
}
}
queen[ n-1 ] = -1;//此时无可行结果,恢复现场并且回溯
return;
}
}
public class Program
{
static void Main(string[] args)//一个C#控制台程序必须包含一个Main方法,且必须为静态而非公共方法
{
eight_queen S = new s();
S.put_queen(1);//调用深搜函数,从第一行开始search
Console.WriteLine();
Console.WriteLine("The end, thanks.");
}
}
(图片来自网络,侵删)