最小生成樹與判斷無向圖是否有回路(並查集)


最小生成樹與判斷無向圖是否有回路(並查集) 

一、最小生成樹算法:

(1)Kruskal算法

  (a)找出權重最小的邊

  (b)判斷加入該邊以后是否會構成回路(並查集),如果不會,將該邊加入生成樹中

  重復(a)(b),直到生成樹中有n-1條邊

(2)Prim算法

  選一個結點作為起始結點,並將其加入已選結點集合;

  (a)尋找與已選結點集合任一 一個(不能是兩個)結點相關聯的最小邊(也就是這條最小邊關聯的結點不能都在已選結點集合中,從而保證了加入這條邊一定不會構成回路)

  重復(a)直到生成樹中有n-1條邊

  注意:該算法不用使用並查集判斷是否有回路,因為加入的最小邊關聯的結點不能都在已選結點集合中

代碼實現:(Prim算法,可AC練習題目,可優化(參考下面算法2練習題目1的代碼))20191206

#include<iostream>
#define inf 999999
using namespace std;
int book[310],e[301][310],n,count;
int maxm = -inf;
int Prim(int cur){
    book[cur] = 1;
    count++;
    int minn = inf;
    int index;
    int node;
    for(int j=1;j<=n;j++){
        if(book[j]==1){
            for(int i=1;i<=n;i++){
                if(book[i]==0 && e[j][i]<minn) {
                    minn = e[j][i];
                    node = j;
                    index = i;//find the smallest distance of two vertices
                }
            }
        }
        
    }
    if(e[node][index]>maxm) maxm = e[node][index];
    return index;
}
int main(){
    int i,j,m,a,b,c;
    cin>>n>>m;
    for(i=1;i<=n;i++){
        for(j=1;j<=n;j++){
            if(i==j) e[i][j]=0;
            else e[i][j]=inf;
        }
    }
    for(i=1;i<=m;i++){
        cin>>a>>b>>c;
        e[a][b]=c;
        e[b][a]=c;//indirected graph
    }
    int next = Prim(1);
    while(count!=n-1){ //count used to judge if all vertices are in the tree
        next = Prim(next);
    }
    cout<<count<<" "<<maxm<<endl;
    return 0;
}

 

 

 

二、用Kruskal判斷無向圖是否有回路的方法總結:

算法1:並查集(推薦)

並查集(與路徑壓縮)算法描述:《算法筆記》P328-332

  • 並查集是一種維護集合的數據結構,基礎操作如下

    • 初始化:每個元素都是獨立的一個集合

    • 查找:反復尋找父親結點,直到找到根節點

    • 合並:將一個集合的根節點的父親指向另一個結合的根節點

  • 路徑壓縮

    • 把當前查詢結點的路徑上的所有結點的父親都指向根節點

    • 目的是為了降低查找的時間復雜度

    • 因此帶有路徑壓縮的合並操作步驟如下:以合並ab兩個結點為例

      • 查找a結點的根結點:將查詢a結點過程中所遍歷到的結點的父親指向根節點

      • 查找b結點的根結點:同上

      • 將a結點所在子樹的根節點的父親指向b結點子樹的根節點(理論上是所在樹結點少的指向結點多的)

代碼實現:(可優化(參考下面算法2練習題目1的代碼))20200130

#include<iostream>
#define inf 9999999 
using namespace std;
int n,edge,maxm;
int e[310][310],father[310];
int findfather(int node){
    int vertex = node;
    while(node != father[node])
        node = father[node];
    while(vertex != father[vertex]){// path compression
        int temp = vertex;
        vertex = father[vertex];
        father[temp] = node;
    }
    return node;
}
void unionsets(int node1,int node2){
    int fa1 = findfather(node1);
    int fa2 = findfather(node2);
    father[fa2] = fa1;
}
bool cycle(int nodea,int nodeb){
    if(findfather(nodea) == findfather(nodeb)) return false;
    else {
        unionsets(nodea,nodeb);
        edge++;// end condition
        return true;
    }
}
void Kruskal(){
    int minn = inf;
    int nodea,nodeb;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(e[i][j] < minn) {
                minn = e[i][j];
                nodea = i;
                nodeb = j;
            }
        }
    }
    if(cycle(nodea,nodeb)){
        maxm = e[nodea][nodeb];
    }
    e[nodea][nodeb] = inf;
    e[nodeb][nodea] = inf;//ignore the edge checked
}

