最小生成树C

最小生成树是所有节点的最小连通子图,即:以最小的成本(边的权值)将图中所有节点链接到一起。图中有n个节点,那么一定可以用n-1条边将所有节点连接到一起。

Prim

prim算法是从节点的角度采用贪心的策略每次寻找距离最小生成树最近的节点并加入到最小生成树中。

prim算法核心就是三步
第一步,选距离生成树最近节点
第二步,最近节点加入生成树
第三步,更新非生成树节点到生成树的距离(即更新minDist数组)
ps. minDist数组用来记录每一个节点距离最小生成树的最近距离

题目

题目描述
在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。
不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来(注意:这是一个无向图)。
给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

输入描述
第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。

接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。

输出描述
输出联通所有岛屿的最小路径总距离

输入示例
7 11
1 2 1
1 3 1
1 5 2
2 6 1
2 4 2
2 3 2
3 4 1
4 5 1
5 6 2
5 7 1
6 7 1
输出示例
6

#include 
#include 
#include 
#include 
#define N 10005

int main(){
    int v, e;
    int v1, v2, val;

    scanf("%d %d", &v, &e);

    int **grid;
    int *minDist;

    grid = (int**) calloc(v+1, sizeof(int*));
    for(int i = 0; i <= v; ++ i){
        grid[i] = (int*) calloc(v+1, sizeof(int));
        for (int j = 0; j <= v; ++j) {
            grid[i][j] = INT_MAX;
        }
    }
    for(int i = 0; i < e; ++ i){
        scanf("%d%d%d", &v1, &v2, &val);
        grid[v1][v2] = val;
        grid[v2][v1] = val;
    }

    minDist = (int *) calloc(v+1, sizeof(int));
    for(int i = 0; i <= v; ++ i) minDist[i] = INT_MAX;
    bool isInTree[N] = {false};

	// 循环n-1次,建立n-1条边,即可把n个节点的图连在一起
    for(int i = 1; i < v; ++ i){
    	// step1:选距离生成树最近节点
        int cur = 1; // 从1开始
        int minVal = INT_MAX;
        for(int j = 1; j <= v; ++ j){
            if(!isInTree[j] && minDist[j] < minVal){
                minVal = minDist[j];
                cur = j;
            }
        }
        // step2:最近节点(cur)加入生成树
        isInTree[cur] = true;

		// step3:更新距离
        for(int j = 1; j <= v; ++ j){
            if(!isInTree[j] && grid[cur][j] < minDist[j]){
                minDist[j] = grid[cur][j];
            }
        }
    }

    int result = 0;
    for(int i = 2; i <= v; ++ i){
        result += minDist[i];
    }
    printf("%d", result);

    for (int i = 0; i <= v; ++i) {
        free(grid[i]);
    }
    free(grid);
    free(minDist);

    return 0;
}

Kruskal

Kruskal 是维护边的集合,思路:

  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合
#include 
#include 

int v, e;
int* father;
Edge* edges;

typedef struct{
    int l;
    int r;
    int val;
}Edge;

// 初始化并查集
void init(){
    for(int i = 0; i <= v; ++ i)
        father[i] = i;
}

// 并查集查找
int find(int u){
    if(u == father[u]) return u;
    else return father[u] = find(father[u]);
}

// 并查集加入集合
void join(int u, int v){
    u = find(u);
    v = find(v);
    if(u == v) return;
    father[v] = u;
}

int comp(const void *a, const void *b){
    Edge *tmp1 = a;
    Edge *tmp2 = b;
    if(tmp1->val > tmp2->val) return 1;
    else if(tmp1->val < tmp2->val) return -1;
    else return 0;
}

int main(){
    int v1, v2, val;
    scanf("%d%d", &v, &e);

    father = (int*)calloc(v+1, sizeof(int));
    edges = (Edge*)calloc(e, sizeof(Edge));

    for(int i = 0; i < e; ++ i){
        scanf("%d%d%d", &edges[i].l, &edges[i].r, &edges[i].val);
    }
    qsort(edges, e, sizeof(Edge), comp); // 按边权值从小到大排序
    init();

    int result = 0;
    for (int i = 0; i < e; ++i) {
        // 并查集,搜出两个节点的祖先
        int x = find(edges[i].l);
        int y = find(edges[i].r);

        // 祖先不同则不在同一集合
        if(x != y){
            result += edges[i].val;
            join(x, y);
        }
    }
    printf("%d", result);

    free(edges);
    free(father);
    
    return 0;
}

