《数据结构与算法之美》30——回溯算法

什么是回溯算法

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

八皇后问题

我们学习了什么是回溯算法,听起来很简单,但具体怎么使用呢?下面我们通过一个例子来说明回溯算法的用法。

八皇后问题:有一个8X8的棋盘,希望往里放8个棋子(皇后),每个棋子所在的行、列、对角线都不能有另一个棋子,找到所有满足这种要求的放棋方式。

解题思路:把这个问题划分成8个阶段,依次将8个棋子放到第一行、第二行、第三行....第八行。在放置的过程中,不停地检查当前的方法,是否满足要求。如果满足,则跳到下一行继续放置棋子;如果不满足,那就换一种方法,继续尝试。

代码实现如下:

public class Solution {
    int[] result = new int[8]; // 全局或成员变量,下标表示行,值表示queen存储在哪一列
    public void Cal8Queens (int row) { // 调用方式:Cal8Queens(0);
        if (row == 8) { // 8个棋子都放置好了,打印结果
            PrintQueens (result);
            return; // 8行棋子都放好了,已经没法再往下递归了,所以就return
        }
        for (int column = 0; column < 8; ++column) { // 每一行都有8中放法
            if (IsOK (row, column)) { // 有些放法不满足要求
                result[row] = column; // 第row行的棋子放到了Column列
                Cal8Queens (row + 1); // 考察下一行
            }
        }
    }

    private bool IsOK (int row, int column) { // 判断row行column列放置是否合适
        int leftup = column - 1, rightup = column + 1;
        for (int i = row - 1; i >= 0; --i) { // 逐行往上考察每一行
            if (result[i] == column) return false; // 第i行的column列有棋子吗?
            if (leftup >= 0) { // 考察左上对角线:第i行leftup列有棋子吗?
                if (result[i] == leftup) return false;
            }
            if (rightup < 8) { // 考察右上对角线:第i行rightup列有棋子吗?
                if (result[i] == rightup) return false;
            }
            --leftup;
            ++rightup;
        }
        return true;
    }

    private void PrintQueens (int[] result) { // 打印出一个二维矩阵
        for (int row = 0; row < 8; ++row) {
            for (int column = 0; column < 8; ++column) {
                if (result[row] == column) Console.Write ("Q ");
                else Console.Write ("* ");
            }
            Console.WriteLine ();
        }
        Console.WriteLine();
    }
}

总结

回溯算法的思想非常简单,大部分情况下,都是用来解决广义的搜索问题:从一组可能的解中,选择出一个满足要求的解。

回溯算法非常适合用递归来实现,在实现的过程中,剪枝操作是提高回溯效率的一种技巧。利用剪枝,并不需要穷举搜索所有的情况,从而提高搜索效率。

参考资料

原文地址:https://www.cnblogs.com/liang24/p/13401922.html