回溯法及N皇后问题

reference:

http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html

http://www.cnblogs.com/Creator/archive/2011/05/20/2052341.html

1、什么是回溯法

 回溯法是一种系统地搜索问题解答的方法。在搜索的过程中尝试找到问题的解,如果发现找不到了,就退一步,往上回溯(剪枝过程)。对于许多复杂问题和大规模问题都可以使用回溯法。
 回溯法的基本思想是按照深度优先搜索的策略,从根节点开始搜索,当到某个节点时要判断是否是包含问题的解,如果包含就从该节点继续搜索下去,如果不包含,就向父节点回溯。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
  回溯法常用的剪枝函数:(1)约束函数:在节点处减去不满足约束的子树。(2)界限函数:减去得不到最优解的子树

2、回溯法解题的一般步骤

  1. 针对所给问题,确定问题的解空间
  2. 利用适于搜索的方法组织解空间
  3. 利用深度优先搜索解空间
  4. 在搜索过程中用剪枝函数避免无效搜索。

3、算法框架

  • 设问题的解是一个n维向量(a1,a2,………,an),约束条件是ai(i=1,2,3,…..,n)之间满足某种条件,记为f(ai)。
  • 非递归回溯框架
int a[n], i;
初始化a[n];
i = 1;
while (i > 0(有路可走) and (未达到目标))//还没有回溯到头
{
    if (i > n)
    {
        搜索到一个解,输出;
    }
    else
    {
        a[i]第一个可能的值;
        while (a[i]在不满足约束条件且在搜索空间内)
        {
            a[i]下一个可能的值;
        }
        if (a[i]在搜索空间内)
        {
            标识占用的资源;
            i = i + 1; //扩展下一个节点
        }
        else
        {
            清理所占的空间状态;  //回溯
            i = i - 1;
        }
    }
}
  • 递归回溯框架
int a[n];
try(int i)
{
    if(i>n)
        输出结果;
    else
   {
    for(j = 下界; j <= 上界; j=j+1)  // 枚举i所有可能的路径
       {
            if(fun(j))                 // 满足限界函数和约束条件
              {
                 a[i] = j;
               ...                         // 其他操作
                 try(i+1);
              回溯前的清理工作(如a[i]置空值等);
              }
              }
          }
 }

4举例:N皇后问题

在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。

/* 用n元组x[1:n]表示n后问题的解。x[i]表示皇后i放置在棋盘的第i行的第x[i]列 */


#include <stdio.h>
#include <math.h>
#include <stdlib.h>

static int n, x[1000];
static long sum;

/* 判断第k个后能不能放在x[k]处 两个皇后不能放在统一斜线上: 若2个皇后放置的位置分别是(i,j)和(k,l), 且 i-j = k -l 或 i+j = k+l,则说明这2个皇后处于同一斜线上。 */

void OutPut()
{
    for (int i = 1; i <= n; ++i)
        printf("(%d, %d) ", i, x[i]);
    printf("\n");
}

int Place(int k)
{
    for (int j = 1; j < k; ++j)
        if (abs(k - j) == abs(x[k] - x[j]) || x[j] == x[k])
            return 0;
    return 1;
}

void BackTrack1(int t)
{
    //如果t>n说明已经完成一次放置
    if (t > n)
    {
        sum++;
        OutPut();
    }
    else
    {
        for (int i = 1; i <= n; ++i)
        {
            x[t] = i;
            if (Place(t))                   //可以放在i位置处,则继续搜索
                BackTrack1(t + 1);
        }
    }
}

void BackTrack()
{
    int k;
    x[1] = 0;       //初始化为0
    k = 1;
    while (k >= 1)  //循环
    {
        x[k] += 1;  //先放在第一个位置
        while ((x[k] <= n) && !(Place(k)))  //如果不能放
            x[k] += 1;                      //放在下一个位置
        if (x[k] <= n)                      //放置完成
        {
            if (k == n)                     //如果已经放完了n个皇后
            {
                sum++;                      //处理次数,输出
                OutPut();
            }
            else                            //没有处理完,让k自加,处理下一个皇后
            {
                k++;
                x[k] = 0;
            }
        }                                   //x[k] > n,说明没有合适的位置了
        else
            k--;                            //回溯回去,回到第k-1步
    }
}

int main()
{
    clock_t start, finish;
    double duration;

    int nn;
    while (scanf_s("%d", &nn) != EOF)
    {
        n = nn;
        sum = 0;
        for (int i = 0; i <= n; ++i)
            x[i] = 0;
        BackTrack();
        //BackTrack1(1);
        printf("%d\n", sum);
    }

    return 0;
}

你可能感兴趣的:(回溯法)