图论:最小生成树之Kruskal算法

最小生成树之Kruskal算法

Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪心算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。

最小生成树:

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
生成树中权值(代价)最小的树

问题分析:
就最小生成树问题而言,解决问题的关键是贪心算法的应用.
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
生成最小生成树有两种方案,一是按边找点,一是按点找边。Kruskal算法的思路是按边找点。
并边+贪心
Kruskal算法本身并不难,他的难度主要是并查集的应用。
并查集。

并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。

  1. 构造一个只有n个顶点、没有边的非连通图T={V, ∅ }(也可以认为是所有点的集合),每个顶点自成一个连通分量(因为用到并查集的思想,可认为所有点都是一棵树,自己是自己的根节点);
  2. 将所有边组成一个集合,并将其按从小到大进行排列,每次选取当前边集合中权值最短的边;
  3. 找出选取出来的边的两端的节点,如果这两个节点分属与不同的连通分量,则将这两个连通分量合并,否则不做处理(两个节点分属于不同的树,则将两棵树合并为一棵。如果两个节点已经属于一棵树,则该边不做处理。)
  4. 重复下去,直到所有顶点在同一连通分量上为止(知道所以的节点均在一颗树上)。
    在这里插入图片描述
    本函数以邻接链表为存储结构运用Kruskal算法,判断改图是否为连通图同时求其最小生成树的权值和
#include <cstdio>
#include <iostream>

using namespace std;

typedef int Vertex;       /* 用顶点下标表示顶点,为整型 */

/* 邻接点的定义 */
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
    
    
    Vertex AdjV;        /* 邻接点下标 */
    int weight;
    PtrToAdjVNode Next; /* 指向下一个邻接点的指针 */
};

/* 顶点表头结点的定义 */
typedef struct Vnode{
    
    
    PtrToAdjVNode FirstEdge; /* 边表头指针 */
    int father;
} *PAdjList, AdjList;     /* AdjList是邻接表类型 */

/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode{
    
    
    int Nv;     /* 顶点数 */
    int Ne;     /* 边数   */
    PAdjList G;  /* 邻接表 */
};

typedef PtrToGNode LGraph; /* 以邻接表方式存储的图类型 */

// 尾插法建立邻接表
LGraph CreateGraph(int cityNum, int roadNum, int*&weights) // 创建图并且将Visited初始化为false
{
    
    
    LGraph graph = (LGraph)malloc(sizeof(GNode));  // 图表的内存申请
    graph -> G = (PAdjList)malloc(cityNum * sizeof(AdjList));  // 邻接表的内存申请
    PAdjList tail = (PAdjList)malloc(cityNum * sizeof(AdjList));

    // 变量初始化
    for(int i = 0; i < cityNum; ++i) {
    
    (graph->G + i)->FirstEdge = nullptr;}
    for(int i= 0; i < cityNum; ++i) {
    
    (tail + i)->FirstEdge = nullptr;}
    for(int i = 0; i < cityNum; ++i) {
    
    (graph ->G + i) ->father = -1;}
    graph ->Nv = cityNum;
    graph->Ne = roadNum;

    for(int j = 0; j < roadNum; ++j) {
    
      // 循环记录数据
        int city, road, cost;
        scanf("%d %d %d", &city, &road, &cost);
        if(city > cityNum || city < 1)
            return nullptr;
        getchar();
        *(weights + j) = cost;
        // 建立正向出度节点
        PtrToAdjVNode node1 = (PtrToAdjVNode)malloc(sizeof(GNode));
        node1 ->weight = cost;
        node1->AdjV = road - 1;
        node1 ->Next = nullptr;
        if((graph ->G + city - 1) -> FirstEdge == nullptr) (graph -> G + city - 1) ->FirstEdge = node1;
        else (tail + city - 1) ->FirstEdge ->Next = node1;
        (tail + city - 1) ->FirstEdge = node1;
    }
    return graph;
}

