最小生成树算法——Prim算法与Kruskal算法

一.算法引入.

生成树,是图论中一个重要的概念,设一张无向图 G = V , E G={V,E} G=V,E,则 G G G的一棵生成树 T = V ′ , E T={V',E} T=V,E,其中 E ′ ⊆ E E'\subseteq E EE,且 ∣ E ′ ∣ = n − 1 |E'|=n-1 E=n1,并要保证生成树连通.

最小生成树,是图论中一个重要的问题,这个问题是求无向图的一棵生成树,使得这棵生成树的点权和最大或边权和最大.

最小生成树可以拓展到有向图上,但由于树必须是无向图,所以这种类似于最小生成树的问题叫做最小树形图问题.

Prim算法,是一种用于求解无向图的最小生成树的算法,这种算法与dijkstra的过程相似,同样基于贪心,时间复杂度也相同,在稠密图上表现较优,并且可以用堆来优化.

Kruskal算法,也是一种求解无向图最小生成树的算法,这种算法基于贪心,并且采用并查集维护连通性,在稀疏图上表现较优.


二.Prim算法流程.

Prim算法的思路与dijkstra相同,都维护了两个点集 S 1 S1 S1 S 2 S2 S2,分别表示已加入生成树中和未加入生成树中.

初始 S 1 S1 S1为空, S 2 S2 S2为所有点,且维护一个 d i s dis dis数组,初始 d i s [ 1 ] dis[1] dis[1] 0 0 0,其它都为.然后重复寻找 S 2 S2 S2中dis值最小的点,加入 S 1 S1 S1中并从 S 2 S2 S2中删除,给答案 a n s ans ans加上这个点的 d i s dis dis值,并用这个点的更新 S 2 S2 S2中剩下点的 d i s dis dis值.

其实整个过程中 d i s dis dis数组的值就是生成树到这个点的距离.

算法时间复杂度 O ( n 2 ) O(n^2) O(n2),当然这个算法可以用堆优化,但是效果往往不如Kruskal,且与堆优化dijkstra相似,这里不详细展开了.


三.Kruskal算法流程.

Kruskal算法十分简单,它的原理就是用边表存边,然后按照权值从小到大排序,然后按排序后的顺序加边.

加边过程中,若一条边要加入,则这条边的两点必须在不同的连通块内,那么维护每个点在哪个连通块内和连通块合并就可以用并查集来维护了.

时间复杂度 O ( m log ⁡ n ) O(m\log n) O(mlogn),若并查集同时采用路径压缩和启发式合并(按秩合并)后,时间复杂度为 O ( m α ( n ) ) ) O(m\alpha(n))) O(mα(n))).


四.例题与代码.

题目:luogu3366.

Prim算法代码:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=5000,M=200000;
const int INF=(1<<30)-1;
 
int e[N+9][N+9],n,m;
int dis[N+9],ans,use[N+9];
 
bool prim(){
  for (int i=1;i<=n;++i)
    dis[i]=INF,use[i]=0;
  dis[1]=0;dis[0]=INF;
  int minv;
  for (int i=1;i<=n;++i){
    minv=0;
    for (int j=1;j<=n;++j)
      if (!use[j]&&dis[j]<dis[minv]) minv=j;
    ans+=dis[minv];
    use[minv]=1;
    if (dis[minv]==INF) return false;
    for (int j=1;j<=n;++j)
      if (!use[j]&&e[minv][j]<dis[j]) dis[j]=e[minv][j];
  }
  return true;
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    for (int j=1;j<=n;++j)
      e[i][j]=i==j?0:INF;
  int x,y,v;
  for (int i=1;i<=m;++i){
    scanf("%d%d%d",&x,&y,&v);
    e[x][y]=min(e[x][y],v);
    e[y][x]=min(e[y][x],v);
  }
}
 
Abigail work(){
}
 
Abigail outo(){
  prim()?printf("%d\n",ans):puts("orz");
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}

Kruskal算法代码:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=5000,M=200000;
const int INF=(1<<30)-1;
 
struct side{
  int x,y,v;
  bool operator < (const side &p)const{return v<p.v;}
}e[M+9];
int n,m;
int fa[N+9],ans;
 
int get(int u){return u==fa[u]?u:fa[u]=get(fa[u]);}
 
bool kruskal(){
  int cnt=0;
  for (int i=1;i<=n;++i) fa[i]=i;
  sort(e+1,e+1+m);
  int x,y;
  for (int i=1;i<=m;++i){
    x=get(e[i].x);y=get(e[i].y);
    if (x==y) continue;
    fa[x]=y;
    ++cnt;
    ans+=e[i].v;
  }
  return cnt==n-1;
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=m;++i)
    scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v);
}
 
Abigail work(){
}
 
Abigail outo(){
  kruskal()?printf("%d\n",ans):puts("orz");
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}

你可能感兴趣的:(算法入门)