算法,通俗来讲就是解决问题的方法,不过算法要解决的问题是具有一定难度且复杂的问题。
我认为,求解一个算法问题,要有三个步骤:问题抽象、算法设计、代码实现。
首先是对问题的抽象,我们需要仔细阅读题目要求,要透过问题去看本质,要将问题去复杂化、结构化,这样才能有效且正确的设计出算法。
其次是算法设计,在第一步骤的基础下,我们已经知道要干什么了,接下来就是要怎么干。解决问题的方法有很多,但有效且便利的方法需要我们根据问题情况去合情挑选和改良,如何设计出最优算法这取决于99%平日各种算法的积累(我觉得起码要能掌握数据结构的所有算法和一些常用算法:例如回溯)和1%的灵光乍现。另外不管算法如何,逻辑是一定要清晰的,否则在下一步代码实现的时候会让你痛不欲生的。
最后是代码实现。这一部分是最能考验一个人的功底。算法设计的再巧妙,如果实现不了也是纸上谈兵。代码实现不只是要会写代码,还需要有严谨的逻辑能力,要考虑到题目要求中的所有情况,以及调试代码修改程序的能力和耐心(这取决与你的算法设计和代码实现)。
综合以上,让我们看看这道2n皇后问题的求解过程吧!
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
注:以下是其他组测试数据:
input 1
3
1 1 0
1 1 1
1 1 0
output1
0
input2
4
1 1 1 1
1 0 1 1
1 1 1 1
1 1 1 1
output2
2
input 3
5
1 1 1 1 1
1 0 1 1 1
1 1 1 1 1
1 0 1 1 1
1 1 1 1 1
output3
12
input4
6
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
output4
12
input5
7
1 1 1 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
output5
408
一、问题抽象
简单来看,给定一个数n,然后在n*n的方格里放2n个皇后,然后每个皇后的位置都有限定,问:有多少种放法?
首先我们把棋盘看成一个二维数组,二维数组中有些位置不能放皇后(能放的就是1,不能的是0),那么我们可以给白皇后取个名字(数字2),黑皇后也取个名字(数字3),那么我们就可以把问题抽象:在一个指定大小的二维数组中如何按照要求填放n个2和n个3?
二、算法设计
由第一步来看,问题最关键的地方其实在于如何合理的摆放这些2和3?其实也就是合理选择二维数组下标来摆放皇后。最直接有效的方法就是一个一个试,把所有的位置(情况)都试一遍。那么关键来了,这种思想其实就是回溯:从上往下,把满足条件的所有情况都试一遍,如果不行的话就返回(回溯)到上一层,接着试,最终把所有的情况都遍历了一边。于是问题就求解出来了,来看看我的算法设计吧:
(1)、创建棋盘:输入n,创建n*n的二维数组,给定数组内容(哪些位置能放皇后,哪些不能放),定义sum变量记录放法数目
(2)、判断位置合法性:由于题目要求对皇后位置较为复杂,我单独创建一个方法,用于判断每次皇后所放位置是否合法,这是回溯算法的基础。
(3)、递归回溯:2n个皇后,我用了2n层递归,每层递归放一个皇后,在2n+1层设出口(合法放法)。每个皇后的列位置都从0到n-1之间选择,行下标可由递归层数表达,在每层递归(皇后放置)结束后进行清空该棋盘位置的皇后(令2或3变为0),这样我就把所有情况都筛选出来了。
三、代码实现
看了算法设计,是不是感觉很简单?不,最难的就在代码实现,我写的时候用了半个小时,不过调试代码用时很少(当时没把黑白皇后标识出来,结果运行错误),这道题的代码实现难在递归和回溯,如果你对二者都掌握不熟练的话,这道题确实有点难度,回溯可以从网上翻阅资料,有很多大佬的详细讲解,但是递归,我个人觉得是比较难理解和运用的。废话不说,下面是我的代码实现:
import java.util.*;
class test
{
static int [ ] x [ ];//二维数组用于创建棋盘
static int sum=0;//用于记录放法数目
static int n;//棋盘的大小
public static void main(String args [ ] )//创建棋盘
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
x=new int[n][n];
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
x[i][j]=sc.nextInt();
}
DFS(0);
System.out.println(sum);
}
public static void DFS(int a)//回溯
{
if(a==2*n)
{
sum++;
return ;
}
int bj;
if(a<n)
bj=2;
else
bj=3;
for(int i=0;i<x.length;i++)
{
if(pd(a,i)==true)
{
x[a%x.length][i]=bj;
DFS(a+1);
x[a%x.length][i]=1;//回溯
}
}
return ;
}
public static boolean pd(int a,int i)//判断皇后摆放位置合法性
{
int bj;
if(a<n)
bj=2;
else
bj=3;
if(x[a%x.length][i]!=1)
return false;
for(int j=0;j<a%x.length;j++)
if(x[j][i]==bj)
return false;
int s=i;
for(int w=a%n-1;w>=0;w--,s--)
if(s-1>=0&&x[w][s-1]==bj)
return false;
s=i;
for(int w=a%n-1;w>=0;w--,s++ )
if(s+1<n&&x[w][s+1]==bj)
return false;
return true;
}