用实例阐述回溯算法

news2024/12/28 19:07:42

目录

什么是回溯算法?

基本概念

示例认知

什么时候可以使用回溯算法?

回溯算法经典应用-无向图两节点之间路径

问题描述

回溯过程

代码示例

回溯算法经典应用-四皇后问题

问题描述

四皇后问题解决步骤

Step 1

Step 2

Step 3

Step 4

Step 5

Step 6

Step 7

Step 8

代码示例如何解决四皇后问题


什么是回溯算法?

基本概念

        回溯是一种用于寻找某些计算问题的全部(或部分)解的通用算法。

        回溯算法的核心思想是逐步构建候选解,如果发现当前构建的候选解不符合要求,就回溯到上一步撤销当前选择,重新选择其他方案,继续构建候选解。这个过程就像是在树形结构中向下逐步探索,到达某个节点时,如果发现无法继续向下搜索,就返回到上一层节点,继续从其他子节点开始探索。

        回溯算法通常使用递归实现,每一次递归都对一个子问题进行求解,直到求得最终解或无法继续求解后回溯到上一步。为了避免重复搜索同一个状态,回溯算法通常需要使用状态重置或剪枝等技巧对搜索空间进行优化。

        咳咳,说了这么多的理论,其实我的脑子都有点浆糊了,也许能动手的情况下还是少动口是一种好习惯。

        下面给大家举例一个生活中最最常见的例子来阐述下这个美丽动人的回溯算法。

示例认知

        下面这张图一眼看过去,它是一个有3条路的迷宫。你想知道它是否有出口(为了精确起见,使用你双眼观察法比回溯更有效)。这就是迷宫:

        这3条路究竟哪一条才是通往出口的路,或者是没有任何一条可以通往出口呢?我们使用回溯法,将每一条路都尝试走下,当所有的路都走完时,真相就会无所遁形!

        上图通过遍历所有的可行路径,当某路不通时返回此路的源点,然后继续搜索可用的路,直到找到正确的出口。

        我们的想法是,我们可以使用递归一步一步地构建解决方案;如果在这个过程中我们意识到这将不是一个有效的解决方案,那么我们停止计算该解决方案,并返回到前面的步骤(回溯)。在迷宫的情况下,当我们处于死胡同时,我们被迫走回头路,但在其它情况下,我们可能会在到达之前意识到,我们正在走向一个无效的(或不好的)解决方案。

什么时候可以使用回溯算法?

        当我们遇到下面的几种类型时,就可以使用回溯算法来解决

  • 决策问题--在这个问题中,我们寻找一个可行的解决方案。
  • 优化问题--在这个问题中,我们寻找最优解。
  • 枚举问题--在这个问题中,我们找到所有可行的解决方案。

        我们平时最为常见的就是解决组合问题、排列问题、迷宫问题、数独问题、四皇后问题、无向图节点路径问题。

        然而,它并不是一个优化的算法,因为它的核心使用了暴力方法。因此,如果时间复杂性受到限制,建议使用其他可能更适合这种情况的优化算法。

回溯算法经典应用-无向图两节点之间路径

问题描述

        下面是一张无向图,现在我们要计算出从A到E两个节点之前所有可行的路径。需要注意的是在计算有向图中两个顶点之间存在的路时,路径不包含循环,原因很简单,因为一个循环包含无限数量的路径。

        这个问题可以使用回溯来解决,即选择一条路径并开始在其上行走,检查它是否引导我们到达目的地顶点,然后计算路径并回溯到另一条路径。如果路径不指向目标顶点,则丢弃该路径。这种类型的图遍历称为回溯。

回溯过程

        上图的回溯过程如下所示(红色顶点为源顶点,淡蓝色顶点为目的顶点,其余为中间路径或丢弃路径)

为什么这个解决方案不适用于包含圈的图?下面的这张图为改造后包含循环的图。

        现在如果在C和B之间再增加一条边,就会形成一个循环(B->D->C->B)。因此,在循环的每个循环之后,长度路径将增加,这将被认为是不同的路径,并且由于循环,将有无限多的路径

