================================================= 废柴日记6:迟到一个月的『构造最小生成树算法』· 其三 =================================================


前言:其实,我每天都还好。

拖了整整1个月零7天的废柴日记6:迟到的『构造最小生成树算法』③,今天它终于来了!
在这段时间里发生了很多事,一边是准备考试防止挂科,另一边要开始进行社团纳新,完善一些社团的规章制度。生活总是匆匆忙忙,我做的事只是拿起与放下罢了。

『废柴日记』系列虽然断更了一段时间,但是博主还是一直有在更新一些CF比赛的题解。也是在此期间,博主终于达成『CSDN百篇博客』,也算是一个小小的成就。
实际上这篇博客应该是作为博主的第100篇博客发布,也算是一个纪念性文章,但是由于某种不可抗拒的原因,导致拖延到了现在。

不瞎扯了,让我们继续『构造最小生成树算法』的旅途吧。


Ⅴ.普里姆(Prim)算法:秦王扫六合,虎视何雄哉!

秦始皇嬴政是历史上第一位完成中国大一统的政治人物,也是中国历史上的第一位皇帝。


历史上人们对秦始皇的评价褒贬不一。

  • 西汉时期著名政论家、文学家贾谊在《过秦论》中如此评价秦始皇:“秦王怀贪鄙之心,行自奋之志,不信功臣,不亲士民,废王道而立私爱,焚文书而酷刑法,先诈力而后仁义,以暴虐为天下始”;
  • 明代思想家李贽将他誉为“千古一帝”,因其实施的政治制度与策略奠定了中国两千余年的政治制度的基本格局。

有关秦始皇的历史桥段,博主记忆最深的还得是『秦灭六国』。
唐代诗人李白在《古风·秦王扫六合》中这样形容:

『秦王扫六合,虎视何雄哉!』
虽然博主是一个 理科生,但这丝毫不会影响到我从诗句中所感受到的勃勃雄姿,咄咄逼人之势。

故事背景介绍完毕,接下来进入正题:

公元前238年秦王嬴政铲除了丞相吕不韦和长信侯嫪毐集团,开始亲政。秦王政在李斯、尉缭等人的协助下制定了“灭诸侯,成帝业,为天下一统”的策略。

现在,你无意中穿越到了战国时期的秦国(没错,又是穿越),附身在了李斯的身上,并且获得了一张战略图。

战略图内容如下所示:

1.两国之间若存在边,则代表两国具有向对方发起进攻的地理条件,即两国可以开战。
2.边上的权值则代表征服对方国家所需要消耗的兵力(单位:万人)。
3.若A、B两国没有具备开战的地理条件,我们就认为A国、B国之间如果开战,消耗的兵力是无法估计的。
4.若A国吞并B国,A国会获得B国所有的攻打权力并获得B国的兵种。

举例:
燕国吞并齐国之后,燕国就可以对魏国开战。
由于燕国吞并了齐国,燕国就可以用齐国的兵去打魏国,所以此时燕国攻打魏国所要消耗的兵力为7万齐兵。

而作为秦王的忠臣,你能否找到一个合适的攻打顺序,使得秦国的兵力总消耗尽可能要少,又可以助秦王一统六国?


如果作为一道益智题,我相信在座的各位都能在3min之内轻松写出答案。

那如果是让你编程实现呢?

其实不难看出,这是一道非常入门的求解最小生成树的题目(最小生成树知识详解:废柴日记4:迟到的『构造最小生成树算法』①)。
而我们最常用于解决求解最小生成树问题的算法有『普里姆算法』『克鲁斯卡尔算法』

接下来,我们介绍如何使用『普里姆算法』解决这道题。


理论部分

解决最小生成树问题『普里姆算法』的核心是两张表格:closest表lowcost表

我们先根据题目描述画出本题的closest表lowcost表

