算法描述

在普利姆算法的lazy实现中,参考:普利姆算法的lazy实现

我们现在来考虑这样一个问题:

我们将所有的边都加入了优先队列,但事实上,我们真的需要所有的边吗?

我们再回到普利姆算法的lazy实现,看一下这个问题:



当顺着顶点0的邻接表考察顶点7时,边7-2和边7-1被加入了优先队列Q.

然而,当我们开始对顶点2进行考察时:

边2-3是最轻边,我们显然不需要对边7-2和边7-1进行再次考察.

但是,由于边7-2和边7-1在对顶点2进行考察之前已经加入了优先队列Q,似乎我们对之前发生的事无可奈何,也必须让优先队列维护着这些不再候选的废边,从而加重了优先队列的负担,影响了效率.

结果是否真的如此?

如果我们仔细思考,会注意到我们可以采取这样的一个技巧去防止将废边加入优先队列:

我们关注的只是当前能看到的最轻边,所以边7-2和边7-1对我们来说只有这样的意义:

边7-2:到顶点2的距离是x;

边7-1:到顶点2的距离是y;

边3-2:到顶点2的距离是z.

z > xz >y.

所以我们既然无法避免在先于顶点2之前就将边7-2和边7-1当做废边(贪心算法),所以我们可以

采取更新的方式来在优先队列Q中维护到某个顶点的最短距离.

换句话说,我们对某个顶点,只在Q中维护一条边,就是当前已知连着它的最轻边.

由此,我们避免了将所有的边都加入优先队列Q,从而使得最差情况下Q的操作与图的顶点数V 成线性渐进:O(V ).

但一般的优先队列只提供了入队(enqueue)和出队(dequeue)操作,要更新到某个顶点的最短距离,我们需要高效地在优先队列中访问这个顶点.

那么按照一般优先队列的方式,比如jdk中的优先队列,它会是这样:

  1. private int indexOf(Object o) {
  2. if (o != null) {
  3. for (int i = 0; i < size; i++)
  4. if (o.equals(queue[i]))
  5. return i;
  6. }
  7. return -1;
  8. }

这虽然可以帮助我们在队列中找到元素,但这显然不高效.

有没有一种办法可以按常量时间来找到所需元素?

答案是:索引(index),由此:

我们需要一个对顶点在队列中的索引.

这可以保证我们以常量的时间在队列中找到顶点.

关于索引式优先队列及实现可以参考:带索引的优先队列

实现分析

万事具备,那么我们对某顶点的邻接点(或邻接的边)的遍历和处理就会是这样:

  1. private void search(int src) {
  2. IndexPriorityQueue<Double> q = indexCrossingEdges;
  3. visited[src] = true;
  4. //遍历邻接的边
  5. for(Edge edge:g.vertices()[src].Adj) {
  6. WeightedEdge we = (WeightedEdge)edge;
  7. int to = we.to;
  8. if(visited[to])
  9. continue;
  10. //到顶点to的距离可以改善了
  11. if(we.weight < distanceTo[to]) {
  12. distanceTo[to] = we.weight;
  13. lastEdgeTo[to] = we;
  14. if(q.contains(to)) {
  15. //我们在队列中只维护一条到某个顶点的距离
  16. //在我们可以改善到这个顶点的距离是,我们更新它
  17. q.decreaseKey(to, distanceTo[to]);
  18. }else {
  19. q.offer(to, distanceTo[to]);
  20. }
  21. }
  22. }
  23. }
  24. }

算法一开始的时候,我们从源点v出发,将其加入队列Q,然后开始进行mst的建立工作:

  1. private void mst(int v) {
  2. IndexPriorityQueue<Double> q = indexCrossingEdges;
  3. distanceTo[v] = 0.0d;
  4. q.offer(v, distanceTo[v]);
  5. while (!q.isEmpty()) {
  6. int src = q.poll();
  7. search(src);
  8. }
  9. }

完整实现

普利姆算法的完整eager实现如下,其中的一些类和字段不明白的

请参考:普利姆算法的lazy实现

  1. /**
  2. * Created by 浩然 on 4/21/15.
  3. */
  4. public class EagerPrim extends LazyPrim {
  5. protected WeightedEdge[] lastEdgeTo;
  6. /**
  7. * 索引式优先队列,用于维护crossing edges
  8. * 用于在eager普利姆算法中高效返回最轻边并支持decrease-key操作
  9. */
  10. protected IndexPriorityQueue<Double> indexCrossingEdges;
  11. public EagerPrim(WeightedUndirectedGraph g) {
  12. super(g);
  13. }
  14. @Override
  15. protected void resetMemo() {
  16. super.resetMemo();
  17. lastEdgeTo = new WeightedEdge[g.vertexCount()];
  18. //重置优先队列
  19. indexCrossingEdges = new IndexPriorityQueue<>();
  20. }
  21. private void setupMST() {
  22. for (int v = 0; v < lastEdgeTo.length; v++) {
  23. WeightedEdge we = lastEdgeTo[v];
  24. if (we != null) {
  25. mst.offer(we);
  26. mstWeight += we.weight;
  27. }
  28. }
  29. }
  30. /**
  31. * eager-prim算法,时间复杂度为最差O(ElogV)
  32. */
  33. @Override
  34. public void performMST() {
  35. resetMemo();
  36. //对图中的所有顶点进行遍历,可以找出MSF(最小生成森林)
  37. //这里我们假设图是连通的,所以可以找出一棵MST
  38. mst(0);
  39. setupMST();
  40. }
  41. private void mst(int v) {
  42. IndexPriorityQueue<Double> q = indexCrossingEdges;
  43. distanceTo[v] = 0.0d;
  44. q.offer(v, distanceTo[v]);
  45. while (!q.isEmpty()) {
  46. int src = q.poll();
  47. search(src);
  48. }
  49. }
  50. private void search(int src) {
  51. IndexPriorityQueue<Double> q = indexCrossingEdges;
  52. visited[src] = true;
  53. //遍历邻接的边
  54. for(Edge edge:g.vertices()[src].Adj) {
  55. WeightedEdge we = (WeightedEdge)edge;
  56. int to = we.to;
  57. if(visited[to])
  58. continue;
  59. //到顶点to的距离可以改善了
  60. if(we.weight < distanceTo[to]) {
  61. distanceTo[to] = we.weight;
  62. lastEdgeTo[to] = we;
  63. if(q.contains(to)) {
  64. //我们在队列中只维护一条到某个顶点的距离
  65. //在我们可以改善到这个顶点的距离是,我们更新它
  66. q.decreaseKey(to, distanceTo[to]);
  67. }else {
  68. q.offer(to, distanceTo[to]);
  69. }
  70. }
  71. }
  72. }
  73. }

