0025算法笔记——【贪心算法】最小生成树问题

0025算法笔记——【贪心算法】最小生成树问题1 问题描述设 G V E 是无向连通带权图 即一个网络

大家好,欢迎来到IT知识分享网。

     1、问题描述

     设G =(V,E)是无向连通带权图,即一个网络。E中每条边(v,w)的权为c[v][w]。如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成树上各边权的总和称为该生成树的耗费。在G的所有生成树中,耗费最小的生成树称为G的最小生成树

     网络的最小生成树在实际中有广泛应用。例如,在设计通信网络时,用图的顶点表示城市,用边(v,w)的权c[v][w]表示建立城市v和城市w之间的通信线路所需的费用,则最小生成树就给出了建立通信网络的最经济的方案。 

     2、MST性质

     设G=(V,E)是连通带权图,U是V的真子集。如果(u,v)ÎE,且uÎU,vÎV-U,且在所有这样的边中,(u,v)的权c[u][v]最小,那么一定存在G的一棵最小生成树,它以(u,v)为其中一条边。这个性质有时也称为MST性质。 

     MST性质的证明:如图所示,假设G的任何一颗最小生成树都不包含边(u,v)。将边(u,v)添加到G的一颗最小生成树T上,将产生含有边(u,v)的圈,并且在这个圈上有一条不同于(u,v)的边(u’,v’),使得u’ÎU,v‘ÎV-U。将边(u’,v’)删去,得到G的另一颗生成树T’。由于c[u][v]<=c[u’][v’],所以T’的耗费<=T的耗费。于是T’是一颗含有边(u,v)的最小生成树,这与假设矛盾。

0025算法笔记——【贪心算法】最小生成树问题

     3、Prim算法

     设G=(V,E)是连通带权图,V={1,2,…,n}。构造G的最小生成树的Prim算法的基本思想是:首先置S={1},然后,只要S是V的真子集,就作如下的贪心选择:选取满足条件iÎS,jÎV-S,且c[i][j]最小的边,将顶点j添加到S中。这个过程一直进行到S=V时为止。在这个过程中选取到的所有边恰好构成G的一棵最小生成树。 

     算法具体代码如下:

[cpp] view plain copy print ?

  1. //4d6 贪心算法 最小生成树 Prim算法   
  2. #include “stdafx.h”   
  3. #include <fstream>     
  4. #include <string>    
  5. #include <iostream>    
  6. using namespace std;   
  7.   
  8. #define inf 9999;   
  9. const int N = 6;  
  10. ifstream fin(“4d6.txt”);  
  11.   
  12. template<class Type>  
  13. void Prim(int n,Type c[][N+1]);  
  14.   
  15. int main()  
  16. {  
  17.     int c[N+1][N+1];  
  18.     cout<<“连通带权图的矩阵为:”<<endl;  
  19.     for(int i=1; i<=N; i++)  
  20.     {  
  21.         for(int j=1; j<=N; j++)  
  22.         {  
  23.             fin>>c[i][j];      
  24.             cout<<c[i][j]<<” “;    
  25.         }  
  26.         cout<<endl;  
  27.     }  
  28.   
  29.     cout<<“Prim算法最小生成树选边次序如下:”<<endl;  
  30.     Prim(N,c);  
  31.     return 0;  
  32. }  
  33.   
  34. template<class Type>  
  35. void Prim(int n,Type c[][N+1])  
  36. {  
  37.     Type lowcost[N+1];//记录c[j][closest]的最小权值   
  38.     int closest[N+1];//V-S中点j在S中的最邻接顶点   
  39.   
  40.     bool s[N+1];  
  41.   
  42.     s[1] = true;  
  43.   
  44.     //初始化s[i],lowcost[i],closest[i]   
  45.     for(int i=2; i<=n; i++)  
  46.     {  
  47.         lowcost[i] = c[1][i];  
  48.         closest[i] = 1;  
  49.         s[i] = false;  
  50.     }  
  51.   
  52.     for(int i=1; i<n; i++)  
  53.     {  
  54.         Type min = inf;  
  55.         int j = 1;  
  56.         for(int k=2; k<=n; k++)//找出V-S中使lowcost最小的顶点j   
  57.         {  
  58.             if((lowcost[k]<min)&&(!s[k]))  
  59.             {  
  60.                 min = lowcost[k];  
  61.                 j = k;  
  62.             }  
  63.         }  
  64.         cout<<j<<‘ ‘<<closest[j]<<endl;  
  65.         s[j] = true;//将j添加到S中   
  66.   
  67.         for(int k=2; k<=n; k++)//将j添加到S中后,修改closest和lowcost的值   
  68.         {  
  69.             if((c[j][k]<lowcost[k] && (!s[k])))  
  70.             {  
  71.                 lowcost[k] = c[j][k];  
  72.                 closest[k] = j;  
  73.             }  
  74.         }  
  75.     }  
  76. }  
