回溯法-n皇后

news2024/9/27 15:30:38

N皇后问题

问题定义

  • 棋盘: 一个n×n的网格。
  • 皇后: 一种特殊棋子,可以攻击同一行、同一列或两条对角线上的任何棋子。
  • 目标: 在棋盘上放置n个皇后,使得它们之间没有任何一个能够攻击到对方。

问题难点

  • 确保皇后之间不在同一行或列。
  • 避免皇后在对角线上相互攻击。

在这里插入图片描述

题目分析

问题概述

在n×n的棋盘上放置n个皇后,要求它们互不攻击。我们使用一个一维数组来存储皇后的坐标。

坐标存储

  • 使用一个长度为n的整型数组 path[] 来存储皇后的坐标。
  • 数组的下标表示行,数组的值表示列。

皇后坐标示例

  • 第0个皇后坐标:(0, path[0])
  • 第1个皇后坐标:(1, path[1])
  • 第i个皇后坐标:(i, path[i])

数组选择理由

  • 使用一维数组 path[] 可以体现深度优先搜索的思想。
  • 一维数组方便进行回溯操作。

主函数设计

编写 main 函数,输入棋盘大小 n 并初始化 path[] 数组。

int main() {
    int n;
    scanf("%d", &n);
    // 动态分配内存,存储皇后放置的路径
    int* path = (int*)malloc(sizeof(int) * n);
    // 注意:需要在使用完毕后释放内存
    printf("\n最终结果为:%d种", cout); // 此处应为算法执行后的输出结果
    free(path); // 释放分配的内存
    return 0;
}

函数规划

1. choose 函数

  • 从第0行开始,逐行选择放置新皇后的坐标。

2. judge 函数

  • 判断新皇后是否可以放置在特定坐标,确保不与已放置的皇后冲突。

3. prt 函数(可选)

  • 打印棋盘格局,传入 path[]n 即可。
    上述代码中的 cout 应替换为具体的算法执行后的输出结果变量,以及 malloc 后的内存需要在使用完毕后通过 free 函数释放。

函数设计概述

为了解决n皇后问题,我们需要设计以下三个核心函数:

1. choose 函数

  • 目的: 从棋盘的第一行开始,逐行放置皇后。
  • 过程: 选择每一行中可以放置皇后的列。

2. judge 函数

  • 目的: 判断新皇后是否可以放置在当前考虑的列上。
  • 条件: 确保新皇后不会与已放置的皇后在同一行、列或对角线上。

3. prt 函数(可选)

  • 目的: 打印出所有皇后的放置位置,展示棋盘的格局。
  • 输入: 路径数组 path[] 和棋盘大小 n

函数效果预览

完成所有函数设计后,将能够实现以下效果:
在这里插入图片描述

  • 逐行放置皇后,直到所有皇后都放置完毕或找到所有可能的解决方案。
  • 通过 judge 函数确保每次放置都符合规则。
  • 使用 prt 函数可视化解决方案,便于理解和验证。

prt 函数实现

首先,我们从最简单的打印函数 prt 开始。这个函数负责打印出棋盘上皇后的位置。

函数参数

  • int* path: 指向存储皇后列坐标的数组的指针。
  • int n: 棋盘的大小。

函数逻辑

  • 通过两个嵌套循环遍历棋盘的每一行和每一列。
  • 根据 path[i] 的值判断当前位置是否有皇后,并打印相应的字符。

代码实现

void prt(int* path, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (path[i] == j) // 如果当前位置有皇后
                printf("e "); // 打印 "e"
            else
                printf("0 "); // 否则打印 "0"
        }
        printf("\n");
    }
    printf("\n\n");
}

choose 函数实现

接下来是 choose 函数,它负责在棋盘上逐行放置皇后。

函数逻辑

  • 从第0行开始,逐行尝试放置皇后。
  • 遇到三种情况:
    1. 当前格子符合条件,记录皇后位置,进入下一行。
    2. 当前格子不符合条件,尝试同一行的下一个格子。
    3. 如果一行内所有格子都不符合条件,回溯至上一行,尝试下一个可能的位置。

首先,在第0行choose到坐标(0,0),发现符合条件,放置。

在这里插入图片描述
放置后触发情况1,进入下一行的choose,发现坐标(1,0)不可以,因为两个皇后在一列。