代码示例

下面我们使用代码示例,逐步的为大家展示整个解决过程。

首先,我们需要定义出无向图中的节点,包括节点之间的关系。

static class Node {
    /**
     * 节点名称
     */
    String name;
    /**
     * 相邻节点集合
     */
    List<Node> adjacentDistance;


    public Node(String name) {
        this.name = name;
        adjacentDistance = new ArrayList<>();
    }

    /**
     * 添加相邻节点
     * @param node 相邻节点
     */
    public void addEdge(Node node) {
        this.adjacentDistance.add(node);
    }

    @Override
    public String toString() {
        return "Node{"+this.name+"}";
    }

}

下面我们把无向图中所有节点以及节点之间的关系全部构建出来。

public static List<Node> buildNodeList() {
    List<Node> nodes = Arrays.asList(
            new Node("A"),//0
            new Node("B"),//1
            new Node("C"),//2
            new Node("D"),//3
            new Node("E")//4
    );

    // 添加节点之间的关系
    nodes.get(0).addEdge(nodes.get(1));//A-B
    nodes.get(0).addEdge(nodes.get(2));//A-C
    nodes.get(0).addEdge(nodes.get(4));//A-E

    nodes.get(1).addEdge(nodes.get(3));//B-D
    nodes.get(1).addEdge(nodes.get(4));//B-E

    nodes.get(2).addEdge(nodes.get(0));//C-A
    nodes.get(2).addEdge(nodes.get(4));//C-E

    nodes.get(3).addEdge(nodes.get(2));//D-C

    return nodes;
}

下面我们就编写回溯算法,来计算A-E节点之间所有可行的路径。代码逻辑实现的核心就是使用递归遍历所有节点,然后找到符合条件的节点。

public static List<String> path(Node start, Node end, String path) {
    List<String> paths = new ArrayList<>();
    // 如果起始节点和终止节点相同,直接返回起始节点
    if (start == end) {
        paths.add(start.name);
        return paths;
    }
    // 将当前节点添加到路径上
    path += start.name + " -> ";
    // 遍历当前节点的相邻节点
    for (Node node : start.adjacentDistance) {
        // 如果相邻节点就是终止节点,将路径添加到结果列表
        if (node == end) {
            paths.add(path + end.name);
        }
        // 如果相邻节点不在路径上,递归遍历该相邻节点
        else if (!path.contains(node.name)) {
            paths.addAll(path(node, end, path));
        }
    }
    return paths;
}

最后使用main方法运行结果

public static void main(String[] args) {

    // 构造节点列表
    List<Node> nodes = buildNodeList();
    // 查找经过节点A和节点E的路径
    List<String> paths = path(nodes.get(0), nodes.get(4), "");
    // 打印所有经过节点B和节点C的路径
    for (String path : paths) {
        System.out.println(path);
    }

}

输出结果为:

A -> B -> D -> C -> E
A -> B -> E
A -> C -> E
A -> E

回溯算法经典应用-四皇后问题

问题描述

        什么是四皇后问题呢?如果你有一个4*4的棋盘,你需要在棋盘上放置4个皇后。皇后有能力攻击与之在同一行、同一列或同一对角线上的其它皇后。(后宫生存法则)so,每个皇后必须得有自己独立的势力范围,皇后的威严不容侵犯。同理N皇后的问题也是一样在N * N

        对于如何排放皇后,我们有两种可能的解决方案。它们如下所示。

四皇后问题解决步骤

        上面己经给出了具体的四皇后的摆放位置 ,但是我们只知道结果,不知道具体的执行过程,下面我们将会一步一步的演示下如何得出这个结果,让大家知其然,知其所以然。

Step 1

        我们先将将女王1号放在一个安全不被攻击的位置。因为棋盘上现在没有任何的女王,所以我们可以把女王1号放在任何地方。如下所示:

Step 2

现在,将女王2号放在一个不会受到攻击的位置。

