数据结构入门:图的基本操作、算法与 C++ 实现

最开始在 2023-07-10 23:53:04 编辑,但一直TJ

一、图的定义

图由顶点集边集 组成,分为无向图和有向图。我们以无向图为例讲解(有向图只需修改边的存储逻辑)。

二、图结构的存储

图的存储常用「邻接矩阵法」和「邻接表法」,我们分别实现:

2.1 邻接矩阵法

邻接矩阵是一个二维数组,matrix[i][j]表示顶点ij之间是否有边(或边的权值)。

cpp 复制代码
#include <iostream>
using namespace std;

// 最大顶点数(可根据需求调整)
const int MAX_VERTEX = 100;

// 邻接矩阵存储的图
struct GraphMatrix {
    char vertices[MAX_VERTEX];  // 顶点数组(存顶点数据,比如字符)
    int matrix[MAX_VERTEX][MAX_VERTEX];  // 邻接矩阵
    int vertexNum;  // 实际顶点数
    int edgeNum;    // 实际边数
};

// 初始化图
void initGraphMatrix(GraphMatrix &G) {
    G.vertexNum = 0;
    G.edgeNum = 0;
    // 初始化邻接矩阵为0(无边)
    for (int i = 0; i < MAX_VERTEX; i++) {
        for (int j = 0; j < MAX_VERTEX; j++) {
            G.matrix[i][j] = 0;
        }
    }
}

// 添加顶点
void addVertex(GraphMatrix &G, char v) {
    if (G.vertexNum >= MAX_VERTEX) {
        cout << "顶点数超过上限!" << endl;
        return;
    }
    G.vertices[G.vertexNum++] = v;
}

// 添加边(无向图,matrix[i][j]和matrix[j][i]都要设为1)
void addEdge(GraphMatrix &G, int i, int j) {
    if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
        cout << "顶点序号非法!" << endl;
        return;
    }
    G.matrix[i][j] = 1;
    G.matrix[j][i] = 1;  // 无向图的关键:双向赋值
    G.edgeNum++;
}

// 打印邻接矩阵
void printGraphMatrix(GraphMatrix &G) {
    cout << "邻接矩阵:" << endl;
    // 打印顶点表头
    cout << "  ";
    for (int i = 0; i < G.vertexNum; i++) {
        cout << G.vertices[i] << " ";
    }
    cout << endl;
    // 打印每行数据
    for (int i = 0; i < G.vertexNum; i++) {
        cout << G.vertices[i] << " ";
        for (int j = 0; j < G.vertexNum; j++) {
            cout << G.matrix[i][j] << " ";
        }
        cout << endl;
    }
}

// 测试邻接矩阵
int main() {
    GraphMatrix G;
    initGraphMatrix(G);
    // 添加顶点
    addVertex(G, 'A');
    addVertex(G, 'B');
    addVertex(G, 'C');
    addVertex(G, 'D');
    // 添加边:A-B、A-C、B-D
    addEdge(G, 0, 1);
    addEdge(G, 0, 2);
    addEdge(G, 1, 3);
    // 打印
    printGraphMatrix(G);
    return 0;
}

运行结果

复制代码
邻接矩阵:
  A B C D
A 0 1 1 0
B 1 0 0 1
C 1 0 0 0
D 0 1 0 0

2.2 邻接表法

邻接表是「数组 + 链表」的结构:数组存顶点,每个顶点对应一个链表,存与它相连的顶点。

cpp 复制代码
#include <iostream>
using namespace std;

const int MAX_VERTEX = 100;

// 邻接表的边节点
struct EdgeNode {
    int adjVertex;  // 邻接顶点的序号
    EdgeNode *next; // 下一个边节点
};

// 邻接表的顶点节点
struct VertexNode {
    char data;      // 顶点数据
    EdgeNode *firstEdge; // 指向第一个边节点
};

// 邻接表存储的图
struct GraphList {
    VertexNode vertices[MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};

// 初始化图
void initGraphList(GraphList &G) {
    G.vertexNum = 0;
    G.edgeNum = 0;
    for (int i = 0; i < MAX_VERTEX; i++) {
        G.vertices[i].firstEdge = NULL;
    }
}

// 添加顶点
void addVertexList(GraphList &G, char v) {
    if (G.vertexNum >= MAX_VERTEX) {
        cout << "顶点数超过上限!" << endl;
        return;
    }
    G.vertices[G.vertexNum].data = v;
    G.vertices[G.vertexNum].firstEdge = NULL;
    G.vertexNum++;
}

// 添加边(无向图,双向添加)
void addEdgeList(GraphList &G, int i, int j) {
    if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
        cout << "顶点序号非法!" << endl;
        return;
    }
    // 1. 给i添加指向j的边
    EdgeNode *newEdge = new EdgeNode;
    newEdge->adjVertex = j;
    newEdge->next = G.vertices[i].firstEdge;
    G.vertices[i].firstEdge = newEdge;
    // 2. 给j添加指向i的边(无向图)
    EdgeNode *newEdge2 = new EdgeNode;
    newEdge2->adjVertex = i;
    newEdge2->next = G.vertices[j].firstEdge;
    G.vertices[j].firstEdge = newEdge2;
    G.edgeNum++;
}

