1.生成樹(minimum spanning trees)node
圖的生成樹是它一棵含有全部頂點的無環聯通子圖ios
最小生成樹:生成樹中權值和最小的(全部邊的權值之和)算法
Prim算法、Kruskal算法就是實現最小生成樹的算法數組
2.Prim算法函數
Prim算法是一種"加點法":
this
1.定義圖中全部頂點集合\(V\),從頂點\(s\)開始;初始化生成樹頂點集合\(u={s}\),\(v=V-u\)
2.遍歷結點\({u,v}\),選擇一條權重最小的邊,加入到生成樹中。\(v\)也進入集合\(u\)中
3.循環步驟2,直至有\(n-1\)條邊,或者全部頂點都在最小生成樹中。spa
#include<iostream> #include<vector> #include<queue> #include<algorithm> using namespace std; #define INFINITE 0xFFFFFFFF #define VertexData unsigned int // 圖頂點數據類型 #define UNIT unsigned int #define VertexCounts 6 // 圖頂點個數 char vextex[]={'A','B','C','D','E','F'}; struct node // prim算法中的邊一直更新,最小代價邊須要一直更新 { VertexData data; unsigned int lowestcost; }closedge[VertexCounts]; typedef struct { VertexData u; VertexData v; unsigned int cost; }Arc; // 圖中結點-邊信息 void AdjMatrix(unsigned int adjMat[][VertexCounts]) // 鄰接矩陣表示法 { for(int i=0;i<VertexCounts;i++) for(int j=0;j<VertexCounts;j++) { adjMat[i][j]=INFINITE; // 矩陣元素的初始化 } adjMat[0][1] = 6; adjMat[0][2] = 1; adjMat[0][3] = 5; adjMat[1][0] = 6; adjMat[1][2] = 5; adjMat[1][4] = 3; adjMat[2][0] = 1; adjMat[2][1] = 5; adjMat[2][3] = 5; adjMat[2][4] = 6; adjMat[2][5] = 4; adjMat[3][0] = 5; adjMat[3][2] = 5; adjMat[3][5] = 2; adjMat[4][1] = 3; adjMat[4][2] = 6; adjMat[4][5] = 6; adjMat[5][2] = 4; adjMat[5][3] = 2; adjMat[5][4] = 6; } int Minmum(struct node* closedge) // 找到最小代價邊 { unsigned int min=INFINITE; int index=-1; // 保存最小代價邊的頂點下標 for(int i=0;i<VertexCounts;++i) { if(closedge[i].lowestcost<min && closedge[i].lowestcost!=0) { min=closedge[i].lowestcost; index=i; } } return index; } void MiniSpanTree_Prim(unsigned int adjMat[][VertexCounts],VertexData s) { for(i=0;i<VertexCounts;++i) // 頂點最小邊的初始化 { closedge[i].lowestcost=INFINITE; } closedge[s].data=s; closedge[s].lowestcost=0; for(int i=0;i<VertexCounts;++i) { if(i!=s) { closedge[i].data=s; closedge[i].lowestcost=adjMat[s][i]; } } for(int e=1;e<VertexCounts-1;e++) // 知足n-1邊時候結束循環 { int k=Minmum(closedge); // 選擇最小代價邊 cout<<vertex[closedge[k].data]<<"--"<<vertex[k]<<endl; closedge[k].lowestcost=0; // 代價置爲0 for(int i=0;i<VertexCounts;i++) // 更新v中的代價信息 { if(adjMat[k][i]<closedge[i].lowestcost) { closedge[i].data=k; closedge[i].lowestcost=adjMat[k][i]; } } } } int main() { unsigned int adjMat[vexCounts][vexCounts] = { 0 }; AdjMatrix(adjMat); //鄰接矩陣 cout << "Prim :" << endl; MiniSpanTree_Prim(adjMat,0); //Prim算法,從頂點0開始. return 0; }
3.Kruskal算法.net
Kruskal算法是一種"加邊法":
code
1.將圖中全部的邊按權重值進行排序
2.圖中n各頂點都是相互獨立的
3.權值由小到大選擇邊,兩個頂點應屬於兩顆不一樣的樹,這樣生成最小生成樹的一條邊。兩顆樹合併成一顆樹。
4.循環步驟3,直至有n-1條邊,或者全部頂點都在最小生成樹中。blog
#include<iostream> #include<vector> #include<queue> #include<algorithm> using namespace std; #define INFINITE 0xFFFFFFFF #define VertexData unsigned int // 圖頂點數據類型 #define UNIT unsigned int #define VertexCounts 6 // 圖頂點個數 char vextex[] = { 'A', 'B', 'C', 'D', 'E', 'F' }; typedef struct { VertexData u; VertexData v; unsigned int cost; // 邊的代價 }Arc; // 原始圖的邊信息 void ReadArc(unsigned int adjMat[][VertexCounts],vector<Arc> &VertexArc) //保存圖的邊代價信息 { Arc* temp=NULL; for(unsigned int i=0;i<VertexCounts;i++) { for(unsigned int j=0;j<VertexCounts;j++) { if(adjMat[i][j]!=INFINITE) { temp=new Arc; temp->u=i; temp->v=j; temp->cost=adjMat[i][j]; VertexArc.push_back(*temp); } } } } bool FindTree(VertexData u, VertexData v,vector<vector<VertexData> > &Tree) { unsigned int index_u = INFINITE; unsigned int index_v = INFINITE; for (unsigned int i = 0; i < Tree.size();i++) //檢查u,v分別屬於哪顆樹 { if (find(Tree[i].begin(), Tree[i].end(), u) != Tree[i].end()) index_u = i; if (find(Tree[i].begin(), Tree[i].end(), v) != Tree[i].end()) index_v = i; } if (index_u != index_v) //u,v不在一顆樹上,合併兩顆樹 { for (unsigned int i = 0; i < Tree[index_v].size();i++) { Tree[index_u].push_back(Tree[index_v][i]); } Tree[index_v].clear(); return true; } return false; } bool compare(Arc A, Arc B) // 比較權值的大小 { return A.cost < B.cost ? true : false; } void MinSpanTree_Kruskal(unsigned int adjMat[][VertexCounts]) { vector<Arc> VertexArc; ReadArc(adjMat,VertexArc); // 讀取邊信息 sort(VertexArc.begin(),VertexArc.end(),compare); // 邊從小到大排列 vetor<vector<VertexData>> Tree(VertexCounts); // 6顆相互獨立的樹 for(unsigned int i=0;<VertexCounts;i++) { Tree[i].push_back(i); // 每棵樹信息的獲取 } for(unsigned int i=0;i<VertexArc.size();i++) { VertexData u=VertexArc[i].u; VertexData v=VertexArc[i].v; if(FindTree(u,v,Tree)) // 檢查兩個頂點是否在一顆樹內 { cout<<vertex[u]<<"--"<<vertex[v]<<endl; } } } int main() { unsigned int adjMat[vexCounts][vexCounts] = { 0 }; cout << "-------------" << endl << "Kruskal:" << endl; MiniSpanTree_Kruskal(adjMat);//Kruskal算法 return 0; }
上面兩個算法都是對於無向有權圖
在有向加權圖中,通常解決最短路徑問題
4.Dijkstra算法
設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分紅兩組,第一組爲已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,之後每求得一條最短路徑 , 就將加入到集合S中,直到所有頂點都加入到S中,算法就結束了),第二組爲其他未肯定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程當中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每一個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點爲中間頂點的當前最短路徑長度。
1.初始時,S只包含起點s;U包含除s外的其餘頂點,且U中頂點的距離爲"起點s到該頂點的距離"[例如,U中頂點v的距離爲(s,v)的長度,而後s和v不相鄰,則v的距離爲∞]。
2.從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。
3.更新U中各個頂點到起點s的距離。之因此更新U中頂點的距離,是因爲上一步中肯定了k是求出最短路徑的頂點,從而能夠利用k來更新其它頂點的距離;例如,(s,v)的距離可能大於(s,k)+(k,v)的距離。
4.重複步驟(2)和(3),直到遍歷完全部頂點。
初始狀態:S是已計算出最短路徑的頂點集合,U是未計算除最短路徑的頂點的集合!
第1步:將頂點D加入到S中。 此時,S={D(0)}, U={A(∞),B(∞),C(3),E(4),F(∞),G(∞)}。 注:C(3)表示C到起點D的距離是3。
第2步:將頂點C加入到S中。 上一步操做以後,U中頂點C到起點D的距離最短;所以,將C加入到S中,同時更新U中頂點的距離。以頂點F爲例,以前F到D的距離爲∞;可是將C加入到S以後,F到D的距離爲9=(F,C)+(C,D)。此時,S={D(0),C(3)}, U={A(∞),B(23),E(4),F(9),G(∞)}。
第3步:將頂點E加入到S中。上一步操做以後,U中頂點E到起點D的距離最短;所以,將E加入到S中,同時更新U中頂點的距離。仍是以頂點F爲例,以前F到D的距離爲9;可是將E加入到S以後,F到D的距離爲6=(F,E)+(E,D)。此時,S={D(0),C(3),E(4)}, U={A(∞),B(23),F(6),G(12)}。
第4步:將頂點F加入到S中。 此時,S={D(0),C(3),E(4),F(6)}, U={A(22),B(13),G(12)}。
第5步:將頂點G加入到S中。 此時,S={D(0),C(3),E(4),F(6),G(12)}, U={A(22),B(13)}。
第6步:將頂點B加入到S中。 此時,S={D(0),C(3),E(4),F(6),G(12),B(13)}, U={A(22)}。
第7步:將頂點A加入到S中。 此時,S={D(0),C(3),E(4),F(6),G(12),B(13),A(22)}。
#include<iostream> #include<string> using namespace std; /* 本程序是使用Dijkstra算法實現求解最短路徑的問題 採用的鄰接矩陣來存儲圖 */ //記錄起點到每一個頂點的最短路徑的信息 struct Dis { string path; int value; bool visit; Dis() { visit = false; value = 0; path = ""; } }; class Graph_DG { private: int vexnum; //圖的頂點個數 int edge; //圖的邊數 int **arc; //鄰接矩陣 Dis * dis; //記錄各個頂點最短路徑的信息 public: //構造函數 Graph_DG(int vexnum, int edge); //析構函數 ~Graph_DG(); // 判斷咱們每次輸入的的邊的信息是否合法 //頂點從1開始編號 bool check_edge_value(int start, int end, int weight); //建立圖 void createGraph(); //打印鄰接矩陣 void print(); //求最短路徑 void Dijkstra(int begin); //打印最短路徑 void print_path(int); }; //構造函數 Graph_DG::Graph_DG(int vexnum, int edge) { //初始化頂點數和邊數 this->vexnum = vexnum; this->edge = edge; //爲鄰接矩陣開闢空間和賦初值 arc = new int*[this->vexnum]; dis = new Dis[this->vexnum]; for (int i = 0; i < this->vexnum; i++) { arc[i] = new int[this->vexnum]; for (int k = 0; k < this->vexnum; k++) { //鄰接矩陣初始化爲無窮大 arc[i][k] = INT_MAX; } } } //析構函數 Graph_DG::~Graph_DG() { delete[] dis; for (int i = 0; i < this->vexnum; i++) { delete this->arc[i]; } delete arc; } // 判斷咱們每次輸入的的邊的信息是否合法 //頂點從1開始編號 bool Graph_DG::check_edge_value(int start, int end, int weight) { if (start<1 || end<1 || start>vexnum || end>vexnum || weight < 0) { return false; } return true; } void Graph_DG::createGraph() { cout << "請輸入每條邊的起點和終點(頂點編號從1開始)以及其權重" << endl; int start; int end; int weight; int count = 0; while (count != this->edge) { cin >> start >> end >> weight; //首先判斷邊的信息是否合法 while (!this->check_edge_value(start, end, weight)) { cout << "輸入的邊的信息不合法,請從新輸入" << endl; cin >> start >> end >> weight; } //對鄰接矩陣對應上的點賦值 arc[start - 1][end - 1] = weight; //無向圖添加上這行代碼 //arc[end - 1][start - 1] = weight; ++count; } } void Graph_DG::print() { cout << "圖的鄰接矩陣爲:" << endl; int count_row = 0; //打印行的標籤 int count_col = 0; //打印列的標籤 //開始打印 while (count_row != this->vexnum) { count_col = 0; while (count_col != this->vexnum) { if (arc[count_row][count_col] == INT_MAX) cout << "∞" << " "; else cout << arc[count_row][count_col] << " "; ++count_col; } cout << endl; ++count_row; } } void Graph_DG::Dijkstra(int begin){ //首先初始化咱們的dis數組 int i; for (i = 0; i < this->vexnum; i++) { //設置當前的路徑 dis[i].path = "v" + to_string(begin) + "-->v" + to_string(i + 1); dis[i].value = arc[begin - 1][i]; } //設置起點的到起點的路徑爲0 dis[begin - 1].value = 0; dis[begin - 1].visit = true; int count = 1; //計算剩餘的頂點的最短路徑(剩餘this->vexnum-1個頂點) while (count != this->vexnum) { //temp用於保存當前dis數組中最小的那個下標 //min記錄的當前的最小值 int temp=0; int min = INT_MAX; for (i = 0; i < this->vexnum; i++) { if (!dis[i].visit && dis[i].value<min) { min = dis[i].value; temp = i; } } //cout << temp + 1 << " "<<min << endl; //把temp對應的頂點加入到已經找到的最短路徑的集合中 dis[temp].visit = true; ++count; for (i = 0; i < this->vexnum; i++) { //注意這裏的條件arc[temp][i]!=INT_MAX必須加,否則會出現溢出,從而形成程序異常 if (!dis[i].visit && arc[temp][i]!=INT_MAX && (dis[temp].value + arc[temp][i]) < dis[i].value) { //若是新獲得的邊能夠影響其餘爲訪問的頂點,那就就更新它的最短路徑和長度 dis[i].value = dis[temp].value + arc[temp][i]; dis[i].path = dis[temp].path + "-->v" + to_string(i + 1); } } } } void Graph_DG::print_path(int begin) { string str; str = "v" + to_string(begin); cout << "以"<<str<<"爲起點的圖的最短路徑爲:" << endl; for (int i = 0; i != this->vexnum; i++) { if(dis[i].value!=INT_MAX) cout << dis[i].path << "=" << dis[i].value << endl; else { cout << dis[i].path << "是無最短路徑的" << endl; } } } //檢驗輸入邊數和頂點數的值是否有效,能夠本身推算爲啥: //頂點數和邊數的關係是:((Vexnum*(Vexnum - 1)) / 2) < edge bool check(int Vexnum, int edge) { if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge) return false; return true; } int main() { int vexnum; int edge; cout << "輸入圖的頂點個數和邊的條數:" << endl; cin >> vexnum >> edge; while (!check(vexnum, edge)) { cout << "輸入的數值不合法,請從新輸入" << endl; cin >> vexnum >> edge; } Graph_DG graph(vexnum, edge); graph.createGraph(); graph.print(); graph.Dijkstra(1); graph.print_path(1); system("pause"); return 0; } /* 輸入: 6 8 1 3 10 1 5 30 1 6 100 2 3 5 3 4 50 4 6 10 5 6 60 5 4 20 */