图论10-哈密尔顿回路和哈密尔顿路径+状态压缩+记忆化搜索

文章目录

  • 1 哈密尔顿回路
  • 2 哈密尔顿回路算法实现
    • 2.1 常规回溯算法
    • 2.2 引入变量记录剩余未访问的节点数量
  • 3 哈密尔顿路径问题
  • 4 状态压缩
    • 4.1 查看第i位是否为1
    • 4.2 设置第i位是为1或者0
    • 4.3 小结
    • 4.4 状态压缩在哈密尔顿问题中的应用
  • 5 记忆化搜索
    • 5.1 记忆化搜索与递推区别
    • 5.2 记忆化搜索的实现 - 力扣980

1 哈密尔顿回路

求解哈密尔顿回路

如何求解一个图是否存在哈密尔顿回路呢?

一个最直观的想法就是暴力求解。暴力求解的思路也很简单:我们遍历图的每一个顶点 v,然后从顶点 v 出发,看是否能够找到一条哈密尔顿回路。

暴力求解的代价同求解全排列问题是等价的,其时间复杂度为 O ( N ! ) O(N!) O(N!),N 为图的顶点的个数。

那么除了暴力求解哈密尔顿回路问题,是否存在更好的算法?

很遗憾我们只能在暴力破解的基础上,尽量去做到更多的优化,譬如回溯剪枝,记忆化搜索等,但是,还没有找到一种多项式级别的算法来解决哈密尔顿问题。

通常,这类问题也被称为 NP(Non-deterministic Polynomial)难问题。

综上所述,求解哈密尔顿回路,我们可以采用回溯+剪枝的思想来进行求解。

在这里插入图片描述

2 哈密尔顿回路算法实现

2.1 常规回溯算法

package Chapter07_Hamilton_Loop_And_Path.Hamilton_Loop;import java.util.ArrayList;
import java.util.Collections;public class HamiltonLoop {private Graph G;private boolean[] visited;private int[] pre;private int end; //用来表示最后一个被遍历的顶点public HamiltonLoop(Graph G){this.G = G;visited = new boolean[G.V()];pre = new int[G.V()];end = -1;dfs(0, 0);}private boolean dfs(int v, int parent){visited[v] = true;pre[v] = parent;for(int w: G.adj(v))if(!visited[w]){if(dfs(w, v))return true;}else if(w == 0 && allVisited()){ //如果回到起始点0并且所有的点都被访问过了,则找到了哈密尔回路end = v;return true;}// 回溯visited[v] = false;return false;}public ArrayList<Integer> result(){ArrayList<Integer> res = new ArrayList<>();if(end == -1) return res;int cur = end;while(cur != 0){res.add(cur);cur = pre[cur];//上一个节点}res.add(0);Collections.reverse(res);return res;}private boolean allVisited(){for(int v = 0; v < G.V(); v ++)if(!visited[v]) return false;return true;}public static void main(String[] args){Graph g = new Graph("g9.txt");HamiltonLoop hl = new HamiltonLoop(g);System.out.println(hl.result());Graph g2 = new Graph("g10.txt");HamiltonLoop hl2 = new HamiltonLoop(g2);System.out.println(hl2.result());}
}

在这里插入图片描述

2.2 引入变量记录剩余未访问的节点数量

