[原]回溯法(组合问题、八皇后问题)
【Title】[原]回溯法(组合问题、八皇后问题)
【Date】2013-03-29
【Abstract】回溯法示例包括组合问题和八皇后问题。很久之前在书上抄的,复习一下,分享出来。自己一个字一个字的打上来的(有部分精简),标上原创,版权属于原作者。
【Keywords】算法、algorithm、backtracking method
【Environment】Windows 7
【Author】wintys (wintys@gmail.com) http://wintys.cnblogs.com
【URL】http://www.cnblogs.com/wintys/archive/2013/03/29/algorithm_backtracking.html
【Content】:
回溯法:首先放弃关于规模大小的限制。
a.试探(扩大规模):满足除规模之外的所有条件,则扩大规模。
b.回溯(缩小规模):当前规模解不是合法解时回溯(不满足约束条件);求完一个解,再求下一个解时,也要回溯。
问题描述:找出从自然数1,2,...,n中任取r个数的所有组合。
【分析】:
采用回溯法找问题的解,将找到的组合以从小到大的顺序存于a[0],a[1],...,a[r-1]中,组合的元素满足以下性质:
(1)、a[i+1] > a[i],后一个数据字比前一个数字大。
(2)、a[i] - i <= n - r + 1。
【伪代码】
问题描述:求出在一个nxn的棋盘上,放置n个不能互相捕捉的国际象棋“皇后”的所有布局。
【分析】
a.直观的方法是采用一个二维数组,但仔细观察就会发现,这种表示方法给调整候选解及检查其合理性带来困难。更好的方法是尽可能直接表示那些常用的信息。对于本题而言,常用信息并不是皇后的具体位置,而是“一个皇后”是否已经在某行某斜线合理地安置好。一列上只能放一个皇后,因此引入col[]数组。
b.为了使程序在找完全部解后回溯到最初位置,设定col[0]的初值为0,当回溯到第0列时,表示已求得全部解,程序结束。
c.每行、每列、每条斜线上只能有一个皇后。引入数组为了便于检查皇后是否安置好。值为1表是没有皇后,为0表示有皇后。
数组a[k]表示第k行上没有皇后。
数组b[k]表示第k列右高左低斜线(/)上没有皇后。
数组c[k]表示第k列左高右低斜线(\)上没有皇后。
d.棋盘中同一右高左低斜线(/)上的方格,它们的行号与列号之和相同;同一左高右低斜线(\)上的方格,它们的行号与列号之差相同。
e.初始时,所有行和斜线上均没有皇后,从第1列的第一行配置第一个皇后开始,在第m列col[m]行放置了一个合理的皇后后,准备考查第m+1列时,在数组a[]、b[]、c[]中为第m列,col[m]行的位置设定有皇后标志;当从第m列回溯到第m-1列,并准备调整第m-1列的皇后配置时,清除在数组a[]、b[]和c[]中设置的关于第m-1列,col[m-1]行有皇后的标志。一个皇后在m列,col[m]行方格内配置是合理的,由数组a[]、b[]和c[]对应位置的值都为1来确定。由于每次只在某列配置一个皇后,故不事能有两个皇后在同一列的情况。
f.棋盘的列数m称为问题的规模。
g.此程序的关键是试探条件:a[col[m]]&&b[m + col[m]]&&c[n+m-col[m]];
【代码】
【Date】2013-03-29
【Abstract】回溯法示例包括组合问题和八皇后问题。很久之前在书上抄的,复习一下,分享出来。自己一个字一个字的打上来的(有部分精简),标上原创,版权属于原作者。
【Keywords】算法、algorithm、backtracking method
【Environment】Windows 7
【Author】wintys (wintys@gmail.com) http://wintys.cnblogs.com
【URL】http://www.cnblogs.com/wintys/archive/2013/03/29/algorithm_backtracking.html
【Content】:
1、回溯法要点
回溯法:首先放弃关于规模大小的限制。
a.试探(扩大规模):满足除规模之外的所有条件,则扩大规模。
b.回溯(缩小规模):当前规模解不是合法解时回溯(不满足约束条件);求完一个解,再求下一个解时,也要回溯。
1.1、组合问题
问题描述:找出从自然数1,2,...,n中任取r个数的所有组合。
【分析】:
采用回溯法找问题的解,将找到的组合以从小到大的顺序存于a[0],a[1],...,a[r-1]中,组合的元素满足以下性质:
(1)、a[i+1] > a[i],后一个数据字比前一个数字大。
(2)、a[i] - i <= n - r + 1。
【伪代码】
void combination(int n, int r)
{
int i,j;
//搜索深度、广度初始化
i = 0;
a[i]=1;
do{
if(a[i] - i <= n - r +1)//深度、广度的限制条件,性质(2)
{
if(i == r - 1)//规模达到要求
{
print a[0..r-1];//输出合格的一组解
a[i]++;//当前规模的下一个解,即广度加大
continue;
}
i++;//扩大问题规模,即深度加大
a[i] = a[i - 1] +1;//为满足性质(1)
}
else//当前规模考查完,回溯。
{
if( i == 0)//回溯至开始,已找到问题全部解,搜索结束。
return;
a[--i]++;//缩小规模,考查下一个候选解,深度减小,广度加大。
}
}while(1);
}
{
int i,j;
//搜索深度、广度初始化
i = 0;
a[i]=1;
do{
if(a[i] - i <= n - r +1)//深度、广度的限制条件,性质(2)
{
if(i == r - 1)//规模达到要求
{
print a[0..r-1];//输出合格的一组解
a[i]++;//当前规模的下一个解,即广度加大
continue;
}
i++;//扩大问题规模,即深度加大
a[i] = a[i - 1] +1;//为满足性质(1)
}
else//当前规模考查完,回溯。
{
if( i == 0)//回溯至开始,已找到问题全部解,搜索结束。
return;
a[--i]++;//缩小规模,考查下一个候选解,深度减小,广度加大。
}
}while(1);
}
1.2、八皇后问题
问题描述:求出在一个nxn的棋盘上,放置n个不能互相捕捉的国际象棋“皇后”的所有布局。
【分析】
a.直观的方法是采用一个二维数组,但仔细观察就会发现,这种表示方法给调整候选解及检查其合理性带来困难。更好的方法是尽可能直接表示那些常用的信息。对于本题而言,常用信息并不是皇后的具体位置,而是“一个皇后”是否已经在某行某斜线合理地安置好。一列上只能放一个皇后,因此引入col[]数组。
b.为了使程序在找完全部解后回溯到最初位置,设定col[0]的初值为0,当回溯到第0列时,表示已求得全部解,程序结束。
c.每行、每列、每条斜线上只能有一个皇后。引入数组为了便于检查皇后是否安置好。值为1表是没有皇后,为0表示有皇后。
数组a[k]表示第k行上没有皇后。
数组b[k]表示第k列右高左低斜线(/)上没有皇后。
数组c[k]表示第k列左高右低斜线(\)上没有皇后。
d.棋盘中同一右高左低斜线(/)上的方格,它们的行号与列号之和相同;同一左高右低斜线(\)上的方格,它们的行号与列号之差相同。
e.初始时,所有行和斜线上均没有皇后,从第1列的第一行配置第一个皇后开始,在第m列col[m]行放置了一个合理的皇后后,准备考查第m+1列时,在数组a[]、b[]、c[]中为第m列,col[m]行的位置设定有皇后标志;当从第m列回溯到第m-1列,并准备调整第m-1列的皇后配置时,清除在数组a[]、b[]和c[]中设置的关于第m-1列,col[m-1]行有皇后的标志。一个皇后在m列,col[m]行方格内配置是合理的,由数组a[]、b[]和c[]对应位置的值都为1来确定。由于每次只在某列配置一个皇后,故不事能有两个皇后在同一列的情况。
f.棋盘的列数m称为问题的规模。
g.此程序的关键是试探条件:a[col[m]]&&b[m + col[m]]&&c[n+m-col[m]];
【代码】
#include <stdio.h>
#include <stdlib.h>
#define MAX_N 20
int n,m,good;
int col[MAX_N + 1],a[MAX_N + 1],b[2*MAX_N + 1],c[2*MAX_N + 1];
void main()
{
int j;
char awn;
printf("Enter n:");
scanf("%d",&n);
for(j=0;j<=n;j++)a[j]=1;//初始时,每列都能放皇后
for(j=0;j<=2*n;j++)b[j]=c[j]=1;//初始时,每斜线都能放皇后。
m = 1; col[1] = 1; good = 1; col[0] = 0;
do{
if(good)
{
if(m == n)//找到一个解
{
printf("列\t行");
for(j=1;j<=n;j++)
printf("%3d\t%3d\n",j,col[j]);//输出皇后位置
scanf("%c",&awn);
if(awn== 'Q'|| awn=='q')exit(0);
while(col[m] == n)//问题当前规模m是否还有其它解
{
m--;//当前规模m无其它解则回溯
a[col[m]]=b[m+col[m]]=c[n+m-col[m]] = 1;//回溯后清除皇后标志
}
col[m]++;//试探当前规模的下一解
}
else//在m列、col[m]行,设置皇后标志
{
a[col[m]]=b[m+col[m]]=c[n+m-col[m]] = 0;
col[++m]=1;//扩大规模,试探该规模下的第一个可能解,即皇后从第一行开始配置
}
}
else//good为0时的情况,即当前规模下试探解失败,需回溯或调整皇后位置
{
while(col[m]==n)//是否当前规模下所有可能解试探都失败
{
m--;//当前规模下所有可能解试探都失败,需回溯
a[col[m]]=b[m+col[m]]=c[n+m-col[m]] = 1;//回溯后,清除皇后标志。
}
col[m]++;//当前规模下可能还有其它解,调整当前规模下的皇后位置。
}
//当前规模m下放置的皇后是否合法
good = a[col[m]]&&b[m + col[m]]&&c[n+m-col[m]];
}while(m != 0);
}
【Reference】
#include <stdlib.h>
#define MAX_N 20
int n,m,good;
int col[MAX_N + 1],a[MAX_N + 1],b[2*MAX_N + 1],c[2*MAX_N + 1];
void main()
{
int j;
char awn;
printf("Enter n:");
scanf("%d",&n);
for(j=0;j<=n;j++)a[j]=1;//初始时,每列都能放皇后
for(j=0;j<=2*n;j++)b[j]=c[j]=1;//初始时,每斜线都能放皇后。
m = 1; col[1] = 1; good = 1; col[0] = 0;
do{
if(good)
{
if(m == n)//找到一个解
{
printf("列\t行");
for(j=1;j<=n;j++)
printf("%3d\t%3d\n",j,col[j]);//输出皇后位置
scanf("%c",&awn);
if(awn== 'Q'|| awn=='q')exit(0);
while(col[m] == n)//问题当前规模m是否还有其它解
{
m--;//当前规模m无其它解则回溯
a[col[m]]=b[m+col[m]]=c[n+m-col[m]] = 1;//回溯后清除皇后标志
}
col[m]++;//试探当前规模的下一解
}
else//在m列、col[m]行,设置皇后标志
{
a[col[m]]=b[m+col[m]]=c[n+m-col[m]] = 0;
col[++m]=1;//扩大规模,试探该规模下的第一个可能解,即皇后从第一行开始配置
}
}
else//good为0时的情况,即当前规模下试探解失败,需回溯或调整皇后位置
{
while(col[m]==n)//是否当前规模下所有可能解试探都失败
{
m--;//当前规模下所有可能解试探都失败,需回溯
a[col[m]]=b[m+col[m]]=c[n+m-col[m]] = 1;//回溯后,清除皇后标志。
}
col[m]++;//当前规模下可能还有其它解,调整当前规模下的皇后位置。
}
//当前规模m下放置的皇后是否合法
good = a[col[m]]&&b[m + col[m]]&&c[n+m-col[m]];
}while(m != 0);
}
[1]《软件设计师考试考点分析与真题详解(最新版)》
作者:wintys (wintys@gmail.com)
出处:http://wintys.cnblogs.com
欢迎转载,转载请注明作者及出处。