使用位运算优化 N 皇后问题

使用,运算,优化,皇后,问题 · 浏览次数 : 183

小编点评

**排版说明** * 使用格式化输出,例如:double、int、String等。 * 使用缩进,例如:int colLimit。 * 使用清晰的标题和注释。 * 使用排版,例如:格式化输出、缩进等。 * 使用简单的排版格式,例如:格式化输出、缩进等。 * 使用清晰的标题和注释。

正文

使用位运算优化 N 皇后问题

作者:Grey

原文地址:

博客园:使用位运算优化 N 皇后问题

CSDN:使用位运算优化 N 皇后问题

问题描述

N 皇后问题是指在 n * n 的棋盘上要摆 n 个皇后,
要求:任何两个皇后不同行,不同列也不在同一条斜线上,
求给一个整数 n ,返回 n 皇后的摆法数。

题目链接:牛客-N皇后问题

常规解法

由于皇后不能共行,所以使用一个一维数组就可以表示整个过程

int[] records = new int[n];

其中 records[i] = j 表示:皇后安排在 i 行的 j 号位置。

在遍历过程中,可以首先给第 0 行安排一个皇后,然后到下一行安排一个皇后,一直安排到最后一行,如果可以顺利走到最后一行,则记录一种有效的摆法。

整体流程代码如下

    public static int num1(int n) {
        if (n < 1 || n == 2 || n == 3) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }
        int[] records = new int[n];
        return process1(0, records, n);
    }

    public static int process1(int i, int[] records, int n) {
        if (i == n) {
            return 1;
        }
        int ways = 0;
        for (int j = 0; j < n; j++) {
            if (isValid(records, i, j)) {
                records[i] = j;
                ways += process1(i + 1, records, n);
            }
        }
        return ways;
    }

对于上述代码进行说明,首先,我们可以过滤掉一些基本的场景,比如

n < 1 || n == 2 || n == 3

这种情况下,怎么摆都不可能满足条件。

对于n == 1的情况,只能有一种摆法。

接下来就是int process1(int i, int[] records, int n)这个递归函数,这个递归函数的递归含义是:在棋盘中,0 ~ i - 1 行都已经安排好皇后了,要开始安排第 i 行的皇后了,安排完 i 行的皇后以后,继续安排到最后,可以得到的有效填充方案有多少?

base case 为

i == n

即:已经安排完最后一行(i == n - 1)的皇后了,说明之前的决策没问题,可以得到了有效的一种方案,返回 1。

接下来是普遍情况


        int ways = 0;
        for (int j = 0; j < n; j++) {
            if (isValid(records, i, j)) {
                records[i] = j;
                ways += process1(i + 1, records, n);
            }
        }

枚举第 i 行每个位置填皇后的情况,然后去下一行继续安排皇后,但是有个前提,给 i 行的第 j 号位置分配皇后的时候,需要首先校验下 i 行能否填 j 号皇后,即 isValid() 方法要解决的问题。

boolean isValid(int[] records, int i, int j);

这个方法表示: 0 ~ i - 1 行都安排好皇后的情况下,第 i 行的 j 号位置放皇后,是否合法。

这就涉及到一个简单的问题:已知二维矩阵中 [x,y][甲,乙] 两个点,如何判断其位置关系是否合法?

不合法的情况有两个,满足下述任何一种情况,两个点位置关系就不合法。

情况一:共列的情况,即 y == 乙

image

情况二: 共对角线的情况,即 (甲-x) 的绝对值等于 (乙-y) 的绝对值相等,即 |甲 - x| == |乙 - y|

image

由于我们每行只安排一个皇后,所以不需要判断两个点是否共行,在我们的算法模型下,这两个点天然不共行。

完整代码如下

public static boolean isValid(int[] records, int i, int j) {
    for (int s = 0; s < i; s++) {
        if (records[s] == j || Math.abs(records[s] - j) == Math.abs(i - s)) {
            return false;
        }
    }
    return true;
}

这个解法的时间时间复杂度是 O(N^N)

位运算优化解