int main(){
    int m;
    cin>>n>>m; 
    for(int i=1;i<=n;i++){
        father[i] = i; //initialize
        for(int j=1;j<=n;j++){
            e[i][j]=inf;
        }
    }
    int a,b,c;
    for(int i=1;i<=m;i++){
        cin>>a>>b>>c;
        e[a][b]=c;
        e[b][a]=c;//indirected 
    }
    while(edge<n-1) Kruskal();
    cout<<edge<<" "<<maxm<<endl;
    return 0;
}
View Code

 

測試數據:見練習題目第1個

 

 

算法2:基於dfs判斷(同有向圖)

算法描述:拓撲排序與判斷有向圖是否有環

區別於有向圖的是:用dfs判斷無向圖是否有回路要注意防止結點“殺回馬槍”,也就是說要防止結點訪問它的父親結點;比如說1dfs到2,要防止2dfs時去訪問1.

解決辦法就是記錄每一個結點的父親結點。

 

代碼實現:

練習題目2:20200129

#include<iostream>
#define inf 99999999
using namespace std;
int book[110],e[110][110],n,sum,father[110],arr[110][110];
int base = inf;

bool dfs (int cur){
    book[cur] = -1;
    for(int i=1;i<=n;i++){
        if(e[cur][i]!=0&&e[cur][i]!=inf&&father[cur]!=i&&book[i]==-1) 
            return false;
        if(e[cur][i]!=0&&e[cur][i]!=inf&&book[i]==0){
            father[i] = cur;
            if(!dfs(i)) return false;    
        } 
    }
    book[cur] = 1;
    return true;
}
bool Kruskal(){
    int minn = inf;
    int nodea,nodeb;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(arr[i][j]<minn) {
                minn = arr[i][j];
                nodea = i;
                nodeb = j;
            }
        }
    }
    if(minn!=inf){
        e[nodea][nodeb] = minn;
        e[nodeb][nodea] = minn;
        for(int i=0;i<110;i++) book[i] = 0;
        bool state=1;
        for(int i=1;i<=n;i++){
            base = inf;
            if(!dfs(i)){
                state = 0;
                break;
            }
        } 
        if(!state){
            e[nodea][nodeb] = inf;
            e[nodeb][nodea] = inf;
        } 
        else sum += minn;    
        arr[nodea][nodeb] = inf;
        
        return true;
    }
    else return false;
    
}
int main(){
    cin>>n;
    int temp;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>temp;
            if(i==j) temp = inf;
            arr[i][j] = temp;
            e[i][j] = inf;
        }
    }
    while(Kruskal()) ;
    cout<<sum<<endl;
    return 0;
}
View Code

 

練習題目1:20200131(相對於練習題目2的代碼有很大優化)

