写给妹妹的编程札记 2 - 穷举: 初识剪枝


        在上次的文章《穷举 - 从循环到递归》中, 介绍了从直接的循环到搜索的穷举实现。 稍有经验的程序员都能看到上文中的八皇后问题效率大大的低, 有很多改进空间。


        从上图很容易看到, 我们穷举所有情况, 然后再一一判断是否是合法的八皇后解。 总共需要穷举8^8 = 16777216个情况。 实际结果, 我们已经知道,只有92个合法的解。很容易, 我们会有一个想法: 能不能早点避开不必要的方案? 如果在最开始的几行已经违反八皇后需要满足的条件,就不要继续下去了?


        这个想法完全可行, 并且是我们编写一个高效的搜索程序必须考虑的问题。 比如,当r[0] = 0 时,对r[1] 又赋值0( r[1] = 0), 这个时候其实第一行和第二行的皇后都被安排在第一列, 已经违反八皇后问题的要求。 这个时候, 完全没有必要继续穷举下去。 如上图中的子树A。 这是一个规模“相当可观”的子树。 提前终止, 可以避免穷举8^6 = 262144个方案。 很给力! 上图还有一个例子, 当r[6] = 0时, r[7] = 0没有必要继续搜索了。 这个剪枝可以避免考虑8^0 = 1个方案。从上面两个剪枝可以看出, ”剪枝进行得越早越好“


        下面, 我们来看看实现这个剪枝的代码, 其实只是在原来代码的基础上做简单的修改, 在递归之前终止,如果该方案已经不合法的话。

void search(int* r, int N, int step) {
    if (step == N) {
        // 输出布局
        for (int i = 0; i < N; i++) {
            for(int j = 0; j < N; j++) if(r[i] == j) printf("Q"); else printf(".");
            printf("\n");
        }
        printf("\n");

		return;
    }
    for(r[step] = 0; r[step] < N; r[step]++) {
        bool isValid = true;
        // 1. 验证 任意两个皇后没有处于同一行。 由于我们穷举的时候已经把ri表示为第i行的皇后位置, 保证了每行有且仅有一个皇后
        // 2. 验证 当前行跟前step行中的任意皇后没有处于同一列。
        for (int i = 0; i < step; i++)
            if (r[i] == r[step]) isValid = false;
        // 3. 验证 任意两个皇后没有处于同一条斜线上
        for (int i = 0; i < step; i++)
            if (r[i] + i == r[step] + step) isValid = false;
        for (int i = 0; i < step; i++)
            if (r[i] - i == r[step] - step) isValid = false;

        if (!isValid) {
            // 如果已经不合法, 说明当前r[step]取值不可行, 终止这个穷举
            continue;
        }else{
            // 当前为止,一切正常, 继续穷举
            search(r, N, step + 1);
        }
    }
}

int main() {

    const int N = 8;
    int r[N];

    search(r, N, 0);
}


        到这里为止, 我们已经熟悉了简单的搜索程序应该是怎样的, 也了解了剪枝能极大地优化搜索的效率。 但, 怎么根据不同的场景, 实现高效的搜索还是需要下很大的功夫的。 小到如八皇后这样的简单搜索, 大到围棋程序整体架构上的大同小异, 但在搜索上引入的技术还有很多, 这些林林总总的技术都是为了使得搜索更高效, 使得最简单朴素的穷举想法更高效。 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值