回溯法

数据结构
Published

November 1, 2018

回溯法其实质就是一个带减枝DFS过程。 有时会遇到这样一类题目,它的问题可以分解,但是又不能得出明确的动态规划或是递归解法,又需要对整个问题进行遍历才可以得到答案时。 回溯问题一般有如下三种:

  1. 遍历求:有没有解
  2. 遍历解求:
    • 求所有解的个数
    • 求所有解的具体信息
  3. 遍历解求:最优解

1. 构造解空间树

我们以第一种问题为例,假设构造出了如下的解空间树 backtracking 我们需要对这颗二叉树进行遍历。

2. 找到约束条件

我们在遍历问题较小的上,比较容易。但是一但问题的规模大到了一种程度,就不可能完全遍历整颗二叉树。所以我们需要些约束条件去限制我们的遍历。这就是约束条件。

3. 带入回溯框架

boolean solve(Node n) {
    if n is a leaf node {
        if the leaf is a goal node, return true  # 减枝
        else return false
    } else {
        for each child c of n {
            if solve(c) succeeds, return true  # 递归
        }
        return false
    }
}

例:Cindy’s Puzzle

你有n个黑色棋子和相同数量的白色棋子,并且有一个2n+1的空间存放棋子。 其原始状态如图下: first 我们要将他移动为: first 我们规定的移动方式为: - 黑色棋子向右移动,白色棋子向左移动。 - 每个棋子只能向前移动一格或跳过一格。

移动例子: first

1. 构造解空间树

首先我们可以想到,每移动一步,这个问题就是一个新的状态。但是这个问题的图像难以构造,较为复杂。

2. 找到约束条件

此问题的约束条件很明显是是否无法移动,当无法移动时此节点就为叶节点,需要返回上一层。

3. 带入回溯框架

根据上面的回溯框架带入如下。

bool solvable(int board[]) {
    if (puzzleSolved(board)) { return true; }                   /* 是否结束 */   
    for (int position= 0; position < BOARD_SIZE; position++) {  /* DFS */
        if (canMove(board, position)) {                         /* 剪枝 */
            int *newBoard= makeMove(board, position);           /* 生成新状态 */
            if (solvable(newBoard)) {                           /* 递归 */
                printBoard(newBoard);                           /* 输出状态 */
                return true;
            }
        }
    }
    return false;
}

4.完整程序

#include <cstring>
#include <iostream>
#include <unistd.h>
using namespace std;
#define Dcout(x) cout << #x << ": " << (x) << endl

#define BOARD_SIZE 7
#define EMPTY -1
#define WHITE 2
#define BLACK 1

int FIRST_BOARD[BOARD_SIZE]= {BLACK, BLACK, BLACK, EMPTY, WHITE, WHITE, WHITE};
int FINAL_BOARD[BOARD_SIZE]= {WHITE, WHITE, WHITE, EMPTY, BLACK, BLACK, BLACK};

bool puzzleSolved(int board[]);
bool canMove(int board[], int position);
int *makeMove(int oldBoard[], int position);
void printBoard(int board[]);

bool solvable(int board[]) {
    if (puzzleSolved(board)) { return true; }
    for (int position= 0; position < BOARD_SIZE; position++) {
        if (canMove(board, position)) {
            int *newBoard= makeMove(board, position);
            if (solvable(newBoard)) {
                printBoard(newBoard);
                return true;
            }
        }
    }
    return false;
}

bool puzzleSolved(int board[]) {
    for (int i= 0; i < BOARD_SIZE; ++i) {
        if (board[i] != FINAL_BOARD[i]) { return false; }
    }
    return true;
}
void printBoard(int board[]) {
    for (int i= 0; i < BOARD_SIZE; ++i) {
        printf("%s ", [&board, &i]() -> const char * {
            switch (board[i]) {
            case EMPTY:
                return "_";
                break;
            case BLACK:
                return "●";
                break;
            case WHITE:
                return "○";
                break;
            default:
                break;
            }
            return "";
        }());
    }
    printf("\n");
}
int *makeMove(int oldBoard[], int position) {
    int *newboard= new int[BOARD_SIZE];
    memcpy(newboard, oldBoard, sizeof(int) * BOARD_SIZE);
    switch (oldBoard[position]) {
    case WHITE: {
        newboard[position]= EMPTY;
        if (newboard[position - 1] == EMPTY) {
            newboard[position - 1]= WHITE;
        } else {
            newboard[position - 2]= WHITE;
        }
    } break;
    case BLACK: {
        newboard[position]= EMPTY;
        if (newboard[position + 1] == EMPTY) {
            newboard[position + 1]= BLACK;
        } else {
            newboard[position + 2]= BLACK;
        }
    } break;

    default:
        break;
    }
    // delete[] oldBoard;
    return newboard;
}
bool canMove(int board[], int position) {
    switch (board[position]) {
    case EMPTY: {
        return false;
    } break;
    case BLACK: {
        if (board[position + 1] == EMPTY || board[position + 2] == EMPTY) {
            return true;
        } else {
            return false;
        }
    } break;
    case WHITE: {
        if (board[position - 1] == EMPTY || board[position - 2] == EMPTY) {
            return true;
        } else {
            return false;
        }
    } break;

    default:
        break;
    }
    return false;
}

int main(int argc, char const *argv[]) {
    int *first_board= new int[BOARD_SIZE];
    for (int i= 0; i < BOARD_SIZE; ++i) { first_board[i]= FIRST_BOARD[i]; }
    solvable(first_board);
    printBoard(FIRST_BOARD);
    return 0;
}

5.程序结果

  Backtracking ./backtrack
 ○ ○ _ ● ● ●
 ○ ○ ● _ ● ●
 ○ _ ● ○ ● ●
 _ ○ ● ○ ● ●
 ● ○ _ ○ ● ●
 ● ○ ● ○ _ ●
 ● ○ ● ○ ● _
 ● ○ ● _ ● ○
 ● _ ● ○ ● ○
_ ● ○ ● ○ ● ○
 _ ○ ● ○ ● ○
 ● ○ _ ○ ● ○
 ● ○ ● ○ _ ○
 ● ○ ● _ ○ ○
 ● _ ● ○ ○ ○
 ● ● _ ○ ○ ○

大家不要以为我这里出错误了。我是在程序返回的时候输出,因为这是一个DFS遍历,所以我的状态输出是先从深到浅的。

动画演示: first