#include<iostream>
#include<algorithm>
#define inf 999999
using namespace std;
int n,counte,maxm;
int book[310],father[310];
bool e[310][310];
typedef struct{
    int nodea;
    int nodeb;
    int weight;
}Edge; 
Edge edge[100010];
bool cmp(Edge e1,Edge e2){
    return e1.weight < e2.weight;
}
bool dfs (int cur){
    book[cur] = -1;
    for(int i=1;i<=n;i++){
        if(e[cur][i]==0) continue;
        if(e[cur][i]==1 && father[cur]!=i && book[i]==-1) 
            return false;
        if(e[cur][i]==1 && book[i]==0){
            father[i] = cur;
            if(!dfs(i)) 
                return false; //注意不能單純地dfs,一定要判斷並return false;不判斷的話就會執行到最后一行並return true        
        } 
    }
    book[cur] = 1;
    return true;
}
void Kruskal(Edge ed){
    e[ed.nodea][ed.nodeb] = 1;
    e[ed.nodeb][ed.nodea] = 1;
    for(int i=0;i<310;i++) book[i] = 0;//每放入一條邊,都要重新判斷是否構成環 
    bool state=1;
    for(int i=1;i<=n;i++){
        if(book[i] != 0) continue;
        if(!dfs(i)){
            state = 0;//有回路 
            break;
        }
    } 
    if(state){
        counte++;
        maxm = ed.weight;
    }
    else{
        e[ed.nodea][ed.nodeb] = 0;
        e[ed.nodeb][ed.nodea] = 0;
    }
}
int main(){
    int m,a,b,c;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>a>>b>>c;
        edge[i].nodea = a;
        edge[i].nodeb = b;
        edge[i].weight = c;
    }
    sort(edge,edge+m,cmp);
    for(int i=1;i<=m;i++) {
        Kruskal(edge[i]);
        if(counte == n-1) break;
    }
    cout<<counte<<" "<<maxm<<endl;
    return 0;
}
View Code

 

 

算法3:如果 邊數 + 連通分支數 - 1 >= 結點數,則圖中有回路

代碼實現:(沒有考慮有多個連通分支的情況,第二組測試數據通不過)20191206

#include<iostream>
#define inf 999999
using namespace std;
int book[101],e[101][101],n,data[101],sum,count,edge;

bool is_cycle(int nodea,int nodeb){
//if edge>=the number of vertices, then there is a cycle
    int statea=0;//to record if nodea is booked before adding the edge
    int stateb=0;
    if(book[nodea]==0){
        book[nodea] = 1;
        count++;
        statea++;
    }
    if(book[nodeb]==0){
        book[nodeb] = 1;
        count++;
        stateb++;
    }
    edge++;//sequence(after count++)
    //after add the edge, if it leads to a cycle, then recover it
    if(edge>=count){
        edge--;
        if(statea){
            count--;
            book[nodea]=0;
        }
        if(stateb){
            count--;
            book[nodeb]=0;
        }
        return true;
    } 
    else return false;
}
void Kruskal(){
    int minn = inf;
    int nodea,nodeb;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(e[i][j]<minn) {
                minn = e[i][j];
                nodea = i;
                nodeb = j;//find the two vertices of the min edge
            }
        }
    }
    if(!is_cycle(nodea,nodeb)){
        cout<<nodea<<" "<<nodeb<<endl;
        book[nodea] = 1;
        book[nodeb] = 1;
        sum += e[nodea][nodeb];
    }
    e[nodea][nodeb] = inf;
    e[nodeb][nodea] = inf;//ignore the edge checked
}
int main(){
    int i,j,m,a,b,c;
    scanf("%d %d",&n,&m);
    for(i=1;i<=n;i++){
        for(j=1;j<=n;j++){
            if(i==j) e[i][j]=0;
            else e[i][j]=inf;
        }
    }
    for(i=1;i<=m;i++){
        scanf("%d %d %d",&a,&b,&c);
        e[a][b]=c;
        e[b][a]=c;
    }
    for(int i=0;i<m;i++) Kruskal();
    cout<<sum<<endl;
    return 0;
}
View Code

 

測試數據:

//左圖(AC)
6 9
1 2 10
2 4 5
1 6 2
2 3 3
4 5 11
6 5 3
3 5 15
4 6 10
1 4 20
//右圖(WA)
8 8
1 2 1
2 3 2
3 4 3
5 6 4
6 7 5
7 8 6
1 4 7
1 5 8
View Code

 

 

練習題目:

P2330 [SCOI2005]繁忙的都市

P1546 最短網絡 Agri-Net


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM