十一、图

  • 表示“多对多”的关系
  • 包含
      一组顶点:通常用V(Vertex)表示顶点集合
      一组边:通常用E(Edge)表示边的集合
             边是顶点对: (v,w) ∈ E,其中v,w ∈ v V——W
             有向边<v,w> 表示从v指向w的边(单行线)V——>W
             不考虑重边和回路
  • 操作集:
             Graph Create():建立并返回空图;
             Graph InsertVertex(Graph G,Vertexv):将v插入G;
             Graph InsertEdge (Graph G,Edge e):将e插入G;
             void DFS (Graph G,Vertexv):从顶点v出发深度优先遍历图G;
             void BFS (Graph G,Vertexv):从顶点v出发宽度优先遍历图G;
             void ShortestPath(Graph G,Vertex v,int Dist[]):计算图G中顶点v到任意其他顶点的最短距离;
             void MST(Graph G):计算图G的最小生成树;

常用术语

无向图: <v,w)> ∈ VR 必有<w,v> ∈ VR, 即VR是对称的,则以无序对(v,w)代替这两个有序对,此时图称为无向图。
有向图: <v,w)> ∈ VR, 从v——>w
完全图: 对于无向图,任意两个顶点都有边的图(有1/2n(n-1)条边的无向图)
有向完全图: 具有n(n-1)条弧的有向图
稀疏图: 有很少条边或弧(如e<nlogn)的图
稠密图: 跟稀疏图相反
连通: 如果从v到w存在一条(无向)路径,则称 V和w是连通的
路径: v到w的路径是一系列顶点{V,V1,V21... Vn/ W}的集合,其中任一对相邻的顶点间都有图中的边。路径的长度是路径中的边数(如果带权,则是所有边的权重和)。如果v到w之间的所有顶点都不同,则称简单路径
回路: 起点等于终点的路径
连通分量: 无向图的极大连通子图
极大顶点数:再加1个顶点就不连通了
极大边数:包含子图中所有顶点相连的所有边
强连通: 有向图中顶点v和w之间存在双向路径,则称V和w是强连通的
强连通图: 有向图中任意两顶点均强连通
强连通分量: 有同图的极大强连通子图

图的存储结构

1. 邻接矩阵表示法

  • 问题:对于无向图的存储,怎么可以省一半的空间?


  • 邻接矩阵 —— 有什么好处?

  • 直观、简单、好理解
  • 方便检查任意一对顶点间是否存在边
  • 方便找任意顶点的所有“邻接点”(有边直接相连的顶点)
  • 方便计算任一顶点的“度”(从该点发出的边数为“出度”,指向该点的边数为“入度”)
         无向图:对应行(或列)非0元素的个数
         有向图:对应行非0元素的个数是“出度”;对应列非0元素的个数是“入度”
  • 邻接矩阵 —— 有什么不好?
  • 浪费空间 —— 存稀疏图(点很多而边很少)有大量无效元素
         对稠密图(特别是完全图)还是很合算的
  • 浪费时间 —— 统计稀疏图中一共有多少条边
#define MaxVertexNum 10
typedef struct GNode *PtrToGNode;
typedef int WeightType;
typedef char DataType;
struct GNode {
    int Nv; //顶点数
    int Ne; //边数
    WeightType G[MaxVertexNum][MaxVertexNum];
    DataType data[MaxVertexNum];
};

typedef PtrToGNode MGraph; //以邻接矩阵存储的图类型

typedef int Vertex;
MGraph CreateGraph(int vertexNum)
{
    Vertex v,w;
    MGraph graphRef;
    graphRef = (MGraph)malloc(sizeof(struct GNode));
    graphRef->Nv = vertexNum;
    graphRef->Ne = 0;
    
    for (v = 0; v<graphRef->Nv; v++)
        for (w = 0; w<graphRef->Nv; w++)
            graphRef->G[v][w] = 0;
    
    return graphRef;
}