以上是 N 皇后的常规解法,接下来是使用位运算来优化 N 皇后算法。

注:位运算只是减少了常数项的时间,整体时间复杂度还是 O(N^N),且位运算优化解目前支持处理 32 皇后问题。

可以通过以下例子熟悉一下位运算的用法,比如,打印一个 32 位整数的二进制形式(不用 Java 现成的 API),应该如何实现?

    // 打印一个32位整数的二进制形式
    public static void printBinary(int num) {
        for (int i = 31; i >= 0; i--) {
            System.out.print((num & (1 << i)) == 0 ? "0" : "1");
        }
        System.out.println();
    }

思路就是用这个整数的二进制的每一位和对应位置上的是 1 ,其余位置是 0 的数进行与(&)运算,如果与完以后结果是 0 ,则该位一定是 0 ,否则该位是 1。

再来一个示例,如何获取一个数二进制最右侧的 1?

比如:

7 这个变量,二进制为 00000000000000000000000000000111,最右侧的 1 就是 00000000000000000000000000000001,所以 7 最右侧的 1 的值是 1;

22 这个变量,二进制为 00000000000000000000000000010110,最右侧的 1 就是 00000000000000000000000000000010,所以 22 最右侧的 1 的值是 2。

结论是,一个数 num,其最右侧的 1 的值是 num & (~num + 1) 或者 num & (-num)

有了上述铺垫,

接下来是 N 皇后问题的优化点,在常规方法中,使用 records[] 数组来记录皇后的位置信息,如果用位运算优化解,可以使用一个 32 位整型变量的二进制状态信息来存储皇后的位置信息,

比如,常规解法中 records[x] == 5,表示某一行的 5 号位置有一个皇后,如果用 32 位状态信息来表示,则为

image

以上信息用一个变量pos来表示,这个变量就用于记录哪个列位置中填了皇后,

还要设置另外三个变量

// 皇后的列限制是什么
int colLim;
// 皇后的左下对角线限制是什么
int leftDiaLim;
// 皇后的右下对角线限制是什么
int rightDiaLim;

比如,5 皇后问题,初始状态下,pos == 0 ,然后在第 4 个位置填了一个皇后,即 pos 二进制的第 4 个位置变为 1,那么其对应的三个变量的变化如下。

image

接下来定义一个变量

int limit = n == 32 ? -1 : (1 << n) - 1;

由于用的是 32 位整型变量,所以最大支持 32 皇后问题,

如果是小于 32 的皇后问题,比如 13 皇后问题,那么 limit 会使用其二进制的最右侧的 13 个位置,会将最右侧的 13 个位置设置为 1,即 1 << n - 1

如果正好是 32 皇后问题,则为 -1,即二进制的 32 个位置都是 1。

有了colLim,leftDiaLim,rightDiaLim,limit这四个变量,就可以决策出下一个可以摆放皇后的位置,

int pos = limit & (~(colLim | leftDiaLim | rightDiaLim)) 得到的结果中,pos的二进制状态上是 1 的,就是可以放皇后的位置。

如果colLim == limit,说明每一列都安排好了皇后,直接返回一种有效解法。

得到 pos 变量后,从右往左依次枚举其二进制上为 1 的位置,在该位置放上皇后,把该位置列限制(即:colLim变量),左对角线限制(即:leftDiaLim变量),右对角线限制(即:rightDiaLim变量)进行对应的调整,然后跑后续的递归过程收集所有的有效布局次数。

完整代码如下

    //  请不要超过32皇后问题
    public static int num2(int n) {
        if (n < 1 || n > 32) {
            return 0;
        }
        // 如果你是13皇后问题,limit 最右13个1,其他都是0
        int limit = n == 32 ? -1 : (1 << n) - 1;
        return process2(limit, 0, 0, 0);
    }

    // 7皇后问题
    // limit : 0....0 1 1 1 1 1 1 1
    // 之前皇后的列影响:colLim
    // 之前皇后的左下对角线影响:leftDiaLim
    // 之前皇后的右下对角线影响:rightDiaLim
    public static int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
        if (colLim == limit) {
            return 1;
        }
        // pos中所有是1的位置,是你可以去尝试皇后的位置
        int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
        int mostRightOne;
        int res = 0;
        while (pos != 0) {
            // 得到 pos 最右侧的 1
            mostRightOne = pos & (~pos + 1);
            // 在该位置放上皇后,即:把该位置设置为 0
            pos = pos - mostRightOne;
            res += process2(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >>> 1);
        }
        return res;
    }