在这里插入图片描述
触发情况2,我们让第一行的皇后向右移动,可还是不行。
在这里插入图片描述
再次触发情况2,再次移动,发现可以,于是触发情况1,到下一行进行choose

在这里插入图片描述
但是这时候我们发现,下一行的皇后无论放到哪一列都与旧的皇后矛盾,不是同列就是对角线
在这里插入图片描述

回溯法的应用

回溯法的精髓

在n皇后问题中,如果某一步发现当前路径已经无法继续往下走,我们就需要回溯到上一步,这是回溯法的核心思想。


剪枝定义

剪枝是一种算法优化策略,它通过去除搜索树中不必要的或无用的分支来减少搜索的时间和空间复杂度。

应用场景

在回溯算法中,剪枝是一种常用的优化手段。

剪枝操作

  • 当确定某一种情况不可能出现解时,算法会直接返回,不再继续深入搜索。

剪枝的好处

  • 当第三行已经没有合适的位置放置皇后时,继续搜索第四行的位置是没有意义的。
  • 通过回溯,计算机避免了在错误路径上浪费时间,这是一种有效的剪枝策略。

避免无效搜索

  • 计算机不会继续考虑那些看似暂时符合规则,但实际上已经不符合条件子解空间的情况。
    在这里插入图片描述

我们回溯到上一行,那么上一行便进入情况2(这个格子不符合条件),皇后向右
在这里插入图片描述
发现可行,于是进入情况1,再次进入下一行搜索,搜索到一个位置。
在这里插入图片描述
继续进入下一行,发现再次全都不行,进入情况3,开始回溯。
在这里插入图片描述
如此循环往复,直到回溯到第0行向右移动,接着搜索,直到一个正确的结果出现了。
在这里插入图片描述


choose 函数设计

choose 函数是回溯算法的核心,负责在棋盘上逐行放置皇后。

函数参数

  • int* path: 已放置皇后的列坐标数组。
  • int line: 当前正在选择的行。
  • int n: 棋盘的总行数。

函数逻辑

  1. 基本情况: 如果所有行都放置了皇后,打印棋盘格局并返回。
  2. 逐列遍历: 对当前行的每一列进行遍历,尝试放置皇后。
  3. 条件判断: 使用judge函数判断新皇后是否与旧皇后冲突。
  • 流程
    • 遍历每一列,逐列尝试放置皇后。
    • 对于每一行,检查是否可以放置皇后:
      • 情况1:如果当前位置可行(即没有与其他皇后相互攻击),则记录皇后的位置 path[line] = column,然后递归调用 choose(path, line + 1, n) 继续放置下一行的皇后。
      • 情况2:如果当前位置不可行,皇后向右移动到下一列 column++
      • 情况3:如果所有列都尝试完毕,无法放置皇后,则回溯到上一行 return false

示例

  • 假设 choose(path, 1, 4) 成功放置了第一个皇后在 (1, 2) 的位置,即 path[1] = 2
  • 然后,递归调用 choose(path, 2, 4) 继续放置第二个皇后。

代码实现

int choose(int* path, int line, int n) {
    // 基本情况:所有皇后都放置完毕
    if (line == n) {
        prt(path, n);
        return 1; // 表示找到一个解决方案
    }
    
    // 遍历当前行的所有列
    for (int column = 0; column < n; column++) {
        // 如果当前列可以放置皇后
        if (judge(path, line, column)) {
            path[line] = column; // 记录皇后位置
            choose(path, line + 1, n); // 递归调用,选择下一行
        }
    }
    
    // 如果当前行无法放置皇后,则回溯
    return 0; // 表示当前路径不可行
}

注意事项

  • judge 函数是判断新皇后是否与已放置的皇后冲突的关键。
  • choose 函数通过递归实现,每找到一个解决方案就打印一次棋盘格局。

判断函数 judge

judge 函数用于判断新放置的皇后是否与已放置的皇后冲突。

函数目的

  • 验证新皇后的位置 (line, column) 是否与已放置的皇后位置冲突。

参数说明

  • path[]:存储已放置皇后的坐标数组。
  • line:新皇后的行号。
  • column:新皇后的列号。

皇后坐标表示

  • 第0个皇后坐标:(0, path[0])
  • 第1个皇后坐标:(1, path[1])
  • 第i个皇后坐标:(i, path[i])