//4d6 贪心算法 最小生成树 Prim算法 #include "stdafx.h" #include <fstream> #include <string> #include <iostream> using namespace std; #define inf 9999; const int N = 6; ifstream fin("4d6.txt"); template<class Type> void Prim(int n,Type c[][N+1]); int main() { int c[N+1][N+1]; cout<<"连通带权图的矩阵为:"<<endl; for(int i=1; i<=N; i++) { for(int j=1; j<=N; j++) { fin>>c[i][j]; cout<<c[i][j]<<" "; } cout<<endl; } cout<<"Prim算法最小生成树选边次序如下:"<<endl; Prim(N,c); return 0; } template<class Type> void Prim(int n,Type c[][N+1]) { Type lowcost[N+1];//记录c[j][closest]的最小权值 int closest[N+1];//V-S中点j在S中的最邻接顶点 bool s[N+1]; s[1] = true; //初始化s[i],lowcost[i],closest[i] for(int i=2; i<=n; i++) { lowcost[i] = c[1][i]; closest[i] = 1; s[i] = false; } for(int i=1; i<n; i++) { Type min = inf; int j = 1; for(int k=2; k<=n; k++)//找出V-S中使lowcost最小的顶点j { if((lowcost[k]<min)&&(!s[k])) { min = lowcost[k]; j = k; } } cout<<j<<' '<<closest[j]<<endl; s[j] = true;//将j添加到S中 for(int k=2; k<=n; k++)//将j添加到S中后,修改closest和lowcost的值 { if((c[j][k]<lowcost[k] && (!s[k]))) { lowcost[k] = c[j][k]; closest[k] = j; } } } }

        
上述代码中,数组closest[j]是j(
j
Î
V-S)在S中的领接顶点,它与j在S中的其它顶点相比较有c[j][cloest[j]]<=c[j][k]。lowest[j]的值就是c[j][cloest[j]]。在算法执行过程中,先找出V-S中是lowest值最小的顶点j,然后根据数组closest选取边(j,cloest[j]),最后将j添加到S中,并对closest和lowest作必要的修改。

     程序运行结果为:

0025算法笔记——【贪心算法】最小生成树问题

      例如,对于下图中的带权图,按Prim算法选取边的过程如图所示:

0025算法笔记——【贪心算法】最小生成树问题0025算法笔记——【贪心算法】最小生成树问题

     利用最小生成树性质和数学归纳法容易证明,上述算法中的边集合T始终包含G的某棵最小生成树中的边。因此,在算法结束时,T中的所有边构成G的一棵最小生成树,Prim算法所需要的计算时间为O(n^2)。

     4、Kruskal算法

     Kruskal算法构造G的最小生成树的基本思想是,首先将G的n个顶点看成n个孤立的连通分支。将所有的边按权从小到大排序。然后从第一条边开始,依边权递增的顺序查看每一条边,并按下述方法连接2个不同的连通分支:当查看到第k条边(v,w)时,如果端点v和w分别是当前2个不同的连通分支T1和T2中的顶点时,就用边(v,w)将T1和T2连接成一个连通分支,然后继续查看第k+1条边;如果端点v和w在当前的同一个连通分支中,就直接再查看第k+1条边。这个过程一直进行到只剩下一个连通分支时为止

     实现算法Kruskal需要准备两个数据结构:(1)最小堆MinHeap,按权的递增顺序查看的边的序列可以看做是一个优先队列,它的优先级为边权;(2)并查集UnionFind,主要包括Union(a,b)和Find(v)两个基本运算:Union(a,b)将两个连通分支a和b连接起来,所得的结果为A或B;Find(v)返货U总包含顶点v的连通分支的名字。这个运算用来确定某条边的两个端点所属的连通分支。

    具体代码如下:

     (1)MinHeap.h

