数据结构-图(二)

文章目录

  • 图的基本应用:深入解析与实践
    • 一、引言
    • 二、最小(代价)生成树
      • (一)概念与性质
      • (二)算法实现
    • 三、最短路径
      • (一)概念与分类
      • (二)单源最短路径算法
      • (三)多源最短路径算法 - Floyd - Warshall 算法

图的基本应用:深入解析与实践

一、引言

图作为一种强大的数据结构,在众多领域有着广泛而重要的应用。从计算机网络到项目管理,从交通规划到电路设计,图的相关算法和概念都发挥着关键作用。本文将详细探讨图的几个基本应用:最小(代价)生成树、最短路径、拓扑排序和关键路径,并结合相关计算公式进行深入剖析。

二、最小(代价)生成树

(一)概念与性质

最小生成树是针对无向带权连通图而言的,它是一棵包含图中所有顶点的无环连通子图,且边的权值之和最小。在一个具有 (n) 个顶点的无向带权连通图中,其最小生成树的边数固定为 (n - 1)。这是因为要连接 (n) 个顶点且保证无环连通,最少需要 (n - 1) 条边,就像构建一个树状结构将所有顶点连接起来。

(二)算法实现

  1. Prim 算法
    • 原理:从图中的任意一个顶点开始,将其加入到已构建的最小生成树顶点集合 (U) 中。然后不断地从 (U) 集合中的顶点与不在 (U) 集合中的顶点之间的边中,选择权值最小的边,将对应的不在 (U) 中的顶点加入到 (U) 中,直到 (U) 包含了图中的所有顶点。
    • 示例代码(C 语言)
#include 
#include 
#include 

#define MAX_VERTEX_NUM 100
#define INF 0x3f3f3f3f

