算法导论 第23章 最小生成树 ,Kruskal算法,和Prim算法d

最小生成树问题是给定一个连通无向图(V,E),相应边的权值,要求一个无环子集T⊆E,并且连接了所有的顶点,而且所有边权值相加最小。由于是连通的无环的,所以必然是一棵树。两种常用的方法Kruskal算法和Prim算法都采取的是贪心策略,只是贪心策略有所不同。

 而且这两个贪心策略都可以用一个通用的方法来描述。就是不停的找安全边加到集合T中,直到集合T成为最小生成树。寻找安全边的方法就是,从一个尊重集合T的切割中, 找一条横跨切割的轻量边,这条边就是安全边。

        把所有点分成两个集合,且这个横跨两个集合的所有边都不在T中,其中权重最小的边就属于最小生成树。

Kruskal算法:

找安全边的方法是,先对边的权重排序,从从最小权重的边开始找,初始化的切割时空集A,和全集V,然后去找横跨A和V集合的边(u,v),且权重最小的边,加到T, 把点u,v叫到集合A,每次找权重最小的边的时候都要判断这条边的点是否已经属于A了,如果属于A了那么这条边不是安全边。所以代码的实现中要用到并查集,来表示点属于的集合。

  实现代码:

void  Kruskal(Graph g)
{
UF  noLink(g.n); // 初始化不相交集合 V个点  V次make_set
vectorA;

sort(g.edges.begin(),g.edges.end(),comp);  //按边权重从小到大 排序, nlgn

for(int i=0;i!=g.edges.size();++i){           //把所有边都试一边 
    edge tem=g.edges[i];
    if(noLink.FindSet(tem.u)!=noLink.FindSet(tem.v)){    //边(u,v)必须是横跨两个集合的  
        A.push_back(tem);     
        noLink.Union(tem.u,tem.v);    //按秩合并 
    }

}

}
Kruskal 的时间复杂度:

排序:  ElgE

V次make_setE次Find_Set和Union        根据并查集的知识 (V+E)α(V) 

  由于α(V)增加很慢,所有Kruskal 运行时间占主要的是 排序! 

ElgE+(V+E)α(V)  

Prim算法:

相对于Kruskal从小权重的边开始找,Prim算法类似于Dijkstra算法,从一个点开始慢慢向外成长。

这里的切割是 已经加入到生成树的点  和 还未加入到生成树点的。 初始是随便找一个点开始, 然后每次找一条边(u,v),其中u属于生成树,v还未属于生成树,且边(u,v)的权重是横跨切割里面最小的。当所有点都加到了生成树,算法结束。

下面具体实现的时候,维护一个点集合Q,里面的点是不属于生成树的点,里面的点v ,都维护一个d值,表示(u,v)的权值,我们每次迭代需要的是权值最小的。每次取出一个点v,都要对它相邻的点进行维护,但是如果相邻的点已经在生成树里了,则不需要维护。具体看实现代码。

Q集合的实现 可以是数组,二叉最小队列,或者斐波纳契堆来实现。我用的是最小堆。但是用二叉堆,貌似都要维护一个图结点与最小堆数组位置互指的句柄。这样才能维护堆的性质。这里的句柄的实现跟Dijkstra方法里的一样。

  实现代码:

#include
#include
#include
#include "MiniPriorityQueue.h"
 using namespace std;