[cpp] view plain copy print ?

  1. #include <iostream>   
  2. using namespace std;  
  3. template<class T>  
  4. class MinHeap  
  5. {  
  6.     private:  
  7.         T *heap; //元素数组,0号位置也储存元素   
  8.         int CurrentSize; //目前元素个数   
  9.         int MaxSize; //可容纳的最多元素个数   
  10.   
  11.         void FilterDown(const int start,const int end); //自上往下调整,使关键字小的节点在上   
  12.         void FilterUp(int start); //自下往上调整   
  13.   
  14.     public:  
  15.         MinHeap(int n=1000);  
  16.         ~MinHeap();  
  17.         bool Insert(const T &x); //插入元素   
  18.   
  19.         T RemoveMin(); //删除最小元素   
  20.         T GetMin(); //取最小元素   
  21.   
  22.         bool IsEmpty() const;  
  23.         bool IsFull() const;  
  24.         void Clear();  
  25. };  
  26.   
  27. template<class T>  
  28. MinHeap<T>::MinHeap(int n)  
  29. {  
  30.     MaxSize=n;  
  31.     heap=new T[MaxSize];  
  32.     CurrentSize=0;  
  33. }  
  34.   
  35. template<class T>  
  36. MinHeap<T>::~MinHeap()  
  37. {  
  38.     delete []heap;  
  39. }  
  40.   
  41. template<class T>  
  42. void MinHeap<T>::FilterUp(int start) //自下往上调整   
  43. {  
  44.     int j=start,i=(j-1)/2; //i指向j的双亲节点   
  45.     T temp=heap[j];  
  46.   
  47.     while(j>0)  
  48.     {  
  49.         if(heap[i]<=temp)  
  50.             break;  
  51.         else  
  52.         {  
  53.             heap[j]=heap[i];  
  54.             j=i;  
  55.             i=(i-1)/2;  
  56.         }  
  57.     }  
  58.     heap[j]=temp;  
  59. }  
  60.   
  61. template<class T>  
  62. void MinHeap<T>::FilterDown(const int start,const int end) //自上往下调整,使关键字小的节点在上   
  63. {  
  64.     int i=start,j=2*i+1;  
  65.     T temp=heap[i];  
  66.     while(j<=end)  
  67.     {  
  68.         if( (j<end) && (heap[j]>heap[j+1]) )  
  69.             j++;  
  70.         if(temp<=heap[j])  
  71.             break;  
  72.         else  
  73.         {  
  74.             heap[i]=heap[j];  
  75.             i=j;  
  76.             j=2*j+1;  
  77.         }  
  78.     }  
  79.     heap[i]=temp;  
  80. }  
  81.   
  82. template<class T>  
  83. bool MinHeap<T>::Insert(const T &x)  
  84. {  
  85.     if(CurrentSize==MaxSize)  
  86.         return false;  
  87.   
  88.     heap[CurrentSize]=x;  
  89.     FilterUp(CurrentSize);  
  90.   
  91.     CurrentSize++;  
  92.     return true;  
  93. }  
  94.   
  95. template<class T>  
  96. T MinHeap<T>::RemoveMin( )  
  97. {  
  98.     T x=heap[0];  
  99.     heap[0]=heap[CurrentSize-1];  
  100.   
  101.     CurrentSize–;  
  102.     FilterDown(0,CurrentSize-1); //调整新的根节点   
  103.   
  104.     return x;  
  105. }  
  106.   
  107. template<class T>  
  108. T MinHeap<T>::GetMin()  
  109. {  
  110.     return heap[0];  
  111. }  
  112.   
  113. template<class T>  
  114. bool MinHeap<T>::IsEmpty() const  
  115. {  
  116.     return CurrentSize==0;  
  117. }  
  118.   
  119. template<class T>  
  120. bool MinHeap<T>::IsFull() const  
  121. {  
  122.     return CurrentSize==MaxSize;  
  123. }  
  124.   
  125. template<class T>  
  126. void MinHeap<T>::Clear()  
  127. {  
  128.     CurrentSize=0;  
  129. }  
