最小生成树与判断无向图是否有回路(并查集)


最小生成树与判断无向图是否有回路(并查集) 

一、最小生成树算法:

(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