struct ENode {
    Vertex v1,v2;     // 有向边<V!,V2>
    WeightType weight;// 权重
};
typedef struct ENode *PtrToENode;
typedef PtrToENode Edge;

void InsertEdge(MGraph graph, Edge e)
{
    //插入边 <V1,V2>
    graph->G[e->v1][e->v2] = e->weight;
    
    //若是无向图,还要插入边 <V1,V2>
    graph->G[e->v2][e->v1] = e->weight;
}

完整的创建一个MGraph
MGraph buildGraph()
{
    MGraph graph;
    Edge e;
    Vertex v;
    int Nv, i;
    
    scanf("%d",&Nv);
    graph = CreateGraph(Nv);
    
    scanf("%d", &(graph->Ne));
    if (graph->Ne != 0) {
        e = (Edge)malloc(sizeof(struct ENode));
        for (i = 0; i < graph->Ne; i++) {
            scanf("%d %d %d",&e->v1, &e->v2, &e->weight);
            InsertEdge(graph, e);
        }
    }
    
    return graph;
}

2. 邻接表表示法

  • 邻接表特点:
  • 方便找任一顶点的所有“邻接点”
  • 节约稀疏图的空间
         需要N个头指针 + 2E个节点(每个结点至少2个)
  • 方便计算任一顶点的“度”
         对无向图:是的
         对有向图:只能计算“出度”;需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”
  • 不方便计算任意一对顶点间是否存在边
#define MaxVertexNum 10
typedef int Vertex;
typedef int WeightType;
typedef char DataType;
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode {
    Vertex adjV; //邻接点下标
    WeightType weight; //边的权重
    PtrToAdjVNode next;
};

typedef struct Vnode {
    PtrToAdjVNode firstEdget;
    DataType data; //存顶点的数据
}AdjList[MaxVertexNum];


struct GNode {
    int Nv; //顶点数
    int Ne; //边数
    AdjList G; //邻接表
};
typedef struct GNode *PtrToGNode;
typedef PtrToGNode LGraph;

struct ENode {
    Vertex v1,v2;     // 有向边<V!,V2>
    WeightType weight;// 权重
};
typedef struct ENode *PtrToENode;
typedef PtrToENode Edge;

LGraph CreateGraph(int vertexNum)
{
    Vertex v,w;
    LGraph graph;
    
    graph = (LGraph)malloc(sizeof(struct GNode));
    graph->Nv = vertexNum;
    graph->Ne = 0;
    
    for (v = 0; v < graph->Nv; v++)
        graph->G[v].firstEdget = NULL;
    
    return graph;
}

void InsertEdge(LGraph graph, Edge e)
{
    PtrToAdjVNode newNode;
    
    /************插入边 <v1, v2>***************/
    newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    newNode->adjV = e->v2;
    newNode->weight = e->weight;
    newNode->next = graph->G[e->v1].firstEdget;
    graph->G[e->v1].firstEdget = newNode;
    
    /************插入边 <v2, v1>***************/
    newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    newNode->adjV = e->v1;
    newNode->weight = e->weight;
    newNode->next = graph->G[e->v2].firstEdget;
    graph->G[e->v2].firstEdget = newNode;
}
完整的创建一个MGraph
LGraph buildGraph()
{
    LGraph graph;
    Edge e;
    Vertex v;
    int Nv, i;
    
    scanf("%d",&Nv);
    graph = CreateGraph(Nv);
    
    scanf("%d", &(graph->Ne));
    if (graph->Ne != 0) {
        e = (Edge)malloc(sizeof(struct ENode));
        for (i = 0; i < graph->Ne; i++) {
            scanf("%d %d %d",&e->v1, &e->v2, &e->weight);
            InsertEdge(graph, e);
        }
    }
    
    return graph;
}

图的遍历

1. 深度优先搜索(DFS)