#include <iostream> using namespace std; template<class T> class MinHeap { private: T *heap; //元素数组,0号位置也储存元素 int CurrentSize; //目前元素个数 int MaxSize; //可容纳的最多元素个数 void FilterDown(const int start,const int end); //自上往下调整,使关键字小的节点在上 void FilterUp(int start); //自下往上调整 public: MinHeap(int n=1000); ~MinHeap(); bool Insert(const T &x); //插入元素 T RemoveMin(); //删除最小元素 T GetMin(); //取最小元素 bool IsEmpty() const; bool IsFull() const; void Clear(); }; template<class T> MinHeap<T>::MinHeap(int n) { MaxSize=n; heap=new T[MaxSize]; CurrentSize=0; } template<class T> MinHeap<T>::~MinHeap() { delete []heap; } template<class T> void MinHeap<T>::FilterUp(int start) //自下往上调整 { int j=start,i=(j-1)/2; //i指向j的双亲节点 T temp=heap[j]; while(j>0) { if(heap[i]<=temp) break; else { heap[j]=heap[i]; j=i; i=(i-1)/2; } } heap[j]=temp; } template<class T> void MinHeap<T>::FilterDown(const int start,const int end) //自上往下调整,使关键字小的节点在上 { int i=start,j=2*i+1; T temp=heap[i]; while(j<=end) { if( (j<end) && (heap[j]>heap[j+1]) ) j++; if(temp<=heap[j]) break; else { heap[i]=heap[j]; i=j; j=2*j+1; } } heap[i]=temp; } template<class T> bool MinHeap<T>::Insert(const T &x) { if(CurrentSize==MaxSize) return false; heap[CurrentSize]=x; FilterUp(CurrentSize); CurrentSize++; return true; } template<class T> T MinHeap<T>::RemoveMin( ) { T x=heap[0]; heap[0]=heap[CurrentSize-1]; CurrentSize--; FilterDown(0,CurrentSize-1); //调整新的根节点 return x; } template<class T> T MinHeap<T>::GetMin() { return heap[0]; } template<class T> bool MinHeap<T>::IsEmpty() const { return CurrentSize==0; } template<class T> bool MinHeap<T>::IsFull() const { return CurrentSize==MaxSize; } template<class T> void MinHeap<T>::Clear() { CurrentSize=0; }

     (2)UnionFind.h

[cpp] view plain copy print ?

  1. class UnionFind  
  2. {  
  3.     public:   
  4.         UnionFind(int);  
  5.         ~UnionFind();  
  6.     public:  
  7.         int Find(int);  
  8.         void Union(intint);  
  9.     private:  
  10.         int EleNum;  
  11.         int *Parents;  
  12.         int *Rank;  
  13. };  
  14.   
  15. UnionFind::UnionFind(int n)  
  16. {  
  17.     EleNum = n;  
  18.     Parents = new int[EleNum];  
  19.     Rank = new int[EleNum];  
  20.     for(int i = 0; i < EleNum; i++)  
  21.     {  
  22.         Parents[i] = -1;  
  23.         Rank[i] = 1;  
  24.     }  
  25. }  
  26.   
  27. UnionFind::~UnionFind()  
  28. {  
  29.     delete[] Parents;  
  30.     delete[] Rank;  
  31. }  
  32.   
  33. int UnionFind::Find(int i)  
  34. {  
  35.     int r = i;  
  36.     while(Parents[r] != -1)   
  37.         r = Parents[r];  
  38.     while(r != i)  
  39.     {  
  40.         int q = Parents[i];  
  41.         Parents[i] = r;  
  42.         i = q;  
  43.     }  
  44.     return r;  
  45. }  
  46.   
  47. void UnionFind::Union(int i, int j)  
  48. {  
  49.     int a = Find(i);  
  50.     int b = Find(j);  
  51.   
  52.     if(a == b) return;  
  53.     if(Rank[a] > Rank[b])  
  54.     {  
  55.         Parents[b] = a;  
  56.         Rank[a] += Rank[b];   
  57.     }  
  58.     else  
  59.     {  
  60.         Parents[a] = b;  
  61.         Rank[b] += Rank[a];  
  62.     }  
  63. }  
