算法导论实战(三)(算法导论习题第二十四章)

news2024/11/17 17:46:26

🌈 个人主页:十二月的猫-CSDN博客
🔥 系列专栏: 🏀算法启示录

💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 

目录

前言

第二十四章 

24.1-3

24.1-4

24.2-4

24.3-2 

24.3-3

24.3-6

24.3-8

24.3-10 

总结


前言

算法导论的知识点学习将持续性更新在算法启示录_十二月的猫的博客-CSDN博客,欢迎大家订阅呀(反正是免费的哦~~)

实战篇也将在专栏上持续更新,主要目的是强化对理论的学习(题目来源:山东大学孔凡玉老师推荐)

第二十四章 

24.1-3

问题描述

假设给定G=(V,E)是一带权重且没有权重为负值的环路的有向图,对于所有的结点𝑣∈𝑉,从源结点s到结点v之间的最短路径中,包含边的条数的最大值为m。请对算法BELLMAN-FORD进行简单修改,可以让其在m+1遍松弛操作之后终止,即使m不是事先知道的一个数值。

问题分析:

如果一个图所有节点到源点s的距离包含的边的条数的最大值为m,那么意味算法在进行m轮松弛后,整个图的所有节点都已经得到最短路径。此时不再需要继续执行m+1轮松弛

问题求解
在BELLMAN-FORD算法的2-4行的执行记录下松弛前各点的最短路径长度,如果某一次松弛循环结束时所有v.d的值跟本次循环开始时v.d的值相比不发生改变时,此次循环即为第m+1次。 

INITALIZE-SINGLE-SOURCE(G,s)
for i=1 to |G.V|-1
  for each v in G.V
     save v.d to T
  for each edge(u,v) in G.E
     RELAX(u,v,w)
  for each v in G.V
    if v.d != T[v].d
     continue //没有点的值被更新,说明所有点都取到最短路径,则停止
  for each edge(u,v) in G.E
    if v.d>u.d+w(u,v)
      return FALSE
  return TRUE

24.1-4

问题描述:
修改Bellman-Ford算法,使其对于所有结点v来说,如果从源结点s都结点v的一条路径上存在权重为负值的环路,则将v.d的值设置为−∞。

问题分析:

贝尔曼福德算法不能够处理存在权重为负值的环路,因为一旦存在则会一直在环路上循环,因为这样的最短路径值会不停缩短。该情况显然是我们不希望发生的,所以我们增加这一个功能来应对存在负值环路的特殊情况。

问题求解:

如果存在负权环路,那么w(u,v)就是无穷小,并且w(s,v)(就是v.d)也是无穷小,于是这个if语句返回值是FLASE。所以只要在if内增加对v.d的修正即可

INITALIZE-SINGLE-SOURCE(G,s)
for i=1 to |G.V|-1
  for each v in G.V
     save v.d to T
  for each edge(u,v) in G.E
     RELAX(u,v,w)
  for each v in G.V
    if v.d != T[v].d
     continue //没有点的值被更新,说明所有点都取到最短路径,则停止
  for each edge(u,v) in G.E
    if v.d>u.d+w(u,v)
      v.d=-∞
      return FALSE
  return TRUE

24.2-4

问题描述:

给出一个有效的算法来计算一个有向无环图中的点的路径总数。分析你自己的算法。

问题分析:

目前我们手头关于图的知识点只有图的基本算法+最短路径算法+最小生成树,利用这些知识点来思考如何找路径总数。考虑到本题不用考虑有权图,所以我们把眼光转向BFS和DFS两个算法。进一步思考,我们能知道BFS常常用在求解最短路径算法中(BFS特点在于得到的是一棵树,同时每个点都是最短路径的),在这里我们需要求解的是到一个点的所有路径。

假如我们有如下一个图,现在要我们来求解顶点6的路径数

朴素想法:我们最朴素的想法就是从顶点1、2、3、5、4、....、这样的顺序去求解路径数。于是我们可以得到p(1)=1; p(2)+=p(1); p(3)+=p(1); p(5)+=p(2); p(4)+=p(2); 等等。

总的来说,就是从上层到下层计算,下层的路径数:下层路径数+=上层路径数

那什么是下层?什么又是上层呢?