prim + kruskal

根据顶点数量来判定图是稀疏图还是稠密图
如果是稠密图,则调用prim算法;反之调用kruscal算法
稠密图和稀疏图没有严格区分,按情况区别

#include 
#include 
#include 
#include 

int V, E;
typedef struct{
    int i, j;
    int value;
}Edge;

int cmp(const void *a, const void *b) {
    return ((Edge *)a)->value - ((Edge *)b)->value;
}

int prim() {
    int **G;
    int *visited;
    int *minDist;

    G = (int **) calloc(V + 1, sizeof(int *));
    visited = (int *) calloc(V + 1, sizeof(int));
    minDist = (int *) calloc(V + 1, sizeof(int));
    for(int i = 1; i <= V; ++ i) {
        G[i] = (int *) calloc(V + 1, sizeof(int));
        for(int j = 1; j <= V; ++ j) G[i][j] = INT_MAX;
    }
    for(int i = 1; i <= V; ++ i) {
        minDist[i] = INT_MAX;
        visited[i] = 0;
    }

    int s, t, value;
    for(int i = 0; i < E; ++ i) {
        scanf("%d %d %d", &s, &t, &value);
        G[s][t] = G[t][s] = value;
    }

// prim
    //对1号顶点特殊处理
    visited[1] = 1;
    for(int i = 2; i <= V; ++ i) {
        if(minDist[i] > G[1][i])
            minDist[i] = G[1][i];
    }
    // 循环V - 2次,添加剩余顶点(1号和V号顶点不需要再添加)
    for(int i = 2; i <= V; ++ i) {
        int cur = -1;
        int dist = INT_MAX;
        // step1 找到【距离生成树最小的】【非生成树内的】顶点cur
        for(int j = 2; j <= V; ++ j) {
            if(!visited[j] && minDist[j] < dist) {
                cur = j;
                dist = minDist[j];
            }
        }
        // step2 将cur加入生成树中
        visited[cur] = 1;
        // step3 更新非生成树顶点到生成树的最小距离
        for(int j = 2; j <= V; ++ j) {
            if(!visited[j] && G[cur][j] < minDist[j])
                minDist[j] = G[cur][j];
        }
    }

    int res = 0;
    for(int i = 2; i <= V; ++ i) res += minDist[i];

    for(int i = 0; i <= V; ++ i) free(G[i]);
    free(G);
    free(visited);
    free(minDist);

    return res;
}

int kruscal() {
    Edge *edges;
    int *vexset;

    edges = (Edge *) calloc(E, sizeof(Edge));
    vexset = (int *) calloc(V + 1, sizeof(int));

    for(int i = 0; i < E; ++ i) scanf("%d %d %d", &edges[i].i, &edges[i].j, &edges[i].value);
    for(int i = 0; i <= V; ++ i) vexset[i] = i;

    int res = 0;
    qsort(edges, E, sizeof(Edge), cmp);
    for(int i = 0; i < E; ++ i) {
        // 获取第i条边的两顶点所属的连通分量
        int vs1 = vexset[edges[i].i];
        int vs2 = vexset[edges[i].j];
        if(vs1 != vs2) {    //如果两顶点属于不同连通分量
            res += edges[i].value;  //选择这条边加入生成树的边集
            for(int j = 1; j <= V; ++ j) {  //合并连通分量
                if(vexset[j] == vs2)
                    vexset[j] = vs1;
            }
        }
    }

    free(edges);
    free(vexset);
    return res;
}

int main(void) {
    scanf("%d %d", &V, &E);
    double test = V * log(V) / log(2);  // 稀疏图与稠密图的临界值
    if(E >= test) printf("%d", prim());
    else printf("%d", kruscal());

    return 0;
}

小知识补充

定义无穷

#include 

#define MAXN INT_MAX   // 定义int类型的最大正数(正无穷)
#define MINN INT_MIN   // 定义int类型的最小负数(负无穷)

// 其他类型示例:
#define UMAX UINT_MAX  // unsigned int的最大值
#define LMAX LONG_MAX  // long类型的最大值
#define LLMAX LLONG_MAX // long long类型的最大值

你可能感兴趣的:(c语言,算法,图论)