主要講解求解最小產生樹的兩種不同的貪心策略,最小產生樹的概念就不講解了,下面直接給出兩種演算法的理解。
在編程中通過結點的flag標記該結點屬於哪棵樹,開始時所有結點的flag大於零,若其所在邊被選擇為最小產生樹的邊,則把該結點的flag標記為相應的負數。下面結合圖講解:
其中紅色邊表示該邊的兩端結點在同一棵樹中,h、g、f結點在同一棵樹中,則h、g、f 結點的標示符flag都為-1;c、i結點在同一棵樹中且不是與h、g、f在同一棵樹中,則c、i結點的標示符flag為-2;其他結點的flag為大於零的整數。當選擇圖中的最小邊時,若邊的兩端結點的flag標誌為負值且相等,則表示該邊在最小產生樹中了,可以排除不會再被選中,中的:權值為1、2、2的三個邊。那麼權值最小的邊為4有三個,可以先選擇其中的任意一個:
1.若選擇邊的兩端結點為a、b,首先判斷a、b結點中的標示符flag都為正,表示a、b兩點在不同的樹中並且樹中只有自己。將兩個樹合并,即a、b結點標示符flag為-3
2. 選擇權值最小的邊,有兩個未被選的邊權值為4(因為邊的端結點為a、b,兩個端點的flag值為負且相等,表明該邊已被選為最小產生樹的邊),此次選擇到端點為e、f 的邊。由於f的標示符flag為負值,表明f和其他結點組成一棵樹,e的結點為正,表明其單獨構成一棵樹,直接把e結點構成的樹合并到 f 結點所在的樹中(把f 結點的flag賦值給e結點的flag,便可完成樹的合并)
3. 選擇權值最小的邊,只有一個未被選的權值為4的邊,端結點為c、f。c、f 的標示符flag都為負且不相等,一個為-2一個為-1。表明c和其他結點組成一棵樹,f 和其他結點組成一棵樹,並且是不同的兩棵樹。那麼要把這兩棵樹合并,做法:把c所在的樹中所有結點的flag 賦值為f 的標示符flag的值(即把c所在樹中的結點c、i的flag賦值為-1),這樣便完成兩棵樹的合并。
一直合并,知道所有的結點都在同一棵樹中,即所有的結點的標示符flag都為負值且相等。
Minspantree.h
#pragma once#include<iostream>#include<string>#include<vector>using namespace std;template<typename Comparable>struct Node //圖中的結點E{Comparable element;//結點的元素vector<Node<Comparable>*> next; //結點的串連指標,指向多個結點vector<Node<Comparable>*> father; //父親結點int flag; //主要用於Kruskal演算法標記該結點屬於那一棵樹Node(Comparable e,int f):element(e),flag(f){}};template<typename Comparable>struct Edge //圖中邊{Node<Comparable>* N1; //結點1Node<Comparable>* N2; //結點2int weight; //兩個結點間的權值Edge(Node<Comparable>* n1,Node<Comparable>* n2,int w):N1(n1),N2(n2),weight(w){}};template<typename Comparable>class graph{public:void insert(Comparable *a,int *matrix,int *w,int n);//a:圖中個結點的元素;matrix:鄰接矩陣 //w為相連結點間的權值,n結點數void Kruskal();void Prim();private:vector<Node<Comparable>*> root; //儲存每個結點的地址vector<Edge<Comparable>*> side; //儲存每條邊的地址vector<Edge<Comparable>*> Mintree;//儲存最小產生樹的邊bool find(Edge<Comparable>* edge,int f);//Kruskal,判斷某邊的兩端結點是否在一顆樹中void changef(Node<Comparable>* t,int f);//Kruskal,將結點合并,}; //即把結點中的標示flag改為相等的數值,表示在同一棵樹中
Minspantree.cpp
#include "stdafx.h"#include"Minspantree.h"#include<iostream>#include<string>#include<vector>using namespace std;template<typename Comparable>void graph<Comparable>::insert(Comparable *a,int *matrix,int *w,int n)//實現圖的構建,包括結點和邊{for(int i=0;i<n;i++){Node<Comparable>* node=new Node<Comparable>(a[i],i+1);root.push_back(node);}Node<Comparable>* node=NULL;Node<Comparable>* temp=NULL;int k=0;for(int i=0;i<n;i++){node=root[i];for(int j=0;j<n;j++){if(matrix[n*i+j]!=0){temp=root[j];node->next.push_back(temp);temp->father.push_back(node);if(j>i){Edge<Comparable>* edge=new Edge<Comparable>(node,temp,w[k]);k=k+1;side.push_back(edge);}}}}}template<typename Comparable>void graph<Comparable>::Kruskal() //Kruskal演算法實現最小產生樹{ //其中結點中的flag標示一個邊的兩端結點是否在同一棵樹中int en=side.size(); //圖中總共有多少條邊Edge<Comparable>* edge=NULL;for(int i=0;i<en;i++) //排序,按邊權重升序排序。註:此處可以採用快排可減少排序的時間{for(int j=i;j<en;j++)if(side[i]->weight>side[j]->weight){edge=side[i];side[i]=side[j];side[j]=edge;}}int f=-1;edge=side[0]; //將權值最小的邊放入到樹中Mintree.push_back(edge);edge->N1->flag=f;edge->N2->flag=f; //將edge兩端結點的標誌flag設定為同一個數,表明兩個結點在同一顆樹中for(int i=1;i<en;i++) {edge=side[i];f=f-1;if(find(edge,f)) //如果edge兩個結點不是同時在同一顆樹中,則串連該條邊Mintree.push_back(edge);}cout<<"Kruskal演算法最小產生樹:"<<endl;int n=Mintree.size();int sum=0;for(int i=0;i<n;i++){edge=Mintree[i];cout<<"("<<edge->N1->element<<","<<edge->N2->element<<","<<edge->weight<<")"<<" ";sum+=edge->weight;}cout<<endl;cout<<"最小權值:"<<sum<<endl;}template<typename Comparable>bool graph<Comparable>::find(Edge<Comparable>* edge,int f){if(edge->N1->flag>0&&edge->N2->flag>0){edge->N1->flag=f;edge->N2->flag=f;return true;}else if(edge->N1->flag>0&&edge->N2->flag<0){edge->N1->flag=edge->N2->flag;return true;}else if(edge->N1->flag<0&&edge->N2->flag>0){edge->N2->flag=edge->N1->flag;return true;}else if(edge->N1->flag<0&&edge->N2->flag<0&&edge->N1->flag!=edge->N2->flag){changef(edge->N2,edge->N1->flag);return true;}elsereturn false;}template<typename Comparable> void graph<Comparable>::changef(Node<Comparable>* t,int f){int n=Mintree.size();int flag=t->flag;Node<Comparable>* n1=NULL;Node<Comparable>* n2=NULL;Edge<Comparable>* edge=NULL;for(int i=0;i<n;i++){edge=Mintree[i];n1=edge->N1;n2=edge->N2;if(n1->flag==flag)n1->flag=f;if(n2->flag==flag)n2->flag=f;}t->flag=f;}template<typename Comparable> void graph<Comparable>::Prim() //Prim演算法實現最小產生樹{Node<Comparable>* node=NULL;Edge<Comparable>* edge=NULL;Edge<Comparable>* result=new Edge<Comparable>(NULL,NULL,1000);Edge<Comparable>* temp=new Edge<Comparable>(NULL,NULL,1000);int en=side.size();int n=root.size();node=root[0];node->flag=-1; //初始化一個根結點int k=0;while(k<n-1){for(int i=0;i<en;i++){edge=side[i];if(edge->N1->flag*edge->N2->flag<0&&edge->weight<result->weight){result=edge;}}if(result->N1->flag<0)result->N2->flag=result->N1->flag;elseresult->N1->flag=result->N2->flag;Mintree.push_back(result);result=temp;k=k+1;}cout<<"prim演算法最小產生樹:"<<endl; n=Mintree.size();int sum=0;for(int i=0;i<n;i++){edge=Mintree[i];cout<<"("<<edge->N1->element<<","<<edge->N2->element<<","<<edge->weight<<")"<<" ";sum+=edge->weight;}cout<<endl;cout<<"最小權值:"<<sum<<endl;}
Algorithm-graph2.cpp
// Algorithm-graph2.cpp : 定義控制台應用程式的進入點。//主要是實現最小產生樹#include "stdafx.h"#include"Minspantree.h"#include"Minspantree.cpp"#include<string>#include<iostream>using namespace std;int _tmain(int argc, _TCHAR* argv[]){graph<string> g;int n=9;int matrix[81]={0,1,0,0,0,0,0,1,0, 1,0,1,0,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,1,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,1,0,1,1,1,1,0,0,0,0,1,0,1,0,0,1,0,0,0,1,1,0};string a[9]={"a","b","c","d","e","f","g","h","i"};int w[14]={4,8,8,11,7,4,2,9,14,10,2,1,6,7};g.insert(a,matrix,w,n);g.Kruskal();g.Prim();return 0;}