一、问题描述
实现图的基本操作,包括:创建图的存储结构、复制已有的图、计算图的结点个数和弧/边条数、深度优先遍历序列、广度优先遍历序列、最小生成树、拓扑排序等。
二、实验目的
掌握图的基本操作。
三、实验内容及要求
1、构造图的存储结构。
2、实现图的创建、复制、计算图的结点个数和弧/边条数、深度优先遍历序列、广度优先遍历序列、最小生成树、拓扑排序等操作。
四、数据结构设计及算法原理
Graph 类:
list<pair<int, int>> *adj
来表示图的邻接表,其中 pair<int, int>
存储相邻顶点和边的权重(对于无权图,权重可忽略)。int V
表示图中顶点的数量。辅助数据结构:
stack<int>
来存储排序的顶点。priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>
来选择最小权重的边。vector<int>
和 vector<bool>
来存储关于顶点的临时信息,如 key
(Prim 算法中的最小权重)和 visited
(用于标记访问过的顶点)。拓扑排序:
Prim 算法(最小生成树):
V
: 图中顶点的数量。adj
: 邻接表,存储图的边信息。visited
: 用于标记顶点是否已被访问。key
: Prim算法中,用于存储到达每个顶点的最小权重。parent
: Prim算法中,用于存储构建最小生成树时每个顶点的父顶点。priority_queue
: 在Prim算法中,用于选择最小权重边。五、测试数据及结果分析
考虑以下有向无环图 (DAG):
{(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (4, 5)}
6
一种可能的拓扑排序序列为:0, 2, 1, 3, 4, 5
在这个DAG中,顶点0
没有任何入度,是排序的起始点。顶点5
没有出度,是排序的终点。拓扑排序输出的顺序满足了图中所有的方向约束。由于拓扑排序可能不唯一,任何满足顶点间依赖关系的排序都是正确的。
考虑以下加权无向图:
{(0, 1, 2), (0, 3, 6), (1, 3, 8), (1, 4, 5), (1, 2, 3), (2, 4, 7)}
5
一种可能的最小生成树边集及权重为:{(0, 1, 2), (1, 2, 3), (0, 3, 6), (1, 4, 5)}
Prim 算法从一个顶点开始,逐渐增加边以构造最小生成树。在这个例子中,每次都选择连接剩余顶点的最小权重边。输出的边集构成了一个覆盖所有顶点的树,并且总权重(2 + 3 + 6 + 5 = 16
)是最小的,这符合最小生成树的定义。
六、总结与思考
图作为一种复杂且强大的数据结构,在解决许多实际问题(如网络分析、路径查找等)中扮演着核心角色。在这个任务中,选择适当的图表示方法(如邻接表)对于有效地实现算法至关重要。邻接表不仅节约空间,尤其是在稀疏图中,而且在遍历图时提供了高效的边访问。
拓扑排序和 Prim 算法是图论中两个非常重要的算法。通过实现这些算法,我加深了对它们的理解:
通过不同的测试数据集来验证算法的正确性非常重要。在这个过程中,我不仅确保了算法在标准情况下的正确运行,还通过边缘案例来测试算法的鲁棒性。这种方法在任何软件开发过程中都是必不可少的。
实现复杂数据结构和算法要求深入的编程技能和理论知识。在解决这个问题的过程中,我不仅需要精确地编写代码来实现这些算法,还需要确保代码的可读性和效率。
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <vector>
#include <climits>
#include <set>
using namespace std;
class Graph
{
private:
int V;
list<pair<int, int>>* adj; // 存储边和权重
void DFSUtil(int v, vector<bool>& visited, stack<int>& Stack)
{
visited[v] = true;
for (auto i = adj[v].begin(); i != adj[v].end(); ++i)
if (!visited[i->first])
DFSUtil(i->first, visited, Stack);
Stack.push(v);
}
public:
Graph(int V)
{
this->V = V;
adj = new list<pair<int, int>>[V];
}
~Graph()
{
delete[] adj;
}
void addEdge(int v, int w, int weight = 0)
{
adj[v].push_back(make_pair(w, weight));
}
// 拓扑排序
void topologicalSort()
{
stack<int> Stack;
vector<bool> visited(V, false);
for (int i = 0; i < V; i++)
if (visited[i] == false)
DFSUtil(i, visited, Stack);
while (!Stack.empty())
{
cout << Stack.top() << " ";
Stack.pop();
}
}
// 最小生成树 - Prim算法
void primMST()
{
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
int src = 0; // 从顶点0开始
vector<int> key(V, INT_MAX);
vector<int> parent(V, -1);
vector<bool> inMST(V, false);
pq.push(make_pair(0, src));
key[src] = 0;
while (!pq.empty())
{
int u = pq.top().second;
pq.pop();
inMST[u] = true;
for (auto i = adj[u].begin(); i != adj[u].end(); ++i) \
{
int v = (*i).first;
int weight = (*i).second;
if (inMST[v] == false && key[v] > weight)
{
key[v] = weight;
pq.push(make_pair(key[v], v));
parent[v] = u;
}
}
}
for (int i = 1; i < V; ++i)
cout << parent[i] << " - " << i << endl;
}
};
int main() {
// 示例用图
Graph g(6);
g.addEdge(0, 1, 5);
g.addEdge(0, 2, 3);
g.addEdge(1, 3, 6);
g.addEdge(1, 2, 2);
g.addEdge(2, 4, 4);
g.addEdge(2, 5, 2);
g.addEdge(2, 3, 7);
g.addEdge(3, 4, -1);
g.addEdge(4, 5, -2);
cout << "拓扑排序: ";
g.topologicalSort();
cout << "\n最小生成树(Prim算法): \n";
g.primMST();
return 0;
}