最近在總結圖的有關算法,參考了一些博客,然後自己實現了一遍,彙總如下:
概念就不多總結了
1.有向無環圖的拓撲排序
基本思路:
假設我們先統計了邊的對應關係鄰接表edge,以及對應每個node的入度數組in,並且因爲是bfs來進行,使用queue來輔助,那麼按照之前說的
1.先遍歷in數組,選取一個入度爲0的節點進queue
2.while(!myqueue.emepty())的情況下一直進行遍歷,對於當前取到的node,我們遍歷相連關係數組edge,找到所有相連的node,in數組中node相應的入度-1,同時判斷如果這個node的入度已經爲0了,進queue
3.記住,最終我們要判斷ans和node節點個數的關係,如果不相等,說明出現了環,是不可能得到一個拓撲排序序列的
#include <bits/stdc++.h>
using namespace std;
int main(){
int v,e; //分別爲頂點數+輸入邊的個數
cout<<"number of the nodes: "<<endl;
cin>>v;
cout<<"number of the edges: "<<endl;
cin>>e;
//現在開始初始化一個map
vector<vector<int>> map(v,vector<int>(v,0));
cout<<"please input the relations of the edges: "<<endl;
int a,b; //分別表示輸入的兩項,並且由於是有向圖,指向爲map[a][b],構建這個關係
for(int i=0;i<e;++i){
cin>>a>>b;
map[a][b]=1;
}
//可以打印這個鄰接矩陣來看一下
cout<<"the realations of the edges: "<<endl;
cout<<endl;
for(int i=0;i<v;++i){
for(int j=0;j<v;++j){
cout<<map[i][j]<<" ";
}
cout<<endl;
}
//統計每個節點的入度
vector<int> in(v,0);
for(int i=0;i<v;++i){
for(int j=0;j<v;++j){
if(map[i][j]){ //i->j,因此j的入度應該+1
in[j]++;
}
}
}
//queue輔助,bfs遍歷,先進一個入度爲0的點
queue<int> Q;
for(int i=0;i<v;++i){
if(in[i]==0){
Q.push(i);
}
}
vector<int> res; //存儲最終結果
while(!Q.empty()){
int cur=Q.front();
Q.pop();
res.push_back(cur);
in[cur]=-1; //注意對應當前節點使用過了,入度設置爲-1
for(int i=0;i<v;++i){
if(map[cur][i]==1){ //具有相鄰關係的點我們將其入度-1
in[i]--; //入度-1
if(in[i]==0){
Q.push(i);
}
}
}
}
if(res.size()<v){ //此時說明其中有一個地方存在環了,拓撲排序是不成功的
cout<<"can not sort the nodes!"<<endl;
return 0;
}
//否則我們按序打印即可
cout<<endl;
cout<<"the result after the sort: "<<endl;
for(auto it:res){
cout<<it<<" ";
}
system("pause");
return 0;
}
最小生成樹
2.Prim算法
我們的基本思路如下:
1.先進行初始化,根據輸入的行與列來進行二維數組初始化,此時i==j的時候爲0,i!=j的時候我們設置成一個極大值表示不連通 2.進行邊的對應關係輸入,進行對應兩個頂點之間的權值更新 3.此時開始我們的主程序,也就是Prim的過程 (1)我們要用到兩個數組,一個是visit數組表示每個點是否已經取過了,假設我們先選取一個點i作爲起點,另一個是dis數組,表示當前點到各個其他點的距離 (2)此時遍歷完之後我們已經找到一個距離當前i點距離最近的點j,那麼我們此時將j同樣進行樹的生成,並且此時,我們要更新dis數組,因爲此時j也是樹的一部分 ,每個點距離樹的距離可能變小 (3)循環往復,直到我們取到了所有點,生成了一顆最小的樹
int main(){
int n,m;
cout<<"input the number of the nodes: "<<endl;
cin>>n;
cout<<"input the number of the edges: "<<endl;
cin>>m;
//此時我們根據n個頂點先初始化一個關係數組
vector<vector<int>> vec(n,vector<int>(n,INT_MAX));
for(int i=0;i<n;++i){ //權值更新,自身爲0,其餘爲一個INT_MAX
vec[i][i]=0;
}
cout<<"input the relations of the edges: "<<endl;
//根據每條邊輸入的權值進行更新,注意是無向圖,因此兩個方向都要更新
int a,b,value;
for(int i=0;i<m;++i){ //總共m條邊的對應關係
cin>>a>>b>>value;
vec[a][b]=value;
vec[b][a]=value;
}
//假設我們取第一個節點作爲起始點
int sum=0; //記錄生成樹的權值
int cnt=0; //記錄訪問了多少個節點了
vector<int> dis(n,INT_MAX); //記錄每個點到樹的權值距離
vector<int> visit(n,0); //0表示未訪問過
vector<pair<int,int>> res; //結果數組,記錄每次取的節點以及對應權值
visit[0]=1; //標記爲已訪問
//因爲選成了樹的節點,因此更新一波距離
for(int i=0;i<n;++i){
dis[i]=vec[0][i];
}
res.push_back({0,dis[0]}); //輸入的是節點以及對應權值
cnt++;
while(cnt!=n){
int min_dis=INT_MAX,idx=0; //目標要選取當前dis中最小的那個節點
for(int i=0;i<n;++i){
if(visit[i]==0 && dis[i]<min_dis){
min_dis=dis[i];
idx=i;
}
}
visit[idx]=1; //選定的節點進行訪問併入res
res.push_back({idx,min_dis});
sum+=min_dis;
cnt++;
//因此此時idx變爲一部分,因此再更新一遍權值
for(int i=0;i<n;++i){
if(visit[i]==0 && dis[i]>vec[idx][i]){
dis[i]=vec[idx][i];
}
}
}
//到目前爲止完成Prim算法,我們打印一遍結果來看一下
cout<<"the result sum is: "<<sum<<endl;
cout<<"the nodes are as below: "<<endl;
for(int i=0;i<res.size();++i){
cout<<"the"<<i<<"th node is: "<<res[i].first<<" "<<"the value is: "<<res[i].second<<endl;
}
system("pause");
return 0;
}
3.Kruskal
Kruskal與Prim的不同是,之前我們是不斷的每次取一個點之後都要進行距離更新,然後取距離最短的。那現在我們在開始時進行一次距離的更新,依次來進行選取,在每次取得一個點之後,我們不再進行權值的更新。此時爲了我們取對應長度最小的邊較爲方便,考慮使用vector>>的結構來存儲,分別爲x,y,value 我們此時需要的輔助函數爲並查集的查找以及合併
vector<int> pre; //用於並查集的查找
int find(int x){
if(pre[x]==x){
return x;
}
pre[x]=find(pre[x]); //否則的話我們進行並查集的查找
return pre[x];
}
int merge(int x,int y){
int t1=find(x);
int t2=find(y);
if(t1!=t2){ //如果兩個並不是相連的,我們修改狀態爲相連,並返回1
pre[t2]=t1;
return 1;
}
return 0;
}
int main(){
int n,m; //同樣我們要先進行對應關係初始化,總共n個節點,m條邊的關係
cout<<"input the number of the nodes: "<<endl;
cin>>n;
cout<<"input the number of the edges: "<<endl;
cin>>m;
vector<pair<int,pair<int,int>>> vec; //存儲對應關係
int x,y,value;
cout<<"input the relations of the edges: "<<endl;
for(int i=0;i<m;++i){
cin>>x>>y>>value;
vec.push_back({x,{y,value}});
}
//我們要根據value值進行從小到大排序
auto cmp=[](const pair<int,pair<int,int>>&a,const pair<int,pair<int,int>>& b){
return a.second.second<b.second.second;
};
sort(vec.begin(),vec.end(),cmp);
//pre數組進行初始化
for(int i=0;i<n;++i){
pre.push_back(i);
}
vector<pair<int,pair<int,int>>> res; //存儲最終的結果
int sum=0; //存儲最終的權值之和
int cnt=0; //記錄當前訪問了多少條邊,到m-1的時候退出循環,完成了構建
//我們此時根據每條邊來進行列舉
for(int i=0;i<m;++i){
int x=vec[i].first;
int y=vec[i].second.first;
int value=vec[i].second.second;
if(merge(x,y)){
cnt++;
sum+=value;
res.push_back({x,{y,value}}); //存儲對應的x,y,value
}
if(cnt==m-1){ //已經取得了m-1條邊的話退出
break;
}
}
cout<<"the result sum: "<<sum<<endl;
cout<<"the select nodes are as below: "<<endl;
for(int i=0;i<res.size();++i){
cout<<"the "<<i<<"th of the edges"<<res[i].first<<"->"<<res[i].second.first<<" ";
cout<<"the value is: "<<res[i].second.second<<endl;
}
system("pause");
return 0;
}
最短路徑Dijkstra
Dijkstra最短路徑
迪傑斯特拉(Dijkstra)算法是典型最短路徑算法,用於計算一個節點到其他節點的最短路徑。它的主要特點是以起始點爲中心向外層層擴展(廣度優先搜索思想),直到擴展到終點爲止。
####注意這裏與Prim的區別,同樣是計算距離,但是Prim是計算點到樹的距離,因此每次求一遍過後需要更新,而Dijkstra是求點到源點的距離,因此也要更新,但仍然是更新到源點的距離
//Dijkstra算法
/*
我們這樣考慮:
使用distance作爲距離數組統計每個節點到源點的記錄
使用s和v分別存儲對應取得的節點信息,以及未被選取的節點的信息
使用flag來標記每一個節點是否進行使用過
1.先進行對應關係的建立,同樣可以選擇無向圖,那麼鄰接矩陣即可
2.選取一個點i作爲源點,那麼我們先進行每個節點到當前源點的距離統計,選取距離最小的節點。
此後進行距離的更新,注意,在這裏與Prim不同的是,我們更新的是點到源點的距離(因此新選取的點只能作爲一箇中間板)
3.如此往復直到選取得到i個節點完畢
*/
int main(){
int n,m;
int max_dis=9999;
cout<<"input the number of the nodes: "<<endl;
cin>>n;
cout<<"input the number of the edges: "<<endl;
cin>>m;
vector<vector<int>> vec(n,vector<int>(n,max_dis=9999));
for(int i=0;i<n;++i){
vec[i][i]=0; //自身先修改成0
}
cout<<"input the relations of the edges: "<<endl;
int a,b,value; //分別爲輸入的a,b以及對應的距離長度value
for(int i=0;i<m;++i){
cin>>a>>b>>value;
vec[a][b]=value;
vec[b][a]=value; //無向圖距離正反都需要更新
}
//打印一下鄰接矩陣看一下
cout<<"the matrix of the nodes are as below: "<<endl;
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
cout<<vec[i][j]<<" ";
}
cout<<endl;
}
//現在來進行Dijkstra的核心部分
vector<pair<int,int>> s; //已經使用的節點,分別爲node和dis
vector<pair<int,int>> v; //未被使用的節點,分別爲node和dis
vector<int> dis(n,max_dis); //對應的距離矩陣
vector<int> flag(n,false); //標記節點是否訪問過了
//我們選取第0個點作爲源點
int pos=3;
int cnt=0; //表明當前選取了幾個節點
flag[pos]=true;
s.push_back({pos,0}); //作爲已經訪問,進入s存儲
//更新距離矩陣
for(int i=0;i<n;++i){
dis[i]=vec[pos][i];
}
//現在我們找一遍dis,選取距離最短的進行記錄,之後再往復進行距離的更新
for(int i=0;i<n;++i){
if(flag[i]==false){
v.push_back({i,dis[i]}); //對應點記錄爲未訪問
}
}
cnt++;
auto cmp=[](const pair<int,int>&a ,const pair<int,int>& b){ //根據距離從小到大排序
return a.second<b.second;
};
while(cnt!=n){
sort(v.begin(),v.end(),cmp);
int cur=v[0].first; //這是我們目前能夠得到的距離最小的node的座標
cnt++;
s.push_back({cur,v[0].second}); //記錄到s中,此時的距離爲dis[cur]
v.erase(v.begin());
//需要進行到源點距離的更新
for(int i=0;i<n;++i){
if(dis[i]>(dis[cur]+vec[i][cur])){ //使用中間板如果距離小了,進行更新,cur作爲中間板
dis[i]=dis[cur]+vec[i][cur];
for(auto it=v.begin();it!=v.end();++it){
if(it->first==i){
it->second=dis[i]; //v中的距離需要更新
break;
}
}
}
}
flag[cur]=true; //標記爲已訪問
}
//現在s中應該有n個節點信息,輸出一下check一下
cout<<"-----------------------"<<endl;
cout<<"after the Dijkstra operation, the result is as below: "<<endl;
for(int i=0;i<s.size();++i){
cout<<"the node is: "<<s[i].first<<" "<<"the distance is: "<<s[i].second<<endl;
}
system("pause");
return 0;
}