【随便搞搞 1】 prim算法的学习和使用

最小生成树是图论中非常有用的算法。

就不知怎么的就学会的最小生成树~~

但是最小生成树是什么呢?

标准定义如下:在边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。

听起来非常的带劲,我们就一起来探讨这一求最小生成树的算法!

prim 的四大特征:

●最小生成树算法中prim算法是耗时最长的

●最小生成树算法中prim算法是适用于求稠密图的

●最小生成树算法中prime算法最简单易懂

●请不要多打一个e否则就是prime质数了

例子:

P3366 【模板】最小生成树

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

输入输出格式

输入格式:

第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)

接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi

输出格式:

输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

输入输出样例

输入样例#1:


4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出样例#1:

7

说明

时空限制:1000ms,128M

数据规模:

对于20%的数据:N<=5,M<=20

对于40%的数据:N<=50,M<=2500

对于70%的数据:N<=500,M<=10000

对于100%的数据:N<=5000,M<=200000

样例解释:

所以最小生成树的总边权为2+2+3=7

【解析】

先来看一个例子,鲜明的阐释了prim算法的工作原理

原理:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

Prim算法的核心:始终保持TE中的边集构成一棵生成树。

图例 说明 不可选 可选 已选(Vnew)

此为原始的加权连通图。每条边一侧的数字代表其权值。 - - -

顶点D被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 C, G A, B, E, F D

下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。 C, G B, E, F A, D

算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 C B, E, G A, D, F


在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。点E最近,因此将顶点E与相应边BE高亮表示。
C, E, G A, D, F, B

这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。 C, G A, D, F, B, E

顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。 G A, D, F, B, E, C

现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 A, D, F, B, E, C, G

对于题目里的例子:我们来分析:

由于求的是最小生成树的边权之和,所以那边是最小生成树的根就无所谓了

这里假设1为最小生成树的根;

与集合{1}相连的边中只有权为2的边权最小,此时是2,3,把3加入集合{1}==>{1,3}

与集合{1,3}相连的边一共有4条,其中1 2之间的边权最小,所以把2加入集合{1,,3}==>{1,2,3}

与集合{1,2,3}相连的边一共有两条,都是3,选择靠后的1 4这条边加入集合{1,2,3}==>{1,2,3,4}

发现所有的点都被加入集合,prim算法结束 权值和为2+2+3=7

【实现】

var n,m,i,j,u,v,d:longint;
    g:array[0..5000,0..5000]of longint;
function min(a,b:longint):longint;
begin
 if a<b then exit(a)
 else exit(b);
end;
procedure prim(v0:longint);
var u:array[0..5000]of boolean;
    min:array[0..5000]of longint;
    i,j,k,tot:longint;
begin
 fillchar(min,sizeof(min),$7f);//min表示各点到集合{}的权值
 fillchar(u,sizeof(u),true);//判断这个点是否加入集合
 min[v0]:=0; d[v0]:=0;//自己到自己设为0
 for i:=2 to n do begin//由于下面加入点为1则从2开始遍历(1也可以)
  k:=0;//k表示下一个被加入集合的点
  for j:=1 to n do
     if u[j] and (min[j]<min[k]) then k:=j;//迭代法找到下一个与集合{}间权值最小的点
  u[k]:=false;//把这个点加入集合
  for j:=1 to n do
   if u[j] and (g[k,j]<min[j]) then min[j]:=g[k,j];//由于新加入一个点也属于集合中所以各点到集合的距离可能会是这个点到k点的距离,所以判
 end;
 tot:=0;
 for i:=1 to n do tot:=tot+min[i];//求出集合到各点之间的距离记为tot
 writeln(tot);//tot就是最小生成树的权值和
end;
begin
 readln(n,m);
 if m<n-1 then begin writeln(‘orz‘); halt; end;//最小生成树的边一定等于点数-1,边数小于点数-1就不构成一个联通的图
 fillchar(g,sizeof(g),$7f);//清零,用fillchar快不少
 for i:=1 to m do begin
  readln(u,v,d);
  g[u,v]:=min(g[u,v],d);
  g[v,u]:=g[u,v];//避免重复读入求最小边
 end;
 prim(1);//假定最小生成树的根为1
end.

【区别】

看完prim算法实现后,我们思考这样一个问题,单元最短路径的dijkstra的算法和prim有何相似之处呢?

不妨把dijkstra放在这里给大家显示一下区别吧!

例子:

P3371 【模板】单源最短路径

题目描述

如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度。

输入输出格式

输入格式:

第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。

接下来M行每行包含三个整数Fi、Gi、Wi,分别表示第i条有向边的出发点、目标点和长度。

输出格式:

一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647)