下层:在上层结点撤去后不存在入度结点的点;上层:存在出度给其他顶点

如果可以从上层到下层计算呢?我们就需要对图进行拓扑排序


拓扑排序:是一种对有向图进行排序的算法,其主要目的是确定图中节点的线性顺序,使得在排序后,所有的边都从左到右指向更大的节点。换句话说,拓扑排序可以将图中的节点按照其依赖关系进行排序,使得所有的依赖关系都被满足。

 实现拓扑排序的方法有两个:一、计算结点入度+队列方法(通用方法)二、深度搜索(用于无环路的有向图

本题是有向无环图可以用方法二


问题求解:

一、利用DFS搜索树,将图进行拓扑排序,得到拓扑排序后的图

二、在新的图中,按照拓扑排序的顺序,对每个点u,找其所指向的点v,执行v.paths+=u.paths

PATHS(G)
    topologically sort the vertices of G
    for each vertex u, taken in topologically sorted order
        for each v ∈ G.Adj[u]
            v.paths = u.paths + v.paths
    return the sum of all paths attributes

24.3-2 

问题描述:

请举出一个包含负权重的有向图,使得 Dijkstra 算法在其上运行时将产生不正确的结果。为什么在有负权重的情况下,定理 24.6 的证明不能成立呢?

问题分析:

看到对本题的其中一个解答: 

大致意思是说:如果存在负权重回路,那么这个RELAX操作是没有意义的,因为RELEX是有限次的,但是通过负权重回路,我们到任何通过这个回路的点的距离都是−∞。于是在RELAX后,u.d并不是(s,u)的最小值,因此不成立

显然上面的证明是没有问题的,但是本题问的是为什么不能有负权重边,而不是负权重回路。所以仅仅有上面的证明是不充分的

问题求解:

Dijkstra算法是贪心贪心算法,也就是说每次都选择贪心策略下的局部最优解,这个解在后续中也不会再修正。那么对于如下的带有负权重的有向图:

假如源点是A,那么首先选择C此时C值为1;后选择B此时B的值为2,然后又会选择C此时C的值应该要修正为0.但是C的值不会被修正,因此Dijkstra每个点只访问一次(贪心策略)。所以最终C.d=1,但是实际上C到A的最小值为0。因此定理 24.6是不成立的,算法结束后,存在点的d的值不是最短路径值

24.3-3

问题描述:

假定将 Dijkstra 算法的第 行改为:

4 :white(lQl)>1

这种改变将让呻证循环的执行次数从 IVI 次降低到 IVI -1次。这样修改后的算法正确吗?

问题分析:

迪杰斯特拉算法最后得到的结果有两个:一、访问序列s,用来记录对点的访问次序;二、每个点的v.d记录每个点到源点的距离。

所以正确性的证明也将从这两个角度展开:

问题求解:

正确的!

一、假如我们已经得到前面V-1个点的访问序列,那么最后一个点不需要放入,我们也可以只知道最终的访问序列

二、迪杰斯特拉对每个结点只访问一次,且每次都能确定一个点的最短路径,此时程序运行了V-1个循环,访问并确定了V-1个点的最短路径。所以最后一个点进行松弛操作时,它并不能缩短其他点到原点的距离,所以最后一个点的RELAX操作也是无意义的。因此算法是正确的

24.3-6

问题描述:

问题分析: 

本题和最短路径问题存在几点不一样:

一、它需要找的是最可靠,也就是值最大的路径,可以认为是最长路径

二、最短路径算法相连路径段之间的关系是求和,但是通信链路相连路径段求可靠性时之间的关系是求积

问题求解:

一、最长路径。那就把贪婪准则改为选择d最大的点

二、总路径和子路径的关系变为乘。那就修改RELAX准则,从+变为*

三、初始化。源点可靠性为1;其他未搜索到的点初始化为0

INITIALIZE(G,s)
    for each vertex v ∈ G.V
        v.d=0
        v.π=NULL
    s.d=1
DIJKSTRA(G,w,s) //s用来记录访问的顺序,是需要返回的值;w边的权重集合
    S=空集合
    Q=G.V
    while(Q <> 空集合)
        u=EXTRACT-MAX(Q)
        S=S U {u}
        for each vertex v∈G.adj[u]
            RELEX(u,v,w) 
RELAX(u,v,w)
    if v.d<u.d*w(u,v)
        v.d=u.d*w(u,v)
        v.π=u

24.3-8

问题描述:

问题分析:

我第一次看这道题的时候,说实话,连题目都没看懂【emmmmm】。具体原因就在权重函数w:E->{0,1,...,W}这里没看明白。

这个权重函数就表示:边E的权重只能取{1,2,....,W}中的数

再来看看这个WV时间复杂度是哪里来的?要解决这个问题

先让友友们先思考另一个问题:WV含义是什么?

WV含义:图中任意点到源点的距离上限(图中任意点到源点的距离不超过WV)


再来思考一个这个E的时间复杂度含义又是什么?

E是指边,也就是说我需要对边进行E次操作,这不禁让我们想到了松弛操作。

E的复杂度就代表我们要对所有边进行一轮的松弛,每次松弛边本质是更新点的V.d,所以本质就是对点进行了E次的操作

问题求解:

从优化“贪心寻找dist最小点”的操作入手。由于边权≤W≤𝑊,那么一个点的dist值不会超过VW。基于这个条件,我们可以抽象出一个长度为VW的队列数组。每个数组的队列存储着“dist值为该数组序号”的点。然后抽象出一个指针,这个指针指向的队列,是我们贪心取点的队列。由于一个点被取出后,被他更新的点只会被push进该队列或者该队列之后的队列,所以指针只会从左到右扫描一遍数组。

最多扫描一遍数组,复杂度为O(VW)。此外,最多会有E次入队、出队操作,复杂度为O(E)。总时间复杂度为O(VW+E)。

#include <bits/stdc++.h>
#define mp make_pair
using namespace std;
const int N = ..., M = ..., W = ...;
queue<int> qs[N*W];
int head[N], ver[M], edge[M], Next[M], tot = 0;//采用数组模拟邻接链表的方式
void add(int x,int y,int z){
  ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
int d[N], v[N];
int n, m, w;
void dijkstra(int s){
  memset(d,0x3f,sizeof(d));//初始化为正无穷
  memset(v,0,sizeof(v));
  d[s] = 0; qs[0].push(s);
  for(int k = 0; k<=n*w; k++){
    while(!qs[k].empty()){
      int x = qs[k].front(); qs[k].pop();
      if(v[x]) continue;
      v[x] = 1;
      for(int i = head[x]; i; i = Next[i]){
        int y = ver[i], z = edge[i];
        if(d[y] > d[x] + z){
          d[y] = d[x] + z;
          qs[d[y]].push(y);
        }
      }
    }
  }
}

24.3-10 

问题描述:

假设给定带权重的有向图 G=(V, E), 从源结点s发出的边的权重可以为负值,而其他所有边的权重全部是非负值,同时,图中不包含权重为负值的环路。证明: Dijkstra 算法可以正确计算出从源结点到所有其他结点之间的最短路径。 

问题分析:

证明Dijkstra 算法是可行的==证明一个贪心策略是有效的。考虑到Dijkstra 算法本身的贪心策略已经证明结束,所以我们只需要针对其中变化的部分进行特别论证即可

特别部分:从源结点s发出的边的权重可以为负值

利用 Dijkstra 算法取出S,并更新与S相邻的点,将得到多个值不为0的点

于是,该问题可以等效为:多源点且初始值不为0的最短路径问题,并且每个源点都可以适用Dijkstra 算法

因此,整个问题同样可以利用 Dijkstra 算法来求解

问题求解:

首先,Dijkstra的算法思路是:对于任意一个从队列取出来的点x,如果它没有被标记过,那么d[x]一定是源结点s到x的最短路径,然后我们不断地进行贪心扩展,最终可以得到源结点s到每个结点的最短路径。它的本质是一个贪心+BFS算法。
现在,题目中给出的限制是:“只有从源结点s出发的边权重可以为负,且图中无负环”。
源结点s一定是在一开始被取出,更新完之后被丢弃。因为只有源结点s出发的边权重可以为负,所以我们在后面更新由“s以外的其他点”更新“s以外的其他点”时,仍旧是在一个无负边权的图中的更新,所以Dijkstra仍正确。而对于这些点,当他们尝试更新s的时候,因为图中无负环,所以无法更新s。此外,由于无负环,s也不会出现有一个权值为负的子环无限更新自身的情况。综上,Dijkstra基于贪心的性质没有发生改变,每次从队列中取出的一个点更新完其他点被丢弃后,这个点在后面一定不会被更新。所以Dijkstra仍旧可以正确运行。

总结

本文到这里就结束啦~~

本篇文章的撰写花了本喵四个多小时

如果仍有不够,希望大家多多包涵~~

如果觉得对你有帮助,辛苦友友点个赞哦~

知识来源:《算法导论》课后习题、山东大学孔凡玉老师ppt。不要用于商业用途转发~

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

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

相关文章

【C语言】05.数组

一、数组的概念 本文来介绍数组&#xff0c;首先我们需要了解数组是什么&#xff1f; 数组是⼀组相同类型元素的集合。 • 数组中存放的是1个或者多个数据&#xff0c;但是数组元素个数不能为0。 • 数组中存放的多个数据&#xff0c;类型是相同的。 数组分为⼀维数组和多维数组…

使用迭代最近点 (ICP) 算法在 Open3D 中对齐点云

一、Open3D 简介及其功能 Open3D 是一个现代库&#xff0c;它提供了用于处理 3D 数据的各种工具。在其功能中&#xff0c;它提供了高效的数据结构和算法来处理点云、网格等&#xff0c;使其成为在计算机视觉、机器人和图形领域工作的研究人员和从业人员的不错选择。Open3D 的特…

网络简史-基于图论的网络

先看一幅图&#xff1a; 如图&#xff0c;我们对类似 crossbar&#xff0c;banyan tree&#xff0c;b-tree&#xff0c;10-tree&#xff0c;256-tree&#xff0c;甚至 dcn fat-tree 等 “规则拓扑” 网络相当熟悉。规则拓扑网络中&#xff0c;地址信息被编码到拓扑本身&#…

继续引爆!5天连出2个里程碑成果,离子阱量子计算机嗨翻天!

5月30日&#xff0c;清华大学的一项成果被Nature审稿人称为“量子模拟领域的巨大进步”&#xff01;“值得关注的里程碑”&#xff01;该成果就是中国科学院院士、清华大学交叉信息研究院教授段路明带领研究组在量子模拟计算领域取得的重要突破。段路明研究组首次实现512离子二…

Java使用XWPFTemplate将word填充数据,并转pdf

poi-tl poi-tl&#xff08;poi template language&#xff09;是基于Apache POI的Word模板引擎。纯Java组件&#xff0c;跨平台&#xff0c;代码短小精悍&#xff0c;通过插件机制使其具有高度扩展性。 主要处理区域有这么几个模块: 依赖 <dependency><groupId>…

【UE5教程】使用蓝图显示鼠标

首先&#xff0c;在您的项目中创建一个新的蓝图类&#xff0c;继承自PlayerController。在蓝图编辑器中&#xff0c;找到Event BeginPlay节点&#xff0c;并从它引出一条线。添加Set Show Mouse Cursor节点&#xff0c;勾选Visible&#xff0c;以确保鼠标在游戏开始时可见。 鼠…

flask_sqlalchemy时间缓存导致datetime.now()时间不变问题

问题是这样的&#xff0c;项目在本地没什么问题&#xff0c;但是部署到服务器过一阵子发现&#xff0c;这个时间会在某一刻定死不变。 重启uwsgi后&#xff0c;发现第一条数据更新到了目前最新时间&#xff0c;过了一会儿再次发送也变了时间&#xff0c;但是再过几分钟再发就会…

一句话说清HDMI ARC eARC功能和区别

HDMI&#xff1a; 高清多媒体接口&#xff0c;主要用于传输高清音视频信号&#xff0c;High Definition Multimedia Interface。 ARC: 音频回传通道&#xff0c;Audio Return Channel eARC: 增强型音频回传通道&#xff0c;第一个E是增强的意思&#xff0c;Enhanced Audio…

超过20W个高质量组件的开源PCB库

项目介绍 Celestial Altium Library是由Altium行业专家Mark Harris创建的一个庞大的免费开源数据库库&#xff0c;专为Altium Designer而设计&#xff0c;库中包含超过20万个优质组件 . 特点 高质量数据&#xff1a;Celestial Altium Library注重数据的质量&#xff0c;用户可…

聊聊二叉堆、红黑树、时间轮在定时任务中的应用

定时任务作为常用的一种调度方式&#xff0c;在各大系统得到了广泛的应用。 笔者也曾写过两篇关于定时任务框架介绍的文章&#xff1a; 《介绍一下,spring cloud下的另一种定时任务解决方案》《四叉堆在GO中的应用-定时任务timer》 之前都是以如何使用为主&#xff0c;这次从…

Dokcer 基础使用 (4) 网络管理

文章目录 Docker 网络管理需求Docker 网络架构认识Docker 常见网络类型1. bridge 网络2. host 网络3. container 网络4. none 网络5. overlay 网络 Docker 网路基础指令Docker 网络管理实操 其他相关链接 Docker 基础使用(0&#xff09;基础认识 Docker 基础使用(1&#xff09;…

Android无障碍服务

Hi I’m Shendi Android无障碍服务 最近想制作一个记录点击操作并重复播放的工具&#xff0c;用以解放双手&#xff0c;因现在的Android高版本基本上难以Root&#xff0c;所以选择了使用无障碍来实现&#xff0c;在这里记录下来。 Android无障碍 可参考文档&#xff1a;https:…

【MySQL】sql语句之库操作

序言 在上篇文章学习当中&#xff0c;我们认识了数据库的相关概念&#xff0c;以及MySQL的框架和基本使用等内容&#xff0c;总之对数据库有了一个大致的认识&#xff0c;那么本篇文章将开始关于sql语句的学习&#xff0c;本文主要是关于库的属性和操作的内容&#xff0c;简单可…

DOS 操作系统

DOS 介绍 DOS&#xff1a;disk operating system&#xff0c;磁盘操作系统。 中国DOS联盟下载 MS-DOS 7.10完整安装版&#xff08;含图形安装程序&#xff09; DOS 环境下的操作 输入部分内容后按下 Tab 可以快速自动补全。 按住 Ctrl 键可以用鼠标滚轮改变字号大小。 DO…

如何在virtualbox上安装Linux系统(centerOS)

提示&#xff1a;共同学习 注意&#xff1a;一定要在BIOS中的虚拟化打开。 文章目录 第一步&#xff1a; 第一步&#xff1a; 启动 、显示开启 centos基础安装 ​ ​

【传知代码】基于曲率的图重新布线(论文复现)

前言&#xff1a;在图形处理中&#xff0c;一个至关重要的问题是图形的重新布线&#xff0c;即在不改变图形基本结构的前提下&#xff0c;通过调整节点间的连接关系&#xff0c;使图形具有更好的性质&#xff0c;如更低的复杂度、更高的可视化效果或更强的鲁棒性。传统的图形重…

暂停系统更新

电脑左下角搜索注册表编辑器 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 找到这个目录 打开FlightSettingsMaxPauseDays&#xff0c;没找到的话就创建一个同名文件夹然后选择10进制填入3550​​​​​​​ 最后进入系统暂停更新界面选择最下面…

[C/C++]_[初级]_[在Windows和macOS平台上导出动态库的一些思考]

场景 最近看了《COM本质论》里关于如何设计基于抽象基类作为二进制接口,把编译器和链接器的实现隐藏在这个二进制接口中,从而使用该DLL时不需要重新编译。在编译出C接口时,发现接口名直接是函数名,比如BindNativePort,怎么不是_BindNativePort?说明 VC++导出的函数默认是使…

Day49 动态规划part08

LC139单词拆分(未掌握) 未掌握分析&#xff1a;将字符串s中的各个字符看成是背包&#xff0c;思考成了多重背包问题单词就是物品&#xff0c;字符串s就是背包&#xff0c;单词能否组成字符串s&#xff0c;就是问物品能不能把背包装满。拆分时可以重复使用字典中的单词&#xf…

缓存方法返回值

1. 业务需求 前端用户查询数据时,数据查询缓慢耗费时间; 基于缓存中间件实现缓存方法返回值:实现流程用户第一次查询时在数据库查询,并将查询的返回值存储在缓存中间件中,在缓存有效期内前端用户再次查询时,从缓存中间件缓存获取 2. 基于Redis实现 参考1 2.1 简单实现 引入…