判断逻辑

  1. 遍历已放置的皇后:检查每个已放置的皇后与新皇后的位置关系。
  2. 冲突判断
    • 同行冲突:由于新皇后是在前 line-1 行放置的,所以无需检查同行。
    • 列冲突:检查新皇后的列号 column 是否与已放置的皇后列号相同。
    • 对角线冲突:通过一种巧妙的方法判断对角线是否冲突。

函数返回

  • 如果所有已放置的皇后与新皇后均不冲突,返回 true
  • 如果存在任何冲突,返回 false

冲突判断方法

  • 对角线冲突:通过计算两个皇后的行和列的差值是否相等来判断对角线冲突。

示例

  • 假设 path = [0, 2, 4],新皇后的位置为 (3, 3)
  • 遍历 path 中的每个皇后,检查与 (3, 3) 是否冲突。
  • 如果没有冲突,judge 函数返回 true
  • 如果存在冲突,judge 函数返回 false

对于(x1,y1)和(x2,y2),若\left | y1 - y2 \right | == \left | x1 - x2 \right |
,则二点处于对角线上。可以分类讨论证明。至于绝对值,利用#include<math.h>中的abs函数,abs(k) == \left | k \right |

int judge(int* path, int line, int column) {
    // 遍历已放置的皇后,检查是否与新皇后冲突
    for (int i = 0; i < line; i++) {
        // 如果新皇后与已放置的皇后在同一列上
        if (path[i] == column) {
            // 返回false,表示存在列冲突
            return false;
        }
        // 如果新皇后与已放置的皇后在同一对角线上
        if (abs(path[i] - column) == abs(i - line)) {
            // 返回false,表示存在对角线冲突
            return false;
        }
        // 如果当前皇后与新皇后不冲突,则继续检查下一个皇后
        else {
            continue;
        }
    }
    // 如果遍历完所有已放置的皇后后没有发现冲突
    // 则返回true,表示新皇后可以放置在指定位置
    return true;
}

完整代码:

#include<stdio.h> // 引入标准输入输出库
#include<math.h>  // 引入数学库,用于使用绝对值函数abs
#include<malloc.h> // 引入内存分配库,用于动态内存分配

// 计数器,用来存储最终有多少种放置皇后的方案
int cout = 0;

// 打印函数,用于打印棋盘上皇后的放置结果
void prt(int* path, int n) {
    for (int i = 0; i < n; i++) { // 遍历每一行
        for (int j = 0; j < n; j++) { // 遍历每一列
            if (path[i] == j) // 如果当前位置是皇后,则打印"e"
                printf("e ");
            else // 否则打印"0"
                printf("0 ");
        }
        printf("\n"); // 每行结束后换行
    }
    printf("\n\n"); // 打印完一个结果后空两行
}

// 判断函数,用于判断新皇后是否与已有皇后冲突
int judge(int* path, int line, int column) {
    for (int i = 0; i < line; i++) { // 遍历已放置的皇后
        // 如果新皇后与老皇后在同一列或对角线上,则返回false
        if ((path[i] == column) || ((abs(path[i] - column)) == (abs(i - line))))
            return false;
    }
    // 如果遍历完所有已放置的皇后都没有冲突,则返回true
    return true;
}

// 选择函数,用于递归选择皇后的位置
int choose(int* path, int line, int n) {
    if (line == n) { // 如果已经放置完所有皇后,则打印结果并计数
        prt(path, n);
        cout++;
        return 0;
    }
    for (int column = 0; column < n; column++) { // 尝试在当前行的每一列放置皇后
        if (judge(path, line, column)) { // 如果当前位置不冲突
            path[line] = column; // 记录皇后的位置
            choose(path, line + 1, n); // 递归放置下一行的皇后
        }
    }
    return 0;
}

// 主函数,程序的入口点
int main() {
    int n; // 棋盘大小
    scanf("%d", &n); // 从标准输入读取棋盘大小
    // 动态分配内存,大小为n个整数,用来存储皇后的放置路径
    int* path = (int*)malloc(sizeof(int) * n);
    // 从第0行开始选择皇后的位置,棋盘大小为n
    choose(path, 0, n);
    // 打印最终的皇后放置方案数量
    printf("\n最终结果为:%d种", cout);
    return 0; // 程序结束
}

总结

题目思路总结

  • 起始点: 从棋盘的第一行开始放置皇后。
  • 逐行放置: 按顺序逐行放置皇后,并进行冲突检查。
  • 冲突与回溯: 如果当前行无法放置皇后,回溯至上一行重新尝试。
  • 合法解的发现: 当所有行都成功放置了皇后,即找到了一个合法解。
  • 记录方式: 使用数组记录每行皇后的列位置。