#define MaxSize 10
int visited[MaxSize];
int adj[MaxSize][MaxSize] = {0};
int n;
int top = -1;
int stack[MaxSize];
void initAdj()
{
    n = 5;
    int add[5][5] = {
                     {0, 1, 0, 1, 0},
                     {1, 0, 1, 0, 1},
                     {0, 1, 0, 1, 1},
                     {1, 0, 1, 0, 0},
                     {0, 1, 1, 0, 0}
                    };
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            adj[i][j] = add[i][j];
        }
    }
    
    for (int i = 0; i < n; i++) {
        visited[i] = 0;
        printf("\n");
        for (int j = 0; j < n; j++) {
            printf("%d ",adj[i][j]);
        }
    }
    printf("\n");
}

void push(int item) {
    if(top >= MaxSize-1) {
        printf("\nStack Overflow.");
    }
    else {
        stack[++top] = item;
    }
}

int pop() {
    if(top < 0) {
        printf("\nStack Underflow.");
        return -1;
    }
    else {
        return stack[top--];
    }
}
非递归法
void dfs(int v) {
    int i;
    push(v);
    while(top >= 0) {
        v = pop();
        if(visited[v] == 0) {
            printf("%d ", v+1);
            visited[v] = 1;
        }
        for(i = 0; i < n; i++) {
            if(adj[v][i] == 1 && visited[i] == 0) {
                push(i);
            }
        }
    }
    printf("\n");
}
递归法
void dfsRecursion(int v)
{
    visited[v] = 1;
    printf("%d ", v+1);
    for(int i = 0; i < n; i++) {
        if(adj[v][i] == 1 && visited[i] == 0) {
            dfsRecursion(i);
        }
    }
}
递归法和非递归法访问节点顺序不同

若有N个顶点、E条边,时间复杂度是

  • 用邻接表存储图,有O(N+E)
  • 用邻接矩阵存储图,有O(N2)

2. 广度优先搜索(BFS)

typedef int  ElementType;
struct QNode{
    ElementType data[MaxSize];
    int front;
    int rear;
};
typedef struct QNode* Queue;

void EnQueue(Queue ptrQ, ElementType item)
{
    if((ptrQ->rear + 1)%MaxSize == ptrQ->front){
        printf("队列已满");
        return;
    }
    
    ptrQ->rear = (ptrQ->rear + 1)%MaxSize;
    ptrQ->data[ptrQ->rear] = item;
}

ElementType DeQueue(Queue ptrQ)
{
    if(ptrQ->front == ptrQ->rear){
        printf("队列空");
        return -1;
    }
    ptrQ->front = (ptrQ->front + 1)%MaxSize;
    return ptrQ->data[ptrQ->front];
}
bool IsEmpty(Queue ptrQ)
{
    return ptrQ->front == ptrQ->rear;
}
void BFS(int v)
{
    struct QNode qNode;
    qNode.front = qNode.rear = 0;
    Queue ptrQ = &qNode;
    visited[v] = 1;
    EnQueue(ptrQ, v);
    while (!IsEmpty(ptrQ)) {
        v = DeQueue(ptrQ);
        printf("%d ",v + 1);
        for(int i = 0; i < n; i++) {
            if(adj[v][i] == 1 && visited[i] == 0) {
                visited[i] = 1;
                EnQueue(ptrQ, i);
            }
        }
    }
}

若有N个顶点、E条边,时间复杂度是

  • 用邻接表存储图,有O(N+E)
  • 用邻接矩阵存储图,有O(N2)

3. 图不连通怎么办?

连通分量: 无向图的极大连通子图
极大顶点数:再加1个顶点就不连通了
极大边数:包含子图中所有顶点相连的所有边


强连通: 有向图中顶点v和w之间存在双向路径,则称V和w是强连通的
强连通图: 有向图中任意两顶点均强连通
强连通分量: 有同图的极大强连通子图