package Chapter07_Hamilton_Loop_And_Path.Hamilton_Loop;import java.util.ArrayList;
import java.util.Collections;public class HamiltonLoop_Optimization {private Graph G;private boolean[] visited;private int[] pre;private int end;public HamiltonLoop_Optimization(Graph G){this.G = G;visited = new boolean[G.V()];pre = new int[G.V()];end = -1;//dfs的过程记录剩余未访问的节点的数量dfs(0, 0, G.V());}private boolean dfs(int v, int parent, int left){visited[v] = true;pre[v] = parent;left --;//出口:如果未访问的节点是0,并且当前节点和初始节点有边if(left == 0 && G.hasEdge(v, 0)){end = v;return true;}for(int w: G.adj(v))if(!visited[w]){if(dfs(w, v, left)) return true;}
//            else if(w == 0 && left == 0){
//                end = v;
//                return true;
//            }visited[v] = false;return false;}public ArrayList<Integer> result(){ArrayList<Integer> res = new ArrayList<>();if(end == -1) return res;int cur = end;while(cur != 0){res.add(cur);cur = pre[cur];}res.add(0);Collections.reverse(res);return res;}public static void main(String[] args){Graph g = new Graph("g9.txt");HamiltonLoop hl = new HamiltonLoop(g);System.out.println(hl.result());Graph g2 = new Graph("g10.txt");HamiltonLoop hl2 = new HamiltonLoop(g2);System.out.println(hl2.result());}
}

3 哈密尔顿路径问题

根据出发位置的不同,路径也会不一样。但是算法实现还是一致的,只需要修改构造函数,传入起点位置。

package Chapter07_Hamilton_Loop_And_Path.Hamilton_Loop;import java.util.ArrayList;
import java.util.Collections;public class HamiltonPath {private Graph G;private int s;private boolean[] visited;private int[] pre;private int end;public HamiltonPath(Graph G, int s){this.G = G;this.s = s;visited = new boolean[G.V()];pre = new int[G.V()];end = -1;dfs(s, s, G.V());}private boolean dfs(int v, int parent, int left){visited[v] = true;pre[v] = parent;left --;if(left == 0){end = v;return true;}for(int w: G.adj(v))if(!visited[w]){if(dfs(w, v, left)) return true;}visited[v] = false;return false;}public ArrayList<Integer> result(){ArrayList<Integer> res = new ArrayList<>();if(end == -1) return res;int cur = end;while(cur != s){res.add(cur);cur = pre[cur];}res.add(s);Collections.reverse(res);return res;}public static void main(String[] args){Graph g = new Graph("g10.txt");HamiltonPath hp = new HamiltonPath(g, 0);System.out.println(hp.result());HamiltonPath hp2 = new HamiltonPath(g, 1);System.out.println(hp2.result());}
}

在这里插入图片描述

4 状态压缩

在这里插入图片描述

4.1 查看第i位是否为1

在这里插入图片描述

4.2 设置第i位是为1或者0

在这里插入图片描述

4.3 小结

在这里插入图片描述

4.4 状态压缩在哈密尔顿问题中的应用

在我们的代码中,一直都使用布尔型的 visited 数组来记录图中的每一个顶点是否有被遍历过。

在算法面试中,对于像哈密尔顿回路/路径这样的 NP 难问题,通常都会有输入限制,一般情况下,求解问题中给定的图不会超过 30 个顶点。

这样,在算法笔试/面试中,我们就可以对 visited 数组进行状态压缩来优化算法类执行的效率。我们知道一个 int 型的数字有 32 位,每一位不是 1 就是 0,这正好对应了布尔型的 true 和 false。

所以,我们可以将 visited 数组简化成一个数字,该数字的每一个比特位用来表示 visited 数组的每一个 true 或 false 值。

来看一下我们的 HamiltonLoop 中 dfs 的代码:

    public HamiltonLoop_StateCompression(Graph G){this.G = G;pre = new int[G.V()];end = -1;int visited = 0; //用一个数的比特位来表示是否被访问过dfs(visited, 0, 0, G.V());}private boolean dfs(int visited, int v, int parent, int left){visited += (1 << v); //第v位置设置为1pre[v] = parent;left --;if(left == 0 && G.hasEdge(v, 0)){end = v;return true;}for(int w: G.adj(v))if((visited & (1 << w)) == 0){ //看数字的第w个位置是否为 0if(dfs(visited, w, v, left)) return true;}visited -= (1 << v); //回溯,第v位置恢复为0return false;}

5 记忆化搜索

记忆化搜索是动态规划的一种实现方式。

在记忆化搜索中,当算法需要计算某个子问题的结果时,它首先检查是否已经计算过该问题。如果已经计算过,则直接返回已经存储的结果;否则,计算该问题,并将结果存储下来以备将来使用。

举个例子,比如「斐波那契数列」的定义是: f ( 0 ) = 0 , f ( 1 ) = 1 , f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(0) = 0, f(1) = 1, f(n) = f(n - 1) + f(n - 2) f(0)=0,f(1)=1,f(n)=f(n1)+f(n2)。如果我们使用递归算法求解第 n n n 个斐波那契数,则对应的递推过程如下:

在这里插入图片描述
从图中可以看出:如果使用普通递归算法,想要计算 f ( 5 ) f(5) f(5),需要先计算 f ( 3 ) f(3) f(3) f ( 4 ) f(4) f(4),而在计算 f ( 4 ) f(4) f(4) 时还需要计算 f ( 3 ) f(3) f(3)。这样 f ( 3 ) f(3) f(3) 就进行了多次计算,同理 f ( 0 ) f(0) f(0) f ( 1 ) f(1) f(1) f ( 2 ) f(2) f(2) 都进行了多次计算,从而导致了重复计算问题。

为了避免重复计算,在递归的同时,我们可以使用一个缓存(数组或哈希表)来保存已经求解过的 f ( k ) f(k) f(k) 的结果。如上图所示,当递归调用用到 f ( k ) f(k) f(k) 时,先查看一下之前是否已经计算过结果,如果已经计算过,则直接从缓存中取值返回,而不用再递推下去,这样就避免了重复计算问题。

5.1 记忆化搜索与递推区别

  • 记忆化搜索:「自顶向下」的解决问题,采用自然的递归方式编写过程,在过程中会保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。

  • 优点:代码清晰易懂,可以有效的处理一些复杂的状态转移方程。有些状态转移方程是非常复杂的,使用记忆化搜索可以将复杂的状态转移方程拆分成多个子问题,通过递归调用来解决。

  • 缺点:可能会因为递归深度过大而导致栈溢出问题。

  • 递推:「自底向上」的解决问题,采用循环的方式编写过程,在过程中通过保存每个子问题的解(通常保存在一个数组或哈希表中)来避免重复计算。

  • 优点:避免了深度过大问题,不存在栈溢出问题。计算顺序比较明确,易于实现。

  • 缺点:无法处理一些复杂的状态转移方程。有些状态转移方程非常复杂,如果使用递推方法来计算,就会导致代码实现变得非常困难。

  • 记忆化搜索解题步骤

我们在使用记忆化搜索解决问题的时候,其基本步骤如下:

  1. 写出问题的动态规划「状态」和「状态转移方程」。 定义一个缓存(数组或哈希表),用于保存子问题的解。
  2. 定义一个递归函数,用于解决问题。在递归函数中,首先检查缓存中是否已经存在需要计算的结果,如果存在则直接返回结果,否则进行计算,并将结果存储到缓存中,再返回结果。
  3. 在主函数中,调用递归函数并返回结果。

5.2 记忆化搜索的实现 - 力扣980

 memo = new int[1 << (R * C)][R * C];
  • 第一个维度是顶点数量的2倍,因为一个位置有两种可能——访问过/未访问过。

    • 假设顶点数量为1,则第一个维度未2: 0, 1.
      假设顶点数量为2,则第一个维度未4: 00, 01, 10, 11.
  • 第二个维度表示顶点数量,记录当前状态对应顶点是否被访问过。

在这里插入图片描述

package Chapter07_HamiltonLoop_And_StateCompression_And_MemoizationSearch.Memoization_Search;import java.util.Arrays;// Leetcode 980
class Solution {private int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};private int R, C;private int[][] grid;private int start, end;private int[][] memo;public int uniquePathsIII(int[][] grid) {this.grid = grid;R = grid.length;C = grid[0].length;int left = R * C;memo = new int[1 << (R * C)][R * C];for(int i = 0; i < memo.length; i ++)Arrays.fill(memo[i], -1);for(int i = 0; i < R; i ++)for(int j = 0; j < C; j ++)if(grid[i][j] == 1){start = i * C + j;grid[i][j] = 0;}else if(grid[i][j] == 2){end = i * C + j;grid[i][j] = 0;}else if(grid[i][j] == -1)left --;int visited = 0;return dfs(visited, start, left);}private int dfs(int visited, int v, int left){if(memo[visited][v] != -1) return memo[visited][v];visited += (1 << v);left --;if(v == end && left == 0){visited -= (1 << v);memo[visited][v] = 1;return 1;}int x = v / C, y = v % C;int res = 0;for(int d = 0; d < 4; d ++){int nextx = x + dirs[d][0], nexty = y + dirs[d][1];int next = nextx * C + nexty;if(inArea(nextx, nexty) && grid[nextx][nexty] == 0 && (visited & (1 << next)) == 0)res += dfs(visited, next, left);}visited -= (1 << v);memo[visited][v] = res;return res;}private boolean inArea(int x, int y){return x >= 0 && x < R && y >= 0 && y < C;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机的空调智能控制器的设计

**单片机设计介绍&#xff0c;基于单片机的空调智能控制器的设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的空调智能控制器需要具备输入输出端口、定时器、计数器等模块&#xff0c;以便对空调进行精确控制。下…

补坑:Java的字符串String类(3):再谈String

不太熟悉字符串的可以看看这两篇文章 补坑&#xff1a;Java的字符串String类&#xff08;1&#xff09;-CSDN博客 补坑&#xff1a;Java的字符串String类&#xff08;2&#xff09;&#xff1a;一些OJ题目-CSDN博客 字符串创建对象 public static void main(String[] args) …

ES6学习

let和const命名 let基本用法-块级作用域 在es6中可以使用let声明变量&#xff0c;用法类似于var ⚠️ let声明的变量&#xff0c;只在let命令所在的代码块内有效 {let a 10;var b 20; } console.log(a); //a is not defined console.log(b); //20不存在变量提升 var命令…

【11】使用透视投影建立一个3D空间的测试

核心操作&#xff1a; 1.proj view model 这三个矩阵 glm::mat4 mvp m_Proj * m_View * model; m_Shader->Bind(); m_Shader->SetUniformMat4f("u_MVP", mvp);着色器里面就&#xff1a; proj:投影矩阵&#xff0c;可以选择正交投影&#xff0c;或者透视投影…

javaSE学习笔记(二)数组,类,对象,成员变量,匿名对象,构造方法,static,final,封装,继承,多态

目录 三、面向对象 1.概述 面向过程与面向对象 面向对象编程特点 面向对象三个基本特征 2.数组 数组定义格式 数组的初始化 动态初始化 静态初始化 数组的内存分配 Java中的内存分配 数组的内存分配 数组的角标 数组的基本操作 二维数组&#xff08;实际开发几乎…

【网络编程】网络层——IP协议

文章目录 基本概念路径选择主机和路由器 IP协议格式分片与组装网段划分IP地址的数量限制私网IP地址和公网IP地址深入认识局域网路由 基本概念 TCP作为传输层控制协议&#xff0c;其保证的是数据传输的可靠性和传输效率&#xff0c;但TCP提供的仅仅是数据传输的策略&#xff0c…

通过商品ID获取到京东商品详情页面数据,京东商品详情官方开放平台API接口,京东APP详情接口,可以拿到sku价格,销售价演示案例

淘宝SKU详情接口是指&#xff0c;获取指定商品的SKU的详细信息。SKU是指提供不同的商品参数组合的一个机制&#xff0c;通过不同的SKU来标识商品的不同组合形式&#xff0c;如颜色、尺寸等。SKU详情接口可以帮助开发者获取指定商品的SKU列表&#xff0c;以及每个SKU的属性、库存…

多目标优化框架

随着模型越来越复杂&#xff0c;优化目标越来越多&#xff0c;传统算法都慢慢地无法胜任复杂优化任务&#xff0c;更为智能的优化方法也就应运而生了。其中有一类是进化优化算法&#xff0c;这类算法的思想来源是自然界的“优胜劣汰”法则&#xff0c;通过不停地保留好的个体最…

ubuntu16.04 交叉编译 mbedtls

在为客户交叉编译项目时需要依赖 mbedtls&#xff0c; 客户的机器是 arm64 的 ubuntu 16.04&#xff0c; 交叉编译过程中遇到几个问题。 首先&#xff0c; mbedtls 需要依赖 python, 在 cmake 的过程中&#xff0c; 如果不是使用系统默认的 cmake 可能会导致&#xff0c;mbedt…

Matlab的多项式留数与极点的计算

Matlab的多项式留数与极点的计算 以下面的多项式为例&#xff1a; 运算代码&#xff1a; clc clear closesyms p % 定义多项式 Zp(5*p^571*p^370*p)/(2*p^635*p^4117*p^236); % 提取分子与分母 [I,D]numden(Zp); Idouble(coeffs(I,p,"All"));%分子 Ddouble(coeffs…

【数据结构】单链表OJ题(一)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 前言一、移除链表元素二、寻找链表中间结点三、输出链表倒数第k个结点四、反转单链表五…

STM32MPU6050角度的读取(STM32驱动MPU6050)

注&#xff1a;文末附STM32驱动MPU6050代码工程链接&#xff0c;需要的读者请自取。 一、MPU6050介绍 MPU6050是一款集成了三轴陀螺仪和三轴加速度计的传感器芯片&#xff0c;由英国飞利浦半导体&#xff08;现为恩智浦半导体&#xff09;公司生产。它通过电子接口&#xff08…

conda环境中pytorch1.2.0版本安装包安装一直失败解决办法!!!

conda环境中pytorch1.2.0版本安装包安装一直失败解决办法 cuda10.0以及cudnn7.4现在以及安装完成&#xff0c;就差torch的安装了&#xff0c;现在torch我要装的是1.2.0版本的&#xff0c;安装包以及下载好了&#xff0c;安装包都是在这个网站里下载的&#xff08;点此进入&…

Postgres的级数生成函数generate_series应用

Postgres的级数生成函数generate_series应用 引用&#xff1a;http://postgres.cn/docs/12/functions-srf.html 函数文档 函数 参数类型 返回类型 描述 generate_series(start, stop) int、bigint或者numeric setof int、setof bigint或者setof numeric&#xff08;与参数类型相…

【React入门实战】实现Todo代办

文章目录 效果功能-状态管理相关接口定义相关方法定义 UIinput输入框&#xff1a;回车添加todo标题列表列表项Main 总体代码 非常简单入门的react-todo练习&#xff0c;代码写的很小白。 效果 技术栈&#xff1a;react-typeScript 数据分为代办Todo和已办完Done&#xff0c;可…

php实现钉钉机器人推送消息和图片内容(完整版)

先来看下实现效果: 代码如下: function send_dingtalk_markdown($webhook , $title , $message "", $atMobiles [], $atUserIds []) {$data ["msgtype" > "markdown","markdown" > ["title" > $title,&quo…

一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

目录 1解题思路&#xff1a; 2代码如下&#xff1a; 3运行结果&#xff1a; 4总结&#xff1a; 5介绍&#xff1a; 1解题思路&#xff1a; 利用循环&#xff08;穷举法&#xff09;来 对 所 需要的数 进行确定 2代码如下&#xff1a; #include <stdio.h>int main() …

Java自学第9课:JSP基础及内置对象

目录&#xff1a; 目录 1 JSP基础知识架构 1 指令标识 1 Page命令 2 Including指令 3 taglib指令 2 脚本标识 1 JSP表达式 2 声明标识 3 代码片段 3 JSP注释 1 HTML注释 2 带有JSP表达式的注释 3 隐藏注释 4 动态注释 4 动作标识 1 包含文件标识 2 请求转发标…

原文远知行COO张力加盟逐际动力 自动驾驶进入视觉时代?

11月7日&#xff0c;通用足式机器人公司逐际动力LimX Dynamics官宣了两位核心成员的加入。原文远知行COO张力出任逐际动力联合创始人兼COO&#xff0c;香港大学长聘副教授潘佳博士为逐际动力首席科学家。 根据介绍&#xff0c;两位核心成员的加入&#xff0c;证明一家以技术驱…

Stable Diffusion webui 源码调试(三)

Stable Diffusion webui 源码调试&#xff08;三&#xff09; 个人模型主页&#xff1a;LibLibai stable-diffusion-webui 版本&#xff1a;v1.4.1 内容更新随机&#xff0c;看心情调试代码~ shared 变量 shared变量&#xff0c;简直是一锅大杂烩&#xff0c;shared变量存放…