Step 3

        好了,大家仔细看上面的图,你会发现我们不可能在不被另外两个女王攻击的情况下将女王3放在第三排。放在第三排的任何位置都会和其它的两个皇后在同一行,同一列,或对角线。这该怎么办呢?下面就该我们的回溯算法发挥其魔力的地方了。

        我们后退一步,检查是否可以更改前面的步骤以获得解决方案。所以,我们回溯并改变了女王2的位置,这样它就不会受到任何其他女王的攻击。如下图所示:

Step 4

经过了下面的调整,我们现在可以将皇后3放在一个不被攻击的位置,如下图所示:

Step 5

        大家仔细观察下上面的图,你会注意到我们不能在第四排的任何地方放置女王4而不受到其他三个女王的攻击。

        所以,继续回溯!回到过去!试着改变女王3的位置。但女王3已经别无选择了。我们不能把女王3放在其他任何地方而不被另外两个女王攻击。

        继续回溯!再次倒退,并试图改变女王2的位置。但很可惜,女王2也已经没有选择了。Oh, God !

        继续回溯!所以再次退回并改变女王1的位置。我们只有一样不断的一步一步的回溯,不断的做出选择,直到找到一种新的解决方法。

        所以我们现在调整女王1的位置,如下图所示:

Step 6

继续将女王 2号摆放到一个不被其它女王攻击的位置 ,如下图所示:

Step 7

我们继续将女王 3号摆放到一个不被其它女王攻击的位置 ,如下图所示:

Step 8

最后我们将女王 4号摆放到一个不被其它女王攻击的位置 ,成功就在眼前!如下图所示:

        OK,经过我们的不断的回溯不断的尝试,终于找到了解决方案。当然大家如果继续回溯,继续寻找,也可以寻找出另外一种解决方案,这里不在赘述,大家自行尝试即可。

        通过上面的演示推理,相信大家对于回溯算法己经有了一个较为深刻的认识了吧。

        下面我们通过使用代码的方式,来实现这个四皇后以及N皇后的问题。通过代码的运行,相信大家对于回溯算法一定可以更加熟悉和掌握它。

代码示例如何解决四皇后问题

我们首先分析下代码思路

  • 首先需要定义常量用来皇后在棋盘中的位置。在这里我们使用一个组数来保存皇后的位置,如position[0] =1 表示皇后在第1行的第2列
  • 然后我们不断的尝试将皇后放在 每一行,每一列的位置,判断是否满足条件,如果不满足则回溯到上一步,更改皇后的位置,直到找到满足条件的位置。
  • 判断皇后是否在同一行,同一列,对角线,具体的判断方法详见代码。

代码如下:
 

public class FourQueens {
    public static void main(String[] args) {
        solution();
    }

    //定义一个变量表示棋盘的大小为4,修改n的值即可改变棋盘的大小
    public static int n = 4;

    // position用于保存每一行中皇后的位置
    public static int[] position = new int[n];

    // 皇后问题求解函数
    public static void solution(){
        // 调用回溯算法函数,参数0表示从第0行开始进行回溯
        backtrack(0);
    }

    // 回溯算法函数
    public static void backtrack(int row) {
        // 递归结束条件
        if (row == n){
            // 如果所有的皇后放置完毕,输出结果
            printResult();
            return;
        }

        // 枚举第row行的所有可能位置
        for (int i = 0; i < n; i++) {

            // 如果当前位置可用
            if (checkPosition(row,i)){

                // 在当前位置放置皇后
                position[row] = i;

                // 继续向下一行进行回溯
                backtrack(row +1);
            }
        }
    }

    // 判断位置是否合法
    public static boolean checkPosition(int row ,int col) {

        // 遍历前面每行是否冲突
        for (int i = 0; i < row; i++) {

            // 检查是否在同一列或者是否在对角线上
            if (position[i] == col || Math.abs(i - row) == Math.abs(position[i] - col)){

                // 如果在同一列或者对角线上,返回false
                return false;
            }
        }

        // 如果当前位置与前面已放置皇后的位置不冲突,则返回true
        return true;
    }