typedef struct {
    int visited[MaxSize];
    int adj[MaxSize][MaxSize];  // 邻接矩阵
    int numVertexes, numEdges;  // 图中当前的顶点数和边数
} MGraph, *GraphRef;
GraphRef graphRef;
void initGraph()
{
    graphRef = malloc(sizeof(MGraph));
    graphRef->numVertexes = 10;
    int add[10][10] = {
                     {0, 1, 0, 1, 0, 0, 0, 0, 0, 0},
                     {1, 0, 1, 0, 1, 0, 0, 0, 0, 0},
                     {0, 1, 0, 1, 1, 0, 0, 0, 0, 0},
                     {1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
                     {0, 1, 1, 0, 0, 0, 0, 0, 0, 0},
                     {0, 0, 0, 0, 0, 0, 1, 0, 1, 0},
                     {0, 0, 0, 0, 0, 1, 0, 1, 0, 0},
                     {0, 0, 0, 0, 0, 0, 1, 0, 0, 1},
                     {0, 0, 0, 0, 0, 1, 0, 0, 0, 1},
                     {0, 0, 0, 0, 0, 0, 0, 1, 1, 0}
                    };
    for (int i = 0; i < graphRef->numVertexes; i++) {
        for (int j = 0; j < graphRef->numVertexes; j++) {
            graphRef->adj[i][j] = add[i][j];
        }
    }
    
    for (int i = 0; i < graphRef->numVertexes; i++) {
        graphRef->visited[i] = 0;
        printf("\n");
        for (int j = 0; j < graphRef->numVertexes; j++) {
            printf("%d ",graphRef->adj[i][j]);
        }
    }
    printf("\n");
}

void DFSRecursion(int v)
{
    graphRef->visited[v] = 1;
    printf("%d ", v+1);
    for(int i = 0; i < graphRef->numVertexes; i++) {
        if(graphRef->adj[v][i] == 1 && graphRef->visited[i] == 0) {
            DFSRecursion(i);
        }
    }
}

void listComponents()
{
    for (int i = 0; i < graphRef->numVertexes; i++) {
        if(!graphRef->visited[i])
            DFSRecursion(i);
    }
}

4. 遍历的应用实例

1. 拯救007
  • 题意:007从水池中脚踩鳄鱼上岸,利用图的遍历实现算法。


  • 总体算法借鉴void listComponents() 算法,代码如下:

int DFS ( Vertex V )
{ 
    visited[v] = true;
    if ( IsSafe(V) ) answer = YES;
    else {
           for ( V 的每个邻接点 W )
             if ( !visited[w] ) {
                answer = DFS (W) :
                if(answer YEs) break;
             }
     } 
         return answer:
}

void save007 ( Graph G )
{ 
      for ( each V in G ) {
           if (!visited[v] && FirstJump(V)) {
                 answer = DES( V ) ;
                if (answer == YES) break;
            }
        }
       if (answer YES)output("Yes") ;
       else output("No") ;
}
2. 六度空间问题
  • 你和任何一个陌生人之间所间隔的人不会超过六个


  • 给定社交网络图,请对每个结点计算符合“六度空间”理论的结点占结点总数的百分比。

算法思路

  • 对每个结点,进行广度优先搜索
  • 搜索过程中累计访问的结点数
  • 需要记录“层”数,仅计算6层以内的结点数
void SDS()
{
   for(each V in G){
     count = BFS(V);
     Output(count/N);
   }
}

int BFS(Vertex V)
{
   visited[V] = true;
   count = 1;
   level = 0;
   last = V;
   Enqueue(V, Q);
   while(!IsEmpty(Q)){
      V = Dequeue(Q);
      for(V 的每个邻接点 W)
         if(!visited[W]){
            visited[W] = true;
            Enqueue(W,Q);
            count++;
            tail = W;
          }
        if( V == last){
             level++;
             last = tail;
        }

      if(level == 6) break;
   }

  return count;
}

最短路径

  • 单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径
       有向(无权图)
       有向(有权图)
  • 多源最短路径问题:求任意两顶点间的最短路径

单源最短路

1. 无权图的单源最短路径算法
  • 按照递增(非递减)的顺序找出到各个顶点的最短路
void unweightedShortPath(LGraph graph, Vertex s)
{
    int *dist = (int *)malloc(sizeof(int) * graph->Nv);
    memset(dist, -1, sizeof(int) * graph->Nv);
    int *path = (int *)malloc(sizeof(int) * graph->Nv);
    memset(path, -1, sizeof(int) * graph->Nv);
    
    dist[s] = 0;
    struct QNode qNode;
    qNode.front = qNode.rear = 0;
    Queue ptrQ = &qNode;
    EnQueue(ptrQ, s);
    Vertex v;
    while (!IsEmpty(ptrQ)) {
        v = DeQueue(ptrQ);
        struct Vnode *vnode = &graph->G[v];
        PtrToAdjVNode adjNode = vnode->firstEdget;
        while (adjNode) {
            if(dist[adjNode->adjV] == -1)
            {
                dist[adjNode->adjV] = dist[v] + 1;
                path[adjNode->adjV] = v;
                EnQueue(ptrQ, adjNode->adjV);
            }
            adjNode = adjNode->next;
        }
    }
    
    for (int i = 0; i < graph->Nv; i++) {
        
        printf("%d -- %d\n", dist[i], path[i]);
        
    }
}
2. 有权图的单源最短路径算法
  • 有权图的单源最短路径不一定是最短路,而是权重和最小的那条路径
  • 有权图中不能有负值,要不然会出现死循环 (此情况不考虑)
  • 按照权重递增(非递减)的顺序找出到各个顶点的最短路
  • 利用Dijkstra(迪杰斯特拉)算法解决有权图的单源最短路径
  • 方法1: 直接扫描所有未收录顶点 - O(IV)
       T= O(IV2+ E)对于稠密图效果好
  • 方法2: 将dist存在最小堆中 - O( loglVI)
       更新dist[w]的值 -(loglVI)
       T= 0( IV logV + E loglVI ) = 0( E loglVI)
void shortPath_Dij(LGraph graph, Vertex s)
{
    int *dist = (int *)malloc(sizeof(int) * graph->Nv);
    for (int i = 0; i < graph->Nv; i++) {
        dist[i] = INT_MAX;
    }
    int *path = (int *)malloc(sizeof(int) * graph->Nv);
    memset(path, -1, sizeof(int) * graph->Nv);
    bool *collected = (bool *)malloc(sizeof(bool) * graph->Nv);
    memset(collected, 0, sizeof(bool) * graph->Nv);
    
    dist[s] = 0;
    collected[s] = true;
    Vertex v = s;
    struct Vnode *vnode = &graph->G[s];
    PtrToAdjVNode adjNode = vnode->firstEdget;
    while (adjNode) {
        if(dist[adjNode->adjV] == INT_MAX)
        {
            dist[adjNode->adjV] = dist[v] + adjNode->weight;
            path[adjNode->adjV] = v;
        }
        adjNode = adjNode->next;
    }
    
    
    while (1) {
        int min = INT_MAX;
        for (int i = 0; i < graph->Nv; i++) {
            if(min > dist[i] && !collected[i])
            {
                min = dist[i];
                v = i;
            }
        }
        if (min == INT_MAX) {
            break;
        }
        collected[v] = true;
        
        vnode = &graph->G[v];
        adjNode = vnode->firstEdget;
        while (adjNode) {
            if(dist[adjNode->adjV] > dist[v] + adjNode->weight)
            {
                dist[adjNode->adjV] = dist[v] + adjNode->weight;
                path[adjNode->adjV] = v;
            }
            adjNode = adjNode->next;
        }
    }
    
    for (int i = 0; i < graph->Nv; i++) {
        
        printf("%d -- %d\n", dist[i], path[i]);
        
    }
}

多源最短路

Floyd(弗洛伊德)算法

  • Dk[i][j] = 路径{i -> {l<=k} -> j} 的最小长度
  • D0,D1,...,D|v|-1[i][j]即给出了i到j的真正最短距离
  • 当Dk-1已经完成,递推到Dk时:
       或者k∈最短路径{i -> {l<=k} -> j} ,则Dk = Dk-1
       或者k ∈/最短路径{i -> {l<=k} -> j} ,则该路径必定由两段最短路径组成: D[i][j]=D[i][k]+D[k][i]
void shortPath_Floyd()
{
    int i, j, k, N;
    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            D[i][j] = G[i][j];
            path[i][j] = -1;
        }
    }
    
    for (k = 0; k < N; k++) {
        for (i = 0; i < N; i++) {
            for (j = 0; j < N; j++) {
                if(D[i][k] + D[k][j] < D[i][j]){
                    D[i][j] = D[i][k] + D[k][j];
                    path[i][j] = k;
                }
            }
        }
    }
}
选择最容易变化的动物
  • 先通过Floyd算法得到多源最短路径
  • 然后获取每个动物最难变的动物
  • 然后在最难变的动物中找最容易变的动物的那个动物
