一.项目介绍
本设计采用数据结构预算法技术,按照软件工程的开发流程设计了以太网布网及故障检测模拟系统,结合任务需求,给出了系统的总体框架,选择Visual Studio等作为开发工具,实现了本系统的各项功能。
(1)网络拓扑分析:首先需要对以太网的网络拓扑进行分析,确定各个计算机之间的连接关系和畅通程度。
(2)回路检测:基于网络拓扑,利用图论的方法来检测是否存在回路。例如,可以采用深度优先搜索(DFS)或广度优先搜索(BFS)算法遍历网络图,判断是否存在环路。
(3)最大畅通程度优化:当发现回路存在时,需要选择合适的网线进行移除,以消除回路并最大化被除去网线的畅通程度。通过网络图的边权重选择和最大连通子图问题。
二.项目功能结构图
- 以太网布网模拟:用户可以设置节点数量和布网方式,系统会自动根据设置生成相应数量的节点,并进行自动布网。该功能模拟以太网的节点连接和拓扑结构,以便用户可以直观地了解网络的布局情况。
- 数据输入和输出:系统提供了相应的界面组件和功能,让用户可以输入节点数量、选择布网方式等信息,并输出布网结果、故障检测结果以及问题报告等信息。用户可以通过界面上的输入框、下拉菜单和按钮等进行操作,并通过界面上的输出区域来获取结果和报告。
- 错误处理和提示:系统在用户输入错误或出现问题时会及时给出错误提示和建议。例如,如果用户输入的节点数量超出范围或布网方式选择有误,系统会弹出相应的提示窗口或在界面上标红显示错误信息,以引导用户进行正确的操作。
- 可视化展示:系统通过图形化展示来呈现网络的布网情况、故障检测结果和问题报告。使用图表、图形或示意图等元素来表示网络的拓扑结构、节点连接状态以及故障节点的位置等信息,以便用户能够直观地了解网络的状态和问题。
三.系统模块功能
本系统主要由数据处理部分算法设计和可视化设计三个部分组成,算法设计包括了Prim算法和Kruskal算法。数据处理部分包括了计算机和网线的储存、邻接矩阵存储以太网的节和边,可视化设计包括了对最小生成树实现过程进行动画展示,以及生成得到最小数的代价并进行输出在可视化窗口上。
- 计算机信息:保存在文件中,包括计算机基本信息,例如:计算机名、位置信息等;
- 网线信息:保存在文件中,包括网线基本信息,例如:网线传输速率信息等
- 建立一个图,其存储方式可以是采用邻接矩阵形式或者是邻接表形式,来存储以太网的节点和边。
- 利用普利姆算法和克鲁斯卡尔算法求网的最小生成树。
- 按顺序输出生成树中各条边以及它们的权值。
- 模拟以太网网络拓扑结构,并显示出来。
- 构建可视化界面,对最小生成树实现过程进行动画展示。
- 分别用普利姆算法和克鲁斯卡尔算法实现,并对比分析得到最下生成树的代价。
四.各流程模块实现
4.1计算机以及网线信息的读入与写出
4.1.1流程图
4.1.2实现方法
定义两个结构体:`Computer` 和 `Cable`。
- `Computer` 结构体包含以下成员:
- - `name`:表示计算机的名称,使用 `std::string` 类型存储。
- - `location`:表示计算机所在的位置,使用 `std::string` 类型存储。
- - `operatingSystem`:表示计算机的操作系统,使用 `std::string` 类型存储。
- - `memorySize`:表示计算机的内存大小,使用 `int` 类型存储。
- `Cable` 结构体包含以下成员:
- - `speed`:表示电缆的速度,使用 `std::string` 类型存储。
- - `manufacturer`:表示电缆的制造商,使用 `std::string` 类型存储。
- - `length`:表示电缆的长度,使用 `int` 类型存储。
- 创建一个名为 "computer_info.txt" 的输出文件流对象 `file`,该文件用于存储计算机信息。
- 检查文件是否成功打开,通过调用 `is_open()` 函数判断。如果文件成功打开,则执行以下步骤:
- - 使用 `<<` 运算符将计算机的名称、位置、操作系统和内存大小写入文件中,每个属性值占据一行。
- - 关闭文件流,调用 `close()` 函数。
- - 输出一个成功信息到标准输出,即输出 "写入计算机信息成功"。
- 如果文件无法打开,即 `is_open()` 返回 `false`,则输出一个错误信息到标准输出,即输出 "无法打开文件"。
- 创建一个名为 "computer_info.txt" 的输入文件流对象 `file`,该文件用于读取计算机信息。
- 检查文件是否成功打开,通过调用 `is_open()` 函数判断。如果文件成功打开,则执行以下步骤:
- - 使用 `std::getline()` 函数逐行从文件中读取计算机的名称、位置和操作系统,并存储到对应的 `Computer` 对象的成员变量中。
- - 使用 `>>` 运算符从文件中读取计算机的内存大小,存储到 `computer.memorySize` 中。
- - 使用 `ignore()` 函数忽略掉输入流中的换行字符。
- - 关闭文件流,调用 `close()` 函数。
- - 输出一个成功信息到标准输出,即输出 "读取计算机信息成功"。
- - 输出读取到的计算机信息到标准输出,包括计算机名、位置信息、操作系统和内存大小。
- 如果文件无法打开,即 `is_open()` 返回 `false`,则输出一个错误信息到标准输出,即输出 "无法打开文件"。
- 创建一个名为 “cable_info.txt” 的输出文件流对象 file,该文件用于存储网线信息。
- 检查文件是否成功打开,通过调用 is_open() 函数判断。如果文件成功打开,则执行以下步骤:
- 使用 << 运算符将网线的速度、制造商和长度写入文件中,每个属性值占据一行。
- 关闭文件流,调用 close() 函数。
- 输出一个成功信息到标准输出,即输出 “写入网线信息成功”。
- 如果文件无法打开,即 is_open() 返回 false,则输出一个错误信息到标准输出,即输出 “无法打开文件”。
- 创建一个名为 “cable_info.txt” 的输出文件流对象 file,该文件用于存储网线信息。
- 检查文件是否成功打开,通过调用 is_open() 函数判断。如果文件成功打开,则执行以下步骤:
- 使用 << 运算符将网线的速度、制造商和长度写入文件中,每个属性值占据一行。
- 关闭文件流,调用 close() 函数。
- 输出一个成功信息到标准输出,即输出 “写入网线信息成功”。
- 如果文件无法打开,即 is_open() 返回 false,则输出一个错误信息到标准输出,即输出 “无法打开文件”。
- 创建了一个名为 `myComputer` 的计算机对象,并通过用户输入分别为其设置名称、位置、操作系统和内存大小。
- 调用 `writeComputerToFile` 函数,将 `myComputer` 对象的信息写入到文件中。
- 创建了一个名为 `readComputer` 的计算机对象。
- 调用 `readComputerFromFile` 函数,从文件中读取计算机信息到 `readComputer` 对象。
- 创建了一个名为 `myCable` 的网线对象,并通过用户输入分别为其设置传输速率、制造商和长度。
- 调用 `writeCableToFile` 函数,将 `myCable` 对象的信息写入到文件中。
- 创建了一个名为 `readCable` 的网线对象。
- 调用 `readCableFromFile` 函数,从文件中读取网线信息到 `readCable` 对象。
- 主函数执行完成后返回 0。
4.2以太网的节点和边存储
4.2.1流程图
4.2.2实现方法
首先定义好成员变量:
然后定义好以下成员函数:
EthernetNetwork(int numNodes, bool useAdjacencyMatrix):构造函数,根据给定的节点数量和是否使用邻接矩阵来初始化EthernetNetwork对象
addEdge(int src, int dest):添加边的函数,根据给定的起点和终点,将边添加到网络中
printNetwork():打印网络的函数,根据使用的数据结构类型,打印网络的邻接矩阵或邻接表
private:
int numNodes; // 节点的数量
bool useAdjacencyMatrix; // 是否使用邻接矩阵
bool** adjacencyMatrix; // 邻接矩阵
int** adjacencyList; // 邻接表
public:
EthernetNetwork(int numNodes, bool useAdjacencyMatrix) {
this->numNodes = numNodes; // 初始化节点数量
this->useAdjacencyMatrix = useAdjacencyMatrix; // 初始化是否使用邻接矩阵
if (useAdjacencyMatrix) { // 如果使用邻接矩阵
adjacencyMatrix = new bool* [numNodes]; // 创建一个大小为numNodes的二维数组
for (int i = 0; i < numNodes; i++) {
adjacencyMatrix[i] = new bool[numNodes]; // 创建大小为numNodes的一维数组
for (int j = 0; j < numNodes; j++) {
adjacencyMatrix[i][j] = false; // 初始化邻接矩阵的元素为false
}
}
}
else { // 如果使用邻接表
adjacencyList = new int* [numNodes]; // 创建一个大小为numNodes的二维数组
for (int i = 0; i < numNodes; i++) {
adjacencyList[i] = new int[2]; // 创建大小为2的一维数组
adjacencyList[i][0] = 0; // 初始化邻接表的数量为0
}
}
}
void addEdge(int src, int dest) { // 添加边
if (useAdjacencyMatrix) { // 如果使用邻接矩阵
adjacencyMatrix[src][dest] = true; // 在邻接矩阵中设置边的存在
adjacencyMatrix[dest][src] = true; // 在邻接矩阵中设置边的存在
}
else { // 如果使用邻接表
int srcCount = adjacencyList[src][0]; // 获取源节点的邻接表数量
int destCount = adjacencyList[dest][0]; // 获取目标节点的邻接表数量
adjacencyList[src][srcCount + 1] = dest; // 在源节点的邻接表末尾添加目标节点
adjacencyList[dest][destCount + 1] = src; // 在目标节点的邻接表末尾添加源节点
adjacencyList[src][0]++; // 更新源节点的邻接表数量
adjacencyList[dest][0]++; // 更新目标节点的邻接表数量
}
}
void printNetwork() { // 打印网络
if (useAdjacencyMatrix) { // 如果使用邻接矩阵
std::cout << "邻接矩阵:" << std::endl; // 打印邻接矩阵标题
for (int i = 0; i < numNodes; i++) {
for (int j = 0; j < numNodes; j++) {
std::cout << adjacencyMatrix[i][j] << " "; // 打印邻接矩阵元素
}
std::cout << std::endl;
}
}
else { // 如果使用邻接表
std::cout << "邻接表:" << std::endl; // 打印邻接表标题
for (int i = 0; i < numNodes; i++) {
std::cout << "节点 " << i << ": "; // 打印节点
for (int j = 1; j <= adjacencyList[i][0]; j++) {
std::cout << adjacencyList[i][j] << " "; // 打印节点的邻居
}
std::cout << std::endl;
}
}
}
};
最后定义一个主函数(main)在主函数(main)中,首先从用户输入中获取节点的数量和是否使用邻接矩阵,然后根据这些信息创建EthernetNetwork对象。接下来,通过循环从用户输入中获取边的起点和终点,将边添加到网络中。最后,打印结果。
int main() {
int numNodes; // 节点的数量
bool useAdjacencyMatrix; // 是否使用邻接矩阵
std::cout << "输入节点数: ";
std::cin >> numNodes; // 输入节点的数量
std::cout << "使用邻接表 (0) 或邻接列矩阵 (1)?";
std::cin >> useAdjacencyMatrix; // 输入是否使用邻接矩阵
EthernetNetwork network(numNodes, useAdjacencyMatrix); // 创建EthernetNetwork对象
int src, dest;
while (true) {
std::cout << "输入边(或输入-1 -1 退出): ";
std::cin >> src >> dest; // 输入边的起点和终点
if (src == -1 && dest == -1) {
break; // 输入-1 -1停止
}
network.addEdge(src, dest); // 添加边到网络中
}
network.printNetwork(); // 打印网络
return 0;}
4.3kruskal和Prim算法
4.3.1流程图
4.3.2实现方法
void Kruskal() {
int Vexnum = 0;
int edge = 0;
cout << "--------------这是kruskal算法-------------" << endl;
cout << "请输入图的顶点数和边数:" << endl;
cin >> Vexnum >> edge;
while (!check(Vexnum, edge)) {
cout << "你输入的图的顶点数和边数不合法,请重新输入:" << endl;
cin >> Vexnum >> edge;
}
//声明一个边集数组
Edge* edge_tag;
//输入每条边的信息
createGraph(edge_tag, Vexnum, edge);
int* parent = new int[Vexnum]; //记录每个顶点所在子树的根节点下标
int* child = new int[Vexnum]; //记录每个顶点为根节点时,其有的孩子节点的个数
int i;
for (i = 0; i < Vexnum; i++) {
parent[i] = i;
child[i] = 0;
}
//对边集数组进行排序,按照权重从小到达排序
qsort(edge_tag, edge, sizeof(Edge), cmp);
int count_vex; //记录输出的边的条数
count_vex = i = 0;
cout << "连接图为:" << endl;
while (i != edge) {
//如果两颗树可以组合在一起,说明该边是生成树的一条边
if (union_tree(edge_tag[i], parent, child)) {
cout << ("v" + std::to_string(edge_tag[i].start))
<< "-----"
<< ("v" + std::to_string(edge_tag[i].end))
<< "="
<< edge_tag[i].weight
<< endl;
edge_tag[i].visit = true;
++count_vex; //生成树的边加1
}
//这里表示所有的边都已经加入成功
if (count_vex == Vexnum - 1) {
break;
}
++i;
}
if (count_vex != Vexnum - 1) {
cout << "此图为非连通图!无法构成最小生成树。" << endl;
}
delete[] edge_tag;
delete[] parent;
delete[] child;
}
struct Assis_array {
int start; //边的起点
int end; //边的终点
int weight; //边的权重
};
//进行prim算法实现,使用的邻接矩阵的方法实现。
void Prim(Graph g, int begin) {
//close_side这个数组记录到达某个顶点的各个边中的权重最大的那个边
Assis_array* close_side = new Assis_array[g.vertex];
int j;
cout << "连接图为:" << endl;
//进行close_side的初始化,更加开始起点进行初始化
for (j = 0; j < g.vertex; j++) {
if (j != begin - 1) {
close_side[j].start = begin - 1;
close_side[j].end = j;
close_side[j].weight = g.a[begin - 1][j];
}
}
//把起点的close_side中的值设置为-1,代表已经加入到集合U了
close_side[begin - 1].weight = -1;
//访问剩下的顶点,并加入依次加入到集合U
for (j = 1; j < g.vertex; j++) {
int min = INT_MAX;
int k;
int index;
//寻找数组close_side中权重最小的那个边
for (k = 0; k < g.vertex; k++) {
if (close_side[k].weight != -1) {
if (close_side[k].weight < min) {
min = close_side[k].weight;
index = k;
}
}
}
//将权重最小的那条边的终点也加入到集合U
close_side[index].weight = -1;
//输出对应的边的信息
cout << g.vname[close_side[index].start]
<< "-----"
<< g.vname[close_side[index].end]
<< "="
<< g.a[close_side[index].start][close_side[index].end]
<< endl;
//更新我们的close_side数组。
for (k = 0; k < g.vertex; k++) {
if (g.a[close_side[index].end][k] < close_side[k].weight) {
close_side[k].weight = g.a[close_side[index].end][k];
close_side[k].start = close_side[index].end;
close_side[k].end = k;
}
}
}
}
4.4顺序输出及以太网拓扑结构图
4.4.1流程图
4.4.2实现方法
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Edge {
int start;
int end;
int weight;//权值
string type;
};
bool compareEdges(Edge a, Edge b) {
return a.weight < b.weight;
}
int main() {
vector<Edge> edges;
int numEdges;
cout << "请输入边的数量:";
cin >> numEdges;
for (int i = 0; i < numEdges; i++) {
Edge edge;
cout << "请输入第 " << i + 1 << " 条边的起点、终点和权值(空格分隔):";
cin >> edge.start >> edge.end >> edge.weight;
if (edge.weight < 0) {
edge.type = "负";
}
else if (edge.weight == 0) {
edge.type = "零节点";
}
else {
edge.type = "正";
}
edges.push_back(edge);
}
sort(edges.begin(), edges.end(), compareEdges);
cout << "边信息如下:" << endl;
for (const auto& edge : edges) {
cout << "起点: " << edge.start << ", 终点: " << edge.end << ", 权值: " << edge.weight << ", 类型: " << edge.type << endl;
}
return 0;
}
通过sfml库实现绘制拓扑结构图
#include <SFML/Graphics.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace sf;
// 添加以下两行代码
const int maxSize = 10;
const int maxEdges = 20;
// 定义节点和边的类或结构体
struct Node {
int id;
vector<int> neighbors;//vector容器
};
// 创建图的类
class Graph {
private:
vector<Node> nodes;
public:
// 添加节点
void addNode(int id) {
Node node;
node.id = id;
nodes.push_back(node);//将节点放进node容器
}
// 添加边
void addEdge(int src, int dst) {
nodes[src].neighbors.push_back(dst);
nodes[dst].neighbors.push_back(src);
}
// 绘制图形化界面
void drawGraph() {
RenderWindow window(VideoMode(400, 400), "Graph Topology");//输出界面标题
while (window.isOpen()) {
Event event;
while (window.pollEvent(event)) {
if (event.type == Event::Closed)
window.close();
}
window.clear();
// 绘制节点和边
for (int i = 0; i < nodes.size(); ++i) {
CircleShape circle(20);
circle.setPosition((i % 3) * 100 + 50, (i / 3) * 100 + 50);
window.draw(circle);
for (int neighbor : nodes[i].neighbors) {
Vertex line[] = {
Vertex(Vector2f((i % 3) * 100 + 50 + 20, (i / 3) * 100 + 50 + 20)),
Vertex(Vector2f((neighbor % 3) * 100 + 50 + 20, (neighbor / 3) * 100 + 50 + 20))
};
window.draw(line, 2, Lines);
}
}
window.display();
}
}
};
int main() {
// 创建图
Graph graph;
// 添加节点
int numNodes;
cout << "输入节点的数量:";
cin >> numNodes;
for (int i = 0; i < numNodes; ++i) {
graph.addNode(i);
}
// 添加边
int numEdges;
cout << "输入边的数量:";
cin >> numEdges;
for (int i = 0; i < numEdges; ++i) {
int src, dst;
cout << "输入边的起始节点和目标节点(以空格分隔):";
cin >> src >> dst;
graph.addEdge(src, dst);
}
// 绘制图形化界面
graph.drawGraph();
return 0;
}
4.5Prim和kreskal可视化界面最小生成树
4.5.1流程图
4.5.2实现方法
(1)// 定义图的基本常量和信息
struct Vertex {
float x, y;
};//表示一个平面上的顶点,其中"x"和"y"分别表示顶点的横坐标和纵坐标
// 定义边结构体
struct Edge {
int src, dest;
float weight;
};//这个结构体可以用来表示一个加权有向图中的边。它存储了边的起点、终点和权重的信息。
// 初始化图的顶点和边数
const int MAX_VERTICES = 10;//顶点最大数量
int numVertices = 0;//顶点的数量
int numEdges = 0;//边的数量
// 定义图的顶点和边列表
Vertex vertices[MAX_VERTICES];
Edge edges[MAX_VERTICES * (MAX_VERTICES - 1) / 2];// 创建图形窗口
sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "最小生成树演示");
(2)绘制图状态
void drawGraph(sf::RenderWindow& window, int selectedEdge) {
window.clear();
//定义了一个名为drawGraph的函数,
//该函数接受一个sf::RenderWindow类型的窗口对象window和一个int类型的参数selectedEdge作为输入参数
// for循环,用于在窗口上绘制一系列圆形,每个圆形表示一个顶点
for (int i = 0; i < numVertices; i++) {
sf::CircleShape circle(10.f);
circle.setFillColor(sf::Color::Red);
//首先创建一个名为circle的sf::CircleShape对象,设置其半径为10.f,即圆形的直径为20.f。
//然后,通过circle.setFillColor(sf::Color::Red)将圆形的填充颜色设置为红色
circle.setPosition(vertices[i].x - 10.f, vertices[i].y - 10.f);
//通过circle.setPosition(vertices[i].x - 10.f, vertices[i].y - 10.f)设置圆形在窗口中的位置。
// //vertices[i]表示第i个顶点的位置,其中vertices是一个顶点数组,
// //元素类型可能是一个自定义的结构或类,具有x和y坐标属性。
//将x和y坐标减去10.f的原因是为了使圆形的中心与顶点的位置对齐
window.draw(circle);
}
// for循环,用于在窗口上绘制一系列的线段,每个线段表示一个边。
for (int i = 0; i < numEdges; i++) {
sf::Vertex line[] = {
sf::Vertex(sf::Vector2f(vertices[edges[i].src].x, vertices[edges[i].src].y)),
sf::Vertex(sf::Vector2f(vertices[edges[i].dest].x, vertices[edges[i].dest].y))
//首先创建一个名为line的sf::Vertex数组对象,数组中包含两个顶点,即线段的起始点和终止点
};
if (i == selectedEdge) {
line[0].color = sf::Color::Green;
line[1].color = sf::Color::Green;
//如果i等于selectedEdge,即当前迭代的边为选定的边,就将线段的颜色修改为绿色
}
window.draw(line, 2, sf::Lines);
window.display();
(3) 构造点和边的集合
// 添加顶点和边
addVertex(100.f, 100.f);
addVertex(200.f, 200.f);
addVertex(300.f, 100.f);
addVertex(400.f, 200.f);
addVertex(500.f, 100.f);
addEdge(0, 1, 100.f);
addEdge(1, 2, 200.f);
addEdge(2, 3, 150.f);
addEdge(3, 4, 250.f);
addEdge(0, 3, 300.f);
addEdge(1, 3, 400.f);
// 普利姆算法
primMST(window);
// 清空图形
numVertices = 0;
numEdges = 0;
// 克鲁斯卡尔算法
kruskalMST(window);
(4)循环处理窗口,最后关闭事件
// 等待窗口关闭
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
}
// 使用普利姆算法构建最小生成树
void primMST(sf::RenderWindow& window) {
// 创建一个数组来存储最小生成树的边
Edge minSpanTree[MAX_VERTICES];
// 创建一个数组来标记顶点是否在最小生成树中
bool inTree[MAX_VERTICES];
for (int i = 0; i < numVertices; i++) {
inTree[i] = false;
}
// 从第一个顶点开始,将其标记为已访问
int currentVertex = 0;
inTree[currentVertex] = true;
// 迭代直到找到最小生成树的所有边
int edgeCount = 0;
while (edgeCount < numVertices - 1) {
// 寻找当前顶点到其他未访问顶点的最短边
int minWeight = INT_MAX;
int minDest = -1;
for (int i = 0; i < numEdges; i++) {
if (edges[i].src == currentVertex && !inTree[edges[i].dest]) {
if (edges[i].weight < minWeight) {
minWeight = edges[i].weight;
minDest = edges[i].dest;
}
}
}
// 将找到的最短边添加到最小生成树中
minSpanTree[edgeCount].src = currentVertex;
minSpanTree[edgeCount].dest = minDest;
minSpanTree[edgeCount].weight = minWeight;
edgeCount++;
// 将当前顶点更新为新添加的顶点,并标记为已访问
currentVertex = minDest;
inTree[currentVertex] = true;
// 绘制图形并暂停一段时间,以实现动画效果
drawGraph(window, edgeCount - 1);
sf::sleep(sf::seconds(1));
}
// 绘制最终的最小生成树
drawGraph(window, -1);
int find(int parent[], int i)
//定义了一个名为find的函数,用于查找并返回给定元素的根节点。
{
while (parent[i] != i)
i = parent[i];
//循环中的条件是parent[i]不等于i,即当前元素不是根节点。
//在循环中,通过不断更新i的值为parent[i]来顺着当前元素的父节点向上查找,直到找到根节点
return i;
//一旦parent[i]等于i,表示找到了元素的根节点,即该元素是集合中的代表。
//此时,函数返回i作为根节点的索引值
}
//定义了一个名为unionSet的函数,用于将两个元素所在的集合进行合并
void unionSet(int parent[], int x, int y) {
int xRoot = find(parent, x);
int yRoot = find(parent, y);
//首先调用了之前提到的find函数,
//分别找到元素x和元素y所在的根节点,将它们分别赋值给xRoot和yRoot
parent[xRoot] = yRoot;
//将parent[xRoot]的值更新为yRoot,实现将元素x所在的集合与元素y所在的集合合并的操作
void kruskalMST(sf::RenderWindow& window) {
// 创建一个数组来存储最小生成树的边
Edge minSpanTree[MAX_VERTICES];
// 对所有边按权重进行升序排序
for (int i = 0; i < numEdges - 1; i++) {
for (int j = 0; j < numEdges - i - 1; j++) {
if (edges[j].weight > edges[j + 1].weight) {
Edge temp = edges[j];
edges[j] = edges[j + 1];
edges[j + 1] = temp;
}
}
// 创建一个数组来存储每个顶点的父节点
int parent[MAX_VERTICES];
for (int i = 0; i < numVertices; i++) {
parent[i] = i;
// 迭代遍历所有边,并将边添加到最小生成树中
int edgeCount = 0;
int i = 0;
while (edgeCount < numVertices - 1 && i < numEdges) {
int src = edges[i].src;
int dest = edges[i].dest;
// 判断是否会创建环路
if (find(parent, src) != find(parent, dest)) {
minSpanTree[edgeCount] = edges[i];
edgeCount++;
unionSet(parent, src, dest);
// 绘制图形并暂停一段时间,以实现动画效果
drawGraph(window, edgeCount - 1);
sf::sleep(sf::seconds(1));
}
i++;
}
// 绘制最终的最小生成树
drawGraph(window, -1);