// 打印邻接表
void printGraphList(GraphList &G) {
    cout << "邻接表:" << endl;
    for (int i = 0; i < G.vertexNum; i++) {
        cout << G.vertices[i].data << " -> ";
        EdgeNode *p = G.vertices[i].firstEdge;
        while (p != NULL) {
            cout << G.vertices[p->adjVertex].data << " ";
            p = p->next;
        }
        cout << endl;
    }
}

// 测试邻接表
int main() {
    GraphList G;
    initGraphList(G);
    // 添加顶点
    addVertexList(G, 'A');
    addVertexList(G, 'B');
    addVertexList(G, 'C');
    addVertexList(G, 'D');
    // 添加边:A-B、A-C、B-D
    addEdgeList(G, 0, 1);
    addEdgeList(G, 0, 2);
    addEdgeList(G, 1, 3);
    // 打印
    printGraphList(G);
    return 0;
}

运行结果

复制代码
邻接表:
A -> C B
B -> D A
C -> A
D -> B

三、图的遍历

先放一个我当初新手时实现的代码,简单的邻接矩阵,这个遍历不是那种"走迷宫"的模式,存储的数组代表边的连通,而不是迷宫的"墙"或"路"!

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;

typedef int Status;

//数组保存图结构
struct Graph
{
	int vexnum = 5;//总长

	bool visited[5] = { 0 };//访问标记

	//数组存储图。a,b,c,d,e 之间的连通关系
	//行节点1,列节点2
	//(a-b,b-d,d-b,d-c,e-d)
	int maze[5][5] = {
	//  a, b, c, d, e
		0, 0, 0, 0, 0,	//a (数组第一下标是行)
		0, 0, 1, 0, 1,	//b
		1, 1, 0, 1, 0,	//c
		0, 1, 1, 0, 1,	//d
		0, 0, 0, 0, 0,	//e
	};

	bool VisitFunc(int v)
	{
		if (visited[v]) return false;

		visited[v] = true;
		cout << (char)(v+'a') << " ";
		return true;
	}


	//深度优先遍历图-从第v个顶点出发
	void DFS(int v) {
		//访问第v个顶点
		if (VisitFunc(v))
		{
			//对v的尚未访问的邻接顶点w递归调用DFS
			for (int j = 0; j < vexnum; j++) {
				if (maze[v][j] == 1)//还有连接的路
				{
					DFS(j);
				}
			}
		}
	}


	//广度优先遍历图-从第v个顶点出发
	void BFS(int Sv) {
		std::queue<int > Q;

		if (VisitFunc(Sv))
		{
			for (int j = 0; j < vexnum; ++j) {
				if (maze[Sv][j] == 1)//还有连接的路
				{
					Q.push(j);
				}
			}
			//第一层完毕↑
		}

		while ( !Q.empty() )
		{
			int u = Q.front();
			Q.pop();//出队后删除

			if (VisitFunc(u))//尝试访问
			{
				for (int j = 0; j < vexnum; j++) {
					if (maze[u][j] == 1)//还有连接的路
					{
						Q.push(j);
					}
				}
			}
		}
	}
};





int main()
{
	int v = 2;
	Graph G1,G2;
	cout << "DFS:" << endl;
	G1.DFS(v);
	cout << endl;
	cout << "BFS:" << endl;
	G2.BFS(v);

	system("pause");
}

图的遍历有「深度优先遍历(DFS)」和「广度优先遍历(BFS)」,基于邻接矩阵实现:

3.1 深度优先遍历(DFS)

类似 "走迷宫",沿着一条路走到头,再回溯。

cpp 复制代码
#include <iostream>
using namespace std;

