[原]回溯法(组合问题、八皇后问题)

【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】:

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);
    }

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】

[1]《软件设计师考试考点分析与真题详解(最新版)》

posted @ 2013-03-29 10:37  wintys  阅读(2121)  评论(0编辑  收藏  举报