1. 项目概述
"sdut-数据结构与算法pta-图论"这个标题包含了几个关键信息点:SDUT(山东理工大学)、数据结构与算法、PTA(程序设计类实验辅助教学平台)以及图论。这是一门结合高校教学平台的数据结构与算法课程中的图论专题内容。
作为计算机科学的核心基础课程,数据结构与算法在各个高校都是重点教学内容。而图论作为其中的重要组成部分,在社交网络分析、路径规划、编译器设计等领域都有广泛应用。PTA平台则是国内高校广泛采用的编程练习和考试系统,其题库和自动评测功能为算法学习提供了实践环境。
2. 图论基础与核心概念
2.1 图的基本定义与术语
图(Graph)是由顶点(Vertex)和边(Edge)组成的数据结构,形式化定义为G=(V,E)。根据边的性质不同,图可以分为:
- 无向图:边没有方向性
- 有向图:边具有方向性
- 加权图:边上带有权值
常用术语包括:
- 度(Degree):与顶点相连的边数
- 路径(Path):顶点序列
- 连通性(Connectivity)
- 生成树(Spanning Tree)
2.2 图的存储结构
在算法实现中,图的存储方式直接影响算法效率:
- 邻接矩阵:
#define MAX_VERTEX 100 int graph[MAX_VERTEX][MAX_VERTEX];适合稠密图,空间复杂度O(V²)
- 邻接表:
struct Node { int vertex; struct Node* next; }; struct Node* adjList[MAX_VERTEX];适合稀疏图,空间复杂度O(V+E)
- 边表:
struct Edge { int u, v, weight; }; struct Edge edgeList[MAX_EDGE];适合某些特定算法如Kruskal
3. 图论经典算法实现
3.1 图的遍历算法
深度优先搜索(DFS)
void DFS(int v, int visited[], struct Node* adjList[]) { visited[v] = 1; printf("%d ", v); struct Node* temp = adjList[v]; while(temp != NULL) { if(!visited[temp->vertex]) DFS(temp->vertex, visited, adjList); temp = temp->next; } }时间复杂度:O(V+E)
广度优先搜索(BFS)
void BFS(int start, struct Node* adjList[]) { int visited[MAX_VERTEX] = {0}; int queue[MAX_VERTEX], front = 0, rear = 0; visited[start] = 1; queue[rear++] = start; while(front != rear) { int v = queue[front++]; printf("%d ", v); struct Node* temp = adjList[v]; while(temp != NULL) { if(!visited[temp->vertex]) { visited[temp->vertex] = 1; queue[rear++] = temp->vertex; } temp = temp->next; } } }时间复杂度:O(V+E)
3.2 最短路径算法
Dijkstra算法(单源最短路径)
void Dijkstra(int graph[MAX_VERTEX][MAX_VERTEX], int src, int V) { int dist[MAX_VERTEX]; int visited[MAX_VERTEX] = {0}; for(int i=0; i<V; i++) dist[i] = INT_MAX; dist[src] = 0; for(int count=0; count<V-1; count++) { int u = minDistance(dist, visited, V); visited[u] = 1; for(int v=0; v<V; v++) { if(!visited[v] && graph[u][v] && dist[u]!=INT_MAX && dist[u]+graph[u][v]<dist[v]) dist[v] = dist[u] + graph[u][v]; } } printSolution(dist, V); }时间复杂度:O(V²)(使用优先队列可优化到O(E+VlogV))
Floyd-Warshall算法(全源最短路径)
void FloydWarshall(int graph[MAX_VERTEX][MAX_VERTEX], int V) { int dist[MAX_VERTEX][MAX_VERTEX]; for(int i=0; i<V; i++) for(int j=0; j<V; j++) dist[i][j] = graph[i][j]; for(int k=0; k<V; k++) for(int i=0; i<V; i++) for(int j=0; j<V; j++) if(dist[i][k]+dist[k][j] < dist[i][j]) dist[i][j] = dist[i][k]+dist[k][j]; printSolution(dist, V); }时间复杂度:O(V³)
3.3 最小生成树算法
Prim算法
void Prim(int graph[MAX_VERTEX][MAX_VERTEX], int V) { int parent[MAX_VERTEX]; int key[MAX_VERTEX]; int mstSet[MAX_VERTEX] = {0}; for(int i=0; i<V; i++) key[i] = INT_MAX; key[0] = 0; parent[0] = -1; for(int count=0; count<V-1; count++) { int u = minKey(key, mstSet, V); mstSet[u] = 1; for(int v=0; v<V; v++) { if(graph[u][v] && !mstSet[v] && graph[u][v]<key[v]) { parent[v] = u; key[v] = graph[u][v]; } } } printMST(parent, graph, V); }时间复杂度:O(V²)(使用优先队列可优化到O(ElogV))
Kruskal算法
void Kruskal(struct Edge edgeList[], int V, int E) { struct Edge result[MAX_VERTEX]; int parent[MAX_VERTEX]; qsort(edgeList, E, sizeof(edgeList[0]), compare); for(int v=0; v<V; v++) parent[v] = v; int e=0, i=0; while(e < V-1 && i<E) { struct Edge next = edgeList[i++]; int x = find(parent, next.u); int y = find(parent, next.v); if(x != y) { result[e++] = next; Union(parent, x, y); } } printMST(result, e); }时间复杂度:O(ElogE)或O(ElogV)
4. PTA平台图论题目解析
4.1 常见题型分类
- 基础遍历问题(DFS/BFS应用)
- 最短路径问题(Dijkstra/Floyd应用)
- 连通性问题(并查集应用)
- 拓扑排序问题
- 关键路径问题
- 网络流问题
4.2 典型题目示例
题目:旅游规划(Dijkstra应用)要求:找到两个城市间最短路径,若有多条则选择花费最少的。
typedef struct { int distance; int cost; } Path; void Dijkstra_Cost(Path graph[MAX_VERTEX][MAX_VERTEX], int src, int dest, int V) { int dist[MAX_VERTEX], cost[MAX_VERTEX]; int visited[MAX_VERTEX] = {0}; for(int i=0; i<V; i++) { dist[i] = INT_MAX; cost[i] = INT_MAX; } dist[src] = 0; cost[src] = 0; for(int count=0; count<V-1; count++) { int u = minDistance(dist, visited, V); visited[u] = 1; for(int v=0; v<V; v++) { if(!visited[v] && graph[u][v].distance && dist[u]!=INT_MAX) { if(dist[u]+graph[u][v].distance < dist[v]) { dist[v] = dist[u] + graph[u][v].distance; cost[v] = cost[u] + graph[u][v].cost; } else if(dist[u]+graph[u][v].distance == dist[v] && cost[u]+graph[u][v].cost < cost[v]) { cost[v] = cost[u] + graph[u][v].cost; } } } } printf("%d %d", dist[dest], cost[dest]); }4.3 PTA答题注意事项
- 输入输出格式必须严格匹配题目要求
- 注意顶点编号通常从0还是1开始
- 大型图(V>1000)需要考虑算法优化
- 边界情况测试:空图、单顶点图、完全图等
- 内存限制:邻接矩阵在V>10000时可能超出限制
5. 图论算法优化技巧
5.1 数据结构选择优化
- 稀疏图优先使用邻接表
- 频繁查询使用邻接矩阵
- 动态图使用十字链表或邻接多重表
5.2 常见优化策略
- Dijkstra算法使用优先队列(堆优化)
// 使用优先队列的Dijkstra伪代码 priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq; pq.push({0, src}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(visited[u]) continue; visited[u] = 1; for(auto &[v, weight] : adjList[u]) { if(dist[v] > dist[u] + weight) { dist[v] = dist[u] + weight; pq.push({dist[v], v}); } } }- Floyd算法利用矩阵对称性优化
for(int k=0; k<V; k++) for(int i=0; i<V; i++) for(int j=0; j<=i; j++) // 只计算一半 if(dist[i][k]+dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; dist[j][i] = dist[i][j]; // 对称赋值 }- 并查集的路径压缩与按秩合并
int find(int parent[], int i) { if(parent[i] != i) parent[i] = find(parent, parent[i]); // 路径压缩 return parent[i]; } void Union(int parent[], int rank[], int x, int y) { int xroot = find(parent, x); int yroot = find(parent, y); if(rank[xroot] < rank[yroot]) // 按秩合并 parent[xroot] = yroot; else if(rank[xroot] > rank[yroot]) parent[yroot] = xroot; else { parent[yroot] = xroot; rank[xroot]++; } }6. 图论在实际工程中的应用
6.1 社交网络分析
- 好友推荐(共同邻居)
- 社区发现(连通分量)
- 影响力传播(PageRank算法)
6.2 路径规划
- 导航系统(A*算法)
- 物流配送(旅行商问题近似解)
- 公共交通换乘(多权值最短路径)
6.3 编译器设计
- 控制流图分析
- 寄存器分配(图着色问题)
- 死代码消除(可达性分析)
6.4 其他领域
- 电路设计(拓扑排序)
- 任务调度(关键路径)
- 生物信息学(蛋白质相互作用网络)
7. 学习建议与资源推荐
7.1 学习路线建议
- 掌握基础:图的存储与遍历
- 理解经典算法原理与实现
- 大量练习PTA/LeetCode相关题目
- 学习高级图论算法(网络流、二分图匹配等)
- 参与实际项目应用
7.2 推荐资源
- 教材:《算法导论》图论章节
- 在线课程:浙江大学《数据结构》MOOC
- 刷题平台:PTA图论专题、LeetCode图论标签
- 可视化工具:Graphviz、Gephi
7.3 调试技巧
- 小规模测试:先用简单案例验证算法正确性
- 可视化调试:打印中间结果或画图辅助理解
- 边界测试:空输入、单节点、完全图等特殊情况
- 性能分析:使用clock()函数测量关键代码段执行时间