lowcost表中记录的是:根据战略图与现在秦朝征服的国家,征服各国所需要的最小兵力。

  • 首先秦国是秦朝的前身,所以不需要派兵,秦国就已经归属于秦朝的疆土了:lowcostlowcostlowcost[秦]=0=0=0。
  • 接下来根据战略图,我们现在可以攻打的国家有:燕国、赵国、韩国,分别需要的兵力为:lowcostlowcostlowcost[燕]=7=7=7,lowcostlowcostlowcost[赵]=9=9=9,lowcostlowcostlowcost[韩]=2=2=2。
  • 除此之外的三个国家,我们现在并没有与他们开战的地理条件,那么他们对于我们的威胁就是未知的。正所谓『知己知彼,方能百战不殆』,我们要尽可能的避免未知的敌人,将所有无法完全掌握信息的情况都按照最坏情况处理,所以我们认为:lowcostlowcostlowcost[燕]===lowcostlowcostlowcost[赵]===lowcostlowcostlowcost[韩]=∞=∞=∞。

closest表中记录的是:秦朝接下来会使用哪国兵力来征服这个国家。

  • 一开始整个秦朝就只有秦国一个国家,所以秦朝的兵种只有秦兵,如果最后征服了六国也只能是出动了秦兵征服的。所以在一开始,closest表中所有国家对应的兵种都是秦兵。

可以结合经典游戏红警理解。
你现在手上只有步兵,假设你接下来不再发展其他的兵种,那么未来你征服其他国家时用的一定只能是你的步兵。


好,如果是你,根据lowcost表的描述,接下来你会攻打哪一国?为什么?

答案很容易就能想到:打韩国,因为这是我们能够摸到的国家之中,攻打起来消耗兵力最少的国家

  • 韩国归顺于秦朝之后,秦朝就不需要对韩国派兵攻打了,所以更新为0。与此同时,秦朝就可以攻打楚国与魏国了。
  • 此时我们发现,我们使用秦兵攻打赵国,需要消耗9万兵力;而我们用韩兵去攻打赵国,只需要消耗1万兵力。此时我们肯定选择最优解——用韩兵攻打赵国

第一次更新closest表lowcost表


此时再根据更新之后的lowcost表,我们可以推断出,下一步应该是用刚刚收服的韩兵来攻打赵国。

  • 打下赵国之后,我们发现:如果我们使用赵兵攻打燕国的话,只需要消耗5万赵兵。此时取最优解——改用赵兵进攻燕国
  • 同理,此时我们发现只需要使用2万赵兵就可以拿下魏国,改变预备方案,改用赵兵对付魏国。

第二次更新closest表lowcost表


重复上述过程,此次我们应该选择使用2万赵兵攻打魏国。

  • 魏国纳入秦朝疆土,此时我们就有和齐国开战的地理条件,可以使用7万魏兵攻打齐国。
  • 同时对于楚国我们有了更优解:用3万魏兵攻打楚国

第三次更新closest表lowcost表



我们根据lowcost表可以推断出下一步:用3万魏兵攻打楚国。

  • 打下楚国之后,我们发现,楚兵的加入并不能改变接下来的战局。

第四次更新closest表lowcost表


最后就是先用5万赵兵征服燕国,再用2万燕兵征服齐国。


最后形成的closest表如下图所示:

这样,我们就可以做出一种适合的攻打顺序:

韩→赵→燕→魏→楚→齐


此时我们根据closestclosestclosest表lowcostlowcostlowcost表将战略图上多余的边全部去掉,得到新的战略图:

这张图就是原战略图的最小生成树之一。


代码部分

首先我们需要先把图存起来,用之前废柴日记5:迟到的『构造最小生成树算法』②中所讲解的邻接矩阵存储即可。
这里为了便于存储与输入,我们用不同的编号来表示各个国家。

