C#写八皇后
引言
刚开始接触到C#是因为想要用Unity自己做游戏 ,把脑海里的创意落到屏幕前。
而所有的Unity教程里都是从C#起步,所以就想好好学门C#
任务
这是老师布置的作业:
2.3 在8X8格的国际象棋棋盘上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。请输出尽可能多的摆法。
环境
Visual stdio 2019
思路
为了对棋盘的布局有个清晰地了解,我们可以这样想:任意两个皇后不能在同一行,那么就说明一行放一个皇后,那么只要找到每行的皇后分别放在哪一列上就可以了。
从第一行第一列开始,检测是否符合放置皇后的要求(不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上),如果符合就放置,并不断往下一行检测。
变量
public static int[] queenes = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; //C#定义数组
public static int counts = 0; //有多少种摆放方法
check函数
简单来说:check函数作用是检验某个位置 是否能放置皇后
即这个位置是否与之前的换后 不在同一列,同一对角线
能放置皇后则返回1 不能放置则返回0
具体来说:我前面已经有人帮我放好了三行皇后,那么我放第四行的时候就只要注意第四行的皇后不能与之前的皇后冲突就行了(即不能处于同一列或同一斜线上),即之前(前三行)的的皇后不在这个的皇后同一列上,不在这个皇后的同一正左斜上方,不在这个皇后的正右斜上方。如果第四行皇后的同一列,正左斜上方,正右斜上方都没有皇后,则这里可以放入皇后,返回1,不符合就返回0
public static int check(int line, int list) //line是所要检验的位置的行, list是所要检验的位置的列
{
for (int line2 = 0; line2 < line; line2++) // 查找他之前行的皇后
// line2是之前皇后的行, list2是之前皇后的列
{
int list2 = queenes[line2];
if (list == list2) { return 0; } //是否在同一列
if ((line2 + list2) == (line + list)) { return 0; }
//之前皇后是否在左斜上方对角线
if ((line2 - list2) == (line - list)) { return 0; }
//之前皇后是否在右斜上方对角线
}
return 1;
}
print函数
print函数作用是输出一个完整的棋盘
例如:
“@”表示皇后
“+”表示没放皇后的位置
public static void print()
{
for (int line = 0; line < 8; line++) //输出一到八行
{
int list;
Console.Write(" ");
for (list = 0; list < queenes[line]; list++) //皇后位置之前空位置放+
Console.Write("+ ");
Console.Write("@ "); //放置皇后
for (list = queenes[line] + 1; list < 8; list++) //皇后位置之后空位置放+
Console.Write("+ ");
Console.Write("\n");
}
Console.Write("=================\n"); //分割线
}
eightqueenes 函数
对于刚开始学习数据结构的我,这个函数好难理解,他包含了回溯和递归(这两种算法的思想在文末有附加的解释)
研究了一个博主大犇的代码后,我是这样理解的,把这个八皇后理解成一棵八叉树,第一行有八种可能,第二行的八种可能也各有八种可能…
就是说每取出一个皇后,放入一行,共有八种不同的放法,然后再放第二个皇后,同样如果不考虑规则,还是有八种放法。于是我们可以用一个八叉树来描述这个过程。从根节点开始,树每增加一层,便是多放一个皇后,直到第8层(根节点为0层),最后得到一个完全八叉树。紧接着我们开始用遍历这个八叉树,在遍历的过程中,进行相应的条件的判断,如果符合就继续向下,不符合就回退到上一层,结束这棵子树的生长。以下是例子:
以下红色线表示某种走法 假设其走到第四行(其实在一二三行都会进行每一列的遍历 未画出),最后发现第四行的最后一列也无法放置皇后,(蓝线)即回溯 回到上一行,检测上一行(第三行)那一列的下一列能否满足。
即发现4行5列不符合就检测此行下一列4行6列,如果直到这行最后一列4行8列都不符合,就回退回上一行(3行)往后挪一列(3行6列)看是否符合放置皇后要求。(此即回溯)
public static void eightqueenes(int line) //接下来会递归每一行
{
for (int list = 0; list < 8; list++) //检测此行每一列
{
if (check(line, list) == 1) //若此行此列能够放置皇后 @1
{
queenes[line] = list;
if (line == 7) //如果已经是最后一列 即已经完全解出一种摆放方法
{
counts++; //摆放方法的+1
print(); //输出此种方法
queenes[line] = 0; //刷新
return;
}
eightqueenes(line + 1);
//如果@1能执行,则此行的皇后确定,递归判断下一行皇后位置
queenes[line] = 0;
}
}
}
完整代码:
using System;
using System.Collections.Generic;
namespace 八皇后928
{
class Program
{
public static int[] queenes = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; //C#定义数组
public static int counts = 0; //有多少种摆放方法
public static int check(int line, int list) //line是所要检验的位置的行, list是所要检验的位置的列
{
for (int line2 = 0; line2 < line; line2++) // 查找他之前行的皇后
// line2是之前皇后的行, list2是之前皇后的列
{
int list2 = queenes[line2];
if (list == list2) { return 0; } //是否在同一列
if ((line2 + list2) == (line + list)) { return 0; }
//之前皇后是否在左斜上方对角线
if ((line2 - list2) == (line - list)) { return 0; }
//之前皇后是否在右斜上方对角线
}
return 1;
}
public static void print()
{
for (int line = 0; line < 8; line++) //输出一到八行
{
int list;
Console.Write(" ");
for (list = 0; list < queenes[line]; list++) //皇后位置之前
Console.Write("+ ");
Console.Write("@ "); //放置皇后
for (list = queenes[line] + 1; list < 8; list++) //皇后位置之后
Console.Write("+ ");
Console.Write("\n");
}
Console.Write("=================\n"); //分割线
}
public static void eightqueenes(int line) //接下来会递归每一行
{
for (int list = 0; list < 8; list++) //检测此行每一列
{
if (check(line, list) == 1) //若此行此列能够放置皇后 @1
{
queenes[line] = list;
if (line == 7) //如果已经是最后一列 即已经完全解出一种摆放方法
{
counts++; //摆放方法的+1
print(); //输出此种方法
queenes[line] = 0; //刷新
return;
}
eightqueenes(line + 1);
//如果@1能执行,则此行的皇后确定,递归判断下一行皇后位置
queenes[line] = 0;
}
}
}
static void Main(string[] args)
{
eightqueenes(0); //从0行开始 递归调用自身 输出所有可能摆盘
Console.Write("一共有{0}种排布方法\n", counts);
Console.ReadKey(); //等待键盘输入,退出程序。使调试时能看到输出结果。
}
}
}
附加关键算法
什么是递归
本题中 eightqueenes(line + 1);便是不断调用自身
以下递归解释来源:链接:https://www.zhihu.com/question/20507130/answer/335087047
什么是递归?这里有一个故事是这样说的:
从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说,从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说,从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说… 这就是递归
言归正传,递归的本质是一个过程或函数在定义时,出现直接或者间接调用自己的成分。
下面这个求 n! 的例子中,递归出口(确定递归什么时候结束)是fun(1)=1,递归体(确定递归求解时的递归关系)是fun(n)=n*fun(n-1),n>1。
int fun(int n){
if(n==1)
return 1;
else
return n*fun(n-1);
}
求n! 的递归思路就是把 f(n)=n! 转化成 fun(n-1) ,再把 fun(n-1) 分解为 f(n-2) 来解决,依此类推直到每个问题都可以直接解决(分解到 fun(1) )。
回溯
回溯算法,又称为“试探法”。解决问题时,每进行一步,都是抱着试试看的态度,如果发现当前选择并不是最好的,或者这么走下去肯定达不到目标,立刻做回退操作重新选择。这种走不通就回退再走的方法就是回溯算法。
总结
八皇后问题主要是2个函数:
-
check函数判断能否放置皇后
-
eightqueenes 函数递归调用,能放皇后就往下,不能就回溯(此路不通 不走此路 走下一列)一步步下满棋盘
参考资料
https://blog.csdn.net/yuer_xiao/article/details/82714734?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param