4.1 N皇后问题求解

595 阅读4分钟

这是我参与更文挑战的第16天,活动详情查看: 更文挑战

内容概要

从今天开始我们来学习OptaPlanner官网提供的各个案例,来强化我们所学的知识内容。其实还有很多的内容需要学习,但是到这一阶段,更需要的是强化实操能力,掌握使用方法后,我们再转过头来学习OptaPlanner的核心概念。

N Queues

在一个n大小的棋盘上放置n个皇后,使得没有两个皇后可以互相攻击。最常见的九宫格谜题是八皇后问题,n=8。

nQueensScreenshot.png

约束条件

  • 使用一个有n列和n行的棋盘。

  • 在棋盘上放置n个皇后。

  • 没有两个皇后可以互相攻击。一个皇后可以攻击同一水平线、垂直线或对角线上的任何其它皇后。

此案例大量使用四皇后的问题作为主要例子。

一个错误的解决方案:

partiallySolvedNQueens04Explained.png

上面的解决方案是错误的,因为皇后A1和B0可以互相攻击(皇后B0和D0也可以)。

一个正确的解决方案:

solvedNQueens04.png

所有的约束条件都得到了满足,所以解决方案是正确的。

请注意,大多数n皇后问题都有多个正确答案。我们将专注于为给定的n寻找单一的正确答案,而不是为给定的n寻找可能的正确答案的数量。

搜索空间大小

4queens   has   4 queens with a search space of    256.
8queens   has   8 queens with a search space of   10^7.
16queens  has  16 queens with a search space of  10^19.
32queens  has  32 queens with a search space of  10^48.
64queens  has  64 queens with a search space of 10^115.
256queens has 256 queens with a search space of 10^616.

OptaPlanner已被证明可以轻松地处理5000个皇后或更多。

模型图

我们看下如何建立对象模型:

NQueues.jpg

Column

列对象的属性只有一个index,就是其列的位置

public class Column {

    private int index;

    // ... getters and setters
}

Row

行对象的属性也只有一个index,就是其行的位置

public class Row {

    private int index;

    // ... getters and setters
}

Queue

皇后对象,包含了行、列属性,其中Row行是一个PlanningVariable规划变量,Column则是在组装问题数据时,初始化进去的,代表第几列的皇后,rowIndex默认是0。

public class Queen {

    private Column column;
    
    @PlanningVariable
    private Row row;
    
    public int getColumnIndex() {
        return column.getIndex();
    }

    public int getRowIndex() {
        if (row == null) {
            return Integer.MIN_VALUE;
        }
        return row.getIndex();
    }

    public int getAscendingDiagonalIndex() {...}
    public int getDescendingDiagonalIndex() {...}

    // ... getters and setters
}

约束规则详解

规则内容

从业务逻辑当中可以抽出三个规则,业务规则是:“一个皇后可以攻击同一水平线、垂直线或对角线上的任何其它皇后”。

  • 垂直线:每个Queue的的column列属性,是在问题初始化进入的,n个皇后对应的column0~(n-1),所以这个规则天然的不会违背,无需为其编写约束规则。
  • **水平线:**这个规则很好理解,即每个QueuerowIndex不相等即可。
  • 对角线:棋盘的对角线,分左上对角线和右下对角线,左上对角线的columnIndex-rowIndex都相等,右上对角线columnIndex+rowIndex都相等,如此只需要判断是否存在这两者相等的1Queue即可。

规则代码

如下便是DRL的约束规则实现,N皇后的问题只有硬约束。

// ############################################################################
// Hard constraints
// ############################################################################

rule "Horizontal conflict"
    when
        Queen($id : id, row != null, $i : rowIndex)
        Queen(id > $id, rowIndex == $i)
    then
        scoreHolder.addConstraintMatch(kcontext, -1);
end

// Vertical conflict is impossible due the model

rule "Ascending diagonal conflict"
    when
        Queen($id : id, row != null, $i : ascendingDiagonalIndex)
        Queen(id > $id, ascendingDiagonalIndex == $i)
    then
        scoreHolder.addConstraintMatch(kcontext, -1);
end

rule "Descending diagonal conflict"
    when
        Queen($id : id, row != null, $i : descendingDiagonalIndex)
        Queen(id > $id, descendingDiagonalIndex == $i)
    then
        scoreHolder.addConstraintMatch(kcontext, -1);
end

总结

这一篇章中的Drools非常的简单的,就是匹配条件,大家可以看一下上一篇章分享的Drools视频讲解,只需几分钟你就可以完全看明白这个约束规则的含义。

作业

请大家参考之前的代码,完成一个Queues的求解程序,给大家留一个问题:

❓ 为什么每条规则都加了一个 id > $id,其作用是什么呢?如果我不加这个条件的话,会带来什么影响呢?

结束语

下一篇章我们来学习一其它的例子。

创作不易,禁止未授权的转载。如果我的文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧💕💕💕💕💕💕