void find_minAnimal(int **D, MGraph graph);
void shortPath_Floyd(MGraph graph)
{
    int **D, **path;
    
    D = (int **)malloc(sizeof(int *) * graph->Nv);
    path = (int **)malloc(sizeof(int *) * graph->Nv);
    for (int i = 0; i < graph->Nv; i++) {
        D[i] = (int *)malloc(sizeof(int) * graph->Nv);
        path[i] = (int *)malloc(sizeof(int) * graph->Nv);
    }
    
    for (int i = 0; i < graph->Nv; i++) {
        for (int j = 0; j < graph->Nv; j++) {
            D[i][j] = graph -> G[i][j];
            path[i][j] = -1;
        }
    }
    printf("\n");
    for (int i = 0; i < graph->Nv; i++) {
        printf("\n");
        for (int j = 0; j < graph->Nv; j++) {
            printf("%d ",graph->G[i][j]);
        }
    }
    
    for (int k = 0; k < graph->Nv; k++) {
            for (int i = 0; i < graph->Nv; i++) {
                for (int j = 0; j < graph->Nv; j++) {
                    if(i!=j && D[i][k] + D[k][j] < D[i][j]){
                        D[i][j] = D[i][k] + D[k][j];
                        path[i][j] = k;
                    }
                }
            }
        }
    
    
    printf("\n");
    for (int i = 0; i < graph->Nv; i++) {
        printf("\n");
        for (int j = 0; j < graph->Nv; j++) {
            printf("%d ",path[i][j]);
        }
    }
    
    find_minAnimal(D,graph);
}

