【ACM】- HDU.1863 畅通工程 【最小生成树】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_26398495/article/details/82689633

【目录】

题目链接
题目分析

最小生成树问题,求路径和

解题思路

算是最小生成树的母题,分别用以下几种方法实现以下:
1、Kruskal算法 + 并查集;
2、Prime算法 (邻接矩阵版本)
3、Prime算法(邻接表版本)
分别再用堆结构(priority_queue)优化一下


| Kruskal算法 + 并查集 (堆优化priority_queue
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;
const int maxn = 110;

struct edge{
    int u, v, cost;
    edge() {}
    edge(int _u, int _v, int _cost) : u(_u), v(_v), cost(_cost) {} //构造函数,便于加入结点
    bool operator < (const edge& n) const { //规定优先级
        return cost > n.cost; //注意和sort函数是相反的
    }
};
int N, M;
int far[maxn]; //并查集

//寻根
int find_root(int a) {
    int root = a;
    while(root != far[root]) root = far[root];

    while(a != far[a]) { //路径压缩
        int cur = a;
        a = far[a];
        far[cur] = root;
    }
    return root;
}

//合并集合
void union_set(int a, int b) {
    int root_a = find_root(a);
    int root_b = find_root(b);
    if(a != b){
        far[root_b] = root_a;
    }
}

int kruskal(priority_queue<edge> E) {
    for(int i = 1; i <= N; i++) far[i] = i; //初始化并查集
    int ans = 0;//权值和
    int edge_num = 0; //已选择的边数
    int cnt = N; //连通块数

    for(int i = 0; i < M; i++) {
        edge e = E.top(); E.pop(); //get fisrt edge
        int root_u = find_root(e.u);
        int root_v = find_root(e.v);

        if(root_u != root_v) {
            union_set(root_u, root_v);
            edge_num++;
            cnt--; //连通块数-1
            ans += e.cost;
        }
        if(edge_num == N - 1) break;  //边数等于结点数-1
    }
    if(cnt != 1) return -1;//只剩一个连通块(edge_num == N - 1 也没问题)
    else return ans;

}//kruskal

int main() {
    int a, b, cost;
    while(scanf("%d %d", &M, &N) != EOF) {
        if(M == 0) break;
        priority_queue<edge> E; //保存所有边(无clear()函数,每次重新定义时间最快)
                                //优先级和sort()函数是相反的
        for(int i = 0; i < M; i++) {
            scanf("%d %d %d", &a, &b, &cost);
            E.push(edge(a, b, cost)); //加入堆
        }

        int ans = kruskal(E);
        if(ans == -1) printf("?\n");
        else printf("%d\n", ans);

    }//while

    system("pause");
    return 0;
}

| Kruskal算法 + 并查集 (sort())
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;
const int maxn = 110;

int N, M;
int far[maxn]; //并查集
struct edge{
    int u, v, cost;
    bool operator < (const edge& n) const { //规定优先级
        return cost < n.cost;
    }
}E[maxn];

bool cmp(edge e, edge f) { //也可以用自定义比较函数
    return e.cost < f.cost;
}

//寻根
int find_root(int a) {
    int root = a;
    while(root != far[root]) root = far[root];

    while(a != far[a]) { //路径压缩
        int cur = a;
        a = far[a];
        far[cur] = root;
    }
    return root;
}

//合并集合
void union_set(int a, int b) {
    int root_a = find_root(a);
    int root_b = find_root(b);
    if(a != b){
        far[root_b] = root_a;
    }
}

int kruskal() {
    for(int i = 1; i <= N; i++) far[i] = i; //初始化并查集
    sort(E, E + M); //边递增排序(也可直接用堆实现priority_queue)
    //sort(E, E + M, cmp);
    int ans = 0;//权值和
    int edge_num = 0; //已选择的边数
    int cnt = N; //连通块数
    for(int i = 0; i < M; i++) {

        int root_u = find_root(E[i].u);
        int root_v = find_root(E[i].v);
        if(root_u != root_v) {
            union_set(root_u, root_v);
            edge_num++;
            cnt--; //连通块数-1
            ans += E[i].cost;
        }
        if(edge_num == N - 1) break;  //边数等于结点数-1
    }
    if(cnt != 1) return -1;//只剩一个连通块
    else return ans;

}//kruskal

int main() {
    int a, b, cost;
    while(scanf("%d %d", &M, &N) != EOF) {
        if(M == 0) break;

        for(int i = 0; i < M; i++) {
            scanf("%d %d %d", &a, &b, &cost);
            E[i].u = a; E[i].v = b;
            E[i].cost = cost;
        }

        int ans = kruskal();
        if(ans == -1) printf("?\n");
        else printf("%d\n", ans);

    }//while

    system("pause");
    return 0;
}


| Prime算法 - (邻接表版本)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;
const int maxn = 110;
const int INF = 0x3fffffff;

int d[maxn];
int vis[maxn];
struct edge {
    int v, cost; //end, cost
    edge() {}
    edge(int _v, int _cost) : v(_v), cost(_cost) {} //构造函数,方便加入结点
};

vector<edge> Adj[maxn]; //Adjacency list
int N, M;

int prime(int st) {
    fill(d, d + maxn, INF);
    memset(vis, false, sizeof(vis));
    d[st] = 0;//start
    int ans = 0;

    for(int i = 1; i <= N; i++) { // add all N nodes
        int u = - 1, min_cost = INF;
        for(int j = 1; j <= N; j++) {
            if(vis[j] == false && d[j] < min_cost) {
                min_cost = d[j];
                u = j;
            }
        }
        if(u == -1) return -1;
        vis[u] = true; //add this node
        ans += d[u]; //累加权值

        for(int j = 0; j < Adj[u].size(); j++) {
            int v = Adj[u][j].v, cost = Adj[u][j].cost;
            if(vis[v] == false && cost < d[v])
                d[v] = cost;
        }
    }//for - i;
    return ans;
}

int main() {
    int st, ed, cost;
    edge e;
    while(scanf("%d %d", &M, &N) != EOF) {
        if(M == 0) break;
        for(int i = 1; i <= N; i++) Adj[i].clear();
        for(int i = 0; i < M; i++) {//input info of edges
            scanf("%d %d %d", &st, &ed, &cost);
            Adj[st].push_back(edge(ed, cost)); //underected graph
            Adj[ed].push_back(edge(st, cost));
        }

        int ans = prime(1);//start from node 1
        if(ans == -1) printf("?\n");
        else printf("%d\n", ans);
    }

    system("pause");
    return 0;
}

| Prime算法 - (邻接表版本)(堆优化 - priority_queue

思路: 以空间换时间

  • priority_queue保存,优化最短距离的选取;
  • 原有的数组d[]还是需要保留,不然距离更新无法用队列操作;
  • 新距离直接加入队列即可,虽然会产生多个到同一点的距离,但是选择出的一定是最后添加最小的那个!
  • 之后由于vis[]的标记,不然再有到已标记结点的边被选择;

时间复杂度: 原有算法的时间度为 O(V^2), 而堆优化后虽然外层循环仍然是O(V),内层查找最短距离为O(logV),整个过程每条边被访问一次,O(E),时间复杂度降为O(VlogV + E)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>

using namespace std;
const int maxn = 110;
const int INF = 0x3fffffff;

//最小距离-结构体【堆优化 - 以空间换时间】
struct dis{
    int u, d;
    dis() {}
    dis(int _u, int _d) : u(_u), d(_d) {}
    bool operator < (const dis & D) const {
        return d > D.d; //距离小的考前
    }
};

priority_queue<dis> D; //堆优化最短距离的选取;应该
int d[maxn];  //需要用数组再保存一份最短距离,便于更新
bool vis[maxn];
struct edge {
    int v, cost; //end, cost
    edge() {}
    edge(int _v, int _cost) : v(_v), cost(_cost) {} //构造函数,方便加入结点
};

vector<edge> Adj[maxn]; //Adjacency list
int N, M;

int prime(int st) {
    for(int i = 1; i <= N; i++) { //初始化距离
        if(i == st) D.push(dis(i, 0)); //起点
        else D.push(dis(i, INF));
    }
    fill(d, d + maxn, INF);
    memset(vis, false, sizeof(vis));
    d[st] = 0;
    int ans = 0;

    for(int i = 1; i <= N; i++) { //add all N nodes
        while(vis[D.top().u] == true) D.pop(); //弹出废点
        if(D.top().d == INF) return -1; //已无边可选,即生成树构造失败
        int u = D.top().u, min_cost = D.top().d; //弹出最小距离
        D.pop();

        vis[u] = true; //add this node
        ans += min_cost; //累加权值

        for(int j = 0; j < Adj[u].size(); j++) { 
            int v = Adj[u][j].v, cost = Adj[u][j].cost;
            if(vis[v] == false && cost < d[v]) {
                d[v] = cost; 
                D.push(dis(v, cost)); //直接加入(虽然会产生到同一点的多个距离,但肯定比原来长度短)
            }
        }
    }//for - i;
    return ans;
}

//主函数完全相同
int main() {
    int st, ed, cost;
    edge e;
    while(scanf("%d %d", &M, &N) != EOF) {
        if(M == 0) break;
        for(int i = 1; i <= N; i++) Adj[i].clear();
        for(int i = 0; i < M; i++) {//input info of edges
            scanf("%d %d %d", &st, &ed, &cost);
            Adj[st].push_back(edge(ed, cost)); //underected graph
            Adj[ed].push_back(edge(st, cost));
        }

        int ans = prime(1);//start from node 1
        if(ans == -1) printf("?\n");
        else printf("%d\n", ans);
    }

    system("pause");
    return 0;
}

| Prime算法 -(邻接矩阵版本)

【邻接矩阵版本堆优化价值不大;因为即使优化了最小距离的选取,每次距离更新时还是要遍历所有边;而prime算法多用于稠密图】

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;
const int INF = 0x3fffffff;
const int maxn = 110;

int N, M;
int G[maxn][maxn]; 
int d[maxn]; //Prime
bool vis[maxn];

//Prime算法
int prime(int st) {
    fill(d, d + maxn, INF);
    fill(vis, vis + maxn, false);

    int ans = 0;
    d[st] = 0; //起点(根)
    for(int i = 1; i <= N; i++) { //加入所有结点

        int u = -1, min_cost = INF;
        for(int j = 1; j <= N; j++) {
            if(vis[j] == false && d[j] < min_cost) {//查找距树最近的结点
                min_cost = d[j];
                u = j;
            }
        }
        if(u == -1) return -1;//非连通图,构造MST失败 可是WA啊
        vis[u] = true; //标记访问
        ans += d[u]; //累加权值

        for(int v = 1; v <= N; v++) { //更新最短距离
            if(vis[v] == false && G[u][v] < d[v]){
                d[v] = G[u][v];
            }
        }//for - v

    }//for - i
    return ans;
}//prime()

int main() {
    int a, b, cost;
    while(scanf("%d %d", &M, &N) != EOF) {
        if(M == 0) break;

        fill(G[0], G[0] + maxn * maxn, INF);

        for(int i = 0; i < M; i++) {
            scanf("%d %d %d", &a, &b, &cost);
            G[a][b] = cost;
            G[b][a] = cost;
        }

        int ans = prime(1); //从1号结点出发寻找
        if(ans == -1) printf("?\n");
        else printf("%d\n", ans);

    }//while

    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_26398495/article/details/82689633