输入输出样例

输入样例#1:

4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

输出样例#1:

0 2 4 3

说明

时空限制:1000ms,128M

数据规模:

对于20%的数据:N<=5,M<=15

对于40%的数据:N<=100,M<=10000

对于70%的数据:N<=1000,M<=100000

对于100%的数据:N<=10000,M<=500000

样例说明:

dijkstra算法详解:

单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。

一.最短路径的最优子结构性质

该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。下面证明该性质的正确性。

假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是从k到s的最短距离,那么必定存在另一条从k到s的最短路径P‘(k,s),那么P‘(i,j)=P(i,k)+P‘(k,s)+P(s,j)<P(i,j)。则与P(i,j)是从i到j的最短路径相矛盾。因此该性质得证。

二.Dijkstra算法

由上述性质可知,如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist[i]+matrix[i][j]}。根据这种思路,

假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。

1.从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;

2.更新与i直接相邻顶点的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.知道U=V,停止。

三、流程图

【dijkstra程序实现】

const longlong=2147483647;
      maxn=5000;
var i,j,n,m,s,x,y,z:longint;
    d:array[1..maxn]of longint;
    g:array[1..maxn,1..maxn]of longint;
function min(a,b:longint):longint;
begin
 if a>b then exit(b) else exit(a);
end;
procedure dijkstra(v0:longint);
var i,j,k,minn:longint;
    u:array[1..maxn]of boolean;
begin
 fillchar(u,sizeof(u),false);
 for i:=1 to n do d[i]:=g[v0,i];d[v0]:=0;//初始值设为所有点到这个点的最短距离 u[v0]:=true;//加入v0到集合{}
 for i:=1 to n do begin//由于不知道是那个点所以不能简单从2开始,若从2开始就是当v0=1时才正确
  minn:=longlong; k:=0;//minn表示d[]数组中最小的数,k表示这个最小的数是属于哪个点的
  for j:=1 to n do
   if (not u[j])and(d[j]<minn) then begin
    minn:=d[j]; k:=j;//求minn和k
   end;
  if k=0 then break;// 如果找不到点说明所有点都被访问,跳出循环
  u[k]:=true;//加入最短距离k到集合{}表明此时k到v0的距离已经是最短了
  for j:=1 to n do
   if (not u[j])and(g[k,j]+d[k]<d[j])then//由于集合中新加入一个点,所以相对应的所有点可能通过到这个新加入的点在到v0是最短路
      d[j]:=g[k,j]+d[k];//更改最短距离
 end;
end;
begin
 readln(n,m,s);
 for i:=1 to n do
  for j:=1 to n do
   if i<>j then g[i,j]:=longlong
   else g[i,j]:=0;
 for i:=1 to m do begin
  readln(x,y,z);
  g[x,y]:=min(g[x,y],z);
 end;
 dijkstra(s);
 for i:=1 to n do write(d[i],‘ ‘);//d数组中存的是点1-n到点s的最短单源路径!
 writeln;
end.

【异同点】

区别:

1.用途:

Prim是计算最小生成树的算法,比如为N个村庄修路,怎么修花销最少。

Dijkstra是计算最短路径的算法,比如从a村庄走到其他任意村庄的距离。

2.概念

Prim出现点到集合的距离(这里用贪心),即这个点到集合中各元素的距离最小才加入最小的边

dijkstra中出现的点到集合的距离是通过每个待定的中转点来实现两点之间距离最短的

相似:

1.有关负权回路,prim和dijkstra在该问题上都是不能运行的

2.朴素的时间复杂度都是O(N2)的

3.求解方法,都是假设全部顶点的集合是V,已经被挑选出来的点的集合是U,那么二者都是从集合V-U中不断的挑选权值最低的点加入U的

【prim的优化】

这里引用百度百科的一段话:

通过邻接矩阵图表示的简易实现中,找到所有最小权边共需O(V)的运行时间。使用简单的二叉堆与邻接表来表示的话,普里姆算法的运行时间则可缩减为O(ElogV),其中E为连通图的边数,V为顶点数。如果使用较为复杂的斐波那契堆,则可将运行时间进一步缩短为O(E+VlogV),这在连通图足够密集时(当E满足Ω(VlogV)条件时),可较显著地提高运行速度。

最小边、权的数据结构 时间复杂度(总计)
邻接矩阵、搜索 O(V^2)
二叉堆、邻接表 O((V + E) log(V)) = O(E log(V))
斐波那契堆、邻接表 O(E + V log(V))

接下来给大家推荐几个好的关于prim 优化的文章,供大家参考:

●二叉堆维护+邻接表:链接

●斐波那契堆维护+邻接表:链接

