Prim算法及其优化

#include <stdio.h>

int main(){
    int n, m;
    scanf("%d%d", &n, &m);

    int e[10][10];  // 任意两点间直接距离
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++){
            if (i == j) e[i][j] = 0;
            else e[i][j] = inf;
        }
    }

    int t1, t2, t3;
    for (int i = 1; i <= m; i++){
        scanf("%d%d%d", &t1, &t2, &t3);
        e[t1][t2] = t3;
        e[t2][t1] = t3;
    }

    int dis[10];    // 生成树到各个顶点的距离,初始只表示1号点到各个顶点的距离
    for (int i = 1; i <= n; i++){
        dis[i] = e[1][i];
    }

    int book[10] = {0};     // 为1表示顶点已加入生成树
    int count = 0;  // 生成树中顶点数量
    int sum = 0;    // 生成树的权值和

    // 将1号顶点加入生成树
    book[1] = 1;
    count++;

    // 核心部分,等待所有结点加入生成树
    while (count < n){
        // 在未加入生成树的结点中寻找离生成树最近的结点
        int min = INT_MAX;
        for (int i = 1; i <= n; i++){
            if (book[i] == 0 && dis[i] < min){
                min = dis[i];
                j = i;
            }
        }

        // 将其加入生成树
        book[j] = 1;
        count++;
        sum += dis[j];

        // 扫描当前顶点j所有的边,以j为中间点,更新生成树到每一个非树顶点的距离
        for (int k = 1; k <= n; k++){
            if (book[k] == 0 && dis[k] > e[j][k]){
                dis[k] = e[j][k];
            }
        }
    }

    printf("%d\n", sum);

    return 0;
}
// 邻接表存储图,用堆来选新边
// 复杂度从O(N^2)变成O(MlogN)

#include <stdio.h>
#include <limits.h>

int heap[20], pos[20], size;  // heap用来保存堆,pos用来存储每个顶点在堆中的位置,size表示堆大小

// 交换堆中两个结点
void swap(int x, int y){
    // 直接交换值
    int t = heap[x];    
    heap[x] = heap[y];
    heap[y] = t;

    // 更新pos
    t = pos[heap[x]];
    pos[heap[x]] = pos[heap[y]];
    pos[heap[y]] = t;
}

// 堆的向下调整函数
void siftDown(int i){
    // i表示向下调整的堆结点编号
    int flag = 1;   // 为1表示需要继续向下调整
    while (i * 2 <= size && flag == 1){
        int t = i;
        // 如果儿子更小,则指向儿子
        if (dis[heap[t]] > dis[heap[i * 2]]){
            t = i * 2;
        }
        if (i * 2 + 1 <= size && dis[heap[t]] > dis[heap[i * 2 + 1]]){
            t = i * 2 + 1;
        }

        // 判断最小结点是当前结点还是在子结点上
        if (t != i){
            // 如果是在子结点上
            swap(t, i); // 交换两个结点
            i = t;  // 当前结点下沉
        }
        else{
            // 如果当前就是最小结点,就不用继续调整了
            flag = 0;
        }
    }
}

// 堆向上调整函数
void siftUp(int i){
    int flag = 1;
    if (i == 1) return; // 堆顶不需要调整
    while (i != 1 && flag == 1){
        // 判断是不是比父结点小
        if (dis[heap[i]] < dis[heap[i / 2]]){
            swap(i, i / 2);
        }
        else{
            flag = 1;
        }
        i /= 2; // 上升到父结点
    }
}

// 从堆顶取元素
int pop(){
    int t = heap[1];    // 记录堆顶元素
    heap[1] = heap[size];   // 将最后一个结点提到堆顶
    pos[heap[1]] = 1;   // 更新pos中的位置信息
    size--;
    siftDown(1);
    return t;
}

int main(){
    int n, m;
    scanf("%d%d", &n, &m);

    int u[20], v[20], w[20];
    for (int i = 1; i <= m; i++){
        scanf("%d%d%d", &u[i], &v[i], &w[i]);
    }

    // 无向图,所有边反向存储一次
    for (int i = m + 1; i <= 2 * m; i++){
        u[i] = v[i - m];
        v[i] = u[i - m];
        w[i] = w[i - m];
    }

    int first[20];  // 表示顶点对应的第一条邻接边, -1表示到头了
    for (int i = 1; i <= n; i++){
        first[i] = -1;
    }
    // 把边都接上
    for (int i = 1; i <= 2 * m; i++){
        next[i] = first[u[i]];  // 第i条边头插上去
        first[u[i]] = i;    // 使第i条边为u[i]结点的第一条边
    }

    int book[20] = {0}; // 为1表示已经加入生成树
    int count = 0;
    int sum = 0;

    // 加入1号顶点到生成树
    book[1] = 1;
    count++;

    int dis[20] = {0};  // 生成树到各个顶点的初始距离,开始为1号顶点到各个顶点的初始距离
    dis[1] = 0;
    for (int i = 2; i <= n; i++) dis[i] = INT_MAX;

    // 开始只要将1号结点的邻接点距离更新到dis中即可
    int k = first[1];   // 1号结点的第一条邻接边
    while (k != -1){
        dis[v[k]] = w[k];
        k = next[k];
    }

    // 初始化堆
    size = n;
    for (int i = 1; i <= size; i++) {
        h[i] = i;   // 初始化堆每个结点保存初始编号
        pos[i] = i;     // 每个图结点在堆中初始位置
    }

    // 调整堆
    for (int i = size / 2; i >= 1; i--){
        siftDown(i);    // 从第一个非叶结点向下调整
    }

    pop();  // 将堆顶的1号结点取出

    // 核心部分,等待所有结点加入到生成树
    while (count < n){
        int j = pop();  // 取出堆顶元素,因为其必然离生成树最近,所以可以加入生成树
        book[j] = 1;
        count++;
        sum += dis[j];

        // 扫描刚加入的结点j相邻的边,更新其到生成树的距离
        int k = first[j];   // j的第一条相邻边
        while (k != -1){
            if (book[v[k]] == 0 && dis[v[k]] > w[k]){
                dis[v[k]] = w[k];

                // 更新某点到生成树距离后要在堆中向上调整
                siftUp(pos[v[k]]);
            }
            k = next[k];
        }
    }

    printf("%d\n", sum);

    return 0;
}

发表评论