void printGraph(Graph g)
{
   for(int i=1;i!=g.n+1;++i){
      adjnode p=g.Adj[i];
      while(p!=NULL){
        cout<adjvex<<" : "<weight<<"      ";
        p=p->nextvex;
      }
      cout<S;
while(Q.length!=0){                      //V次
    vex x=Q.ExtractMin(g);  //lgV        //从Q中取出

    int th=x.n; //取出点的序号
    S.push_back(g.vexs[th]);
    g.vexs[th].visit=true;  //这个点已被取出  其实是取出一条安全边( vexs[th], vexs[th].predecessor )  横跨切割(Q,S)的
    for(adjnode p=g.Adj[th];p!=NULL;p=p->nextvex){  //
        int m=p->adjvex;
      if( p->weight< g.vexs[m].d && !g.vexs[m].visit){   // 更新还未取出点Q中的点的d值
        g.vexs[m].d=p->weight;
        g.vexs[m].predecessor=th;
        Q.DecreaseKey(g.vexs[m].handle,p->weight,g );   //2E 次  O(E*lgv)
      }
    }
}

cout<<"各边如下:"<
头文件:(图的建立,堆的建立)
#ifndef MINIPRIORITYQUEUE_H_INCLUDED
#define MINIPRIORITYQUEUE_H_INCLUDED

#include
#include
#include
 using namespace std;
 class node
 {   //邻接链表结构
  public:
      node():adjvex(0),nextvex(NULL){}
     int  adjvex;
     int  weight; //邻接链表结点直接带权重值
     node *nextvex;
 };

class vex
{ //点结构
public:
  int d;
  int predecessor; //前驱
  int n; //表明自己几号点
  int handle; //句柄,指示在堆的位置,堆由数组建立
  bool visit;
};

typedef node *  adjnode;


 class Graph
{ //图结构
 public:
    int n;//顶点个数
    adjnode *Adj;
    vex * vexs;
 };



void CreateGraph(Graph &g)
{

    ifstream  infile("test.txt");
    cin.rdbuf(infile.rdbuf());
 if(g.Adj==NULL)cout<<1<>n;

 g.Adj=new adjnode[n+1];// 0号不用
 g.vexs=new vex[n+1];
 g.n=n;

cout<<"依次输入与每个顶点相邻的顶点号和权值,以-1为结束"<>x>>w;
    if(x==-1)
        break;
  else
   if(ex!=0){
    ne->nextvex=new node;
    ne=ne->nextvex;
   }
   ++ex;
    ne->adjvex=x;
    ne->weight=w;
  }//while
}
}

class  MiniPriorityQueue{   //自建堆

public:

 MiniPriorityQueue(Graph g){ //初始化
    Q=new vex[g.n+1];
    length=g.n;
    for(int i=1;i!=g.n+1;++i){
    Q[i]=g.vexs[i];
    g.vexs[i].handle=i; //更新句柄
 }
 }

void MinHeapFix(int i,Graph &g);  //维护堆的性质

void BuildMinHeap(Graph &g);//建堆

void DecreaseKey(int j,int k,Graph &g);   //更新d值

vex  ExtractMin(Graph &g){ //取出最小值,
vex t=Q[1];
g.vexs[Q[length].n].handle=1;// 更新句柄
Q[1]=Q[length];
length--;
MinHeapFix(1,g);
return t;
}



public:
    vex* Q;
    int length;
};



void MiniPriorityQueue::DecreaseKey(int j,int k,Graph &g){  //更新d值

Q[j].d=k;

      int i=j;
   while(i>1 && Q[i/2].d>Q[i].d){
 g.vexs[Q[i/2].n].handle=i;
 g.vexs[Q[i].n ].handle=i/2;
swap(Q[i/2],Q[i]);
  i=i/2;
   }


}




void  MiniPriorityQueue::MinHeapFix(int i,Graph &g){ //堆建立过程中也要时刻更新句柄
    int l=i*2;
    int r=i*2+1;
    int minest;
    if(l<=length&& Q[l].d=1;--start){
    MinHeapFix(start,g);
    }
    }



#endif // MINIPRIORITYQUEUE_H_INCLUDED

时间复杂度分析:

初始化和建堆  O(V)

while循环V次 每次包含一个EXTRACT-MIN 操作lgV, VlgV

for循环 无向图邻接表的长度和为2E, 在循环里可能执行Decrease-key操作,   2E*lgV=O(ElgV)

 总的时间复杂度  (E+V)lgV


图, a ,b,c,d,e,i,e,h,g,f  分别对应 1-9

算法导论 第23章 最小生成树 ,Kruskal算法,和Prim算法d_第1张图片


运行结果:



算法导论 第23章 最小生成树 ,Kruskal算法,和Prim算法d_第2张图片


你可能感兴趣的:(算法导论)