有许多问题,可以利用试探和回溯的搜索技术求解。
求解的过程实际是先序遍历一棵“状态树”的过程。
例1:有集合A = {1, 2, ...n},求A的全部子集。
思路:从求全部子集(幂集)这个事情上,实际上对每个元素只有两个状态:要么在一个子集中,要么不在。
因此,可以逐一对A中每个元素进行“取”和“舍”,从而构造一棵完全二叉树。
算法如下:
注意这里是1~n,不是0~n
void mj(int n, int pos, int* flag)
{
if(pos>n)
{
int i = 1;
for(i=1;i<=n;i++)
{
if(flag[i])
{
printf("%d ", i);
}
}
printf("\n");
} else
{
flag[pos] = 0;
mj(n, pos+1, flag);
flag[pos] = 1;
mj(n, pos+1, flag);
}
}
然而,很多问题用回溯法求解时,描述求解过程的状态树并不是一棵满二叉树。当位于和解矛盾的状态时,不能继续试探下去,而要剪去哪些不满足条件的分支。
下面来看一下八皇后问题:
八皇后问题即有八个皇后,要放到8×8的棋盘上,要求任何时刻,棋盘的同一行、同一列、同一对角线都只有一个皇后。
最基础解法就是回溯。
首先说下合法条件:
(1)行、列不说了。
(2)假设两个皇后位于(i, j)和(m, n),则斜对角线的条件是abs(m-i) = abs(n-j)。
此外,我们可以不用n*n的数组记录皇后位置,而是用n数组表示,其中a[0]表示第0行皇后位于第几列。
其他就很简单了!
#include <stdio.h>
#include <string.h>
#define MAX 8
int is_valid(int* arr, int n, int row, int col)
{
int i,j;
if(row>=n || col>=n || row<0 || col<0)
{
return 0;
}
// No need to check same row
// Check xie
for(i=0;i<row;i++)
{
// Check same col
if(arr[i]==col)
{
return 0;
}
// Check xie 1
// two queen (i, j) and (k, l) xie is |k-i| = |l-j|
if(abs(row-i)==abs(col-arr[i]))
{
return 0;
}
}
return 1;
}
int res = 0;
void four_queen(int row, int* arr, int n)
{
if(row==n)
{
// Successfully place 4 queen
int i,j;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(j==arr[i])
{
printf("%2d", 1);
}else
{
printf("%2d", 0);
}
}
printf("\n");
}
printf("\n");
res++;
}else
{
// Choose four col for current row
int i;
for(i=0;i<n;i++)
{
if(is_valid(arr, n, row, i))
{
arr[row] = i;
four_queen(row+1, arr, n);
}
}
}
}
int main()
{
int arr[MAX];
memset(arr, 0, sizeof(int)*MAX);
res = 0;
four_queen(0, arr, MAX);
printf("%d\b", res);
return 0;
}