回溯算法

回溯(backtracking)是一种系统地搜索问题解答的方法, 也叫试探法。为了实现回溯,首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。下一步是组织解空间以便它能被容易地搜索。典型的组织方法是图(迷宫问题)或树(N皇后问题)。一旦定义了解空间的 组织方法,这个空间即可按 深度优先的方法从开始节点进行搜索。

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

回溯方法的 步骤如下:
     1) 对给定的问题,定义问题的解空间。
     2) 确定状态空间树的结构。
     3) 用深度优先法搜索该空间,用约束方程和目标函数的界对状态空间树进行修剪,生成搜索树,得到问题的解。

回溯算法的一个有趣的特性是 在搜索执行的同时产生解空间。在搜索期间的任何时刻,仅保留从开始节点到当前节点的路径。因此,回溯算法的空间需求为O(从开始节点起最长路径的长度)。这个特性非常重要,因为解空间的大小通常是最长路径长度的指数或阶乘。所以如果要存储全部解空间的话,再多的空间也不够用。

这里我们用N皇后问题来进一步学习回溯算法。

如图,这是一个八皇后问题。如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。


算法思路:

首先我们分析一下问题的解,我们每取出一个皇后,放入一行,共有八种不同的放法,然后再放第二个皇后,同样如果不考虑规则,还是有八种放法。于是我们可以用一个八叉树来描述这个过程。从根节点开始,树每增加一层,便是多放一个皇后,直到第8层(根节点为0层),最后得到一个完全八叉树。这就是问题的解空间(状态空间树)。

接下来我们先对状态空间树进行修剪,然后才是使用深度优先搜索法进行遍历。

按照问题的题意,可以利用下述约束方程修剪状态空间树。

x[i] ≠ x[j]   1≤i≤8,1≤j≤8, i≠j ; (1)

| x[i] - x[j] | ≠ | i - j |     1≤i≤8,1≤j≤8, i≠j ; (2)

其中x[i] 表示第 i 行皇后的列位置。式(1)保证第 i 行的皇后和第 j 行的皇后不会在同一列;式(2)保证了两个皇后行号之差的绝对值不会等于列号之差的绝对值,因此它们不会在斜率为±1的同一斜线上。这两个关系式还保证i 和j 的取值范围应该为1到8。

使用上述约束方程对空间树进行修剪后,就可以使用DFS进行搜索了。

(网上看到个通俗易懂的说法,放上来帮助理解)

0. 将皇后编号1,2,3,4,5,6,7,8,并且排号为i的皇后,放在第i行

1. 将1号皇后放在第1行(1号皇后是肯定不会冲突的)

2. 1号皇后放好之后,放2号皇后(3号及以后的类似),从第2行第1列开始检测,不冲突就可以落子; 当本行没有合法的位置时,说明上一行皇后放的位置不好,则撤销上一行皇后的位置,并重新摆放(所谓回溯)

3. 如果8个皇后放好之后,按照上文逻辑该放8 + 1 号皇后了,但是已经放完了,所以将摆放结果打印。再重新摆放第8行(很多人不理解这里为什么还回溯

回溯发生的位置:

1. 皇后i的摆放位置,使得皇后i+1怎么放都不行,即这条路走不通了,回溯(重新摆放皇后i的位置,使得i+1可以摆放,就好像迷宫遇到墙了,要往回走)

2. 8个皇后都摆放完毕,打印出了摆放结果,回溯(就好像迷宫找到了一件宝物(一共需要集齐n件),你找到之后还要回头找另外几件,直到你集齐或者把所有的路都走了(DFS))。

这里贴上Java代码实现。

public class WolfQueen {  
    /** 
     * 一共有多少个皇后(此时设置为8皇后在8X8棋盘,可以修改此值来设置N皇后问题) 
     */  
    int max = 8;  
    /** 
     * 该数组保存结果,第一个皇后摆在array[0]列,第二个摆在array[1]列 
     */  
    int[] array = new int[max];  
  
    public static void main(String[] args) {  
        new WolfQueen().check(0);  
    }  
  
    /** 
     * n代表当前是第几个皇后 
     * @param n 
     * 皇后n在array[n]列 
     */  
    private void check(int n) {  
        //终止条件是最后一行已经摆完,由于每摆一步都会校验是否有冲突,所以只要最后一行摆完,说明已经得到了一个正确解  
        if (n == max) {  
            print();  
            return;  
        }  
        //从第一列开始放值,然后判断是否和本行本列本斜线有冲突,如果OK,就进入下一行的逻辑  
        for (int i = 0; i < max; i++) {  
            array[n] = i;  
            if (judge(n)) {  
                check(n + 1);  
            }  
        }  
    }  
  
    private boolean judge(int n) {  
        for (int i = 0; i < n; i++) {  
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {  
                return false;  
            }  
        }  
        return true;  
    }  
  
    private void print()  {  
        for (int i = 0; i < array.length; i++) {  
            System.out.print(array[i] + 1 + " ");  
        }  
        System.out.println();  
    }  
}  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值