大话数据结构-普里姆算法(Prim)和克鲁斯卡尔算法(Kruskal)

5 最小生成树

  构造连通网的最小代价生成树称为最小生成树,即Minimum Cost Spanning Tree,最小生成树通常是基于无向网/有向网构造的。

  找连通网的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法。

5.1 普里姆(Prim)算法

  普里姆算法,即Prim算法,大致实现过程如下:

  (1) 新建数组adjVex[n],初始值均为0;新建数组lowCost[n],初始值均为infinity;

  (2) 从第一个顶点X(下标为0)开始,把它与各顶点连接的权记录下来,放到lowCost数组里面,然后找到权最小的那个顶点Y,得到最小生成树的第一条边(X,Y),然后把lowCost数组里面Y对应的下标的元素设置为0;

  (3) 然后处理顶点Y,把它与除X外的其他各顶点连接的权,与lowCost数组下标相同的权比较,将小的放入到lowCost里面,并把较小的权对应的顶点的下标记录在adjVex数组里面,也即,adjVex[j]要么是Y,要么是除X外的其他顶点;

  (4) 找到lowCost数组中权最小的那个(显然不会是X,也不会是Y),得到最小生成树的第二条边(adjVex[j],j),然后把lowCost[j]设置为0;

  (5) 然后按(3)、(4)的规则,处理第j个顶点,直到所有顶点都被连接起来(注意,最小连通树是针对连通网的);

  下面我们会根据各种存储方式进行举例。

5.1.1 邻接矩阵的最小生成树

  假设有以下无向网:

image.png

  我们定义两个数组,一个X={},Y={V0、v1、v2、v3、v4、v5、v6、v7、v8},其中X表示已连通的顶点,Y表示未连通的顶点。

  先从第一个顶点v0开始,把它加入X,表示已连通,这时,X={v0},Y={v1、v2、v3、v4、v5、v6、v7、v8}。

  接下来,看X中的V0与其他顶点关联时权值情况,发现(v0,v1)的权值最小,因此认为,v0和v1是最小生成树的一个边,此时X={v0、v1},Y={v2、v3、v4、v5、v6、v7、v8}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V0,v5)之间的权值最小,因此认为(V0,v5)是最小生成树的一条边,此时X={v0、v1、v5},Y={v2、v3、v4、v6、v7、v8}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V1,v8)之间的权值最小,因此认为(V1,v8)是最小生成树的一条边,此时X={v0、v1、v5、v8},Y={v2、v3、v4、v6、v7}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V8,v2)之间的权值最小,因此认为(V8,v2)是最小生成树的一条边,此时X={v0、v1、v5、v8、v2},Y={v3、v4、v6、v7}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V1,v6)之间的权值最小,因此认为(V1,v6)是最小生成树的一条边,此时X={v0、v1、v5、v8、v2、v6},Y={v3、v4、v7}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V6,v7)之间的权值最小,因此认为(V6,v7)是最小生成树的一条边,此时X={v0、v1、v5、v8、v2、v6、v7},Y={v3、v4}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V7,v4)之间的权值最小,因此认为(V7,v4)是最小生成树的一条边,此时X={v0、v1、v5、v8、v2、v6、v7、v4},Y={v3}:

image.png

  然后,再看X中的所有顶点与Y中的所有顶点的权值,发现边现(V7,v3)之间的权值最小,因此认为(V7,v3)是最小生成树的一条边,此时X={v0、v1、v5、v8、v2、v6、v7、v4、v3},Y={}:

image.png

  这时,Y已处理完毕,所有顶点都连起来了,形成了最小生成树。

  观察一下,我们在获取最小生成树的边时,第一步是从arc[0][j]取权最小的,取到了(v0,v1),第二步,是从arc[0][j]和arc[1][j]中取权值最小的,取到了(v0,v5),第三步,是从arc[0][j]、arc[1][j]和arc[5][j]中取权最小的,取到了(v1,v8),也即,规则是:

  (1) 从arc[0][j]中取最小权对应的边(v0,v1),此时X={v0},把v1加入到X中;

  (2) 从arc[0][j]、arc[1][j]中取最小权对应的边(v0,v5),此时X={v0,v1},把v5加入到X中;

  (3) 从arc[0][j]、arc[1][j]、arc[5][j]中取最小权对应的边(v1,v8),此时X={v0,v1,v5},把v8加入到X中;

  (4) 从arc[0][j]、arc[1][j]、arc[5][j]…arc[x][j]中取最小权对应的边(vi,vk),然后判断vi和vk是否在X中,不在则加入到X中;

  当在arc[0][j]、arc[1][j]、arc[5][j]…arc[x][j]中取最小的权时,我们要比较x个一元数组的值,我们再做一下优化:

  (1) 从arc[0][j]中取最小权对应的边(v0,v1),此时X={v0},把v1加入到X中;

  (2) 从arc[0][j]、arc[1][j]中取最小权对应的边(v0,v5),此时X={v0,v1},把v5加入到X中,然后我们把arc[0][j]、arc[1][j]组合一下,取出arc[0][j]、arc[1][j]中较小的权放到lowCost[j]里面,同时使用一个数组adjVex[j]记录lowCost[j]对应的起始顶点下标,同时标注lowCost[0]、lowCost[1]的值为负无穷大,如下:

