最小生成树

最小生成树

生成树的定义:在一个图中寻到这样一种子图:去除子图中的一些边,剩下的节点和边可以构成一棵树,那么这课树就是生成树。

最小生成树:如果边上有权值,那么是的权值最小的生成树就叫做最小生成树(MST)。

常用的最小生成树算法为Kruskal算法和Prim算法。

最小生成树示例图:

这里写图片描述

Prim算法

Prim算法和Dijkstra算法十分相似,都是从某个顶点出发不断添加边的算法。

算法思想:

  1. 我们选取一个顶点v作为起点树T;
  2. 然后贪心的选取T和其它顶点之间连接的最小权值的边,并把它加入到T中。
  3. 不断重复操作2,直到所有的顶点被选择完。

那么如何查找到最小权值的边呢?把已选顶点集合X和顶点V连接的最小权值记为mincost[v]。在向X里添加 顶点u时,只需要查看和u相连的边就可以了。对于每条边,更新mincost[v]=min(mincost[v], 边(u,v)的权值 )即可。

如果每次都遍历未包含在X中的点mincost[v],需要时间复杂度 O ( ∣ V ∣ 2 ) O(|V|_2) O(V2) 。如果使用堆来维护那么时间复杂度就是 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(ElogV)

#include<iostream>

#define MAX_V 1000
#define INF 999999999

using namespace std;

int cost[MAX_V][MAX_V]; //存放边的权值
int mincost[MAX_V]; //从集合X出发的边到每个顶点的最小权值
bool used[MAX_V];  //顶点是否已经包含到X中
int V;

void init(){  //按要求输入数据
    
}

int prim() {
    for (int i = 0; i < V; i++) {
        mincost[i] = INF;
        used[i] = false;
    }
    mincost[0] = 0; //选取零号节点作为起始点
    int res = 0;  //用来记录总权值
    while (true) {
        int v = -1;
        for (int u = 0; u < V; u++) {  //选取权值最小的点,初始时会选中0号点
            if (!used[u] && (v == -1 || mincost[u] < mincost[v])) v = u;
        }
        if (v == -1) break; //如果v还是-1说明所有的点都被用过了
        used[v] = true;  //标记v被使用
        res += mincost[v];  //记录总权值,很显然第一次循环时v=0,mincost[0]=0;
        for (int u = 0; u < V; u++) {  
        //更新所有点到X(已选点的集合),因为新加入了节点v,所以将原来的距离和cost[v][u]比较。
        // 注意这里, Prim和Dijkstra算法的区别就再这一句上。
            mincost[u] = min(mincost[u], cost[v][u]);
        }
    }
    return res;

}

int main() {


    return 0;
}

Kruskal算法

算法思路:

  1. 按照边的权值顺序从小到大选择边,如果选中的边加入到已选边中不会产生圈,那么就把该边加入到已选边中。
  2. 重复步骤1,直到所有的顶点被选则。

该算法的重点在于如何检查是否存在圈。每次选中边检查该边的两个顶点是否在已选顶点中,如果连个都在,那么一定会产生圈。

该算法主要是在边排序上花费时间,算法的时间复杂度是 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(ElogV)

算法实现如下:

bigncaji.h头文件

//bingcaji.h
// Created by wyc on 18-7-19.
//

#ifndef UNTITLED2_BINGCAJI_H
#define UNTITLED2_BINGCAJI_H
#include <iostream>

#define  MAXN 1000
using namespace std;

int par[MAXN], ranks[MAXN];  //par表示parent代表父亲编号,par[x]指的是x的父节点编号

//初始化n个元素
void initUnionFind(int n) {
    for (int i = 0; i < n; i++) {
        par[i] = i;
        ranks[i] = 0;
    }
}

//查询树的根
int find(int x) {
    if (par[x] == x) {
        return x;
    } else {
        return par[x] = find(par[x]);
    }
}

//合并x和y所属的集合
void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) return;
    if (ranks[x] < ranks[y]) {
        par[x] = y;
    } else {
        par[y] = x;
        if (ranks[x] == ranks[y]) ranks[x]++;
    }
}
//x和y是否属于同一集合
bool same(int x,int y){
    return find(x) ==find(y);
}

#endif //UNTITLED2_BINGCAJI_H

main.cpp文件

#include<iostream>
#include <algorithm>
#include "bingcaji.h"

#define MAX_V 1000
#define INF 999999999

using namespace std;

struct edge{int u,v,cost;};
edge es[MAX_V];
int V,E;

bool comp(const edge & e1, const edge & e2){
    return e1.cost <e2.cost;
}


void init(){
    cin>>V>>E;
    for(int i=0;i<E;i++){
        cin>>es[i].u>>es[i].v>>es[i].cost;
    }
}

int kruskal(){
    sort(es,es+E,comp); //按照从小到达排序
    initUnionFind(V);
    int res=0;
    for(int i=0;i<E;i++){
        edge e=es[i];
        if(! same(e.u,e.v)){
            unite(e.u,e.v);
            res+=e.cost;
        }
    }
    return res;

}



int main() {
    init();
    cout<<kruskal();

    return 0;
}

/* 测试数据 ,该数据即为文章开始的图,输入为V E  边(u,v,cost)
8   11
0 2 2
1 2 3
2 3 4
2 5 1
3 4 3
5 6 5
3 6 7
3 7 9
6 7 8
6 4 1
4 7 5

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值