// 邻接矩阵存储图
typedef struct {
    int adj[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
} MGraph;

// 记录顶点是否在最小生成树顶点集合中
bool inMST[MAX_VERTEX_NUM];

// 初始化图
void InitGraph(MGraph* G, int vexnum) {
    G->vexnum = vexnum;
    G->arcnum = 0;
    for (int i = 0; i < vexnum; i++) {
        for (int j = 0; j < vexnum; j++) {
            G->adj[i][j] = INF;
        }
    }
}

// 添加边
void AddEdge(MGraph* G, int v, int w, int weight) {
    G->adj[v][w] = weight;
    G->adj[w][v] = weight;
    G->arcnum++;
}

// Prim 算法求最小生成树
void Prim(MGraph G) {
    int sumWeight = 0;
    // 初始化,先将第一个顶点加入最小生成树顶点集合
    inMST[0] = true;
    for (int i = 1; i < G.vexnum; i++) {
        inMST[i] = false;
    }
    // 循环 n - 1 次,每次找到一条最小生成树的边
    for (int k = 0; k < G.vexnum - 1; k++) {
        int minWeight = INF;
        int minV = -1, minW = -1;
        // 遍历已在最小生成树顶点集合中的顶点与不在集合中的顶点之间的边
        for (int v = 0; v < G.vexnum; v++) {
            if (inMST[v]) {
                for (int w = 0; w < G.vexnum; w++) {
                    if (!inMST[w] && G.adj[v][w] < minWeight) {
                        minWeight = G.adj[v][w];
                        minV = v;
                        minW = w;
                    }
                }
            }
        }
        if (minV!= -1 && minW!= -1) {
            sumWeight += minWeight;
            inMST[minW] = true;
            printf("选择边 (%d, %d) 加入最小生成树,权值为 %d\n", minV, minW, minWeight);
        }
    }
    printf("最小生成树的总权值为: %d\n", sumWeight);
}
  1. Kruskal 算法
    • 原理:首先将图中的所有边按照权值从小到大进行排序。然后依次从排序后的边集中选取边,若选取的边不会与已选取的边构成回路,则将其加入到最小生成树中,直到选取了 (n - 1) 条边。
    • 示例代码(C 语言)
#include 
#include 
#include 

#define MAX_VERTEX_NUM 100
#define MAX_EDGE_NUM MAX_VERTEX_NUM * (MAX_VERTEX_NUM - 1) / 2
#define INF 0x3f3f3f3f

// 边结构体
typedef struct Edge {
    int v;
    int w;
    int weight;
} Edge;

// 并查集结构体
typedef struct {
    int* parent;
    int size;
} UFSet;

// 初始化并查集
void UFSet_Init(UFSet* uf, int n) {
    uf->parent = (int*)malloc(n * sizeof(int));
    uf->size = n;
    for (int i = 0; i < n; i++) {
        uf->parent[i] = i;
    }
}

// 查找操作
int UFSet_Find(UFSet* uf, int x) {
    while (x!= uf->parent[x]) {
        x = uf->parent[x];
    }
    return x;
}

// 合并操作
void UFSet_Union(UFSet* uf, int x, int y) {
    int rootx = UFSet_Find(uf, x);
    int rooty = UFSet_Find(uf, y);
    if (rootx!= rooty) {
        uf->parent[rooty] = rootx;
    }
}

// 比较函数,用于边的排序
int compare(const void* a, const void* b) {
    Edge* e1 = (Edge*)a;
    Edge* e2 = (Edge*)b;
    return e1->weight - e2->weight;
}

// Kruskal 算法求最小生成树
void Kruskal(MGraph G) {
    int sumWeight = 0;
    Edge edges[MAX_EDGE_NUM];
    int edgeIndex = 0;
    // 将图中的边存储到边数组中
    for (int v = 0; v < G.vexnum; v++) {
        for (int w = v + 1; w < G.vexnum; w++) {
            if (G.adj[v][w]!= INF) {
                edges[edgeIndex].v = v;
                edges[edgeIndex].w = w;
                edges[edgeIndex].weight = G.adj[v][w];
                edgeIndex++;
            }
        }
    }
    // 对边数组进行排序
    qsort(edges, edgeIndex, sizeof(Edge), compare);
    UFSet uf;
    UFSet_Init(&uf, G.vexnum);
    // 依次选取边构建最小生成树
    for (int i = 0; i < edgeIndex; i++) {
        int v = edges[i].v;
        int w = edges[i].w;
        int rootv = UFSet_Find(&uf, v);
        int rootw = UFSet_Find(&uf, w);
        if (rootv!= rootw) {
            sumWeight += edges[i].weight;
            UFSet_Union(&uf, rootv, rootw);
            printf("选择边 (%d, %d) 加入最小生成树,权值为 %d\n", v, w, edges[i].weight);
        }
    }
    printf("最小生成树的总权值为: %d\n", sumWeight);
}

三、最短路径

(一)概念与分类

最短路径问题是在图中寻找从一个顶点到另一个顶点(或多个顶点)的路径,使得路径上的边权值之和最小。主要分为单源最短路径和多源最短路径问题。

(二)单源最短路径算法

  1. Dijkstra 算法(适用于非负权值图)
    • 原理:从源顶点开始,逐步确定到其他顶点的最短路径。维护一个集合 (S),表示已确定最短路径的顶点集合。每次从不在 (S) 中的顶点中选择距离源顶点最近的顶点加入到 (S) 中,并更新其他顶点到源顶点的距离。
    • 示例代码(C 语言)
#include 
#include 
#include 

#define MAX_VERTEX_NUM 100
#define INF 0x3f3f3f3f

// 邻接矩阵存储图
typedef struct {
    int adj[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
} MGraph;

// 记录顶点是否已确定最短路径
bool visited[MAX_VERTEX_NUM];

// 记录从源顶点到其他顶点的最短距离
int dist[MAX_VERTEX_NUM];

// 初始化图
void InitGraph(MGraph* G, int vexnum) {
    G->vexnum = vexnum;
    G->arcnum = 0;
    for (int i = 0; i < vexnum; i++) {
        for (int j = 0; j < vexnum; j++) {
            G->adj[i][j] = INF;
        }
    }
}

// 添加边
void AddEdge(MGraph* G, int v, int w, int weight) {
    G->adj[v][w] = weight;
    G->adj[w][v] = weight;
    G->arcnum++;
}

// Dijkstra 算法求单源最短路径
void Dijkstra(MGraph G, int source) {
    // 初始化距离数组和访问数组
    for (int i = 0; i < G.vexnum; i++) {
        dist[i] = G.adj[source][i];
        visited[i] = false;
    }
    dist[source] = 0;
    visited[source] = true;
    // 循环 n - 1 次,确定到其他顶点的最短路径
    for (int k = 0; k < G.vexnum - 1; k++) {
        int minDist = INF;
        int minV = -1;
        // 找到未确定最短路径的顶点中距离源顶点最近的顶点
        for (int v = 0; v < G.vexnum; v++) {
            if (!visited[v] && dist[v] < minDist) {
                minDist = dist[v];
                minV = v;
            }
        }
        if (minV!= -1) {
            visited[minV] = true;
            // 更新其他顶点的距离
            for (int w = 0; w < G.vexnum; w++) {
                if (!visited[w] && G.adj[minV][w]!= INF && dist[minV] + G.adj[minV][w] < dist[w]) {
                    dist[w] = dist[minV] + G.adj[minV][w];
                }
            }
        }
    }
    // 输出最短路径信息
    printf("从顶点 %d 到其他顶点的最短路径如下:\n", source);
    for (int i = 0; i < G.vexnum; i++) {
        if (i!= source) {
            if (dist[i] == INF) {
                printf("到顶点 %d 无可达路径\n", i);
            } else {
                printf("到顶点 %d 的最短路径长度为 %d\n", i, dist[i]);
            }
        }
    }
}
  1. Bellman - Ford 算法(可处理负权值图,但不能处理负权回路)
    • 原理:对图中的所有边进行 (n - 1) 轮松弛操作。松弛操作是指对于边 ((u, v)),如果 (dist[u]+w(u, v)
    • 示例代码(C 语言)
#include 
#include 
#include 

#define MAX_VERTEX_NUM 100
#define INF 0x3f3f3f3f

// 边结构体
typedef struct Edge {
    int v;
    int w;
    int weight;
} Edge;

// 邻接表存储图
typedef struct {
    Edge* edges;
    int* head;
    int* next;
    int vexnum;
    int arcnum;
} ALGraph;

// 初始化邻接表图
void InitALGraph(ALGraph* G, int vexnum) {
    G->vexnum = vexnum;
    G->arcnum = 0;
    G->edges = (Edge*)malloc(MAX_EDGE_NUM * sizeof(Edge));
    G->head = (int*)malloc(MAX_VERTEX_NUM * sizeof(int));
    G->next = (int*)malloc(MAX_EDGE_NUM * sizeof(int));
    for (int i = 0; i < vexnum; i++) {
        G->head[i] = -1;
    }
}

// 添加边到邻接表
void AddArc(ALGraph* G, int v, int w, int weight) {
    G->edges[G.arcnum].v = v;
    G->edges[G.arcnum].w = w;
    G->edges[G.arcnum].weight = weight;
    G->next[G.arcnum] = G->head[v];
    G->head[v] = G.arcnum;
    G->arcnum++;
}

// Bellman - Ford 算法求单源最短路径
bool BellmanFord(ALGraph G, int source) {
    // 初始化距离数组
    int dist[MAX_VERTEX_NUM];
    for (int i = 0; i < G.vexnum; i++) {
        dist[i] = INF;
    }
    dist[source] = 0;
    // 进行 n - 1 轮松弛操作
    for (int k = 0; k < G.vexnum - 1; k++) {
        bool updated = false;
        for (int i = 0; i < G.arcnum; i++) {
            int v = G.edges[i].v;
            int w = G.edges[i].w;
            int weight = G.edges[i].weight;
            if (dist[v]!= INF && dist[v] + weight < dist[w]) {
                dist[w] = dist[v] + weight;
                updated = true;
            }
        }
        if (!updated) {
            break;
        }
    }
    // 检查是否存在负权回路
    for (int i = 0; i < G.arcnum; i++) {
        int v = G.edges[i].v;
        int w = G.edges[i].w;
        int weight = G.edges[i].weight;
        if (dist[v]!= INF && dist[v] + weight < dist[w]) {
            printf("图中存在负权回路,无法求出最短路径\n");
            return false;
        }
    }
    // 输出最短路径信息
    printf("从顶点 %d 到其他顶点的最短路径如下:\n", source);
    for (int i = 0; i < G.vexnum; i++) {
        if (i!= source) {
            if (dist[i] == INF) {
                printf("到顶点 %d 无可达路径\n", i);
            } else {
                printf("到顶点 %d 的最短路径长度为 %d\n", i, dist[i]);
            }
        }
    }
    return true;
}

(三)多源最短路径算法 - Floyd - Warshall 算法

  • 原理:通过动态规划的思想,逐步更新任意两个顶点之间的最短路径。设 (d_{ij}^k) 表示从顶点 (i) 到顶点 (j) 且中间顶点编号不超过 (k) 的最短路径长度。初始时,(d_{ij}^0) 就是图中边 ((i, j)) 的权值(如果存在)或 (INF)(如果不存在)。然后通过状态转移方程 (d_{ij}k=\min(d_{ij}{k - 1},d_{ik}^{k - 1}+d_{kj}^{k - 1})) 进行更新,其中 (k) 从 (1) 到 (n) 迭代。
  • 示例代码(C 语言)
#include 
#include 

#define MAX_VERTEX_NUM 100
#define INF 0x3f3f3f3f

// 邻接矩阵存储
```c
// 邻接矩阵存储图
typedef struct {
    int adj[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int vexnum;
    int arcnum;
} MGraph;

// Floyd - Warshall 算法求多源最短路径
void FloydWarshall(MGraph G) {
    int dist[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    // 初始化距离矩阵
    for (int i = 0; i < G.vexnum; i++) {
        for (int j = 0; j < G.vexnum; j++) {
            dist[i][j] = G.adj[i][j];
        }
    }
    // 动态规划更新最短路径
    for (int k = 0; k < G.vexnum; k++) {
        for (int i = 0; i < G.vexnum; i++) {
            for (int j = 0; j < G.vexnum; j++) {
                if (dist[i][k]!= INF && dist[k][j]!= INF && dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }
    // 输出最短路径信息
    printf("多源最短路径矩阵如下:\n");
    for (int i = 0; i < G.vexnum; i++) {
        for (int j = 0; j < G.vexnum; j++) {
            if (dist[i][j] == INF) {
                printf("INF ");
            } else {
                printf("%d ", dist[i][j]);
            }
        }
        printf("\n");
    }
}

你可能感兴趣的:(数据结构,数据结构,算法)