image.png

  (3) 这时,只需要从lowCost[j]、arc[5][j]中取最小权对应的边就行了,不用再迭代arc[0][j]和arc[1][j];

  因此,我们定义两个数组,lowCost[n]表示已处理过的顶点跟其他顶点之间的权值最小值列表,其中已处理过的顶点之间的权值设置为负无穷大,adjVex[n]表示最小权值对应的起始顶点,我们重新来看看上述无向网。

  第一步,定义lowCost[n]和adjVex[n],adjVex[n]默认值为0,lowCost[n]默认值为负无穷大:

image.png

  接下来处理第一个顶点,找到第一个顶点与其他顶点中权值最小的那条边(自身除外),具体做法是,令adjVex[n]的元素均为0,令lowCost[n]的元素值为arc[0][j],然后找到权值最小的arc[0][x],取边(adjVex[x],x),即(v0,v1):

image.png

  接下来,要比较的应是arc[0][j]和arc[1][j]中,除arc[0][0]、arc[0][1]、arc[1][0]和arc[1][1]外的其他值,取最小值,因为我们要取的是“其他顶点与顶点v0、v1中权值最小的边”,因此我们把arc[1][j]与lowCost[n](这时,lowCost即为arc[0][j])比较,把较小的写入到lowCost中,同时把较小权对应对应的顶点下标写入到adjVex[n]中,如下:

image.png

  如上可知,arc[1][2]小于lowCost[2],因此令lowCost[2]=arc[1][2]、adjVex[2]=1,arc[1][6]和arc[1][8]也相似处理。

  然后,我们取出其中的最小权,得到下一条最小生成树的边,即(v0,v5):

image.png

  接下来,是取得顶点v0、v1、v5与其他顶点的权中的最小值,生成下一条边,整体处理方式与上文类似:

image.png

  后续操作也类似,直到所有顶点处理完毕,得到最小生成树。

  让我们来看有向网的处理,按上述过程处理,得到以下结果:

image.png

  即:

image.png

  但注意观察,同样是连通B顶点,弧<B,A>实际上比<C,B>权小,所以最小生成树应该为:

image.png

  因此,有向树的最小生成树生成过程中,不仅要看“顶点X指向的顶点中权最小的”,还要看“顶点X被指向的顶点中权最小的”。

  因此,寻找最低代码的边/弧的逻辑应是“找到该顶点指向的即该顶点被指向的顶点中,代价最小的边或弧”,对于无向网当然这两个概念是一样的,但对于有向网,则要进行双向处理,因此上述有向网的处理逻辑有所不同。

  以以上有向网为例,第一步,定义三个数组:lowCost与无向网相同,tailVex和headVex代替adjVex,分别表示箭头的尾巴和头(若为无向网则尾巴和头可以任意),初始化tailVex和headVex为0,lowCost为无穷大:

image.png

  然后使用arc[0][j]和a

普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法都是用于解决最小生成树问题的算法。 最小生成树问题是指在一个无向连通图中,找到一棵生成树,使得树上所有边的权值之最小。生成树是指一个无向图的生成子图,它是一棵树,且包含图中所有顶点。 下面我们分别介绍普里姆算法克鲁斯卡尔算法: 1. 普里姆算法 普里姆算法是一种贪心算法,它从一个任意点开始,逐步扩展生成树,每次选择当前生成树到未加入的点中距离最近的点,并将其加入生成树。 具体实现步骤如下: - 随机选择一个起始点,将其加入生成树。 - 在生成树中的所有节点中,找到到未加入生成树的节点中距离最小的节点,将其加入生成树。 - 重复以上步骤,直到生成树包含了所有节点。 2. 克鲁斯卡尔算法 克鲁斯卡尔算法也是一种贪心算法,它从边集合中选择边,逐步扩展生成树,每次选择当前边集合中权值最小的边,并将其加入生成树。 具体实现步骤如下: - 将所有边按照权值从小到大排序。 - 从权值最小的边开始,逐个加入生成树,如果加入当前边会形成环,则不加入该边。 - 重复以上步骤,直到生成树包含了所有节点。 两种算法的时间复杂度都是O(ElogE),其中E为边数。普里姆算法在处理稠密图时效率更高,而克鲁斯卡尔算法在处理稀疏图时效率更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值