class UnionFind { public: UnionFind(int); ~UnionFind(); public: int Find(int); void Union(int, int); private: int EleNum; int *Parents; int *Rank; }; UnionFind::UnionFind(int n) { EleNum = n; Parents = new int[EleNum]; Rank = new int[EleNum]; for(int i = 0; i < EleNum; i++) { Parents[i] = -1; Rank[i] = 1; } } UnionFind::~UnionFind() { delete[] Parents; delete[] Rank; } int UnionFind::Find(int i) { int r = i; while(Parents[r] != -1) r = Parents[r]; while(r != i) { int q = Parents[i]; Parents[i] = r; i = q; } return r; } void UnionFind::Union(int i, int j) { int a = Find(i); int b = Find(j); if(a == b) return; if(Rank[a] > Rank[b]) { Parents[b] = a; Rank[a] += Rank[b]; } else { Parents[a] = b; Rank[b] += Rank[a]; } }

     (3)4d6-2.cpp

[cpp] view plain copy print ?

  1. //4d6-2 贪心算法 最小生成树 Kruskal算法   
  2. #include “stdafx.h”   
  3. #include “MinHeap.h”   
  4. #include “UnionFind.h”   
  5. #include <fstream>     
  6. #include <string>    
  7. #include <iostream>    
  8. using namespace std;   
  9.   
  10. const int N = 10;//图的边数   
  11. const int M = 6;//图的顶点数   
  12. ifstream fin(“4d6-2.txt”);  
  13.   
  14. template <class Type>  
  15. class EdgeNode  
  16. {  
  17.     friend ostream& operator <<(ostream&,EdgeNode<Type>);  
  18.       
  19.     //不知道为什么,一去掉注释就会报错误LNK2019: 无法解析的外部符号 错误   
  20.     //friend bool Kruskal(int,int,EdgeNode<Type>[],EdgeNode<Type>[]);   
  21.     friend int main(void);  
  22.   
  23.     public:  
  24.         operator Type() const  
  25.         {  
  26.             return weight;  
  27.         }  
  28.     //暂时这么放了,日后解决   
  29.     //private:   
  30.         Type weight;  
  31.         int u,v;  
  32. };  
  33.   
  34. template <class Type>  
  35. bool Kruskal(int n,int e,EdgeNode<Type> E[],EdgeNode<Type> t[]);  
  36.   
  37. int main()  
  38. {  
  39.     EdgeNode<int> E[N+1],t[N+1];//存储连通带权图所有边的两端顶点,权)   
  40.     cout<<“连通带权图所有边的两端顶点,权分别为:”<<endl;  
  41.     for(int i=1; i<=N; i++)  
  42.     {  
  43.         fin>>E[i].u>>E[i].weight>>E[i].v;      
  44.         cout<<“u:”<<E[i].u<<“,weight:”<<E[i].weight<<“,v:”<<E[i].v;    
  45.         cout<<endl;  
  46.     }  
  47.   
  48.     if(Kruskal(M+1,N,E,t))  
  49.     {  
  50.         cout<<“Kruskal算法最小生成树选择结果为:”<<endl;  
  51.         for(int i=1; i<M; i++)  
  52.         {  
  53.             cout<<“u:”<<t[i].u<<“,weight:”<<t[i].weight<<“,v:”<<t[i].v;   
  54.             cout<<endl;  
  55.         }  
  56.     }  
  57.       
  58.     return 0;  
  59. }  
  60.   
  61. template <class Type>  
  62. bool Kruskal(int n,int e,EdgeNode<Type> E[],EdgeNode<Type> t[])  
  63. {  
  64.     MinHeap<EdgeNode<Type>> H(e);  
  65.   
  66.     //初始化最小堆   
  67.     for(int i=1; i<=e; i++) H.Insert(E[i]);  
  68.   
  69.     UnionFind U(n);  
  70.   
  71.     int k = 1;  
  72.     while(e && k<n-1)  
  73.     {  
  74.         EdgeNode<int> x;  
  75.         x = H.RemoveMin();  
  76.         e–;  
  77.   
  78.         //返回u中包含顶点V的连通分支的名字   
  79.         int a = U.Find(x.u);  
  80.         int b = U.Find(x.v);  
  81.   
  82.         if(a!=b)  
  83.         {  
  84.             t[k++] = x;  
  85.             U.Union(a,b);  
  86.         }  
  87.     }  
  88.   
  89.     return (k==n-1);  
  90. }  