    // 打印当前皇后的位置
    public static void printResult() {
        // 打印分割线
        System.out.println("-----------------------");
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (position[i] == j){
                    // 如果该位置有皇后,则输出Q
                    System.out.print("Q ");
                }else{
                    // 如果该位置没有皇后,则输出"."
                    System.out.print(". ");
                }
            }
            // 打印换行符
            System.out.println();
        }
    }
}

        如果大家对四皇后的分析己经理解的话,那么代码也是非常通俗易懂,就是使用迭代不断的遍历皇后在每一格中的位置,当发现某一格子不满足时在回溯到上一步重新摆放皇后的位置,直到找到满意的位置为止。

        好了,回溯算法就为大家介绍到这里,如果大家有什么想法,可以在留言区进行讨论。

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

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

相关文章

ICC2:polygon多边形操作

有时候想画一个环形或者不规则形状的metal shape/blockage,一遇到更新floorplan都要重新画,手工活如果能被脚本替代肯定是最优解,ICC2就提供这样的一组命令有效提高工作效率。 1.创建polygon 先看一下创建polygon的操作: create_poly_rect:提供一组或多组boundary坐标,工…

OpenCL编程指南-5.2数学函数

数学函数 OpenCL C实现了C99规范中描述的数学函数。使用这些数学函数的应用程序需要在代码中包含math.h头文件。这些数学函数可以作为OpenCL内核的内置函数。 对于表5-2和表5-3中的数学函数&#xff0c;我们将使用泛型类型名gentype指示这些函数可以取float、float2、float3、…

【AUTOSAR】CCP协议的代码分析与解读(二)----CCP协议格式和命令代码

CCP协议介绍 CCP的全称是CAN Calibration Protocol (CAN标定协议)&#xff0c;是基于CAN总线的ECU标定协议规范。CCP协议遵从CAN2.0通信规范&#xff0c;支持11位标准与29位扩展标识符。 CCP通信方式 CCP协议采用主从通信方式&#xff0c;如上图所示&#xff0c;其中从设备是…

Visual modflow Flex地下水数值模拟教程

详情点击链接&#xff1a;Visual modflow Flex地下水数值模拟及参数优化、抽水实验设计与处理、复杂的饱和/非饱和地下水流分析 一&#xff0c;地下水数值软件的操作流程、建模步骤和所需资料处理 [1] Visual MODFLOW Flex特征[2] Visual MODFLOW Flex软件界面及模块 [3] 地…

详细讲解接口自动化攻略

目录 前言&#xff1a; 为什么要做接口自动化 问题在哪里 全靠参数化 接口间参数传递 测试数据参数化 测试断言 测试管理 导入测试用例 接口执行顺序 使用测试数据集 测试参数配置 运行结果&测试报告 测试套件 前言&#xff1a; 接口自动化是提高测试效率和…

ThreadX在gcc下的移植

本文介绍ThreadX在arm-none-eabi-gcc编译器下的移植方法。 1、ThreadX介绍和源码获取 threadx的介绍和源码获取请参考之前的博文&#xff1a;ThreadX在mdk(AC5)中的移植。 2、准备工作 本篇主要介绍threadx在corex-m7上的移植&#xff0c;编译器使用arm-none-eabi-gcc。 在…

智能井盖传感器:以科技破解城市顽疾

在城市的道路网络中&#xff0c;井盖扮演着重要的角色&#xff0c;用于覆盖下方的管道和设施&#xff0c;然而&#xff0c;由于井盖的老化、损坏或被盗&#xff0c;常常会导致安全问题的发生&#xff0c;如路面塌陷、行人受伤等。井盖的状态监测和维护一直是城市管理者面临的挑…

2023年8月PMP考试,考生需要关注这些!

经PMI和中国国际人才交流基金会研究决定&#xff0c;中国大陆地区2023年第三期PMP认证考试定于8月19日举办。考生须认真阅读下文&#xff0c;知悉考试安排及注意事项&#xff0c;并遵守考试有关规定。 考生须认真阅读下文&#xff0c;知悉考试安排及注意事项&#xff0c;并遵守…

阿里推出了一个集成AI的数据库客户端,霸榜GitHub