void find_minAnimal(int **D, MGraph graph)
{
    int minAnimal = 1000, rowMaxAniaml = 0 , path;
    for (int i = 0; i < graph->Nv; i++) {
        rowMaxAniaml = 0;
        for (int j = 0; j < graph->Nv; j++) {
            if(i!=j && rowMaxAniaml < D[i][j])
                rowMaxAniaml = D[i][j];
        }
        if (minAnimal > rowMaxAniaml) {
            minAnimal = rowMaxAniaml;
            path = i;
        }
    }
    
    printf("weight:%d -- path:%d\n",minAnimal,path);
}

最小生成树

1. Prim算法

void prim(LGraph graph, Vertex s)
{
    int *lowcost = (int *)malloc(sizeof(int) * graph->Nv);
    for (int i = 0; i < graph->Nv; i++) {
        lowcost[i] = INT_MAX;
    }
    int *parent = (int *)malloc(sizeof(int) * graph->Nv);
    memset(parent, -1, sizeof(int) * graph->Nv);
    bool *collected = (bool *)malloc(sizeof(bool) * graph->Nv);
    memset(collected, 0, sizeof(bool) * graph->Nv);
    int *mst = (int *)malloc(sizeof(Vertex) * graph->Nv);
    memset(mst, -1, sizeof(Vertex) * graph->Nv);
    
    int mstIndex = 0;
    lowcost[s] = 0;
    collected[s] = true;
    mst[mstIndex] = s;
    Vertex v = s;
    struct Vnode *vnode = &graph->G[s];
    PtrToAdjVNode adjNode = vnode->firstEdget;
    
    while (adjNode) {
        if (lowcost[adjNode->adjV] == INT_MAX) {
            lowcost[adjNode->adjV] = adjNode->weight;
            parent[adjNode->adjV] = v;
        }
        adjNode = adjNode->next;
    }
    
    while (1) {
        int min = INT_MAX;
        for (int i = 0; i < graph->Nv; i++) {
            if (min > lowcost[i] && !collected[i]) {
                min = lowcost[i];
                v = i;
            }
        }
        
        if(min == INT_MAX) break;
        
        collected[v] = true;
        mst[++mstIndex] = v;
        vnode = &graph->G[v];
        adjNode = vnode->firstEdget;
        while (adjNode) {
            if (lowcost[adjNode->adjV] > adjNode->weight && !collected[adjNode->adjV]) {
                lowcost[adjNode->adjV] = adjNode->weight;
                parent[adjNode->adjV] = v;
            }
            adjNode = adjNode->next;
        }
    }
    
    for (int i = 0; i < graph->Nv; i++) {
        
        printf("%d -- %d\n", lowcost[i], parent[i]);
        
    }
}