const int MAX_VERTEX = 100;
struct GraphMatrix {
    char vertices[MAX_VERTEX];
    int matrix[MAX_VERTEX][MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};
// (初始化、添加顶点/边的函数同2.1,此处省略)

// 标记顶点是否被访问过
bool visited[MAX_VERTEX] = {false};

// 深度优先遍历(从顶点v开始)
void DFS(GraphMatrix &G, int v) {
    // 访问当前顶点
    cout << G.vertices[v] << " ";
    visited[v] = true;
    // 遍历所有邻接顶点
    for (int i = 0; i < G.vertexNum; i++) {
        // 如果有边且未被访问,递归访问
        if (G.matrix[v][i] == 1 && !visited[i]) {
            DFS(G, i);
        }
    }
}

// 测试DFS
int main() {
    GraphMatrix G;
    initGraphMatrix(G);
    addVertex(G, 'A');
    addVertex(G, 'B');
    addVertex(G, 'C');
    addVertex(G, 'D');
    addEdge(G, 0, 1);
    addEdge(G, 0, 2);
    addEdge(G, 1, 3);
    cout << "深度优先遍历结果:";
    DFS(G, 0); // 从A开始遍历
    cout << endl;
    return 0;
}

运行结果

复制代码
深度优先遍历结果:A B D C

3.2 广度优先遍历(BFS)

类似 "水波扩散",先访问当前顶点的所有邻接顶点,再依次访问它们的邻接顶点(用数组模拟队列)。

cpp 复制代码
#include <iostream>
using namespace std;

const int MAX_VERTEX = 100;
struct GraphMatrix {
    char vertices[MAX_VERTEX];
    int matrix[MAX_VERTEX][MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};
// (初始化、添加顶点/边的函数同2.1,此处省略)

bool visited[MAX_VERTEX] = {false};

// 广度优先遍历(从顶点v开始)
void BFS(GraphMatrix &G, int v) {
    // 用数组模拟队列,front是队头,rear是队尾
    int queue[MAX_VERTEX];
    int front = 0, rear = 0;
    
    // 访问当前顶点,入队
    cout << G.vertices[v] << " ";
    visited[v] = true;
    queue[rear++] = v;
    
    while (front < rear) {
        // 出队
        int cur = queue[front++];
        // 遍历所有邻接顶点
        for (int i = 0; i < G.vertexNum; i++) {
            if (G.matrix[cur][i] == 1 && !visited[i]) {
                cout << G.vertices[i] << " ";
                visited[i] = true;
                queue[rear++] = i;
            }
        }
    }
}

// 测试BFS
int main() {
    GraphMatrix G;
    initGraphMatrix(G);
    addVertex(G, 'A');
    addVertex(G, 'B');
    addVertex(G, 'C');
    addVertex(G, 'D');
    addEdge(G, 0, 1);
    addEdge(G, 0, 2);
    addEdge(G, 1, 3);
    cout << "广度优先遍历结果:";
    BFS(G, 0); // 从A开始遍历
    cout << endl;
    return 0;
}

四、图的相关应用

我们实现两个经典应用:最小生成树(Prim 算法)和最短路径(Dijkstra 算法)。

4.1 最小生成树

连通分量

Prim 算法

从一个顶点出发,每次选 "与当前树相连的权值最小的边",最终生成权值和最小的树(以带权无向图为例)。

cpp 复制代码
#include <iostream>
#include <climits> // 用INT_MAX表示无穷大
using namespace std;

const int MAX_VERTEX = 100;
const int INF = INT_MAX; // 无穷大(表示无边)

struct GraphMatrix {
    char vertices[MAX_VERTEX];
    int matrix[MAX_VERTEX][MAX_VERTEX]; // 带权邻接矩阵
    int vertexNum;
    int edgeNum;
};

// 初始化带权图
void initWeightGraph(GraphMatrix &G) {
    G.vertexNum = 0;
    G.edgeNum = 0;
    for (int i = 0; i < MAX_VERTEX; i++) {
        for (int j = 0; j < MAX_VERTEX; j++) {
            G.matrix[i][j] = INF;
        }
    }
}

// 添加带权边(无向图)
void addWeightEdge(GraphMatrix &G, int i, int j, int weight) {
    if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
        cout << "顶点序号非法!" << endl;
        return;
    }
    G.matrix[i][j] = weight;
    G.matrix[j][i] = weight;
    G.edgeNum++;
}

// Prim算法求最小生成树(从顶点v开始)
void Prim(GraphMatrix &G, int v) {
    int minWeight; // 当前最小权值
    int u;         // 当前选中的顶点
    int sum = 0;   // 最小生成树的总权值
    // 存储"每个顶点到当前生成树的最小权值"
    int lowCost[MAX_VERTEX];
    // 初始化lowCost
    for (int i = 0; i < G.vertexNum; i++) {
        lowCost[i] = G.matrix[v][i];
    }
    lowCost[v] = 0; // 标记v已加入生成树

    for (int i = 1; i < G.vertexNum; i++) { // 选n-1条边
        minWeight = INF;
        u = -1;
        // 找当前权值最小的边
        for (int j = 0; j < G.vertexNum; j++) {
            if (lowCost[j] != 0 && lowCost[j] < minWeight) {
                minWeight = lowCost[j];
                u = j;
            }
        }
        if (u == -1) {
            cout << "图不连通,无法生成最小生成树!" << endl;
            return;
        }
        // 输出选中的边
        cout << "选中边:" << G.vertices[v] << " - " << G.vertices[u] << ",权值:" << minWeight << endl;
        sum += minWeight;
        lowCost[u] = 0; // 标记u加入生成树
        // 更新lowCost:用u的边更新其他顶点的最小权值
        for (int j = 0; j < G.vertexNum; j++) {
            if (lowCost[j] != 0 && G.matrix[u][j] < lowCost[j]) {
                lowCost[j] = G.matrix[u][j];
            }
        }
    }
    cout << "最小生成树总权值:" << sum << endl;
}

// 测试Prim
int main() {
    GraphMatrix G;
    initWeightGraph(G);
    // 添加顶点
    G.vertices[0] = 'A';
    G.vertices[1] = 'B';
    G.vertices[2] = 'C';
    G.vertices[3] = 'D';
    G.vertexNum = 4;
    // 添加带权边
    addWeightEdge(G, 0, 1, 2);
    addWeightEdge(G, 0, 2, 3);
    addWeightEdge(G, 1, 3, 4);
    addWeightEdge(G, 2, 3, 1);
    // 运行Prim
    Prim(G, 0);
    return 0;
}

运行结果

复制代码
选中边:A - B,权值:2
选中边:A - C,权值:3
选中边:C - D,权值:1
最小生成树总权值:6

Kruskal 算法

Kruskal 算法与 Prim 算法思路不同 ------按边的权值从小到大选边,避开环,直到选够 n-1 条边(n 为顶点数)。核心:用「并查集」判断是否形成环(新手友好版实现,无 STL)。

cpp 复制代码
#include <iostream>
#include <climits>
using namespace std;

const int MAX_VERTEX = 100;
const int MAX_EDGE = 1000;
const int INF = INT_MAX;

// 边的结构体(存储起点、终点、权值)
struct Edge {
    int start;  // 起点序号
    int end;    // 终点序号
    int weight; // 权值
};

// 图的结构体(顶点+边集)
struct Graph {
    char vertices[MAX_VERTEX]; // 顶点数组
    Edge edges[MAX_EDGE];      // 边集数组
    int vertexNum;             // 顶点数
    int edgeNum;               // 边数
};

// 并查集数组(记录每个顶点的父节点,用于判断环)
int parent[MAX_VERTEX];

// 初始化并查集(每个顶点的父节点是自己)
void initUnionFind(int n) {
    for (int i = 0; i < n; i++) {
        parent[i] = i;
    }
}

// 查找顶点v的根节点(带路径压缩,优化效率)
int findRoot(int v) {
    if (parent[v] != v) {
        parent[v] = findRoot(parent[v]); // 路径压缩
    }
    return parent[v];
}

// 合并两个顶点的集合
void unionSet(int a, int b) {
    int rootA = findRoot(a);
    int rootB = findRoot(b);
    if (rootA != rootB) {
        parent[rootB] = rootA;
    }
}

// 交换两条边(用于冒泡排序)
void swapEdge(Edge &a, Edge &b) {
    Edge temp = a;
    a = b;
    b = temp;
}

// 对边按权值从小到大排序(冒泡排序,新手易理解)
void sortEdges(Edge edges[], int edgeNum) {
    for (int i = 0; i < edgeNum - 1; i++) {
        for (int j = 0; j < edgeNum - 1 - i; j++) {
            if (edges[j].weight > edges[j+1].weight) {
                swapEdge(edges[j], edges[j+1]);
            }
        }
    }
}

// Kruskal算法求最小生成树
void Kruskal(Graph &G) {
    int sum = 0;          // 最小生成树总权值
    int selectedEdge = 0; // 已选中的边数
    initUnionFind(G.vertexNum);
    sortEdges(G.edges, G.edgeNum); // 边按权值排序

    cout << "Kruskal选中的边:" << endl;
    for (int i = 0; i < G.edgeNum && selectedEdge < G.vertexNum - 1; i++) {
        int start = G.edges[i].start;
        int end = G.edges[i].end;
        int weight = G.edges[i].weight;

        // 若起点和终点不在同一集合(无环),则选中这条边
        if (findRoot(start) != findRoot(end)) {
            cout << G.vertices[start] << " - " << G.vertices[end] << ",权值:" << weight << endl;
            sum += weight;
            unionSet(start, end);
            selectedEdge++;
        }
    }

    if (selectedEdge < G.vertexNum - 1) {
        cout << "图不连通,无法生成最小生成树!" << endl;
    } else {
        cout << "最小生成树总权值:" << sum << endl;
    }
}

// 测试Kruskal
int main() {
    Graph G;
    // 初始化顶点
    G.vertices[0] = 'A';
    G.vertices[1] = 'B';
    G.vertices[2] = 'C';
    G.vertices[3] = 'D';
    G.vertexNum = 4;
    // 初始化边集(无向图,每条边只存一次)
    G.edges[0] = {0, 1, 2};  // A-B,权2
    G.edges[1] = {0, 2, 3};  // A-C,权3
    G.edges[2] = {1, 3, 4};  // B-D,权4
    G.edges[3] = {2, 3, 1};  // C-D,权1
    G.edgeNum = 4;

    Kruskal(G);
    return 0;
}

运行结果

复制代码
Kruskal选中的边:
C - D,权值:1
A - B,权值:2
A - C,权值:3
最小生成树总权值:6

4.2 最短路径

Dijkstra 算法

求一个顶点到其他所有顶点的最短路径(带权有向 / 无向图,权值非负)。

cpp 复制代码
#include <iostream>
#include <climits>
using namespace std;

const int MAX_VERTEX = 100;
const int INF = INT_MAX;

struct GraphMatrix {
    char vertices[MAX_VERTEX];
    int matrix[MAX_VERTEX][MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};
// (初始化、添加顶点/边的函数同4.1,此处省略)

// Dijkstra算法求最短路径(从顶点v开始)
void Dijkstra(GraphMatrix &G, int v) {
    int dist[MAX_VERTEX]; // dist[i]:v到i的最短距离
    bool visited[MAX_VERTEX] = {false}; // 标记是否已确定最短路径
    // 初始化dist
    for (int i = 0; i < G.vertexNum; i++) {
        dist[i] = G.matrix[v][i];
    }
    dist[v] = 0;
    visited[v] = true;

    for (int i = 1; i < G.vertexNum; i++) { // 确定n-1个顶点的最短路径
        int minDist = INF;
        int u = -1;
        // 找当前未确定的顶点中,dist最小的
        for (int j = 0; j < G.vertexNum; j++) {
            if (!visited[j] && dist[j] < minDist) {
                minDist = dist[j];
                u = j;
            }
        }
        if (u == -1) break; // 无连通路径
        visited[u] = true;
        // 更新dist:通过u中转,看是否能缩短路径
        for (int j = 0; j < G.vertexNum; j++) {
            if (!visited[j] && G.matrix[u][j] != INF && dist[u] + G.matrix[u][j] < dist[j]) {
                dist[j] = dist[u] + G.matrix[u][j];
            }
        }
    }

    // 输出结果
    cout << G.vertices[v] << "到各顶点的最短路径:" << endl;
    for (int i = 0; i < G.vertexNum; i++) {
        if (dist[i] == INF) {
            cout << G.vertices[v] << " -> " << G.vertices[i] << ":无路径" << endl;
        } else {
            cout << G.vertices[v] << " -> " << G.vertices[i] << ":" << dist[i] << endl;
        }
    }
}

// 测试Dijkstra
int main() {
    GraphMatrix G;
    initWeightGraph(G);
    G.vertices[0] = 'A';
    G.vertices[1] = 'B';
    G.vertices[2] = 'C';
    G.vertices[3] = 'D';
    G.vertexNum = 4;
    addWeightEdge(G, 0, 1, 2);
    addWeightEdge(G, 0, 2, 5);
    addWeightEdge(G, 1, 2, 1);
    addWeightEdge(G, 1, 3, 4);
    addWeightEdge(G, 2, 3, 2);
    // 运行Dijkstra
    Dijkstra(G, 0);
    return 0;
}

运行结果

复制代码
A到各顶点的最短路径:
A -> A:0
A -> B:2
A -> C:3
A -> D:5

Floyd 算法

Floyd 算法是「多源最短路径算法」------ 一次性求出所有顶点之间的最短路径(核心:动态规划,三重循环,新手易实现)。

cpp 复制代码
#include <iostream>
#include <climits>
using namespace std;

const int MAX_VERTEX = 100;
const int INF = INT_MAX;

// 带权邻接矩阵图
struct GraphMatrix {
    char vertices[MAX_VERTEX];
    int matrix[MAX_VERTEX][MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};

// 初始化带权图
void initWeightGraph(GraphMatrix &G) {
    G.vertexNum = 0;
    G.edgeNum = 0;
    for (int i = 0; i < MAX_VERTEX; i++) {
        for (int j = 0; j < MAX_VERTEX; j++) {
            G.matrix[i][j] = (i == j) ? 0 : INF; // 自己到自己距离为0,否则无穷大
        }
    }
}

// 添加带权边(有向图,若无向图则双向赋值)
void addWeightEdge(GraphMatrix &G, int i, int j, int weight) {
    if (i < 0 || i >= G.vertexNum || j < 0 || j >= G.vertexNum) {
        cout << "顶点序号非法!" << endl;
        return;
    }
    G.matrix[i][j] = weight;
    G.edgeNum++;
}

// Floyd算法求所有顶点间最短路径
void Floyd(GraphMatrix &G) {
    // dist[i][j]:i到j的最短距离
    int dist[MAX_VERTEX][MAX_VERTEX];
    // 初始化dist为邻接矩阵
    for (int i = 0; i < G.vertexNum; i++) {
        for (int j = 0; j < G.vertexNum; j++) {
            dist[i][j] = G.matrix[i][j];
        }
    }

    // 核心:k为中转顶点,i为起点,j为终点
    for (int k = 0; k < G.vertexNum; k++) {
        for (int i = 0; i < G.vertexNum; i++) {
            for (int j = 0; j < G.vertexNum; j++) {
                // 避免溢出:跳过INF的情况
                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];
                }
            }
        }
    }

