[圖算法實現][拓撲/Prim/Kruskal/Dijkstra][C++實現]

最近在總結圖的有關算法,參考了一些博客,然後自己實現了一遍,彙總如下:

概念就不多總結了

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章