八皇后问题
问题简述
一个8x8的棋盘,放置8个棋子,要求任意两个棋子不能处于同一列或同一行或同一斜线上,问有多少种摆法。
简单思路
1.编码: 计算中表示一个现实世界的符号本质都是0,1。那就是用0,1以及它的组合来表示现实里的行为操作符号等。试想如果我们人为去操作,那就是放置一个棋子在第一行第一列,然后逐个试验,我们可以将放置一个棋子在第一行第一列这种情况(行为或符号)为1,第一行第二列为2,这样每一行每一格都有一个唯一的标识,这样就是64个数,所以就是从64个数中任选8个,对应的排列组合中的组合,也就是C(64,8),这就是人工放置的集合总数。
当然到这我们意识到问题严重性了;这种集合数量上几十亿了,另谋他法。
在分析一下,其实一行从8个选一个;剩下只能从7个选,问题就变成8的阶乘了,但是集合还是很大。
这里设想下,我们可以用一个8位数来表示,每位数表示某一行的某一列,比如个位数2,就表示第一行放置在第二列上,以此类推。可能这里有人觉得是怎么想到这种方式的呢,其实编码最基本的翻译方式就是 数字了,之后才是数组列表这类。 所以其实很多问题,二维线性的都可以考虑用数字来编码。 那我们这里就用8位数来表示这个 8x8 的棋盘了。
遍历不用从0 开始,变量也不用按1自增
static void main(string[] args)
{
int sum = 0;
for(int i = 12345678; i < 87654321;i+=9)
{
if(CheckEachNum(i) == 0)
{
continue;
}
if(CheckNumDia(i) == 0)
{
continue;
}
sum++;
Console.WriteLine(i)
}
Console.WriteLine(sum)
}
2.约束条件
检测行列是否唯一,那就是检测8位数,每位是不是都是唯一的,可以用计数数组太处理
static int CheckEachNum(int i)
{
int flag = 1;
int[] temp = new int[8];
int j = 0;
while(i>0)
{
temp[i%10] += 1;
i / 10;
j++;
}
for(j = 0; j < 8; j++)
{
if(temp[j] != 1)
{
flag = 0;
break;
}
}
return flag;
}
检查斜线是否一致。那就是考虑斜率是否为1
static int CheckNumDia(int i)
{
int flag = 1;
int j = 0;
int[] temp = new int[8];
while(i > 0)
{
temp[j] = i % 10;
i / 10;
j++;
}
for(j = 8; j > 0; j--)
{
for(int k = j-1; k>0; k--)
if(Math.abs(temp[j] - temp[k]) = Math.abs(j - k))
{
flag = 0
break;
}
}
return flag;
}
回溯法
思路和算法
根据 N 皇后问题的规则,任意两个皇后不能在同一行、同一列或同一条斜线上,因此每行必须放置一个皇后。为了得到 N 皇后的全部方案,需要依次在棋盘的每一行放置一个皇后,确保新放置的皇后和已经放置的皇后都不在同一列或同一条斜线上。斜线有两个方向,分别是从左上到右下和从右上到左下,为方便表述,将从左上到右下方向的斜线称为方向一斜线,将从右上到左下的斜线称为方向二斜线。
为了确保任意两个皇后之间不能相互攻击,需要维护三个哈希集合分别存储已经放置的皇后所在的列集合、方向一斜线集合与方向二斜线集合,每个集合中存储的元素如下。
列集合中存储已经放置的皇后所在的列下标。
由于同一条方向一斜线满足行下标与列下标之差为定值,因此方向一斜线集合中存储已经放置的皇后所在的行下标与列下标之差。
由于同一条方向二斜线满足行下标与列下标之和为定值,因此方向二斜线集合中存储已经放置的皇后所在的行下标与列下标之和。
可以使用回溯模拟依次在棋盘的每一行放置一个皇后的过程。用 row 表示行下标,初始时 row=0。回溯的做法如下。
如果 row=n,则棋盘上已经放置 n 个皇后,根据每个皇后所在的行下标和列下标生成棋子放置方案并添加到结果中。
如果 row<n,则需要在当前行 row 放置一个皇后。用 column 表示皇后所在的列下标,0≤column<n,对于每个 column,如果位置 (row,column)和已经放置的皇后都不在同一列或同一条斜线上,则在位置 (row,column) 处放置一个皇后,继续对行下标 row+1 回溯。
回溯结束时,即可得到所有不同的 N 皇后问题的方案。
为了生成棋子放置方案,需要记录每行的皇后所在的列下标,当棋盘上放置 nnn 个皇后时,根据每行的皇后所在的列下标生成棋子放置方案的列表。
public class Solution
{
IList<IList<string>> solutions = new List<IList<string>>();
int n;
int[] queens;
public IList<IList<string>> SolveNQueens(int n)
{
this.n = n;
this.queens = new int[n];
Array.Fill(queens, -1);
ISet<int> columnSet = new HashSet<int>();
ISet<int> diagonal1Set = new HashSet<int>();
ISet<int> diagonal2Set = new HashSet<int>();
Backtrack(0, columnSet, diagonal1Set, diagonal2Set);
return solutions;
}
public void Backtrack(int row, ISet<int> columnSet, ISet<int> diagonal1Set, ISet<int> diagonal2Set)
{
if (row == n)
{
solutions.Add(GetSolution());
}
else
{
for (int column = 0; column < n; column++)
{
int diagonal1 = row - column, diagonal2 = row + column;
if (columnSet.Contains(column) || diagonal1Set.Contains(diagonal1) || diagonal2Set.Contains(diagonal2))
{
continue;
}
queens[row] = column;
columnSet.Add(column);
diagonal1Set.Add(diagonal1);
diagonal2Set.Add(diagonal2);
Backtrack(row + 1, columnSet, diagonal1Set, diagonal2Set);
queens[row] = -1;
columnSet.Remove(column);
diagonal1Set.Remove(diagonal1);
diagonal2Set.Remove(diagonal2);
}
}
}
public IList<string> GetSolution()
{
IList<string> solution = new List<string>();
for (int i = 0; i < n; i++)
{
char[] arr = new char[n];
Array.Fill(arr, '.');
arr[queens[i]] = 'Q';
solution.Add(new string(arr));
}
return solution;
}
}