//4d6-2 贪心算法 最小生成树 Kruskal算法 #include "stdafx.h" #include "MinHeap.h" #include "UnionFind.h" #include <fstream> #include <string> #include <iostream> using namespace std; const int N = 10;//图的边数 const int M = 6;//图的顶点数 ifstream fin("4d6-2.txt"); template <class Type> class EdgeNode { friend ostream& operator <<(ostream&,EdgeNode<Type>); //不知道为什么,一去掉注释就会报错误LNK2019: 无法解析的外部符号 错误 //friend bool Kruskal(int,int,EdgeNode<Type>[],EdgeNode<Type>[]); friend int main(void); public: operator Type() const { return weight; } //暂时这么放了,日后解决 //private: Type weight; int u,v; }; template <class Type> bool Kruskal(int n,int e,EdgeNode<Type> E[],EdgeNode<Type> t[]); int main() { EdgeNode<int> E[N+1],t[N+1];//存储连通带权图所有边的两端顶点,权) cout<<"连通带权图所有边的两端顶点,权分别为:"<<endl; for(int i=1; i<=N; i++) { fin>>E[i].u>>E[i].weight>>E[i].v; cout<<"u:"<<E[i].u<<",weight:"<<E[i].weight<<",v:"<<E[i].v; cout<<endl; } if(Kruskal(M+1,N,E,t)) { cout<<"Kruskal算法最小生成树选择结果为:"<<endl; for(int i=1; i<M; i++) { cout<<"u:"<<t[i].u<<",weight:"<<t[i].weight<<",v:"<<t[i].v; cout<<endl; } } return 0; } template <class Type> bool Kruskal(int n,int e,EdgeNode<Type> E[],EdgeNode<Type> t[]) { MinHeap<EdgeNode<Type>> H(e); //初始化最小堆 for(int i=1; i<=e; i++) H.Insert(E[i]); UnionFind U(n); int k = 1; while(e && k<n-1) { EdgeNode<int> x; x = H.RemoveMin(); e--; //返回u中包含顶点V的连通分支的名字 int a = U.Find(x.u); int b = U.Find(x.v); if(a!=b) { t[k++] = x; U.Union(a,b); } } return (k==n-1); }

     
例如,对前面的连通带权图,按Kruskal算法顺序得到的最小生成树上的边如下图所示:

0025算法笔记——【贪心算法】最小生成树问题
     当图的边数为e时,Kruskal算法所需的计算时间是 0025算法笔记——【贪心算法】最小生成树问题。当0025算法笔记——【贪心算法】最小生成树问题时,Kruskal算法比Prim算法差,但当0025算法笔记——【贪心算法】最小生成树问题时,Kruskal算法却比Prim算法好得多。程序运行结果为:

0025算法笔记——【贪心算法】最小生成树问题   

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/149058.html

(0)
上一篇 2025-03-26 16:10
下一篇 2025-03-26 16:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信