先说一下回溯法
回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。
1、回溯法的一般描述
可用回溯法求解的问题P,通常要能表达为:对于已知的由n元组(x1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。其中Si是分量xi的定义域,且 |Si| 有限,i=1,2,…,n。我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。
解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。但显然,其计算量是相当大的。
我们发现,对于许多问题,所给定的约束集D具有完备性,即i元组(x1,x2,…,xi)满足D中仅涉及到x1,x2,…,xi的所有约束意味着j(j<i)元组(x1,x2,…,xj)一定也满足D中仅涉及到x1,x2,…,xj的所有约束,i=1,2,…,n。换句话说,只要存在0≤j≤n-1,使得(x1,x2,…,xj)违反D中仅涉及到x1,x2,…,xj的约束之一,则以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)一定也违反D中仅涉及到x1,x2,…,xi的一个约束,n≥i>j。因此,对于约束集D具有完备性的问题P,一旦检测断定某个j元组(x1,x2,…,xj)违反D中仅涉及x1,x2,…,xj的一个约束,就可以肯定,以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)都不会是问题P的解,因而就不必去搜索它们、检测它们。回溯法正是针对这类问题,利用这类问题的上述性质而提出来的比枚举法效率更高的算法。
回溯法首先将问题P的n元组的状态空间E表示成一棵高为n的带权有序树T,把在E中求问题P的所有解转化为在T中搜索问题P的所有解。树T类似于检索树,它可以这样构造:
设Si中的元素可排成xi(1) ,xi(2) ,…,xi(mi-1) ,|Si| =mi,i=1,2,…,n。从根开始,让T的第I层的每一个结点都有mi个儿子。这mi个儿子到它们的双亲的边,按从左到右的次序,分别带权xi+1(1) ,xi+1(2) ,…,xi+1(mi) ,i=0,1,2,…,n-1。照这种构造方式,E中的一个n元组(x1,x2,…,xn)对应于T中的一个叶子结点,T的根到这个叶子结点的路径上依次的n条边的权分别为x1,x2,…,xn,反之亦然。另外,对于任意的0≤i≤n-1,E中n元组(x1,x2,…,xn)的一个前缀I元组(x1,x2,…,xi)对应于T中的一个非叶子结点,T的根到这个非叶子结点的路径上依次的I条边的权分别为x1,x2,…,xi,反之亦然。特别,E中的任意一个n元组的空前缀(),对应于T的根。
因而,在E中寻找问题P的一个解等价于在T中搜索一个叶子结点,要求从T的根到该叶子结点的路径上依次的n条边相应带的n个权x1,x2,…,xn满足约束集D的全部约束。在T中搜索所要求的叶子结点,很自然的一种方式是从根出发,按深度优先的策略逐步深入,即依次搜索满足约束条件的前缀1元组(x1i)、前缀2元组(x1,x2)、…,前缀I元组(x1,x2,…,xi),…,直到i=n为止。
在回溯法中,上述引入的树被称为问题P的状态空间树;树T上任意一个结点被称为问题P的状态结点;树T上的任意一个叶子结点被称为问题P的一个解状态结点;树T上满足约束集D的全部约束的任意一个叶子结点被称为问题P的一个回答状态结点,它对应于问题P的一个解。
【问题】 组合问题
问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。
例如n=5,r=3的所有组合为:
(1)1、2、3 (2)1、2、4 (3)1、2、5
(4)1、3、4 (5)1、3、5 (6)1、4、5
(7)2、3、4 (8)2、3、5 (9)2、4、5
(10)3、4、5
则该问题的状态空间为:
E={(x1,x2,x3)∣xi∈S ,i=1,2,3 } 其中:S={1,2,3,4,5}
约束集为: x1<x2<x3
显然该约束集具有完备性。
2、回溯法的方法
对于具有完备约束集D的一般问题P及其相应的状态空间树T,利用T的层次结构和D的完备性,在T中搜索问题P的所有解的回溯法可以形象地描述为:
从T的根出发,按深度优先的策略,系统地搜索以其为根的子树中可能包含着回答结点的所有状态结点,而跳过对肯定不含回答结点的所有子树的搜索,以提高搜索效率。具体地说,当搜索按深度优先策略到达一个满足D中所有有关约束的状态结点时,即“激活”该状态结点,以便继续往深层搜索;否则跳过对以该状态结点为根的子树的搜索,而一边逐层地向该状态结点的祖先结点回溯,一边“杀死”其儿子结点已被搜索遍的祖先结点,直到遇到其儿子结点未被搜索遍的祖先结点,即转向其未被搜索的一个儿子结点继续搜索。
在搜索过程中,只要所激活的状态结点又满足终结条件,那么它就是回答结点,应该把它输出或保存。由于在回溯法求解问题时,一般要求出问题的所有解,因此在得到回答结点后,同时也要进行回溯,以便得到问题的其他解,直至回溯到T的根且根的所有儿子结点均已被搜索过为止。
例如在组合问题中,从T的根出发深度优先遍历该树。当遍历到结点(1,2)时,虽然它满足约束条件,但还不是回答结点,则应继续深度遍历;当遍历到叶子结点(1,2,5)时,由于它已是一个回答结点,则保存(或输出)该结点,并回溯到其双亲结点,继续深度遍历;当遍历到结点(1,5)时,由于它已是叶子结点,但不满足约束条件,故也需回溯。
3、回溯法的一般流程和技术
在用回溯法求解有关问题的过程中,一般是一边建树,一边遍历该树。在回溯法中我们一般采用非递归方法。下面,我们给出回溯法的非递归算法的一般流程:
在用回溯法求解问题,也即在遍历状态空间树的过程中,如果采用非递归方法,则我们一般要用到栈的数据结构。这时,不仅可以用栈来表示正在遍历的树的结点,而且可以很方便地表示建立孩子结点和回溯过程。
例如在组合问题中,我们用一个一维数组Stack[ ]表示栈。开始栈空,则表示了树的根结点。如果元素1进栈,则表示建立并遍历(1)结点;这时如果元素2进栈,则表示建立并遍历(1,2)结点;元素3再进栈,则表示建立并遍历(1,2,3)结点。这时可以判断它满足所有约束条件,是问题的一个解,输出(或保存)。这时只要栈顶元素(3)出栈,即表示从结点(1,2,3)回溯到结点(1,2)。
【问题】 组合问题
问题描述:找出从自然数1,2,…,n中任取r个数的所有组合。
采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质:
(1) a[i+1]>a[i],后一个数字比前一个大;
(2) a[i]-i<=n-r+1。
按回溯法的思想,找解过程可以叙述如下:
首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部解。按上述思想写成程序如下:
【程序】
# define MAXN 100
int a[MAXN];
void comb(int m,int r)
{ int i,j;
i=0;
a[i]=1;
do {
if (a[i]-i<=m-r+1
{ if (i==r-1)
{ for (j=0;j<r;j++)
printf(“%4d”,a[j]);
printf(“/n”);
}
a[i]++;
continue;
}
else
{ if (i==0)
return;
a[--i]++;
}
} while (1)
}
main()
{ comb(5,3);
}
下面是关于八皇后的
Ⅰ
八皇后是一个相当著名的算法问题:题目说是在8*8国际象棋棋盘上,要求在第一行放置一个皇后,且能做到在竖方向,斜方向都没有冲突。
这里首先谈一下解集合的选取方式,如果用一个二维数组来存放八皇后的位置,难免比较浪费,注意到八个皇后不可能位于同一行,所以只要存放各个皇后在竖方向的位置即可。所以存放解集仅需要一个长度为8的整型数组即可.
首先看到这个问题,最容易想到的想法就是穷举法,每次在当前行中的第一列起摆当前这个皇后的位置,发现有冲突时则终止当前的次的搜索,当发现当前的这个行的皇后与前面的皇后没有冲突时,则跳到下一行,继续从第一列起摆放皇后的位置.如果当前行的皇后已摆至最后一列了,则应跳到上一行.
由于行数已经不可能相同了,所以了不起也就是做一个8!的次运行次数的算法便可以得到最终解了.不过,很快,这种自然的一蹋糊涂的想法便要被打住了,如果不是8皇后呢,如果是20皇后呢?效率低不说,光是要修改源程序这一个不足点便可以将这种想法否决掉了.
优化的出路在哪里呢,其实,改进的工作也没有动啥”大手术”,主要是做到下面两件事情:
1) 一旦当前的集不满足条件时,便终止当前的搜索树的分枝,并回退到前一层继续搜索.
(其实,前面的蛮力法退出是满足的,就是这个回退做不了。)
2 如果当前的解满足部分解的条件时,则继续跳到下一行搜索,发现有全解时终止搜索.
(前面的蛮力法做这个也是没问题的)
关于搜索树的最基本的想法,可以参考我前面的一篇愚作---http://blog.csdn.net/emilmatthew/archive/2005/08/03/445132.aspx
所以,从蛮力法跳到相对“高级”的回溯法我们就提高了一步----能回退到上一层.
而这个动作,可以通过递归和非递归两种方式来实现,递归的会相对好理解些,另一种非递归的也不难理解的.
而一旦使用回溯法,则程序的柔韧性得到了极大的提高,可以求解N皇后问题.
在展示最后的实现版本之前,先来看几个输助的函数,判定是否有冲突和是否局部完成位置的函数。
由于我们的结果数组存放的仅仅是列中的位置,而行中的位置肯定是相异的,所以,只需要判定斜方向是否有冲突和竖直方向是否有冲突就可以了:
//判定当前结点在斜方向和另一个结点是否有冲突:
int checkXiePos(int r1,int r2,int c1,int c2)
{
return !(fabs(r1-r2)==fabs(c1-c2));
}
//判定当前结点和所有已搜索的竖直方向上的结点是否有冲突.
int checkVPos(int* arr,int len)
{
int flag=1;
int i,j;
assertF(arr!=NULL,"in checkVPos,arr is null/n");
for(i=0;i<len-1;i++)
for(j=i+1;j<len;j++)
if(arr[i]==arr[j])
{
flag=0;
break;
}
return flag;
}
//总的判定当前结点和已搜索结点之前是否有冲突的函数:
int partFinished(int* arr,int len)
{
int flag=1;
int i,j;
assertF(arr!=NULL,"in partFinished,arr is null/n");
flag=flag&&checkVPos(arr,len);
for(i=0;i<len-1;i++)
for(j=i+1;j<len;j++)
{
flag=flag&&checkXiePos(i,j,arr[i],arr[j]);
if(!flag)goto out;
}
out:
return flag;
}
//判定是完成了一个N皇后的排列,用的还是partFinished,但在算法的表达上更为贴切.
int fullFinished(int* arr,int len)
{
assertF(arr!=NULL,"in fullFinished,arr is null/n");
eturn partFinished(arr,len);
}
好了,有了这些底层的函数作为铺垫,那么再来实现我们上层的算法那就要清楚和明朗多了.
1) 首先看一下递归版本的实现: (相当的简洁和好懂)
//NqueensProblems Solution Recursive1 Search and Print out all the answers.
void NQueensProblem2(int* arr,int depth,int size,int* count,FILE* outputFile)
{ /*default:a[1..n]=0 , 0 , size, 1, outputFile*/
//nowSize,finished rows. [1..n]
//size,total problem size. [n]
int i=0;
assertF(outputFile!=NULL,"in NQueensProblems2 outputFile is null/n");
assertF(count!=NULL,"in NQueensProblems2 count is null/n");
if(!partFinished(arr,depth+1))return;
//如果当前的结点和前面的结点有冲突,则终断当前搜索的子分支.
else if(fullFinished(arr,size)&&depth+1==size)/*如果全部完成了,则同样终断当前搜支,并打印出当前的结集.*/
{
fprintf(outputFile,"Answer of%dQueens Problem No.%d:/r/n",size,*count);
(*count)++;//结果数目加1
}
else //partFinished ,部分解
{
depth++; //branch to a deeper levels,展开搜索树到下一个深度搜索
for(i=0;i<size;i++)
{
arr[depth]=i+1;//搜索下一个深度的所有结点
//because it is a depth first search ,it will not affect another search branch.
NQueensProblem2(arr,depth,size,count,outputFile);
}
}
}
这个递归的版本有一个不足之处在于,第一层树的展开工作要手工来做:
即:
for(i=0;i<problemSize;i++)
{
ansCol[0]=i+1;//first level branch. node the col pos is between [1..n]
NQueensProblem2(ansCol,0,problemSize,count,outputFile2);
}
2有了递归版本作铺垫,再来看这个非递归版本也不会有太大的困难了:
//Nqueens Problem Solution1 Unrecursive,could get the full or only one answer set.
void NQueensProblem1(int size,int mode)
{
//attention ,the output position use 1...n.
int* arr;
int i=0,k=0;
int count=0;
FILE* outputFile;
outputFile=createFile('w',"NQueensAns.txt");
arr=(int*)malloc(sizeof(int)*size);
for(i=0;i<size;i++)
arr[i]=0;
k=0;
count=1;
while(k>=0)//k: stands for the branch tree's levels,which start from 0 to size-1
{
while(arr[k]<=size-1)//arr[0..size-1]: stands for the v position of the queens.from(1 to size).
{
arr[k]++;//search the parallel level's next possible position.
if(fullFinished(arr,k+1)&&(k+1==size))//full answer check.
{
fprintf(outputFile,"Answer of%dQueens Problem No.%d:/r/n",size,count);
outputListArrInt(arr,0,size,outputFile);
outputQueensAns(arr,size,outputFile);
count++;
if(!mode)/*no need all resolved.根据参数调节是否要输出全部解集,如果/。要,则从这里跳出.*/
goto out;
}
else if(partFinished(arr,k+1))//if only part finished. Branching to a deeper level.
{
k++;//move to a deeper level---branching new search child tree.
}
}
/*If current search has no solution ,roll back to the up level*/
arr[k]=0;//set current node to unUsed.
k--;//roll back to the pre level.
}
out:
free(arr);
}
3这里给出第三个版本,同样是一个递归的版本,这个版本只能打到第一组解,但是比较好的地方在于可以不用在外部调用层把第一层的情况给展开:
void NQueensProblem3(int* arr,int depth,int size,int* count,FILE* outputFile)
{ //arrValue=1..n 0 .. size-1
assertF(outputFile!=NULL,"in NQueensProblems2 outputFile is null/n");
assertF(count!=NULL,"in NQueensProblems2 count is null/n");
if(depth>=size-1&&partFinished(arr,depth+1))
{
fprintf(outputFile,"Answer of%dQueens Problem No.%d:/r/n",size,*count);
outputListArrInt(arr,0,size,outputFile);
outputQueensAns(arr,size,outputFile);
(*count)++;
}
else if(!partFinished(arr,depth+1)||arr[depth]>size)//have chong tu
{
if(arr[depth]>=size)//roll back to the pre level
{
arr[depth]=1;
depth--;
arr[depth]++;
NQueensProblem3(arr,depth,size,count,outputFile);
}
else{ //search for the parallel row's next possible position
arr[depth]++;
NQueensProblem3(arr,depth,size,count,outputFile);
}
}
else //part answer is ok ,branch to a deeper level for search.
{
depth++;
arr[depth]=1;//here,at first you use 0 as start pos,wrong!
NQueensProblem3(arr,depth,size,count,outputFile);
}
}
插一句:关于第三个版本的调试.
现在看看上面这个递归版本也是相当清楚的,但我调试时却调了半天。下面把我调试记录文件中最后的比较重要的几步贴出来,和大家共同分享程序设计过程中所遇到的点点滴滴。可能有些乱的话.
debug1
else//part answer is ok ,branch to a deeper level for search.
{
//printf("%d-",arr[1]);
//push_Seq(myStack,arr[depth]);//storage current level's col pos
depth++;
arr[depth]=1;//here,at first you use 0 as start pos,wrong!
fprintf(outputFile,"brahch to deeper to %dlevel/r/n",depth);
fprintf(outputFile,"pre level node val:%d/r/n",arr[depth-1]);
outputListArrInt(arr,(int)0,(int)size,outputFile);
NQueensProblem3(arr,depth,size,count,outputFile);
}
brahch to deeper to 1level
pre level node val:1
1 1 1 1
brahch to deeper to 2level
pre level node val:3
1 3 1 1
brahch to deeper to 3level
pre level node val:5
1 3 5 1
从这个错误中,可以看出,由于没有对partResolve的上限加以限定,导致部分解没有上限~~~
debug2
试图将成功的条件上移,但是对上面的BUG是无效的.
if(depth>=size-1&&partFinished(arr,depth+1))
{
fprintf(outputFile,"Answer of%dQueens Problem No.%d:/r/n",size,*count);
outputListArrInt(arr,0,size,outputFile);
outputQueensAns(arr,size,outputFile);
(*count)++;
}
将部分不成功解中,回到上层的判定条件改成>=size
if(arr[depth]>=size)//roll back to the pre level
brahch to deeper to 3level
pre level node val:2
1 4 2 1
roll back to 2level
roll back to 1level
brahch to deeper to 2level
pre level node val:4
1 4 1 1
brahch to deeper to 3level
pre level node val:2
1 4 2 1
roll back to 2level
roll back to 1level
brahch to deeper to 2level
pre level node val:4
1 4 1 1
在第一个主分叉中的最后一棵子树中出现的死循环,原因在于当本层与先前没有冲突但已到最后一个位置时,会新生成一棵子树,然后,子树不成功,退上来,再生成,再退...
3解决方案,在不成功处加一个判定:
!partFinished(arr,depth+1)||arr[depth]>size)
if(arr[depth]>=size)//roll back to the pre level
{
arr[depth]=1;
depth--;
fprintf(outputFile,"roll back to %dlevel/r/n",depth);
arr[depth]++;//加了这一句~~~,好~~
//本来写的是if(arr[depth]<size)arr[depth]++;
//虽然看到了有多余位置的BUG,但是没挑准地方.
NQueensProblem3(arr,depth,size,count,outputFile);
}
BUG找到了,第三个版本的递归Version也实现了,呵呵~~~
算法效率的简单测试.
size=10,not all answer
version:1 time:0.0000s
version:3 time:0.0000s
Answer of10Queens Problem No.1:
1 3 6 8 10 5 9 2 4 7
(o代表皇后,*代表空余位)
o * * * * * * * * *
* * o * * * * * * *
* * * * * o * * * *
* * * * * * * o * *
* * * * * * * * * o
* * * * o * * * * *
* * * * * * * * o *
* o * * * * * * * *
* * * o * * * * * *
* * * * * * o * * *
================================================
size=15,not all answer
version:1 time:0.0930s
version:3 失效了,估计程序有错误.
size=10, all answer.(包括打出程序的结果)
version:1 time:0.89100s
version:2 time:0.53100s
当size变得更大时,程序的运行就有些长了,size=20时,第一个版本的竟然在南海诸求一组解时用了32秒,而第二个版本在一分钟内已经算不出来了。
Ⅱ
/****************************************************/
/* */
/* 问题: 在8×8的国际象棋棋盘上放置8个皇后,要求任意两个皇后 */
/* 不能在同一行、同一列或同一条对角线上。 */
/* */
/* 本程序使用递归-回溯法求解8皇后问题。Visual C++ 6.0 调试通过。 */
/* 作者 晨星 2002年5月9日 */
/* */
/************************************************************************/
#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");
}
Ⅱ
#include
/* SIZE >= 4 时均有解,缺省定义8 */
#define SIZE 8
/* SIZE*SIZE棋盘 */
int chess[SIZE][SIZE] = {0};
/* 当前行/列是否越界 */
bool IsOut(int row,int col)
{
if(row >= 0 && row < SIZE && col >= 0 && col < SIZE)
{
return false;
}
else
{
return true;
}
}
bool queen(int n)
{
/* 递归终止条件 */
if(n == SIZE)
{
return true;
}
/* 遍历第n行 */
for(int j = 0; j < SIZE; j ++)
{
/* 假设在第n行j列放置一枚棋子 */
chess[n][j] = 1;
/* 检验在"问题描述"中的三个条件 */
for(int k = 0; k < SIZE; k++)
{
/* 行扫描 */
if(k != j && chess[n][j] == chess[n][k])
{
chess[n][j] = 0;
}
/* 列扫描 */
if(n != k && chess[n][j] == chess[k][j])
{
chess[n][j] = 0;
}
/* 反斜线扫描 */
if(k!= 0 && !IsOut(n - k,j-k) && chess[n][j] == chess[n-k][j-k])
{
chess[n][j] = 0;
}
if(k!= 0 && !IsOut(n + k,j+k) && chess[n][j] == chess[n+k][j+k])
{
chess[n][j] = 0;
}
/* 正斜线扫描 */
if(k!= 0 && !IsOut(n - k,j+k) && chess[n][j] == chess[n-k][j+k])
{
chess[n][j] = 0;
}
if(k!= 0 && !IsOut(n + k,j-k) && chess[n][j] == chess[n+k][j-k])
{
chess[n][j] = 0;
}
}
if(chess[n][j] == 1 && queen(n+1))
{
/* 放置正确 */
return true;
}
else
{
/* 假设不成立 */
chess[n][j] = 0;
}
}
return false;
}
int main(int argc, char* argv[])
{
queen(0);
for(int i = 0; i < SIZE; i++)
{
for(int j = 0; j < SIZE; j++)
{
printf("%d ",chess[i][j]);
}
printf("/n");
}
return 0;
}
Ⅲ