typedef struct
{VertexType vexs[MAXVEX];            /** vexs数组存储图G所有的顶点,充当集合V的作用,下标从0开始。  vexs[i+1]=X  =>  图G的第i个顶点为X。 **/EdgeType arc[MAXVEX][MAXVEX];       /** arc数组存储图G中所有的边,充当集合E的作用。 **/int numNodes,numEdges;              /** numNodes代表当前图G中的顶点数量,numEdges代表当前图G中的边的数量。 **/
} MGraph;MGraph G;void CreateUMGraph()
{int i,j,x,y,w;printf("请输入无向图G的顶点数:\n");scanf("%d",&G.numNodes);printf("请输入无向图G的边数:\n");scanf("%d",&G.numEdges);for(i=1; i<=G.numNodes; i++){printf("请输入第%d个结点的编号:\n",i);scanf("%d",&G.vexs[i]);}for(i=1; i<=G.numNodes; i++)for(j=1; j<=G.numNodes; j++)G.arc[i][j]=INF;for(i=0; i<G.numEdges; i++){printf("请输入边(i,j)上的国家i,国家j与权值w:\n");scanf("%d%d%d",&x,&y,&w);G.arc[x][y]=w;G.arc[y][x]=G.arc[x][y];}
}

接下来需要用代码来实现博主叨叨半天的Prim算法


第一步:定义closest表lowcost表并进行初始化

int closest[MAXVEX];
int lowcost[MAXVEX];lowcost[1]=0;
closest[1]=1;
for(int i=2; i<=G.numNodes; i++)
{lowcost[i]=G.arc[1][i];closest[i]=1;
}

第二步:执行一个『循环』

我们来细看每次『循环』的流程:

  1. 每次在closest表lowcost表更新之后,我们第一步要做的,是在lowcost表中找到当前消耗兵力最小的那个国家,然后下一次就攻打他。
int minx=INF;   //INF表示无穷大
int pos=-1;
for(int j=1; j<=G.numNodes; j++)if(lowcost[j]!=0&&lowcost[j]<minx)  //lowcost[j]=0:国家j已经被纳入秦朝的疆土{minx=lowcost[j];pos=j;}
  1. 将这个国家jjj纳入秦朝疆土之后我们就开始思考,能否用国家jjj的兵种减少当前我们攻打其他国家的成本

例如在第二次更新表之后,我们选择攻打魏国。魏国打下之后,我们发现,如果我们用魏兵来攻打楚国,会比之前的成本少很多,此时我们选择最优解——准备3万魏兵攻打楚国。

for(int j=1; j<=G.numNodes; j++)if(lowcost[j]!=0&&G.arc[pos][j]<lowcost[j]){lowcost[j]=G.arc[pos][j];closest[j]=pos;}
  1. 这个循环要持续6(7−1)6(7-1)6(7−1)次,也就是n−1n-1n−1次。
for(int i=2; i<=G.numNodes; i++)
{int minx=INF;int pos=-1;for(int j=1; j<=G.numNodes; j++)if(lowcost[j]!=0&&lowcost[j]<minx){minx=lowcost[j];pos=j;}printf("第%d轮:使用%s进攻%s。\n",i-1,solder[closest[pos]],country[pos]);lowcost[pos]=0;for(int j=1; j<=G.numNodes; j++)if(lowcost[j]!=0&&G.arc[pos][j]<lowcost[j]){lowcost[j]=G.arc[pos][j];closest[j]=pos;}
}

接下来写个主函数,把这些补一补就ok了。