更多

算法和数据结构笔记

参考资料

算法和数据结构体系班-左程云

与使用位运算优化 N 皇后问题相似的内容:

使用位运算优化 N 皇后问题

使用位运算优化 N 皇后问题 作者:Grey 原文地址: 博客园:使用位运算优化 N 皇后问题 CSDN:使用位运算优化 N 皇后问题 问题描述 N 皇后问题是指在 n * n 的棋盘上要摆 n 个皇后, 要求:任何两个皇后不同行,不同列也不在同一条斜线上, 求给一个整数 n ,返回 n 皇后的摆法

5.8 汇编语言:汇编高效除法运算

通常情况下计算除法会使用`div/idiv`这两条指令,该指令分别用于计算无符号和有符号除法运算,但除法运算所需要耗费的时间非常多,大概需要比乘法运算多消耗10倍的CPU时钟,在Debug模式下,除法运算不会被优化,但Release模式下,除法运算指令会被特定的算法经过优化后转化为为乘法,这样就可以提高除法运算的效率。

带约束条件的运筹规划问题求解(模拟退火算法实现)

使用模拟退火解带约束条件的运筹优化问题,可线性也可非线性。

[转帖]【SOP】最佳实践之 TiDB 业务写变慢分析

https://zhuanlan.zhihu.com/p/647831844 前言 在日常业务使用或运维管理 TiDB 的过程中,每个开发人员或数据库管理员都或多或少遇到过 SQL 变慢的问题。这类问题大部分情况下都具有一定的规律可循,通过经验的积累可以快速的定位和优化。但是有些情况下不一定很好排查

[转帖]《Linux性能优化实战》笔记(24)—— 动态追踪 DTrace

使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术。动态追踪技术通过探针机制来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码就获得丰富的信息,帮你分析、定位想要排查的问题。 以往,在排查和调试性能问题时,我们往往需要先为应用程

使用 TensorRT C++ API 调用GPU加速部署 YOLOv10 实现 500FPS 推理速度——快到飞起!!

NVIDIA ® TensorRT ™ 是一款用于高性能深度学习推理的 SDK,包含深度学习推理优化器和运行时,可为推理应用程序提供低延迟和高吞吐量。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销...

[转帖]《Linux性能优化实战》笔记(四)—— CPU 使用率

一、 节拍率与CPU时间 前一篇说到,Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。 为了维护 CPU 时间,Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jif

13.1 使用DirectX9绘图引擎

DirectX 9 是由微软开发的一组多媒体应用程序接口API,用于创建和运行基于Windows平台的多媒体应用程序,尤其是游戏。它是DirectX系列中的一个版本,于2002年发布,是DirectX系列中的一个重要版本,DirectX 9在其发布时引入了许多新的功能和性能优化,成为当时PC游戏开发...

[转帖]针对容器的nginx优化

针对容器的nginx优化 本篇文章介绍了 Nginx 在容器内使用遇到的CPU核数获取问题以及对应的解决方法。 回顾上篇文章:TCP 半连接队列和全连接队列 背景 容器技术越来越普遍,很多公司已经将容器技术作为基础架构的一部分,容器中可以运行任何软件,包括 Web Server、Applicatio

容器开发运维人员的 Linux 操作机配置优化建议

"工欲善其事必先利其器", 作为一个PAAS平台架构师, 容器相关技术(docker, k8s等)是必不可少的. 本文简单介绍下我自己的Linux操作机配置. 提升工作效率, 提高使用体验. :heart::heart::heart: :exclamation: 注意: 本文以CentOS 7.6