    // 输出结果
    cout << "所有顶点间的最短路径:" << endl;
    // 打印表头
    cout << "   ";
    for (int i = 0; i < G.vertexNum; i++) {
        cout << G.vertices[i] << "  ";
    }
    cout << endl;
    // 打印每行
    for (int i = 0; i < G.vertexNum; i++) {
        cout << G.vertices[i] << "  ";
        for (int j = 0; j < G.vertexNum; j++) {
            if (dist[i][j] == INF) {
                cout << "∞  ";
            } else {
                cout << dist[i][j] << "  ";
            }
        }
        cout << endl;
    }
}

// 测试Floyd
int main() {
    GraphMatrix G;
    initWeightGraph(G);
    // 添加顶点
    G.vertices[0] = 'A';
    G.vertices[1] = 'B';
    G.vertices[2] = 'C';
    G.vertices[3] = 'D';
    G.vertexNum = 4;
    // 添加带权边(有向图)
    addWeightEdge(G, 0, 1, 2);  // A->B,权2
    addWeightEdge(G, 0, 2, 5);  // A->C,权5
    addWeightEdge(G, 1, 2, 1);  // B->C,权1
    addWeightEdge(G, 1, 3, 4);  // B->D,权4
    addWeightEdge(G, 2, 3, 2);  // C->D,权2

    Floyd(G);
    return 0;
}

运行结果

复制代码
所有顶点间的最短路径:
   A  B  C  D  
A  0  2  3  5  
B  ∞  0  1  3  
C  ∞  ∞  0  2  
D  ∞  ∞  ∞  0  

3.拓扑排序

拓扑排序(针对有向无环图 DAG)

拓扑排序是「按依赖关系排序」------ 若存在边 A→B,则 A 排在 B 前面(核心:找入度为 0 的顶点,逐步删除)仅适用于有向无环图(DAG),解决依赖排序问题

cpp 复制代码
#include <iostream>
using namespace std;

const int MAX_VERTEX = 100;

// 邻接表存储有向图
struct EdgeNode {
    int adjVertex;
    EdgeNode *next;
};
struct VertexNode {
    char data;
    int inDegree; // 入度(关键:拓扑排序需要入度)
    EdgeNode *firstEdge;
};
struct GraphList {
    VertexNode vertices[MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};

// 初始化图
void initGraphList(GraphList &G) {
    G.vertexNum = 0;
    G.edgeNum = 0;
    for (int i = 0; i < MAX_VERTEX; i++) {
        G.vertices[i].inDegree = 0;
        G.vertices[i].firstEdge = NULL;
    }
}

// 添加顶点
void addVertexList(GraphList &G, char v) {
    if (G.vertexNum >= MAX_VERTEX) {
        cout << "顶点数超限!" << endl;
        return;
    }
    G.vertices[G.vertexNum].data = v;
    G.vertexNum++;
}

// 添加有向边(u->v,v的入度+1)
void addEdgeList(GraphList &G, int u, int v) {
    if (u < 0 || u >= G.vertexNum || v < 0 || v >= G.vertexNum) {
        cout << "顶点序号非法!" << endl;
        return;
    }
    // 添加边u->v
    EdgeNode *newEdge = new EdgeNode;
    newEdge->adjVertex = v;
    newEdge->next = G.vertices[u].firstEdge;
    G.vertices[u].firstEdge = newEdge;
    // v的入度+1
    G.vertices[v].inDegree++;
    G.edgeNum++;
}

// 拓扑排序(数组模拟队列)
void topologicalSort(GraphList &G) {
    int queue[MAX_VERTEX]; // 存放入度为0的顶点
    int front = 0, rear = 0;
    int count = 0; // 记录已排序的顶点数

    // 第一步:将所有入度为0的顶点入队
    for (int i = 0; i < G.vertexNum; i++) {
        if (G.vertices[i].inDegree == 0) {
            queue[rear++] = i;
        }
    }

    cout << "拓扑排序结果:";
    while (front < rear) {
        // 出队一个入度为0的顶点
        int cur = queue[front++];
        cout << G.vertices[cur].data << " ";
        count++;

        // 遍历cur的所有邻接顶点,入度-1
        EdgeNode *p = G.vertices[cur].firstEdge;
        while (p != NULL) {
            int adjV = p->adjVertex;
            G.vertices[adjV].inDegree--;
            // 若入度变为0,入队
            if (G.vertices[adjV].inDegree == 0) {
                queue[rear++] = adjV;
            }
            p = p->next;
        }
    }

    // 若排序顶点数≠总顶点数,说明有环
    if (count != G.vertexNum) {
        cout << "\n图中存在环,无法完成拓扑排序!" << endl;
    } else {
        cout << endl;
    }
}

// 测试拓扑排序
int main() {
    GraphList G;
    initGraphList(G);
    // 添加顶点:A(0), B(1), C(2), D(3), E(4)
    addVertexList(G, 'A');
    addVertexList(G, 'B');
    addVertexList(G, 'C');
    addVertexList(G, 'D');
    addVertexList(G, 'E');
    // 添加有向边:A->B, A->C, B->D, C->D, D->E
    addEdgeList(G, 0, 1);
    addEdgeList(G, 0, 2);
    addEdgeList(G, 1, 3);
    addEdgeList(G, 2, 3);
    addEdgeList(G, 3, 4);

    topologicalSort(G);
    return 0;
}

运行结果

复制代码
拓扑排序结果:A B C D E 

4.关键路径

关键路径(基于拓扑排序,求项目最短完成时间,工程管理常用)

关键路径是**「从起点到终点的最长路径」------ 决定项目的最短完成时间**(核心:求最早开始时间 ve、最晚开始时间 vl,ve=vl 的边为关键活动)。

cpp 复制代码
#include <iostream>
#include <climits>
using namespace std;

const int MAX_VERTEX = 100;
const int INF = INT_MAX;

// 带权有向边(存储权值,即活动耗时)
struct EdgeNode {
    int adjVertex;
    int weight; // 活动耗时
    EdgeNode *next;
};
struct VertexNode {
    char data;
    int inDegree;
    EdgeNode *firstEdge;
};
struct GraphList {
    VertexNode vertices[MAX_VERTEX];
    int vertexNum;
    int edgeNum;
};

// 初始化、添加顶点/边函数同拓扑排序(边需增加权值)
void initGraphList(GraphList &G) {
    G.vertexNum = 0;
    G.edgeNum = 0;
    for (int i = 0; i < MAX_VERTEX; i++) {
        G.vertices[i].inDegree = 0;
        G.vertices[i].firstEdge = NULL;
    }
}
void addVertexList(GraphList &G, char v) {
    if (G.vertexNum >= MAX_VERTEX) return;
    G.vertices[G.vertexNum].data = v;
    G.vertexNum++;
}
// 添加带权有向边u->v,权值为weight(活动耗时)
void addWeightEdgeList(GraphList &G, int u, int v, int weight) {
    if (u < 0 || u >= G.vertexNum || v < 0 || v >= G.vertexNum) return;
    EdgeNode *newEdge = new EdgeNode;
    newEdge->adjVertex = v;
    newEdge->weight = weight;
    newEdge->next = G.vertices[u].firstEdge;
    G.vertices[u].firstEdge = newEdge;
    G.vertices[v].inDegree++;
    G.edgeNum++;
}

// 关键路径算法
void criticalPath(GraphList &G) {
    int ve[MAX_VERTEX] = {0}; // 顶点的最早发生时间
    int vl[MAX_VERTEX];       // 顶点的最晚发生时间
    int queue[MAX_VERTEX];
    int front = 0, rear = 0;
    int count = 0;

    // 第一步:拓扑排序,求ve
    // 入度为0的顶点入队
    for (int i = 0; i < G.vertexNum; i++) {
        if (G.vertices[i].inDegree == 0) {
            queue[rear++] = i;
        }
    }

    while (front < rear) {
        int cur = queue[front++];
        count++;
        EdgeNode *p = G.vertices[cur].firstEdge;
        while (p != NULL) {
            int adjV = p->adjVertex;
            // 更新ve[adjV]:取最大值
            if (ve[cur] + p->weight > ve[adjV]) {
                ve[adjV] = ve[cur] + p->weight;
            }
            G.vertices[adjV].inDegree--;
            if (G.vertices[adjV].inDegree == 0) {
                queue[rear++] = adjV;
            }
            p = p->next;
        }
    }

    if (count != G.vertexNum) {
        cout << "图中有环,无法求关键路径!" << endl;
        return;
    }

    // 第二步:初始化vl(终点的vl=ve)
    int maxVe = ve[0];
    for (int i = 1; i < G.vertexNum; i++) {
        if (ve[i] > maxVe) maxVe = ve[i];
    }
    for (int i = 0; i < G.vertexNum; i++) {
        vl[i] = maxVe;
    }

    // 第三步:逆拓扑排序,求vl
    // 重新入队(逆序,用栈模拟,数组实现)
    int stack[MAX_VERTEX], top = -1;
    for (int i = 0; i < G.vertexNum; i++) {
        stack[++top] = queue[i];
    }

    while (top >= 0) {
        int cur = stack[top--];
        EdgeNode *p = G.vertices[cur].firstEdge;
        while (p != NULL) {
            int adjV = p->adjVertex;
            // 更新vl[cur]:取最小值
            if (vl[adjV] - p->weight < vl[cur]) {
                vl[cur] = vl[adjV] - p->weight;
            }
            p = p->next;
        }
    }

    // 第四步:找关键活动(e=lf,即ve[cur]+weight=vl[adjV])
    cout << "关键路径(关键活动):" << endl;
    for (int i = 0; i < G.vertexNum; i++) {
        EdgeNode *p = G.vertices[i].firstEdge;
        while (p != NULL) {
            int adjV = p->adjVertex;
            int e = ve[i];          // 活动最早开始时间
            int l = vl[adjV] - p->weight; // 活动最晚开始时间
            if (e == l) { // 关键活动
                cout << G.vertices[i].data << " -> " << G.vertices[adjV].data 
                     << "(耗时:" << p->weight << ")" << endl;
            }
            p = p->next;
        }
    }
    cout << "项目最短完成时间:" << maxVe << endl;
}

// 测试关键路径
int main() {
    GraphList G;
    initGraphList(G);
    // 添加顶点:A(0), B(1), C(2), D(3), E(4), F(5)
    addVertexList(G, 'A');
    addVertexList(G, 'B');
    addVertexList(G, 'C');
    addVertexList(G, 'D');
    addVertexList(G, 'E');
    addVertexList(G, 'F');
    // 添加带权边(活动耗时)
    addWeightEdgeList(G, 0, 1, 2); // A->B,2
    addWeightEdgeList(G, 0, 2, 3); // A->C,3
    addWeightEdgeList(G, 1, 3, 4); // B->D,4
    addWeightEdgeList(G, 2, 3, 2); // C->D,2
    addWeightEdgeList(G, 3, 4, 3); // D->E,3
    addWeightEdgeList(G, 3, 5, 2); // D->F,2
    addWeightEdgeList(G, 4, 5, 1); // E->F,1

    criticalPath(G);
    return 0;
}

运行结果

复制代码
关键路径(关键活动):
A -> C(耗时:3)
C -> D(耗时:2)
D -> E(耗时:3)
E -> F(耗时:1)
项目最短完成时间:9

五、实际比赛例题(略)

CCF

相关推荐
蒙奇D索大2 小时前
【数据结构】排序算法精讲 | 快速排序全解:高效实现、性能评估、实战剖析
数据结构·笔记·学习·考研·算法·排序算法·改行学it
程序员良辰2 小时前
【算法新手入门】基本数据类型
算法
Blossom.1182 小时前
基于混合检索架构的RAG系统优化实践:从Baseline到生产级部署
人工智能·python·算法·chatgpt·ai作画·架构·自动化
断剑zou天涯2 小时前
【算法笔记】有序表——AVL树
笔记·算法
巧克力味的桃子2 小时前
算法:大数除法
算法
@小码农2 小时前
2025年12月 GESP认证 图形化编程 一级真题试卷(附答案)
开发语言·数据结构·算法
巧克力味的桃子2 小时前
让程序在读取到整数0时就终止循环
c语言·算法
_OP_CHEN2 小时前
【算法基础篇】(三十九)数论之从质数判定到高效筛法:质数相关核心技能全解析
c++·算法·蓝桥杯·埃氏筛法·acm/icpc·筛质数·欧拉筛法
算法与编程之美2 小时前
损失函数与分类精度的关系
人工智能·算法·机器学习·分类·数据挖掘