重学算法-回溯法
1.背景
解决n皇后问题常用的方法是回溯法。回溯法本质上也是遍历所有解空间的方法。下面就以n皇后问题为例,介绍回溯法。
2.例子
n皇后问题。在n×n棋盘上放置n个皇后,使她们不会互相攻击。
3.算法思想
用回溯法有几个条件。(1)解的空间是已知的。(2)每个解都有判定条件。(3)每个解都有规模。
以n皇后问题为例:(1)解的空间是1,2,...,n的排列。(2)判定条件是横、竖、斜都不能有多于1个皇后。(3)解的规模就是n。只有在棋盘上摆上n个皇后,才能满足条件。
4.算法描述
用board[n]=m表示在第n行的m列摆上皇后
(1)首先board[0]=0,在第一行摆第一个皇后
(2)如果满足判定条件,且解的规模也达到目标,则输出
(3)如果满足判定条件,但解的规模还没到,则扩充解的规模。board[1]=0,...,board[n-1]=0
(4)如果不满足判定条件,则修改当前解,得到下个解。从(2)开始重复。board[1]++。如果board[1]已经达到最大值,就board[0]++。这个过程叫回溯。直到不能回溯,(board[-1]++),结束。
5.代码
#include
<
stdio.h
>
// output
void OutBoard( int board[], int n)
... {
int k,i;
static int count=0;
count++;
printf("count=%d/n ",count);
for (i=0;i<n;i++)
...{
for(k=0;k<n;k++)
...{
if(board[i]==k)
printf("* ");
else
printf("O ");
}
printf("/n ");
}
printf("/n ");
}
// check if queens will attach each other
bool IsInterAttack( int board[], int row)
... {
int i,k;
for(i=0;i<row;i++)
...{
for(k=0;k<row;k++)
...{
if(k==i) continue;
//check column
if(board[i] == board[k]) return true;
//check slash diagonal direction
if(i+board[i] == board[k] + k) return true;
//check back slash diagonal direction
if(i-board[i] == k-board[k]) return true;
}
}
return false;
}
// main algorithms
void Queen( int n)
... {
int i;
int *board=new int[n];
i=0;
board[0]=0;
while(i>=0)
...{
if(!IsInterAttack(board,i+1))
...{
if(i==n-1)
...{
OutBoard(board,n);
i--;
}
else
...{
i++;
board[i]=0;
continue;
}
}
else
...{
if(board[i]<n-1)
...{
board[i]++;
continue;
}
else
...{
i--;
}
}
while(i>=0)
...{
if(board[i]<n-1)
...{
board[i]++;
break;
}
else
...{
i--;
}
}
}
delete [] board;
}
// main
int main()
... {
Queen(4);
return 0;
}
// output
void OutBoard( int board[], int n)
... {
int k,i;
static int count=0;
count++;
printf("count=%d/n ",count);
for (i=0;i<n;i++)
...{
for(k=0;k<n;k++)
...{
if(board[i]==k)
printf("* ");
else
printf("O ");
}
printf("/n ");
}
printf("/n ");
}
// check if queens will attach each other
bool IsInterAttack( int board[], int row)
... {
int i,k;
for(i=0;i<row;i++)
...{
for(k=0;k<row;k++)
...{
if(k==i) continue;
//check column
if(board[i] == board[k]) return true;
//check slash diagonal direction
if(i+board[i] == board[k] + k) return true;
//check back slash diagonal direction
if(i-board[i] == k-board[k]) return true;
}
}
return false;
}
// main algorithms
void Queen( int n)
... {
int i;
int *board=new int[n];
i=0;
board[0]=0;
while(i>=0)
...{
if(!IsInterAttack(board,i+1))
...{
if(i==n-1)
...{
OutBoard(board,n);
i--;
}
else
...{
i++;
board[i]=0;
continue;
}
}
else
...{
if(board[i]<n-1)
...{
board[i]++;
continue;
}
else
...{
i--;
}
}
while(i>=0)
...{
if(board[i]<n-1)
...{
board[i]++;
break;
}
else
...{
i--;
}
}
}
delete [] board;
}
// main
int main()
... {
Queen(4);
return 0;
}
6.运行结果
count
=
1
O * O O
O O O *
* O O O
O O * O
count
=
2
O O * O
* O O O
O O O *
O * O O
7.进一步思考
count
=
1
O * O O
O O O *
* O O O
O O * O
count = 2
O O * O
* O O O
O O O *
O * O O
O * O O
O O O *
* O O O
O O * O
count = 2
O O * O
* O O O
O O O *
O * O O
回溯法的思想就是基本这样的。但例子中的代码不是最优的。我是用IsInterAttack函数来判断是否符合判定条件。我在别人的代码中看到有用三个数组来判断横、竖、斜是否符合判定条件的。例如a[n]=1代表第n列已经有皇后了,x[column+row]=1代表右高左低的斜线上已经有皇后了等等。