#include<bits/stdc++.h>
using namespace std;
/** 邻接矩阵的结构体定义代码 **/
typedef int VertexType;                 /** VertexType为顶点的数据类型,我们在第一篇博客中所展示的图中,所有的顶点的数据类型为int。 **/
typedef int EdgeType;                   /** EdgeType为边上权值的数据类型,我们接下来所研究的最小生成树是基于带权图的。 **/
#define MAXVEX 105                      /** 顶点的最大数量,这里也可以看出邻接矩阵的局限性:顶点数量过多的时候无法处理。 **/
#define INF 0x3f3f3f3f                  /** 用0x3f3f3f3f代表∞,可以是其他数字,但推荐是这个。 **/typedef struct
{VertexType vexs[MAXVEX];            /** vexs数组存储图G所有的顶点,充当集合V的作用,下标从0开始。  vexs[i+1]=X  =>  图G的第i个顶点为X。 **/EdgeType arc[MAXVEX][MAXVEX];       /** arc数组存储图G中所有的边,充当集合E的作用。 **/int numNodes,numEdges;              /** numNodes代表当前图G中的顶点数量,numEdges代表当前图G中的边的数量。 **/
} MGraph;MGraph G;
int closest[MAXVEX];
int lowcost[MAXVEX];
char solder[10][35]= {"","秦兵","齐兵","楚兵","燕兵","魏兵","赵兵","韩兵"};
char country[10][35]= {"","秦国","齐国","楚国","燕国","魏国","赵国","韩国"};void CreateUMGraph()
{int i,j,x,y,w;printf("请输入无向图G的顶点数:\n");scanf("%d",&G.numNodes);printf("请输入无向图G的边数:\n");scanf("%d",&G.numEdges);for(i=1; i<=G.numNodes; i++){printf("请输入第%d个结点的编号:\n",i);scanf("%d",&G.vexs[i]);}for(i=1; i<=G.numNodes; i++)for(j=1; j<=G.numNodes; j++)G.arc[i][j]=INF;for(i=0; i<G.numEdges; i++){printf("请输入边(i,j)上的国家i,国家j与权值w:\n");scanf("%d%d%d",&x,&y,&w);G.arc[x][y]=w;G.arc[y][x]=G.arc[x][y];}
}void Prim()
{lowcost[1]=0;closest[1]=1;for(int i=2; i<=G.numNodes; i++){lowcost[i]=G.arc[1][i];closest[i]=1;}for(int i=2; i<=G.numNodes; i++){int minx=INF;int pos=-1;for(int j=1; j<=G.numNodes; j++)if(lowcost[j]!=0&&lowcost[j]<minx){minx=lowcost[j];pos=j;}printf("第%d轮:使用%s进攻%s。\n",i-1,solder[closest[pos]],country[pos]);lowcost[pos]=0;for(int j=1; j<=G.numNodes; j++)if(lowcost[j]!=0&&G.arc[pos][j]<lowcost[j]){lowcost[j]=G.arc[pos][j];closest[j]=pos;}}
}
int main()
{CreateUMGraph();Prim();
}

代码效果图


后话

终于是肝完了,从来没想过我有一天会重新拾起我的初中历史,更没有想过是因为数据结构才拾起的。

今天是10月24日,也是属于我们程序猿的节日"1024"。
在自己的节日中码一篇自己喜欢的领域的文章,也就算是属于博主的庆祝方式。

程序员这三个字始于程序,但又远不止程序。我们需要面对的,所在做的,绝不止写写代码那么简单。
祝所有程序员节日快乐,也祝大家活出自己的精彩。


中二少年的日常

"就算梦想已经破灭," "就算生活已经面目全非," "就算全世界没有一个人了解你," "还是要挺直腰板活下去啊,混蛋!"


吾日三省吾身:日更否?刷题否?快乐否? 更新了,但不是日更;已刷;happy! 吾心满意足。