时间复杂度

由于避免了对废弃边的访问,所以在优先队列中最多维护V条记录.

优先队列的操作耗时O(logV ).

遍历所有边的操作耗时O(E ),则整体耗时O(ElogV)

最小生成树-普利姆算法eager实现的更多相关文章

  1. 最小生成树-普利姆算法lazy实现

    算法描述 lazy普利姆算法的步骤: 1.从源点s出发,遍历它的邻接表s.Adj,将所有邻接的边(crossing edges)加入优先队列Q: 2.从Q出队最轻边,将此边加入MST. 3.考察此边的 ...

  2. 最小生成树-普利姆(Prim)算法

    最小生成树-普利姆(Prim)算法 最小生成树 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一种特殊的图),或者 ...

  3. 图论---最小生成树----普利姆(Prim)算法

    普利姆(Prim)算法 1. 最小生成树(又名:最小权重生成树) 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一 ...

  4. POJ-2421-Constructing Roads(最小生成树 普利姆)

    Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 26694   Accepted: 11720 Description The ...

  5. 普利姆算法(prim)

    普利姆算法(prim)求最小生成树(MST)过程详解 (原网址) 1 2 3 4 5 6 7 分步阅读 生活中最小生成树的应用十分广泛,比如:要连通n个城市需要n-1条边线路,那么怎么样建设才能使工程 ...

  6. 图->连通性->最小生成树(普里姆算法)

    文字描述 用连通网来表示n个城市及n个城市间可能设置的通信线路,其中网的顶点表示城市,边表示两城市之间的线路,赋于边的权值表示相应的代价.对于n个定点的连通网可以建立许多不同的生成树,每一棵生成树都可 ...

  7. 最小生成树---普里姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)

    普里姆算法(Prim算法) #include<bits/stdc++.h> using namespace std; #define MAXVEX 100 #define INF 6553 ...

  8. 算法与数据结构(五) 普利姆与克鲁斯卡尔的最小生成树(Swift版)

    上篇博客我们聊了图的物理存储结构邻接矩阵和邻接链表,然后在此基础上给出了图的深度优先搜索和广度优先搜索.本篇博客就在上一篇博客的基础上进行延伸,也是关于图的.今天博客中主要介绍两种算法,都是关于最小生 ...

  9. HDU 1879 继续畅通工程 (Prim(普里姆算法)+Kruskal(克鲁斯卡尔))

    继续畅通工程 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

随机推荐

  1. 十分钟搞懂快速傅里叶变换(FFT)

    己学习的笔记,欢迎大家指正.

  2. SQL-如果指定值存在返回1,如果不存在返回0的SQL语句

    想实现简单的判断一个表中是否有一条记录,可以用这个方式.如以下,table_name是表名,column1是列名. 这条语句会在此条记录存在的时候返回1,不存在时返回0. FROM table_nam ...

  3. NOIP2002普及T3【产生数】

    做完发现居然没人用map搞映射特意来补充一发 很容易看出这是一道搜索题考虑搜索方案,如果按字符串转移,必须存储每种状态,空间复杂度明显会爆炸观察到每一位之间是互不影响的 考虑使用乘法原理搜索出每一位的 ...

  4. Unix IPC之基于共享内存的计数器

    目的 本文主要实现一个基于共享内存的计数器,通过父子进程对其访问. 本文程序需基于<<Unix网络编程-卷2>>的环境才能运行.程序中大写开头的函数为其小写同名函数的包裹函数, ...

  5. LAMP:用yum安装

    LAMP:用yum安装 Table of Contents 1 什么是LAMP 1.1 L 1.2 A 1.3 M 1.4 P 2 什么是yum 3 如何使用yum 3.1 基本的yum命令 3.1. ...

  6. Java MongoDB : Save image example

    In this tutorial, we show you how to save an image file into MongoDB, via GridFS API. The GridFS API ...

  7. 使用apt install和使用apt-get install的区别是什么

    apt-get是老版的命令,apt是新版的命令,apt还包含了apt-get cache等等,用起来更方便.因为apt刚刚出来,所以允许有apt-get和apt共存,以后apt-get就要淘汰了.

  8. SQL Server中的快捷键

    新建查询:Ctrl + N 反撤销:Ctrl + Y 撤销:Ctrl + Z 查找:Ctrl + F 启动调试:Alt + F5 注释:Ctrl + K + C 取消注释:Ctrl + K + U 执 ...

  9. 浅谈DDD

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

  10. 32 从1到n整数中1出现的次数

    输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数. 主要思路:设定整数点(如1.10.100等等)作为位置点i(对应n的各位.十位.百位等等),分别对每个数位上有多少包含1的点进行分析 ...