算法与数据结构-回溯法及八皇后问题求解

序言

在笔试中会遇到一些可以用常用算法就能快速解决的问题,如果对这些算法不熟悉的话,在笔试中是比较吃亏的。

这篇文章学习回溯法及用回溯法解决的八皇后问题。

1. 回溯法

  • 基本思想

    • 有时我们要得到问题的解,先从其中某一种情况进行试探,在试探过程中,一旦发现原来的选择是错误的,那么就退回一步重新选择,然后继续向前试探,反复这样的过程直到求出问题的解。

    • 回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。

  • 算法步骤

    • 明确问题的解空间:描述解的形式,定义一个解空间,它包含问题的所有解。

    • 构造约束函数/剪枝函数:约束函数是根据题意给出的,通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。

    • 通过DFS思想完成回溯

2. 八皇后问题

  • 问题描述

    • 八皇后问题是在8*8的棋盘上放置8枚皇后,使得棋盘中每个纵向、横向、左上至右下斜向、右上至左下斜向均只有一枚皇后。即各个皇后之间不再同一行、不在同一列也不在对角线。
  • 问题求解

    • 采用回溯法求解。从上至下依次在每一行放置皇后,进行搜索,若在某一行的任意一列放置皇后均不能满足要求,则不再向下搜索,而进行回溯,回溯至有其他列可放置皇后的一行,再向下搜索,直到搜索至最后一行,找到可行解,输出。

    • 步骤

    1. 构造n × n的数组。数组下标表示皇后所在行、数组元素表示皇后所在列。
    
    2. 回溯过程:假设前n-1行的皇后已经按照规则排列好,那么可以使用回溯法逐个试出第n行皇后的合法位置。
       所有皇后的初始位置都是第0列,那么逐个尝试就是从0试到N-1,如果达到N,仍未找到合法位置,
       回退一行,且该行的皇后的位置加1,继续尝试。如果目前处于第0行,还要再回退,说明此问题已再无解。
    
    3. 剪枝函数/约束函数:如果当前行的皇后的位置还是在0到N-1的合法范围内,那么首先要判断该行的皇后是否与前几
       行的皇后互相冲突,如果冲突,该行的皇后的位置加1,继续尝试;如果不冲突,判断下一行的皇后。
       如果已经是最后一行,说明已经找到一个解,输出这个解。
    
    4. 然后最后一行的皇后的位置加1,继续尝试下一个解。

代码(C,递归方式)

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

#define bool char
#define true 1
#define false 0

#define N 8

void solve(int row);
bool valid(int row, int column);

void output(void);      //输出函数
int matrix[N][N] = {{0}};       //棋盘,声明为全局变量简化valid函数参数
int counter = 0;        //方案计数

/* 主函数 */
int main()
{
    BackTrack(1);
    return 0;
}

/* 递归函数实现回溯算法 */
void BackTrack(int row)
{
    int j;
    for (j = 0; j < N; j++)
    {
        matrix[row - 1][j] = 1;         //先在第一列放置皇后
        if (valid(row - 1, j))
        {
            if (row == N) output();     //有解,将一直递归至最后一行并输出
            else BackTrack(row + 1);
        }
        matrix[row - 1][j] = 0;         //否则,看当前行下一列
    }
}

/* 剪枝函数:验证第row行第column列放置皇后是否可行 */
bool valid(int row, int column)
{
    if (row == 0) return true;          //第一行,无条件输出
    //不在同一列
    int i = 0, j = 0;
    for (; i < row; i++)
    {
        if (matrix[i][column] == 1)
            return false;
    }
    //不在对角线:主对角线和副对角线
    i = row - 1;                        //当前行是row - 1行,上一行row - 2
    j = column - (row - i);         //对角线:行差 = 列差,即row - 1 - i = column - j
    while (i >= 0 && j >= 0)
    {
        if (matrix[i][j] == 1)
            return false;
        i--;
        j--;
    }
    i = row - 1;
    j = column + (row - i);         //对角线:行差 = 列差,即row - 1 - i = j - column
    while (i >= 0 && j <= 7)
    {
        if (matrix[i][j] == 1)
            return false;
        i--;
        j++;
    }
    //否则,可行
    return true;
}

/* 可行方案输出 */
void output(void)
{
    int i, j;
    counter++;
    printf("solution %dth: \n", counter);
    for (i = 0; i < N; i++)
    {
        for (j = 0; j < N; j++)
        {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

Acknowledgements:

http://zephiruswt.blog.51cto.com/5193151/895797

http://blog.csdn.net/daniel_ustc/article/details/17040315

http://blog.csdn.net/sinat_33052719/article/details/51447531

2017.09.12

    原文作者:回溯法
    原文地址: https://blog.csdn.net/baidu_35692628/article/details/77894529
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