废柴日记6:迟到的『构造最小生成树算法』③相关推荐

  1. 废柴日记7:迟到的『构造最小生成树算法』④

    ================================================= 废柴日记7:迟到的『构造最小生成树算法』· 其四(完结撒花) =================== ...

  2. 『数据结构与算法』解读树(Tree)和二叉树(Binary Tree)!

    『数据结构与算法』解读树(Tree)和二叉树(Binary Tree)! 文章目录 一. 树 1.1. 树的定义 1.2. 树的基本术语 1.3. 树的性质 二. 二叉树 2.1. 二叉树的定义 2. ...

  3. 作业1-采用Prim算法和Kruskal算法构造最小生成树

    采用Prim算法和Kruskal算法构造最小生成树 实验报告 1.问题 2.解析 (1)Prim算法 (2)Kruskal算法 3.设计 (1)Prim算法 (2)Kruskal算法 4.分析 (1) ...

  4. 1分钟解决Prim算法构造最小生成树

    数据结构期末上分必备 前言:Prim 算法构造最小生成树!!!跟着画一遍就会了!!! Kruskal 请移步 kruskal 题目 设有如下图所示的无向连通图,从顶点A出发,使用 Prim 算法构造最 ...

  5. 技术图文:如何利用C# 实现 Prim 最小生成树算法?

    背景 我们上一篇图文介绍了 如何利用 C# 实现 Kruskal 最小生成树算法?,Kruskal 算法通过寻找边最优的方式来构造最小生成树,本篇图文介绍如何利用 C# 实现 Prim 最小生成树算法 ...

  6. 技术图文:如何利用C# 实现 Kruskal 最小生成树算法?

    背景 以前我写过一些图文来介绍有关数据结构与算法的知识: 8大排序算法之:直接插入排序(Straight Insertion Sort) 8大排序算法之:希尔插入排序(Shell Insertion ...

  7. 2017-2018-2 20155303『网络对抗技术』Final:Web渗透获取WebShell权限

    2017-2018-2 『网络对抗技术』Final:Web渗透获取WebShell权限 --------CONTENTS-------- 一.Webshell原理 1.什么是WebShell 2.We ...

  8. 证明kruskal算法求解图的最小生成树具有贪心选择性质_将并查集应用在图论中的最小生成树算法——Kruskal...

    点击上方蓝字,和我一起学技术. 今天是算法和数据结构专题的第19篇文章,我们一起来看看最小生成树. 我们先不讲算法的原理,也不讲一些七七八八的概念,因为对于初学者来说,看到这些术语和概念往往会很头疼. ...

  9. 【数据结构与算法python】最小生成树算法-Prim算法

    1.引入 本算法涉及到在互联网中网游设计者和网络收音机所面临的问题:信息广播问题,如网游需要让所有玩家获知其他玩家所在的位置,收音机则需要让所有听众获取直播的音频数据 2.算法介绍 (1)单播解法 信 ...

最新文章

  1. 转发和重定向的区别?
  2. SIGIR 2021|用于搜索多样化的意图图建模
  3. 从实验现象详细分析BGP的路由策略与选路原则
  4. Java中sum和Sum相同吗,Java认为变量Sum 和sum相同。
  5. 数学作图工具_科研论文作图系列-从PPT到AI (一)
  6. AjAx下拉列表框(SELECT)jquery插件
  7. 什么是Vue?为什么要学习Vue?如何使用Vue?
  8. SQL中创建外键约束
  9. [转载]【深入Java虚拟机】之四:类加载机制
  10. Spss典型相关分析的常见问题
  11. OD点击寄存器变色OD
  12. minimax算法_如何通过使用minimax算法使Tic Tac Toe游戏无与伦比
  13. local class incompatible: stream classdesc serialVersionUID = -6230081990944906418, local class seri
  14. win11家庭中文版 安装docker 步骤
  15. python使用pika库调用rabbitmq的参数使用
  16. python编程单词排序_Python实现针对中文排序的方法
  17. Canvas 绘制点线相交
  18. Matlab学习1.0
  19. PHP单例模式 构造方法
  20. boost库在工作(23)任务之三

热门文章

  1. VS2017:64位调试操作花费的时间比预期要长,无法运行调试解决办法
  2. 抽象工厂模式(图画版)
  3. MacOS中dyld: Library not loaded的错误修正
  4. FPGA-04 触摸按键控制LED灯
  5. 鱼刺图java_使用java实现鱼刺图
  6. 神灯搜索软件测试,《没头脑和不高兴》阅读检测及答案
  7. linux报表系统架构,综合报表系统设计方案.doc
  8. 步 入 网 络 攻 防 的 神 秘 世 界
  9. 计算平均指令时间_技术人员正在组装新计算机。在将主板装入机箱之前通常先安装哪两个组件?(选择两项。)...
  10. Win10怎么设置开机自动连接宽带