图论——最小生成树

最小生成树

首先明白这几个概念:

(Tree):如果一个无向连通图中不存在回路,则这种图称为树。

生成树 (Spanning Tree):无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树。

生成树是连通图的极小连通子图。这里所谓极小是指:若在树中任意增加一条边,则将出现一条回路;若去掉一条边,将会使之变成非连通图。

  1) 应用场景
设想有9个村庄,这些村庄构成如下图所示的地理位置,每个村庄的直线距离都不一样。若要在每个村庄间架设网络线缆,若要保证成本最小,则需要选择一条能够联通9个村庄,且长度最小的路线。

 

 2) 最小生成树概念

  • 一个带权值的图:网。所谓最小成本,就是用n-1条边把n个顶点连接起来,且连接起来的权值最小。
  • 最小生成树不能有回路。
  • 最小生成树可能是一个,也可能是多个。

最小生成树的算法一般有两种,分别为 Kruskal算法 和 Prim算法

Kruskal算法

知识点:并查集:数据结构——并查集

克鲁斯卡尔算法的基本思想:始终选择当前可用(所选的边一定不会构成回路)的最小权植边。

具体步骤:

1. 建立一个升序优先级队列(或者用容器进行排序)。

2. 每次选择堆顶元素(当前可用权值最小的边)。

3. 若该边的两个顶点落在不同的连通分量上,选择这条边;若这条边的两个顶点落到同一连通分量上,舍弃这条边。反复执行2,3,直到所有的都在同一连通分量上。

几个例题:

板子:

#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
int N, M;
int pre[5001];
class Node {
public:
    int from, to, weight;
    Node(int f, int t, int w) {
        from = f; to = t; weight = w;
    }
    bool operator<(Node m)const {
        return m.weight < weight;
    }
};
int find(int x) {
    if (x == pre[x])return pre[x];
    else return pre[x] = find(pre[x]);
}

int kurskal(priority_queue<Node>&q, int cnt) {
    int res = 0;
    while (!q.empty() && cnt > 1) {
        Node m = q.top(); q.pop();
        int fx = find(m.from), fy = find(m.to);
        if (fx != fy) {
            pre[fx] = fy;
            cnt--;
            res += m.weight;
        }
    }
    if (cnt == 1)
        return res;
    else return -1;
}


int main() {
    priority_queue<Node>q;
    //n个节点,m条边
    cin >> N >> M;
    for (int i = 0; i <= 5000; i++) {
        pre[i] = i;
    }
    int from, to, weight;
    for (int i = 0; i < M; i++) {
        scanf("%d%d%d", &from, &to, &weight);
        q.push(Node(from, to, weight));
    }

    int res = kurskal(q, N);
    if (res == -1) {
        //无解        
    }
    else {
        //cout << res << endl;
    }

    return 0;
}
View Code

 

HDU-1863:http://acm.hdu.edu.cn/showproblem.php?pid=1863

#include <iostream>
#include <queue>

using namespace std;
int pre[105]; //并查集
class edge {
public:
    int from, to, weight;
    edge(int f, int t, int w) { from = f; to = t; weight = w; }
    bool operator < (edge n) const { return n.weight < weight; }
};
int M, N;

int Find(int x) {
    if (pre[x] == x)return x;
    return pre[x] = Find(pre[x]);
}

int kruskal(priority_queue<edge>& q) {
    for (int i = 1; i <= M; i++) pre[i] = i;
    int res = 0;
    while(!q.empty()){
        edge e = q.top(); q.pop();
        int fx = Find(e.from);
        int fy = Find(e.to);
        if (fx != fy) {
            pre[fx] = fy;
            M--;
            res += e.weight;
        }
    }
    if (M != 1) return -1;
    return res;
}

int main() {
    while (cin >> N >> M) {
        int c1, c2, c3;
        if (N == 0) break;
        priority_queue<edge> v;
        for (int i = 0; i < N; i++) {
            cin >> c1 >> c2 >> c3;
            v.push(edge(c1, c2, c3)); 
        }
        int res = kruskal(v);
        if (res == -1) cout << "?" << endl;
        else cout << res << endl;
    }
    return 0;
}
View Code

HDU-1879:http://acm.hdu.edu.cn/showproblem.php?pid=1879

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int pre[110];
int N;
class edge {
public:
    int from, to, weight;
    edge(int f, int t, int w) {
        from = f; to = t; weight = w;
    }
};

bool cmp(edge e1, edge e2) {
    return e1.weight < e2.weight;
}

int Find(int x) {
    if (pre[x] == x)return x;
    return pre[x] = Find(pre[x]);
}


void Kruskal(vector<edge>v) {
    int res = 0, cnt = N;
    for (int i = 0; i < v.size() && cnt > 1; i++) {
        int fx = Find(v[i].from); int fy = Find(v[i].to);
        if (fx != fy) {
            pre[fx] = fy;
            cnt--;
            res += v[i].weight;
        }
    }
    cout << res << endl;
}
int main() {
    while (scanf("%d", &N)) {
        if (N == 0)break;
        vector<edge>v;
        int n = N * (N - 1) / 2;
        for (int i = 1; i <= N; i++)pre[i] = i;
        while (n--) {
            int from, to, weight, flag;
            scanf("%d%d%d%d", &from, &to, &weight, &flag);
            if (flag == 1) {
                int fx = Find(from); int fy = Find(to);
                if (fx != fy) {
                    pre[fx] = fy;
                    N--;
                }
            }
            else v.push_back(edge(from, to, weight));
        }
        sort(v.begin(), v.end(), cmp);
        Kruskal(v);
    }
    return 0;
}
View Code

