参考资料:
算法设计与分析,宋文 吴晟 杜亚军 编著,重庆大学出版社
晨星大哥写的用递归解决的皇后问题:http://dev.hackbase.com/hackbase33/how522349.htm
回溯法是一种满足某约束条件的穷举式搜索技术,适应于解决一些组合数相当大的问题,是算法设计的重要方法之一。对于那些涉及到寻找一组解的问题或者求满足某约束条件的最优解答问题,可以用回溯法来求解。
对于考虑用回溯法求解的问题,其解必须能表示成一个n元组(x1, x2, ... , xn),其中xi是取自某个有穷集Si,|Si| = mi。解是使判定函数P取极值或满足P的问题。
一般地,不断用修改过的判定函数Pi(x1, x2, ..., xi)去测试正在构造中的n元组的部分向量(x1, x2, ..., xi),看其是否可能导致最优解。如果判定(x1, x2, ..., xi)不可能导致最优解,那么就不再测试可能要测试的mi+1,mi+2,..., mn个向量。
对于用回溯法求解的许多问题,它们要求所有的解满足一组综合的约束条件。这些约束条件可以分为两类:显示约束和隐式约束。显示约束条件可以与所求的问题的输入有关,也可以无关;而隐式约束条件则规定输入的解空间中那些实际上满足判定函数P的元组。
下面的程序实现了8皇后问题。
#include <stdio.h>
#define NUMBERQUEEN 8
int place(int x[], int k)
{
int i = 0;
while (i < k)//将显示约束条件定为i < K
{
//以下的判定条件就是隐式约束条件了
if (x[i] == x[k])//同一列上有两个皇后
{
return 0;
}
if (abs(x[i] - x[k]) == abs(i - k)))/*在同一斜对角线上有两个皇后*/
{
return 0;
}
i++;
}
return 1;
}
void NQEENS(int n)
{
int x[NUMBERQUEEN], k;
x[0] = -1;//列
k = 0; //行
while (k > -1)
{
x[k] = x[k] + 1;
while ((x[k] < n) && (place(x, k) == 0))
{
x[k] += 1;
}
if (x[k] < n)
{
if (k == n - 1)//到达棋盘的最后一行
{
int i = 0;
int count = 0;
for (i = 0; i < n; i++)
{
count++;
printf("(%d,%d)\t", i, x[i]);
if (count % 8 == 0)//很多输出时则暂停显示
{
printf("\n");
getch();
}
}
printf("\n\n");
}
else
{
k++; //下一行
x[k] = -1; //第一列开始找位置放皇后
}
}
else //退回到上一行
{
k--;
}
}
return;
}
void main(void)
{
NQEENS(NUMBERQUEEN);
getch();
return;
}
然而,对于皇后问题能否用解决呢?为了实现堆栈解决问题的想法,在网上搜索到了星辰大哥的一个递归算法。他的程序如下:
#include <stdio.h>
#include <conio.h>
#include <math.h>
#define QUEENS 8
//!记录解的序号的全局变量。
int iCount = 0;
//!记录皇后在各列上的放置位置的全局数组。
int Site[QUEENS];
//!递归求解的函数。
void Queen(int n);
//!输出一个解。
void Output();
//!判断第n个皇后放上去之后,是否有冲突。
int IsValid(int n);
/*----------------------------Main:主函数。----------------------------*/
void main()
{
//!从第0列开始递归试探。
Queen(0);
//!按任意键返回。
getch();
}
/*-----------------Queen:递归放置第n个皇后,程序的核心!----------------*/
void Queen(int n)
{
int i;
//!参数n从0开始,等于8时便试出了一个解,将它输出并回溯。
if(n == QUEENS)
{
Output();
return;
}
//!n还没到8,在第n列的各个行上依次试探。
for(i = 1 ; i <= QUEENS ; i++)
{
//!在该列的第i行上放置皇后。
Site[n] = i;
//!如果放置没有冲突,就开始下一列的试探。
if(IsValid(n))
Queen(n + 1);
}
}
/*------IsValid:判断第n个皇后放上去之后,是否合法,即是否无冲突。------*/
int IsValid(int n)
{
int i;
//!将第n个皇后的位置依次于前面n-1个皇后的位置比较。
for(i = 0 ; i < n ; i++)
{
//!两个皇后在同一行上,返回0。
if(Site[i] == Site[n])
return 0;
//!两个皇后在同一对角线上,返回0。
if(abs(Site[i] - Site[n]) == (n - i))
return 0;
}
//!没有冲突,返回1。
return 1;
}
/*------------Output:输出一个解,即一种没有冲突的放置方案。------------*/
void Output()
{
int i;
//!输出序号。
printf("No.%-5d" , ++iCount);
//!依次输出各个列上的皇后的位置,即所在的行数。
for(i = 0 ; i < QUEENS ; i++)
printf("%d " , Site[i]);
printf("\n");
}
于是根据他的程序,思索再三,有了堆栈解决问题的自然语言描述的算法:
1.nextrow从0开始直到NUMBERQUEEN
2.nextcol从0开始直到NUMBERQUEEN
3.如果栈空或者位置合适,则将该元素入栈,并从下一行继续
4.如果位置不合适,找下一个位置即下一列
5.如果栈满,则表示找到一条合适的路径,出栈并依次赋值后打印棋盘,继续寻找下个路径
6.如果列号<棋盘总的列数,称合法退出了内层循环,则继续下一行的第一列开始寻找下一个元素
7.如果列号 = 棋盘总的列数,称无合适位置而退出了内层循环
8.得到栈顶元素的行和列,并赋给循环变量,出栈,回溯到上一行的下一列寻找合适的位置元素
于是,写出了如下的程序:
#include <stdio.h>
#include <math.h>
#include <conio.h>
#define NUMBERQUEEN 8
#define MAXSIZE 8
typedef struct
{
int col; //列
int row; //行
}matrix;
typedef struct/*定义一个栈类型*/
{
matrix array[MAXSIZE];
int top;//堆栈指针
}stack;
int initstack(stack *stacks)
{
stacks->top = -1;
return 0;
}
/*将数据data压入栈stacks*/
int push(stack *stacks, int row, int col)
{
if (stacks->top == MAXSIZE)/*判断栈是否已满*/
{
//puts("The stack is full!");
return 1;
}
stacks->top++;
stacks->array[stacks->top].row = row;
stacks->array[stacks->top].col = col;
return 0;
}
/*从栈stacks中取一个元素*/
int pop(stack *stacks)
{
if (stacks->top == -1)
{
//puts("The stack is empty!");
return 1;
}
stacks->top--;
return 0;
}
int getRow(stack *stacks)
{
int itopRow;
if (stacks->top == -1)
{
//puts("The stack is empty!");
return -1;
}
itopRow = stacks->array[stacks->top].row;
return itopRow;
}
int getCol(stack *stacks)
{
int itopCol;
if (stacks->top == -1)
{
//puts("The stack is empty!");
return -1;
}
itopCol = stacks->array[stacks->top].col;
return itopCol;
}
//判栈空否
int Empty(stack stacks)
{
if (stacks.top == -1)
{
return 1;//空
}
else
{
return 0;//不空
}
}
int Full(stack stacks)
{
if (stacks.top == MAXSIZE)/*判断栈是否已满*/
{
puts("The stack is full!");
return 1;
}
return 0;
}
int IsValid(stack *stacks, int row, int col)
{
int colin, rowin;
//!将第n个皇后的位置依次于前面n-1个皇后的位置比较。
colin = getCol(stacks);
if (colin == col)
return 0;
//!两个皇后在同一对角线上,返回0。
if (abs(rowin - row) == abs(col - colin))
return 0;
//!没有冲突,返回1。
return 1;
}
void main(void)
{
int chessboard[NUMBERQUEEN][NUMBERQUEEN];
int nextrow = 0, nextcol = 0, colin = 0, rowin = 0;
stack s;
//棋盘初始化
for (nextrow = 0; nextrow < NUMBERQUEEN; nextrow++)
{
for (nextcol = 0; nextcol < NUMBERQUEEN; nextcol++)
{
chessboard[nextrow][nextcol] = 0;
printf("%5d", chessboard[nextrow][nextcol]);
}
printf("\n");
}
printf("\n");
initstack(&s);
//试探放皇后
nextrow = 0;
nextcol = 0;
while (nextrow < NUMBERQUEEN)
{
while (nextcol < NUMBERQUEEN)
{
int rIsValid;
if (Empty(s) == 1 || (rIsValid = IsValid(&s, nextrow, nextcol)) == 1)//栈空或位置合适则进栈
{ //并且转到下一行
push(&s, nextrow, nextcol);
printf("[%d][%d]\n", nextrow, nextcol);
break;
}
if (rIsValid == 0) //不合,则测试下一列
{
nextcol++;
}
}
if (Full(s) == 1)
{
int iRow, iCol;
//此处为占位代码,添加输出栈内容代码
while (!Empty(s))
{
//出栈
iRow = getRow(&s);
iCol = getCol(&s);
chessboard[iRow][iCol] = 1;
pop(&s);
}
//打印
for (rowin = 0; rowin < NUMBERQUEEN; rowin++)
{
for (colin = 0; colin < NUMBERQUEEN; colin++)
{
printf("%5d", chessboard[rowin][colin]);
}
printf("\n");
}
//修改循环变量
nextrow = iRow;
nextcol = iCol + 1;
continue;
}
if (nextcol < NUMBERQUEEN)//找到了合适的位置,则从下一行的第一列开始下个皇后的查找
{
nextrow++;
nextcol = 0;
continue;
}
if (nextcol == NUMBERQUEEN)//没有合适的位置,从上一行合法位置的下一列开始寻找新的皇后合法位置
{
nextrow = getRow(&s);//得到栈顶元素的行
nextcol = getCol(&s);//得到栈顶元素的列
pop(&s); //将不合适的栈顶元素弹出
nextcol++;//nextrow取为当前行,不修改
continue;
}
}
getch();
return;
}
这个程序包含了堆栈的一些基本操作,比较长。但可以归结为无法实现预期设想!究其原因,是因为第一行第一列元素进栈后,第二行的元素找到第三列,此时,元素chessboard[2][3]即为栈顶元素,当下一个元素再次请求进栈时,它只和栈顶元素比较位置的合法性,而和栈中其他元素无法比较位置的合法性!
期待另一种思路的出现!