2. kruskal算法

int fa[55], n, m, cnt;
struct Node {
    int u,v,cost;
}edge[300];

int find(int x) //包含路径压缩
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int a, int b)
{
    int x = find(a);
    int y = find(b);
    if(x != y) fa[y] = x;
}

void add(int a, int b, int c)
{
    edge[cnt].u = a;
    edge[cnt].v = b;
    edge[cnt++].cost = c;
}

bool cmp(Node x, Node y)
{
    return x.cost < y.cost;
}

int kruskal()
{
    int sum = 0, k = 0;
    sort(edge, edge + m, cmp);
    for (int i = 0; i < m; i++) {
        if (find(edge[i].u) == find(edge[i].v))
            continue;
        else
            merge(edge[i].u,edge[i].v);
        sum+=edge[i].cost;
        k++;
        if (k==n-1) {
            return sum;
        }
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    // insert code here...
  
    int x,y,z;
    while (cin>>n && n) {
        cnt = 0;
        cin>>m;
        for (int i = 1; i <= n; i++) {
            fa[i] = i;
        }
        for (int i = 0; i < m; i++) {
            cin>>x>>y>>z;
            add(x, y, z);
        }
        cout<<kruskal()<<endl;
    }
    
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 0.什么是图? <0>:表示“多对多”的关系 <2>:包括 i:一组顶点:通常用V(Vertex)表示顶点的集合 ...
    BrightHewei阅读 397评论 0 1
  • 转自:https://segmentfault.com/a/1190000010794621 摘 要 : 图 论 ...
    鸭蛋蛋_8441阅读 7,930评论 2 1
  • 主要知识点 图的概述 图的存储结构 图的遍历 最小生成树 最短路径 拓扑排序 关键路径 一、图的概念 图的定义: ...
    JiaJianHuang阅读 1,677评论 0 0
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,071评论 0 0
  • 1、图的相关概念 1.1图的定义和术语 图的定义:图是由顶点的有穷非空集合和顶点之间的边的集合组成,通常表示为:G...
    无涯之涯阅读 1,676评论 0 0