P3366 : https://www.luogu.org/problemnew/show/P3366

#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
int N, M;
int pre[5001];
class Map {
public:
    int from, to, weight;
    Map(int f,int t,int w) {
        from = f; to = t; weight = w;
    }
    bool operator<(Map m)const {
        return m.weight < weight;
    }
};
void initialize() {
    for (int i = 0; i <= 5000; i++) {
        pre[i] = i;
    }
}
int find(int x) {
    if (x == pre[x])return pre[x];
    else return pre[x] = find(pre[x]);
}

int main() {
    priority_queue<Map>q;
    cin >> N >> M;
    initialize();
    int c1, c2, c3;
    for (int i = 0; i < M; i++) {
        scanf("%d%d%d",&c1,&c2,&c3);
        q.push(Map(c1, c2, c3));
    }
    int res = 0;

    while (!q.empty()) {
        Map m = q.top(); q.pop();
        int fx = find(m.from), fy = find(m.to);
        if (fx != fy) {
            pre[fx] = fy;
            N--;
            res += m.weight;
        }
    }
    if (N == 1) {
        cout << res << endl;
    }
    else {
        cout << "orz" << endl;
    }
    return 0;
}
View Code

 


Prim算法

Prim算法思想:首先将图的点分为两部分,一种是访问过的u(第一条边任选),一种是没有访问过的v

1: 每次找u到v的权值最小的边。

2: 然后将这条边中的v中的顶点添加到u中,直到v中边的个数=顶点数-1

板子:

#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;

class Node {
public:
    int to, weight;
    Node* next ;
    void push(int to, int weight) {
        Node* s = new Node;
        s->to = to; s->weight = weight; 
        s->next = next;
        next = s;
    }

    bool operator<(Node n)const {
        return n.weight < weight;
    }

}*head;

int n, m;
bool fuck[5005];
int lowcost[5005];
const int inf = 1 << 30;
priority_queue<Node>q;

void initiliaze() {
    fuck[1] = true;
    for (int i = 1; i <= n; i++) {
        lowcost[i] = inf;
    }

    Node* p = head[1].next;
    while (p) {
        lowcost[p->to] = p->weight;
        q.push(*p);
        p = p->next;
    }
}

int prim() {
    int res = 0, cnt = n;
    while (!q.empty() && cnt > 1) {
        Node s = q.top();
        q.pop();
        if (!fuck[s.to]) {
            fuck[s.to] = true;
            cnt--;
            res += s.weight;
            Node* p = head[s.to].next;
            while (p) {
                if (!fuck[p->to]) {
                    q.push(*p);
                    if (p->weight < lowcost[p->to])
                        lowcost[p->to] = p->weight;
                }
                p = p->next;
            }
        }
    }
    if (cnt == 1)return res;
    else return - 1;
}
int main() {
    cin >> n >> m;
    head = new Node[n + 1];
    for (int i = 1; i <= n; i++) {
        head[i].next = NULL;
    }

    for (int i = 0; i < m; i++) {
        int from, to, weight;
        scanf("%d%d%d", &from, &to, &weight);
        head[from].push( to, weight);
        head[to].push( from, weight);
    }

    initiliaze();

    int res = prim();
    if (res == -1)cout << "orz" << endl;
    else cout << res << endl;
    return 0;
}
View Code

 

图示步骤:

建立一个dis数组,初始值为节点1(任意一个都可以)到各点的值,规定到自己是0,到不了的是inf。

找到其中权值最小的,1→4

将dis[4]赋值为0,标记为已访问过,同时借助4节点更新dis数组。

找到其中权值最小,4→3

后面的操作都是一样的了。

 

最后整个dis数组都是0了,最小生成树也就出来了,如果dis数组中还有 inf 的话,说明这不是一个连通图。


 

HDU-1863:http://acm.hdu.edu.cn/showproblem.php?pid=1863

用邻接矩阵实现的,inf表示到不了,0表示已经访问过的。

#include <iostream>
#include<cstring>
#include<algorithm>
#define inf INT_MAX
using namespace std;

int N, M;
int map[106][105];
int dis[105];

int prim(){

    int  min, v, res = 0;
    dis[1] = 0;
    for (int i = 2; i <= M; i++){
        dis[i] = map[1][i];
    }
    for (int i = 2; i <= M; i++){
        v = 0;
        for (int j = 2; j <= M; j++){
            //到不了的,访问过的不进行比较
            if (dis[j] != inf && dis[j] != 0 && (v == 0 || dis[j] < dis[v])) {
                v = j;
            }
        }
        if (v == 0)//没找够M-1条线,就没路了
            return -1;
        res += dis[v];
        dis[v] = 0;
        for (int j = 2; j <= M; j++){
            if (map[v][j] < dis[j]) {
                dis[j] = map[v][j];
            }
        }
    }
    return res;
}
int main(){
    int i, a, b, c;
    while (cin >> N >> M) {
        if (N == 0)break;
        for (int i = 1; i <= M; i++) {
            for (int j = 1; j <= M; j++) {
                map[i][j] = inf;
            }
        }
        for (i = 1; i <= N; i++){
            cin >> a >> b >> c;
            map[a][b] = map[b][a] = c;
        }
        int ans = prim();
        if (ans == -1)cout << "?" << endl;
        else cout << ans << endl;
    }
    return 0;
}
View Code

两者区别:

点多边少可以用Kruskal,因为Kruskal算法每次查找最短的边。 点少边多可以用Prim,因为它是每次加一个顶点。

prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。

转载于:https://www.cnblogs.com/czc1999/p/10354505.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值