时间: 2024-07-29 17:16:02

【随便搞搞 1】 prim算法的学习和使用的相关文章

【随便搞搞 2】Kruskal 的学习和使用

Tips:本题解是[随便搞搞 1]Prim算法的学习和使用 的姊妹篇,希望先阅读Prim算法. 预习及预备知识: 克鲁斯卡尔(Kruskal)算法是实现图的最小生成树最常用的算法. 大家知道,存储图的方法有2种:邻接矩阵表示法.邻接表表示法: 这里介绍的是介于这两种之间的一种方法:边接存储法(即直接用边来存储图) 但是最小生成树是什么呢? 标准定义如下:在边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小. 听起来非常的带劲,我们就一起来探讨这一求最小生成树的算法!

算法导论学习-prim算法

一. 关于最小生成树 对于无向连通图G=(V,E),其中V表示图的顶点,E表示图的边,对于每条边都有一个权值,可以理解为边a->b的权值C为从a走到b要走的路程为C.现在我们希望找到一个无回路的子集T,且有T是E的子集,T连接了所有的顶点,且其权值和最小.那么这样一个子图G‘=(V,T)称之为图G的最小生成树. 二. 最小生成树的基本性质 最小生成树的边数|T|必然服从|T|=|V|-1. 最小生成树不可以有循环 最小生成树不必是唯一的. 三. Prim算法 对于最小生成树有两种算法:prim算

数据结构(C实现)------- 最小生成树之Prim算法

[本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020] 算法描述 如果连通图是一个网,则称该网中所有生成树中权值总和最小的生成树为最小生成树,也称最小代价生成树.利用Prim算法构造的最小生成树方法思想: 假设G=(V,E)是一个具有n个顶点的连通网,顶点集V={v1,v2,...,vn}.设所求的最小生成树T=(U,TE),其中U是T的顶点集,TE是T的边集,U和TE初值均为空集. Prim算法的基本思想如下:首先从V中任取一

Hihocoder 之 #1097 : 最小生成树一&#183;Prim算法 (用vector二维 模拟邻接表,进行prim()生成树算法, *【模板】)

#1097 : 最小生成树一·Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了! 但是,问题也接踵而来——小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道路所需要的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A.B.C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过

hihoCoder - hiho一下 第二十六周 - A - 最小生成树一&#183;Prim算法

题目1 : 最小生成树一·Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了! 但是,问题也接踵而来--小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道路所需要的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A.B.C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过这两

hiho一下 第二十六周---最小生成树一&#183;Prim算法

最小生成树一·Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了! 但是,问题也接踵而来--小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道路所需要的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A.B.C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过这两条道路连通的

算法导论学习之线性时间排序+排序算法稳定性终结

前面我们学习的几种排序算法都是基于比较的,对于任何输入数据他们都是适用的,其最坏的时间复杂度不会低于nlgn: 但对于一些比较特殊的输入数据,我们可以不采取比较的方法而是采用其它的方法对其进行排序,以达到线性的时间复杂度.下面就来介绍三种这样的算法:计数排序,基数排序,桶排序(因为这几种算法不常见,我只实现了计数排序,其它两种排序用伪代码表示). 一.计数排序 算法思想:给定n个位于0–k之间的数(k是一个不太大的整数),我们可以统计出每个数前面有多少个小于它的数,然后就可以直接确定这个数在数组

图的最小生成树(二)—Prim算法

上一篇中写了图的最小生成树求法一--Kruskal算法 http://blog.csdn.net/wtyvhreal/article/details/43526695 这一篇中用另外一种方法来求解图的最小生成树,Prim算法. 图中随便选一个顶点开始,看看这个顶点有哪些边,在它的边中找一条最短的.1号有1-2,1-3,其中1-2短,选择1-2.通过它把1和2连接在一起.接下来开始枚举1和2号顶点所有的边,看看哪些边可以连接到没有被选中的顶点,并且边越短越好. Prim算法的基本思路: 将图中的所

leetcode 刷500道题,笔试/面试稳过吗?谈一谈这些年来算法的学习

想要学习算法.应付笔试或者应付面试手撕算法题,相信大部分人都会去刷 Leetcode,有读者问?如果我在 leetcode 坚持刷它个 500 道题,以后笔试/面试稳吗? 这里我说下我的个人看法,我认为不稳.下面说说为啥不稳以及算法题应该如何刷.如何学才比较好,当然,也会推荐自己学过的资料. 一.先说说笔试题 在刷 leetcode 的时候,你会发现,每道题的题意都很短,你只需要花十几秒的时间,就知道这道题是要你干嘛了,并且每道题所用道的算法思想都很明确,动态规划.递归.二分查找等,你可能很快就