具体实现策略

  • 从第一行开始,逐行尝试放置皇后。
  • 在放置每个皇后时,检查与已放置皇后的冲突情况。
  • 若发生冲突,回溯至上一行重新放置。
  • 所有行放置完毕,记录一个解决方案。

回溯法与剪枝总结

  • 回溯法: 一种通过试错枚举所有可能解的算法。
  • 剪枝: 在回溯过程中,通过特定条件避免无效搜索,优化搜索树。
  • 优化目的: 减少搜索的时间和空间消耗,提升算法效率。
  • 剪枝重要性: 作为回溯法中的重要优化手段,显著提高算法性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2088729.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

英伟达:相同的剧本

财报超预期&#xff0c;盘后却大跌8%&#xff0c;最近好公司好像都犯了这种病。 英伟达在美股财报季压轴登场&#xff0c; 营收净利那可都是三位数的增长&#xff0c;再创新高。 都说炒股看的的是未来&#xff0c;在英伟达这贯彻地很彻底&#xff0c;业绩爆表只能算及格&#…

【操作系统】有A、B和C三个作业同时到达,执行时间分别为4,3,6,且在系统中以单道方式运行,则可以获得最短的平均周转时间的执行顺序为()。

目录 题目分析答案类似题 题目 有A、B和C三个作业同时到达&#xff0c;执行时间分别为4,3,6&#xff0c;且在系统中以单道方式运行&#xff0c;则可以获得最短的平均周转时间的执行顺序为&#xff08;&#xff09;。 分析 周转时间&#xff1a;程序从进入系统到完成的时间总…

nodejs基于微信小程序的书籍销售系统论文源码调试讲解

2 开发环境与相关技术 2.1 NODEJS技术 Nodejs语言是目前使用率最高的一个语言类程序&#xff0c;并且他的代码还是开源的&#xff0c;任何的软件开发者都可以进行使用&#xff0c;目前已经在人类计算机编程语言发展史上产生了深远影响。所以Nodejs语言是很成熟的&#xff0c;将…

平价运动耳机品牌推荐有哪些?五大爆款推荐,小白购前必看

对于很多人来说&#xff0c;运动可能是为了减肥&#xff0c;但是对我而言&#xff0c;运动从来不是为了身材焦虑&#xff0c;而是为了享受挥洒汗水后的畅快淋漓&#xff0c;尤其在天气渐暖的时节&#xff0c;约上三五好友&#xff0c;一起在夕阳下奔跑&#xff0c;在微风中骑行…

在线流程图制作指南:轻松绘制高质量流程图的方法!

流程图作为一种过程诊断工具&#xff0c;广泛应用于工作和生活中。无论是软件程序的算法流程图、请假审批流程图、产品工艺流程图&#xff0c;还是医院就诊流程等&#xff0c;流程图都能直观地描述具体的工作步骤&#xff0c;帮助决策者识别问题并制定解决方案。本文将通过即时…

800道软件测试面试题与答案+pdf+在线版

2024年软件测试行情不行&#xff0c;今年很多人想着金九银十换一个好工作&#xff0c;几次面试总感觉很多东西明明记住了&#xff0c;突然又忘了。 在整理资料的时候&#xff0c;被我发现一个宝藏内容&#xff01;&#xff01;⚠ 如何准备好面试&#xff0c;大家都头疼我总结…

C++语法基础(二)

C复合类型 结构体 1. C的结构&#xff0c;定义结构体类型的变量时&#xff0c;可以省略struct关键字 2. 可以定义成员函数&#xff0c;在结构体中的成员函数内部可以直接访问本结构体的成员&#xff0c;无需通过“.”或“->” 联合 1. C的联合,定义联合体类型的变…

聊聊Promise,catch和then的关系,rejected状态如何在then链中”透传“直到被处理

Promise在前端开发中用的很多了&#xff0c;感觉好像很熟了&#xff0c;但真的有些细节追究起来好像又有点是是而非。 今天聊聊Promise中的then和catch&#xff0c;以下面这个代码片段为例&#xff0c;如果能正确说出打印的内容&#xff0c;说明已经掌握了&#xff0c;后面的内…

Linux教程六:linux系统目录介绍