背景 &#x1f4d6; 简介    Chat2DB 是一款有开源免费的多数据库客户端工具&#xff0c;支持windows、mac本地安装&#xff0c;也支持服务器端部署&#xff0c;web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力&#xff0c;能够将自然语…

基于vue的可拖拽设计的报表看板设计器

gitee上的不错项目&#xff0c;基于vue实现的可拖拽的看板设计器可以自由搭配颜色和图标&#xff0c;开发者可以只关注业务数据接口&#xff0c;前端不擅长的人员可以直接轻松上手。 1.可支持的元素 文字&#xff0c;边框&#xff0c;常见图表&#xff0c;柱形图&#xff0c;…

pgsql查询分页不对和属性转json的mapper映射

pgsql查询分页不对和属性转json的mapper映射 第一种&#xff1a; select * from xxx left join (selectarray_agg(jsonb_build_object(labelId,dl.label_id,labelName,dl.label_name)) as labelList,array_agg(dl.label_name) as labelNames,array_agg(dl.label_id) labelIdLi…

使用Python和Selenium自动化爬取 #【端午特别征文】 探索技术极致,未来因你出“粽” # 的投稿文章

文章目录 介绍&#xff1a;界面展示知识点详解导入相关模块设置Chrome驱动程序的路径创建ChromeDriver服务和启动Chrome浏览器发送GET请求获取网页内容模拟向下滚动加载更多内容获取完整的HTML内容关闭浏览器使用正则表达式提取文章信息构建数据表格和导出到Excel 扩展知识点代…

HTTP中的API是什么?

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言什么是API&#xf…

活动选择问题|贪婪算法-1

贪婪是一种算法范式&#xff0c;它一点一点地构建解决方案&#xff0c;总是选择下一个提供最明显和最直接好处的部分。贪婪算法用于优化问题。 如果优化问题具有以下属性&#xff0c;则可以使用贪婪解决该问题&#xff1a; 在每一步中&#xff0c;我们都可以做出一个目前看起来…

scrapy学习(scrapy项目学习)

创建scrapy项目 创建爬虫项目 scrapy startproject ss1_miove创建爬虫文件&#xff08;&#xff09; 命令格式&#xff1a;scrapy genspider <爬虫名称> <网站域名> scrapy genspider ss1_scrapy ssr1.scrape.centerscrapy框架的组成 spider文件夹&#xff1a…

ELK之Elasticsearch7.17.4安装(yum方式)和三节点集群配置

一、下载Elasticsearch7.17.4 的rpm包 下载地址&#xff1a; https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-17-4 二、建立elasticsearch的yum源 vim /etc/yum.repos.d/es.repo [elasticsearch] nameElasticsearch repository for 7.x packages bas…

IO多路复用之poll

文章目录 一&#xff1a;poll函数接口参数说明&#xff1a;返回结果&#xff1a; 二&#xff1a;poll的优缺点优点&#xff1a;缺点&#xff1a; 三&#xff1a;poll代码示例 一&#xff1a;poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, i…

Java设计模式之行为型-模板方法模式(UML类图+案例分析)

目录 一、概念 二、角色设计 三、代码实现 案例一 案例二 四、总结 一、概念 定义一个操作中的算法骨架&#xff0c;而将算法的一些步骤延迟到子类当中&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的特定步骤&#xff0c;即在一个抽象类中公开定义了执…

信驰达科技携手TI将CC2340推向更广市场领域

根据蓝牙技术联盟&#xff08;Bluetooth SIG&#xff09;2023年最新发布《2023年蓝牙市场最新资讯》&#xff0c;市调机构ABI Research预测数据显示&#xff0c;蓝牙市场在未来五年将会实现高增长&#xff0c;蓝牙设备年出货量将保持强劲增长势头&#xff0c;预计到2027年将达7…

python高频函数—CSV() 读写

Part.1 csv.reader()函数 csv.reader(csvfile, dialectexcel, **fmtparams) 返回一个 reader 对象&#xff0c;该对象将逐行遍历 csvfile。 一个简短的用法示例: >>> import csv>>> with open(eggs.csv, newline) as csvfile:... spamreader csv.r…