// 并查集时更新各节点跟值
void UpdateTree(LGraph &Graph, int preFather, int currFather) {
    
    
    for (int i = 0; i < Graph->Nv; ++i) {
    
    
        if ((Graph->G + i)->father == preFather)
            (Graph->G + i)->father = currFather;
    }
    (Graph ->G + preFather) ->father = currFather;
}

// 对所有边进行排序,从而每次取最权值最短的边
void Sort(int* &weights, int length) {
    
    
    for (int i = 1; i < length; i++) {
    
    
        for (int j = 0; j < length - i; j++) {
    
    
            if (weights[j] > weights[j + 1]) {
    
    
                int temp = weights[j];
                weights[j] = weights[j + 1];
                weights[j + 1] = temp;
            }
        }
    }
}

// 寻找最少花费
// 本函数将并查集与Kruskal合并为一个函数,最好是将其分成两个函数。
int Kruskal ( LGraph &Graph, const int* weights){
    
    
    int flag = 0;
    int sum = 0;
    PtrToAdjVNode ptemp = nullptr; // 定义外循环表头指针
    for(int i = 0; i < Graph ->Ne; ++i){
    
     // 对weights进行遍历,依次寻找最小边
        for(int j = 0; j < Graph ->Nv; ++j){
    
      // 对邻接表表头便利寻找对应边的节点
            ptemp = (Graph ->G + j) ->FirstEdge;  //  定义邻接表表头指针
            while(ptemp && flag < 1) {
    
    
                if(ptemp ->weight == *(weights + i) && flag  < 1){
    
      // 并查集操作
                    // 如果两个节点都是根
                    if((Graph ->G + j) ->father == -1 && (Graph -> G + (ptemp ->AdjV)) ->father == -1) {
    
    
                        //(Graph->G + (ptemp->AdjV))->father = j;
                        UpdateTree(Graph, j, ptemp ->AdjV);
                        flag = 1;
                    }
                        // 两个节点在不同的树上
                    else if((Graph ->G + j) ->father != (Graph -> G + (ptemp ->AdjV)) ->father
                            && (Graph ->G + j) ->father != ptemp ->AdjV
                            && (Graph -> G + (ptemp ->AdjV)) ->father != j) {
    
    
                        if ((Graph->G + j)->father == -1 && (Graph->G + (ptemp->AdjV))->father > -1)
                            UpdateTree(Graph, (Graph->G + (ptemp->AdjV))->father, j);
                            // 第二个是根,第一个不是根
                        else if ((Graph->G + j)->father > -1 && (Graph->G + (ptemp->AdjV))->father == -1)
                            UpdateTree(Graph, (Graph->G + j)->father, ptemp->AdjV);
                        else if ((Graph->G + j)->father > -1 && (Graph->G + (ptemp->AdjV))->father > -1)
                            UpdateTree(Graph, (Graph->G + (ptemp->AdjV))->father, (Graph->G + j)->father);
                        flag = 1;
                    }
                }
                ptemp = ptemp ->Next;
            }
        }
        if(flag == 1) sum += *(weights + i);
        flag = 0;
    }
    return sum;
}

int main()
{
    
    
    LGraph graph = NULL;
    int cityNum = 0;
    int roadNum = 0;
    scanf("%d %d", &cityNum, &roadNum);
    getchar();
    int* weights = (int*)malloc(roadNum * sizeof(int));
    graph = CreateGraph(cityNum, roadNum, weights);
    if(!graph){
    
    
        printf(("Impossible"));
        return 0;
    }
    Sort(weights, roadNum);
    int cost = Kruskal(graph, weights);
    int count = 0;
    for(int i = 0; i < graph ->Nv; ++i)
    {
    
    
        //printf("%d\n", (graph ->G + i) ->father);
        if((graph ->G + i) ->father == -1)
            ++count;
    }

    if(count > 1)
        printf(("Impossible"));  // 打印输出
    else
        printf("%d", cost);
    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_44733706/article/details/103780317