一、Linux系统目录介绍 1、关于目录的命令行讲解 需要确保自己使用了root账号登陆 cd / # 进入根目录 cd 目录路径 #进入到指定目录中去 #路径有绝对路径和相对路径&#xff0c;在Linux中&#xff0c;绝对路径以/开头ll #列举当前目录下所有文件和文件夹 &#xff08;ls -l…

活动报道 | 盘古信息亮相东莞中小数转供需对接会(滨海片区),深度剖析典型案例

为积极响应国家关于加快中小企业数字化转型的号召&#xff0c;推动东莞市中小企业数字化进程&#xff0c;8月29日&#xff0c;由东莞市工业和信息化局主办&#xff0c;长安镇经济发展局承办&#xff0c;东莞市软件行业协会协办的东莞市中小企业数字化转型城市试点供需对接会&am…

Vue3中的defineExpose的认识

文章目录 defineExpose子组件父组件&#xff1a;总结&#xff1a; defineExpose 使用 <script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例&#xff0c;** 不会 **暴露任何在 <script setup> 中声明的绑定。 可以通过 def…

BH1750光照传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.工作原理&#xff1a;结构框图 三、程序设计 main.c文件 bh1750.h文件 bh1750.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 BH1750是一款数字型光照强度传感器&#xff0c;能够获取周围环境的光照强度。内置16bitAD转…

通帆科技“液氢微型发电站”:点亮氢能产业新征程

新质生产力的蓬勃发展正以磅礴之力推动产业的转型升级&#xff0c;氢能产业作为新质生产力的璀璨之星&#xff0c;成为新能源领域的关键增长极。 8 月 28 日&#xff0c;通帆新能源科技&#xff08;山东&#xff09;有限公司精心研发的 500kw “液氢微型发电站”产品成功下线交…

【C/C++】C语言中的内存分布

在C语言中&#xff0c;内存分布主要可以分为以下几个区域&#xff1a; 栈&#xff08;Stack&#xff09;&#xff1a;由编译器自动分配和释放&#xff0c;存放函数的参数值、局部变量的值等。 堆&#xff08;Heap&#xff09;&#xff1a;一般由程序员分配和释放&#xff0c;若…

电容应用原理

电容器是电子电路中不可或缺的元件&#xff0c;其在电路中承担的任务繁多&#xff0c;既可以用作储能元件&#xff0c;也能用于滤波、旁路和去耦。 电容的基本原理 电容的基本工作原理可以理解为电荷的存储和释放。电容器由两块金属板和夹在中间的绝缘介质构成&#xff0c;当…

Unity 中使用SQLite数据库

文章目录 0.参考文章1.Presentation —— 介绍2.&#xff08;SQLite4Unity3d&#xff09;Unity中直接使用SQLite的插件3.创建数据库4.创建表5.Navicat Premium&#xff08;数据库可视化&#xff09;6.增删改查6.1 增6.2 删6.3 改6.4 查 0.参考文章 https://blog.csdn.net/Chin…

结合系统架构设计的非功能性需求开发一套文化用品商城系统

案例 阅读以下关于软件系统设计的叙述&#xff0c;在答题纸上回答问题 1 至问题 3。 【题目】 某文化产业集团委托软件公司开发一套文化用品商城系统&#xff0c;业务涉及文化用品销售、定制、竞拍和点评等板块&#xff0c;以提升商城的信息化建设水平。该软件公司组织项目组完…

2024最新盘点:这12款plm项目管理系统值得推荐!

本文将盘点主流的plm项目管理系统&#xff0c;为企业选型提供参考 。 高效的plm项目管理系统是确保工程顺利进行、按时交付以及控制成本的关键&#xff0c;据美国建筑行业研究院的研究数据表明&#xff0c;实施高效项目管理的建筑企业&#xff0c;能够将项目成本降低 5%-10%。我…

1.7 离散频率

1.7 离散频率 离散时间和采样率 模拟到数字转换器 (ADC) 对连续时间信号进行采样以生成离散时间样本。对于数字信号处理器来说&#xff0c;该信号仅存储在内存中作为一系列数字。因此&#xff0c;采样率 F S F_S FS​ 的知识是数字域中信号处理的关键。 对于时间而言&#…

光敏电阻传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.光敏电阻传感器介绍 2.原理图 三、程序设计 main.c文件 ldr.h文件 ldr.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 光敏电阻器是利用半导体的光电导效应制成的一种电阻值随入射光的强弱而改变的电阻器&#xff0c;又称为光…