拓扑排序软件设计——ToplogicalSort_app(含有源码、需求分析、可行性分析、概要设计、用户使用手册)
拓扑排序软件设计
- 前言
- 1. 需求分析
- 2. 可行性分析
- 2.1 简介
- 2.2 技术可行性分析
- 2.2.1 技术实现方案
- 2.2.2 开发人员技能要求
- 2.2.3 可行性
- 2.3 操作可行性分析
- 2.4 结论
- 3. 项目报告
- 3.1 修订历史记录
- 3.2 软硬件环境
- 3.3 需求分析
- 3.4 详细设计
- 3.4.1 类设计
- 3.4.2 核心流程描述
- 3.4.3 核心算法设计
- 4. 运行结果截图
- 4.1 样例1
- 4.2 样例2
- 4.3 样例3
- 4.4 样例4
- 4.5 样例5
- 5. 测试
- 5.1 测试样例1
- 5.2 测试样例2
- 5.3 测试样例3
- 5.4 测试样例4
- 5.5 测试样例5
- 6. 系统特色以及可扩展点
- 6.1系统特色:
- 6.2可扩展点:
- 4. 源代码部分
- 4.1 项目层级
- 4.2 运行环境
- 4.3 项目核心代码
- 4.3.1 拓扑排序算法TopologicalSort.cpp
- 4.3.2 draw_diagram.py
- 4.3.3 file_manager.py
- 4.3.4 input_manager.py
- 4.3.5 main_window.py
- 4.3.6 topology_manager.py
- 4.4 GitHub仓库
- 5. 用户使用手册
- 5.1 运行软件APP
- 5.2 操作app
- 6. 结束语
前言
这篇博客可能会有点长,因为是一个课程的大作业,包含的内容比较多,这个项目的开发的时间在两周左右,所以这个软件指是一个简单又比较简陋的小桌面应用。
1. 需求分析
-
导入文件:
用户能够通过界面导入描述课程依赖关系的文本文件(.txt),文件格式为每行表示一个有向边的关系。 -
绘制拓扑排序图:
根据导入的课程依赖关系,能够绘制出对应的拓扑排序图,使用直观的图形方式展示课程间的依赖关系。 -
导出图像:
用户可以将绘制好的拓扑排序图导出为图片(.png格式),以便于保存和分享。 -
导出拓扑排序结果:
用户可以将拓扑排序的结果导出为文本文件(.txt格式),用于后续分析和处理。 -
展示C++程序输出:
显示调用外部C++程序计算拓扑排序后的输出结果,以便用户查看拓扑排序的详细信息。 -
主界面:
提供导入文件、绘制图像、导出图像、导出拓扑排序结果的按钮,以及显示绘制好的图像和C++程序输出的区域。 -
用户操作反馈:
显示错误、警告等反馈信息,确保用户能够清晰了解操作结果。 -
性能需求
若所有拓扑排序的结果非常多则需要很快速的返回正确并且结果个数正确的结果,避免用户等待过长的时间。同时绘制拓扑排序图应在合理的时间范围内完成,避免用户等待时间过长。 -
其他需求
①软件应该通过所给样例的测试;
②软件应该支持在Windows操作系统上运行
2. 可行性分析
2.1 简介
该文档对拓扑排序图绘制工具项目——TopologicalSort_app软件进行可行性分析,主要包括技术可行性和操作可行性的分析,以确保项目的实施和开发是合理、可行的。
2.2 技术可行性分析
2.2.1 技术实现方案
①使用PySide2库实现图形用户界面,提供友好的交互。
②使用networkx和matplotlib库绘制拓扑排序图,能够高效、准确地展示图形。
③利用Python内置的文件处理功能实现文件导入、导出功能。
④使用subprocess库调用外部C++程序进行拓扑排序,实现图的计算。
⑤使用tempfile库创建临时文件,以保存绘制好的图像。
⑥通过在Python项目中调用C++程序,实现了对拓扑排序的计算。
⑦使用Python的subprocess库调用外部C++程序,并获取其输出。
⑧这种跨语言调用对实现拓扑排序算法具有良好的技术可行性,确保了项目核心功能的实现。
2.2.2 开发人员技能要求
①开发人员需熟悉Python编程语言及其相关库,如PySide2、networkx、matplotlib、subprocess等。
②需要了解图论中的拓扑排序算法以及相关概念。
2.2.3 可行性
①技术方案基于成熟的Python库实现,具有较高的技术可行性。
②Python具有丰富的第三方库和开发资源,能够快速实现项目需求。
2.3 操作可行性分析
①项目设计简单明了,操作界面直观友好,用户容易上手。
②提供了导入、导出、绘制图像等功能按钮,用户操作便捷,符合用户使用习惯。
③C++程序的调用对用户是透明的,用户只需使用界面提供的功能,不需要关心底层实现语言。
④用户操作界面简单明了,易于上手,提供了直观的导入、导出、绘制图像等功能按钮,满足用户操作习惯,操作可行性较高。
2.4 结论
①该项目具有较高的技术可行性,开发成本较低,运维成本也较低。操作界面简单明了,用户操作便捷。
②调用C++程序作为拓扑排序的计算引擎是技术上可行的,不会对整体的可行性产生负面影响。
③用户无需关心C++程序调用细节,操作界面简单易用,用户操作的可行性较高,确保了项目的实施和开发是合理、可行的。
3. 项目报告
3.1 修订历史记录
日期 | 版本 | 说明 | 作者 |
---|---|---|---|
2023.9.10 | 1.0.0 | 创建好初步的页面 | hiddenSharp |
2023.9.11 | 1.0.1 | 完善了页面的排版 | hiddenSharp |
2023.9.14 | 1.1.0 | 1. 为生成的拓扑排序图片添加了放大和缩小按钮 2. 为生成的拓扑图片添加了背景颜色 | hiddenSharp |
2023.9.15 | 1.2.0 | 1. 删除了放大和缩小按钮 2. 优化了图片大小格式以及清晰度 3. 新增导入文件后可以之间生成该图的所有拓扑排序结果 | hiddenSharp |
2023.9.16 | 1.2.1 | 1. 重新导入文件后将清空之前生成的图片并且进度条置零 2. 初始化进度条值为0,导入文件后增加50,生成图片后再加50 3. 删除了自动导出结果的功能,修改为用户手动点击Export进行结果的导出 4. 新增用户进行导出操作后,可以下拉选择导出的文件类型(.txt为所有的拓扑排序结果,.png为拓扑图) | hiddenSharp |
2023.9.17 | 1.2.2 | 1. 固定了软件的窗口大小,不可调整窗口大小以及最大化 2. 调整了进度条的逻辑,取消了50的值,只有0与100 3. 完善了ADD 和 DEL按钮后生成图片以及拓扑排序结果的逻辑 4. DEL 按钮和 ADD按钮异常BUG | hiddenSharp |
2023.9.23 | 1.3.0 | 进行了项目结构的重构,更加具有面向对象的思想,将各模块分离出来了。新增FileManager类、InputManager类、TopologyManager类,将MainWindow类进行解耦和重构。 | hiddenSharp |
3.2 软硬件环境
- 操作系统:Windows
- 硬件要求:暂无特定硬件需求
- 开发工具:主要的IDE为PyCharm + Visual Stadio 2010;使用到的编程语言为Python + C++;文本编辑器使用的为Notepad++(不是硬性要求);编译器为Python3.6
- 第三方库和依赖项:PySide2 v5.15.2.1、networkx v2.5.1
3.3 需求分析
TopologicalSort_app的主要功能是执行拓扑排序算法,用户可以导入图的节点和边,一条边的格式应该为 <arc_start,arc_end>
然后调用C++算法进行排序,随后返回结果并显示在软件屏幕上。上面已经详细说明了需求分析,故在此不再赘述。
3.4 详细设计
3.4.1 类设计
- MainWindow类:
- 在_init_函数中创建主窗口页面、存储主窗口信息、初始化控制器、连接槽函数。
- 在clear_topology_graph函数中清空拓扑图
- 在run_cpp函数中调用编译后的.cpp文件
- 在display_cpp_output函数中显示C++程序的输出到指定位置
- 在export_image函数中设置导出拓扑排序图片的操作
- 在export_topology函数中设置导出拓扑排序结果的操作
- FileManager类:
- 在_init_函数中载入MainWindow实例和InputManager实例
- 在import_file函数中设置导入文件的操作
- 在export函数中设置导出的操作
- InputManager类:
- 在__init__函数中载入MainWindow实例
- 在fill_input_boxes函数中填写相关数据到输入框中
- 在add_input_field函数中设置添加输入框的操作
- 在del_input_field函数中设置删除输入框的操作
- TopologyManager类:
- 在__init__函数中载入MainWindow实例
- 在generate_draw函数中通过调用draw_diagram文件的相关函数来完成将图片显示在相关位置上。
注:并没有draw_diagram类,只是一个py文件,里面写了一个draw_directed_graph函数,该函数通过调用networkx和matplotlib来完成图片的生成。
3.4.2 核心流程描述
定义一个课程结构体,声明二维向量,利用dfs函数递归进行深度优先搜索,生成所有可能的结果,判断是否存在循环依赖关系,用户可导出排序结果
3.4.3 核心算法设计
- dfs 函数接收两个参数:课程向量 courses 和拓扑排序的结果向量 result。
- 在函数开始定义了一个递归停止条件判断:如果当前拓扑排序结果的大小等于课程向量 courses 的大小,即 result 的大小等于 courses 的大小,说明已经生成了一个完整的拓扑排序结果。
- 遍历课程向量 courses。
(1)对于每一个课程,判断当前课程是否满足拓扑排序的条件,即入度为0且未被访问过;
(2)如果满足条件,将其添加到 result 中并将当前课程标记为已访问;
(3)对于每一个课程,判断当前课程是否满足拓扑排序的条件,即入度为0且未被访问过;
(4)如果满足条件,将其添加到 result 中并将当前课程标记为已访问;
(5)遍历当前课程的后继课程;将所有当前课程对应的后继课程入度减1;
(6)递归调用 dfs 函数处理下一个课程;
(7)回溯,将当前课程标记为未访问,回复后继课程的入度,从 result 中移除最后一门课程,得出其他分支结果;
(8)继续遍历下一个课程(更换拓扑排序的起始课程),重复上述步骤(在循环里);
(9)当所有的课程都被遍历完后,dfs 函数执行结束;将当前的拓扑排序结果 result 添加到 allTopologicalSorts 向量中。
4. 运行结果截图
4.1 样例1
4.2 样例2
4.3 样例3
4.4 样例4
4.5 样例5
5. 测试
5.1 测试样例1
- Filename:data_1.txt
- Content:
<a,b>
<b,c>
<c,d>
<d,f>
<f,g> - Result:
sort ruselt_1:a b c d f g - photo:
5.2 测试样例2
- Filename:data_2.txt
- Content:
<CS 100,CS 200>
<CS 200,CS 250>
<CS 200,CS 300>
<CS 250,CS 350> - Result:
sort ruselt_1:CS 100 CS 200 CS 250 CS 300 CS 350
---------------------
sort ruselt_2:CS 100 CS 200 CS 250 CS 350 CS 300
---------------------
sort ruselt_3:CS 100 CS 200 CS 300 CS 250 CS 350 - photo:
5.3 测试样例3
- Filename:data_3.txt
- Content:
<CS 350,CS 250>
<MA 140,MA 141>
<MA 141,CS 150>
<CS 250,CS 225>
<MA 141,CS 225>
<CS 225,CS 155>
<CS 150,CS 155>
<CS 155,CS 200>
<CS 225,CS 230>
<CS 225,CS 300>
<CS 300,CS 301>
<CS 300,CS 340>
<CS 340,CS 345>
<CS 340,CS 360>
<CS 250,CS 360>
<CS 360,CS 390> - Result:
sort ruselt_1:CS 350 CS 250 MA 140 MA 141 CS 150 CS 225 CS 155 CS 200 CS 230 CS 300 CS 301 CS 340 CS 345 CS 360 CS 390
---------------------
sort ruselt_2:CS 350 CS 250 MA 140 MA 141 CS 150 CS 225 CS 155 CS 200 CS 230 CS 300 CS 301 CS 340 CS 360 CS 345 CS 390
……
……
……
sort ruselt_113399:MA 140 MA 141 CS 150 CS 350 CS 250 CS 225 CS 300 CS 340 CS 360 CS 390 CS 345 CS 301 CS 155 CS 230 CS 200
---------------------
sort ruselt_113400:MA 140 MA 141 CS 150 CS 350 CS 250 CS 225 CS 300 CS 340 CS 360 CS 390 CS 345 CS 301 CS 230 CS 155 CS 200
5.4 测试样例4
-
Filename:data_4.txt
-
Content:
< 0,1>
< 1,3>
< 0,2>
< 2,4>
< 4,5>
< 3,5> -
Result:
sort ruselt_1:0 1 3 2 4 5
---------------------
sort ruselt_2:0 1 2 3 4 5
---------------------
sort ruselt_3:0 1 2 4 3 5
---------------------
sort ruselt_4:0 2 1 3 4 5
---------------------
sort ruselt_5:0 2 1 4 3 5
---------------------
sort ruselt_6:0 2 4 1 3 5 -
Photo:
5.5 测试样例5
- Filename:data_5.txt
- Content:
<0,1>
<1,2>
<2,0> - Result:
存在循环依赖关系 - Photo:
6. 系统特色以及可扩展点
6.1系统特色:
TopologicalSort_app 是一个Python项目,它基于PySide2库实现的图形用户界面(GUI)应用程序,用于创建、导入、导出拓扑排序图。下面将介绍它的一些系统特色:
- 图形用户界面(GUI):
使用PySide2的QtDesigner创建了一个图形用户界面名为main.ui存放在statics文件夹下面,提供了文件导入、图形绘制、导出等功能接口。 - 拓扑排序图绘制:
通过调用Draw_diagram.py里面的draw_directed_graph函数返回给主界面一个图片的临时地址,生成后若不保存则自动释放资源,不占用计算机内部关键资源。在python文件调用了networkx和matplotlib库实现了拓扑排序图的绘制功能。使用有向图表示节点和边的关系,并在GUI中名为photoLable的QLabel显示该图。 - 文件导入和导出:
允许用户导入文本文件(.txt)以填充输入框并绘制拓扑排序图,同时也支持将绘制好的图像导出为图片(.png)或拓扑排序结果导出为文本文件。 - C++程序调用:
由于C++运行速度比python快,故复杂的计算任务交给C++来完成,项目能够调用外部的C++程序(通过使用g++来编译.cpp文件,从而生成.exe文件)python运行该.exe并传入相关参数给.exe文件,最后将输出c++文件返回的结果显示在GUI中。此外,C++程序的输出可以导出为文本文件。 - 动态UI加载:
使用PySide2的QUiLoader动态加载UI文件,使得UI可以通过简单的编辑UI文件而不需要修改代码。以此来到模块化编程,更加灵活多变。 - 用户操作反馈:
使用QMessageBox提供信息、警告和错误提示,以向用户提供反馈。 - 临时文件处理:
使用tempfile库创建临时文件以保存绘制的图像,以便导出功能可以使用这些临时文件。 - 异常处理:
项目中实现了异常处理机制,能够捕获并显示错误信息,提高应用程序的健壮性。
6.2可扩展点:
- 将鼠标放置在按钮上会显示出相应信息
- 结果图的大小可以自行调节
4. 源代码部分
4.1 项目层级
TopologicalSort_app├─.idea├─build├─core (核心算法)├─data (读入的文件信息)├─dist (发布软件的各个版本)├─docs (所有文档信息)├─lib├─log├─statics (静态资源)├─test (测试)├─venv└─__pycache__
4.2 运行环境
-
IDE:pycharm community v2023+
-
解释器:python v3.6.8
-
外部库: PySide2 v5.15.2.1、networkx v2.5.1、matplotlib v3.3.4
-
GUI工具:QtDesigner
4.3 项目核心代码
4.3.1 拓扑排序算法TopologicalSort.cpp
写在前面:这个c++程序在整个项目中是比较核心的一个部分,它利用c++运行速度更快来作为核心程序,让整个python项目调用,以达到核心业务和整个项目解耦的目的。这个.cpp文件不会直接调用,项目只会调用编译过后的TopologicalSort.exe,而这个文件会存放在statics文件夹下面。
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <algorithm>using namespace std;struct Course {string name; // 课程名vector<Course*> prerequisites; // 对应的先修课程的指针向量int indegree; // 入度(有多少个先修条件)bool visited; // 判断课程是否被访问过Course(const string& n) : name(n), indegree(0), visited(false) {} // 构造函数
};vector<vector<string>> allTopologicalSorts;void dfs(vector<Course*>& courses, vector<string>& result) {if (result.size() == courses.size()) {allTopologicalSorts.push_back(result);return; // 递归终止条件:完成了一次拓扑排序}for (size_t i = 0; i < courses.size(); ++i) { // 遍历每个课程Course* course = courses[i]; // 当前课程if (course->indegree == 0 && !course->visited) { // 如果当前课程的入度为0且未被访问过course->visited = true; // 标记当前课程已访问result.push_back(course->name); // 将当前课程添加到当前排列中for (size_t j = 0; j < course->prerequisites.size(); ++j) { // 减少当前课程的邻接课程的入度Course* prerequisite = course->prerequisites[j];prerequisite->indegree--;}dfs(courses, result); //递归course->visited = false; // 标记当前课程为未访问状态for (size_t j = 0; j < course->prerequisites.size(); ++j) { // 回溯:撤销之前的修改Course* prerequisite = course->prerequisites[j];prerequisite->indegree++; // 恢复后续邻接课程的入度}result.pop_back(); // 移除当前排列中的最后一门课程}}
}bool printAllTopologicalSorts(vector<Course*>& courses) {vector<string> result;dfs(courses, result);return !allTopologicalSorts.empty();
}void shuchu(vector<Course*>& courses) {int count = 0;for (size_t i = 0; i < allTopologicalSorts.size(); ++i) {cout << "sort ruselt_" << ++count << ':';for (size_t j = 0; j < allTopologicalSorts[i].size(); ++j) {cout << allTopologicalSorts[i][j] << " ";}cout << endl;if (i != allTopologicalSorts.size() - 1) {cout << "---------------------" << endl;}}
}int main(int argc, char* argv[]) {if (argc < 2) {cout << "Usage: " << argv[0] << " <filename>" << endl;return 0;}string filename = argv[1]; vector<Course*> courses;unordered_map<string, Course*> courseMap;ifstream file(filename);if (file.is_open()) {string line;while (getline(file, line)) {if (line.empty()) {continue;}line = line.substr(1, line.size() - 2);stringstream ss(line);string courseName, prereqName;getline(ss, courseName, ',');getline(ss, prereqName);Course* course = courseMap[courseName];if (!course) {course = new Course(courseName);courses.push_back(course);courseMap[courseName] = course;}Course* prereq = courseMap[prereqName];if (!prereq) {prereq = new Course(prereqName);courses.push_back(prereq);courseMap[prereqName] = prereq;}course->prerequisites.push_back(prereq);prereq->indegree++;}file.close();} else {cout << "无法打开文件" << endl;return 0;}if (printAllTopologicalSorts(courses)) {shuchu(courses);} else {cout << "存在循环依赖关系" << endl;}for (size_t i = 0; i < courses.size(); i++) {delete courses[i];}courses.clear();return 0;
}
4.3.2 draw_diagram.py
import networkx as nx
import matplotlib.pyplot as plt
import tempfiledef draw_directed_graph(edges, figsize=(3, 3)):try:# 创建一个有向图对象G = nx.DiGraph()# 添加有向边for data in edges:data = data.strip('<>')source, target = data.split(',')G.add_edge(source, target)# 设置图片的大小plt.figure(figsize=figsize)# 绘制有向图pos = nx.spring_layout(G)# Adjust node positions for labels to be around the nodespos_labels = {node: (x, y + 0.01) for node, (x, y) in pos.items()}nx.draw(G, pos, with_labels=False, node_color='g', node_size=200, arrows=True)# Draw labels separately with adjusted positionsnx.draw_networkx_labels(G, pos_labels, font_size=10)# 保存图形到临时文件with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:plt.savefig(tmpfile, format="png", bbox_inches="tight")# 返回临时文件的路径return tmpfile.nameexcept Exception as e:print(f"生成图时出现错误:{str(e)}")
4.3.3 file_manager.py
from PySide2.QtWidgets import QFileDialog, QMessageBox
class FileManager:def __init__(self, main_window, input_manager):self.main_window = main_windowself.input_manager = input_managerdef import_file(self):options = QFileDialog.Options()file_path, _ = QFileDialog.getOpenFileName(self.main_window.ui, "选择要导入的文件", "", "文本文件 (*.txt);;所有文件 (*)", options=options)if file_path:try:self.main_window.clear_topology_graph() # 清空拓扑排序图self.main_window.file_path = file_pathwith open(file_path, 'r', encoding='utf-8') as file:file_content = file.read()# 调用C++文件并获取结果cpp_output = self.main_window.run_cpp(file_path)# 将结果显示在plainTextEdit上self.main_window.display_cpp_output(cpp_output)self.input_manager.fill_input_boxes(file_content)except Exception as e:QMessageBox.critical(self.main_window.ui, "错误", f"导入文件时出现错误:{str(e)}")else:QMessageBox.warning(self.main_window.ui, "警告", "未选择任何文件")def export(self):options = QFileDialog.Options()export_option, _ = QFileDialog.getSaveFileName(self.main_window.ui, "选择导出路径", "","Images (*.png);;Text Files (*.txt)", options=options)if export_option:if export_option.endswith(".png"):# 导出图片self.main_window.export_image(export_option)elif export_option.endswith(".txt"):# 导出拓扑排序结果self.main_window.export_topology(export_option)else:QMessageBox.warning(self.main_window.ui, "警告", "不支持的导出格式")
4.3.4 input_manager.py
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QListWidgetItem, QLineEdit, QAbstractItemViewclass InputManager:def __init__(self, main_window):self.main_window = main_windowdef fill_input_boxes(self, file_content):# 清空现有输入框的内容self.main_window.ui.inputList.clear()data_list = file_content.split('\n') # 按换行符分割数据for data in data_list:data = data.strip()if data:# 提取源节点和目标节点source, target = data.split(',')source = source.strip()target = target.strip()# 创建适当的输入格式input_item = QListWidgetItem()input_line_edit = QLineEdit(f"{source},{target}")input_line_edit.setAlignment(Qt.AlignCenter)self.main_window.ui.inputList.addItem(input_item)self.main_window.ui.inputList.setItemWidget(input_item, input_line_edit)def add_input_field(self):input_item = QListWidgetItem()input_line_edit = QLineEdit('<start,end>')input_line_edit.setAlignment(Qt.AlignCenter) # 设置文本居中对齐self.main_window.ui.inputList.addItem(input_item)self.main_window.ui.inputList.setItemWidget(input_item, input_line_edit)# 将边信息添加到列表中self.main_window.edge_info.append('<start,end>')def del_input_field(self):selected_items = self.main_window.ui.inputList.selectedItems()for item in selected_items:index = self.main_window.ui.inputList.row(item)self.main_window.ui.inputList.takeItem(index)# 从列表中删除对应的边信息if index < len(self.main_window.edge_info):del self.main_window.edge_info[index]
4.3.5 main_window.py
import os
import subprocessfrom PySide2.QtWidgets import (QFileDialog, QMessageBox, QListWidgetItem,QLineEdit, QAbstractItemView, QWidget,QVBoxLayout, QLabel
)
from PySide2.QtUiTools import QUiLoader
from PySide2.QtCore import Qt
from .file_manager import FileManager
from .input_manager import InputManager
from .topology_manager import TopologyManagerclass MainWindow:def __init__(self):# 动态加载.ui文件self.ui = QUiLoader().load('statics/main.ui')# 禁止调整窗口大小self.ui.setFixedSize(self.ui.size())# 设置窗口属性,禁止最大化self.ui.setWindowFlags(self.ui.windowFlags() & ~Qt.WindowMaximizeButtonHint)# 用于存储边信息的列表self.edge_info = []# 存储文件路径self.file_path = None# 初始化控制器self.input_manager = InputManager(self)self.file_manager = FileManager(self, self.input_manager)self.topology_manager = TopologyManager(self)# 连接相关操作的槽函数self.ui.actionImport.triggered.connect(self.file_manager.import_file)self.ui.actionExport.triggered.connect(self.file_manager.export)self.ui.addButton.clicked.connect(self.input_manager.add_input_field)self.ui.delButton.clicked.connect(self.input_manager.del_input_field)self.ui.generateButton.clicked.connect(self.topology_manager.generate_draw)# 设置输入框为单选模式self.ui.inputList.setSelectionMode(QAbstractItemView.SingleSelection)# 设置按钮的提示文本self.ui.addButton.setToolTip("添加输入框 (Alt + A)")self.ui.delButton.setToolTip("删除输入框 (Alt + D)")self.ui.generateButton.setToolTip("生成拓扑排序结果 (Alt + Enter)")# 创建一个 QWidget 作为容器self.photo_container = QWidget()self.ui.photoLabel.layout().addWidget(self.photo_container)self.ui.photoLabel.setStyleSheet("background-color: white;")# 在容器上设置布局container_layout = QVBoxLayout()self.photo_container.setLayout(container_layout)# 将 QLabel 添加到容器中self.photo_label = QLabel()container_layout.addWidget(self.photo_label)def clear_topology_graph(self):self.photo_label.clear() # 清空 QLabel 上的图像self.ui.progressBar.setValue(0) # 重置进度条的值def run_cpp(self, file_path):try:# 获取当前脚本所在目录script_directory = os.path.dirname(os.path.abspath(__file__))# 构建调用命令cpp_executable_path = os.path.join(script_directory, 'TopologicalSort.exe')command = f'"{cpp_executable_path}" "{file_path}"'# 调用外部程序result = subprocess.run(command, shell=True, stdout=subprocess.PIPE)# 返回结果return result.stdoutexcept Exception as e:print("Error during running C++ program:", str(e))return "Error: Unable to run C++ program"# 在plainTextEdit中显示C++程序的输出def display_cpp_output(self, cpp_output):try:# 将字节串解码为字符串cpp_output_str = cpp_output.decode('utf-8')# 在plainTextEdit中显示C++程序的输出self.ui.plainTextEdit.setPlainText(cpp_output_str)except Exception as e:print("Error: Unable to display C++ output:", str(e))self.ui.plainTextEdit.setPlainText("Error: Unable to display C++ output")def export_image(self, export_path):pixmap = self.photo_label.pixmap()if pixmap:pixmap.save(export_path, "PNG")QMessageBox.information(self.ui, "导出成功", f"图像已成功导出到:{export_path}")else:QMessageBox.warning(self.ui, "警告", "没有图像可导出")def export_topology(self, export_path):try:# 获取C++程序的输出cpp_output = self.ui.plainTextEdit.toPlainText().encode('utf-8')# 写入文件with open(export_path, 'wb') as file:file.write(cpp_output)QMessageBox.information(self.ui, "导出成功", f"拓扑排序结果已成功导出到:{export_path}")except Exception as e:QMessageBox.critical(self.ui, "错误", f"导出拓扑排序结果时出现错误:{str(e)}")if __name__ == "__main__":# Create the application instanceimport sysfrom PySide2.QtWidgets import QApplicationapp = QApplication(sys.argv)# Create and show the main windowmainWindow = MainWindow()mainWindow.ui.show()# Start the event loopsys.exit(app.exec_())
4.3.6 topology_manager.py
from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QMessageBoxfrom .draw_diagram import draw_directed_graphclass TopologyManager:def __init__(self, main_window):self.main_window = main_windowdef generate_draw(self):try:if not self.main_window.file_path:QMessageBox.warning(self.main_window.ui, "警告", "未选择任何文件")return# 获取所有输入框的值input_items = [self.main_window.ui.inputList.itemWidget(self.main_window.ui.inputList.item(i)).text()for i in range(self.main_window.ui.inputList.count())]# 将边信息更新为当前输入框中的值self.main_window.edge_info = input_items# 调用绘图函数并获取图形文件路径graph_image_path = draw_directed_graph(input_items)if graph_image_path:# 将图形文件设置为QLabel的图像pixmap = QPixmap(graph_image_path)self.main_window.photo_label.setPixmap(pixmap)self.main_window.ui.progressBar.setValue(100)# 更新文件中的边信息(覆盖原文件)with open(self.main_window.file_path, 'w', encoding='utf-8') as file:file.write('\n'.join(input_items))# 重新调用C++程序并更新输出cpp_output = self.main_window.run_cpp(self.main_window.file_path)self.main_window.display_cpp_output(cpp_output)except Exception as e:print("Error during generate_draw:", str(e))
4.4 GitHub仓库
有这些核心源代码可能远远不够,因为还有些不是代码的核心文件,如:使用QtDesigner设计的页面UI——main.ui文件,因此在下面我会放上这个项目的GitHub仓库地址,如果有需要可以自取哦~
https://github.com/hiddenSharp429/ToplogicalSort_app
5. 用户使用手册
5.1 运行软件APP
①打开TopologicalSort_app文件夹,双击dist文件夹进入所有发布程序选择页面
②选择需要运行的软件版本
各个版本的区别请看\ToplogicalSort_app\docs\ToplogicalSort_app更新日志.md
③进入某一个版本的文件夹后下拉找到main.exe文件
④双击后启动软件
5.2 操作app
①点击左上角菜单
ico
②选择导入选项卡
③选择需要导入的文件
一般测试样例都会放在\ToplogicalSort_app\data\里面
④导入后左边的输入框将会生成,文件中的文本将会被匹配填充,并且在下面的输入框中输出了所有拓扑排序的结果
测试样例的格式如下
⑤随后点击Generate按钮生成拓扑排序图片
⑥至此完成基本的功能,可以点击菜单
选择导出选项卡
⑦可选择导出.txt类型文件还是.png文件,若为.txt文件则导出所有拓扑排序的结果,若为.png文件则导出拓扑图
运行结果:
6. 结束语
如果有疑问欢迎大家留言讨论,你如果觉得这篇文章对你有帮助可以给我一个免费的赞吗?我们之间的交流是我最大的动力!
相关文章:

拓扑排序软件设计——ToplogicalSort_app(含有源码、需求分析、可行性分析、概要设计、用户使用手册)
拓扑排序软件设计 前言1. 需求分析2. 可行性分析2.1 简介2.2 技术可行性分析2.2.1 技术实现方案2.2.2 开发人员技能要求2.2.3 可行性 2.3 操作可行性分析2.4 结论 3. 项目报告3.1 修订历史记录3.2 软硬件环境3.3 需求分析3.4 详细设计3.4.1 类设计3.4.2 核心流程描述3.4.3 核心…...

ESP32网络开发实例-将数据保存到InfluxDB时序数据库
将数据保存到InfluxDB时序数据库 文章目录 将数据保存到InfluxDB时序数据库1、InfluxDB介绍与安装3、软件准备4、硬件准备5、代码实现6、InfluxDB数据可视化在本文中,将介绍 InfluxDB 以及如何将其与 ESP32 开发板一起使用。 我们将向展示如何创建数据库桶并将 ESP32 数据发送…...

NestJS——基于Node.js 服务器端应用程序的开发框架
文章目录 前言什么是 NestJS? 一、NestJS特性?二、使用步骤Typescript 知识后端开发基本知识新建项目目录结构 前言 Nestjs中文文档 什么是 NestJS? Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用…...

EXCEL中将UTC时间戳转为日期格式(精确到秒)
UTC时间戳的格式通常是一个整数,表示从1970年1月1日00:00:00 UTC到当前时间的总秒数。它可以以秒或毫秒为单位表示。例如,如果当前时间是2023年3月17日 12:34:56 UTC,则对应的UTC时间戳为1679839496(以秒为单位)或1679…...

2023年【起重机械指挥】考试试卷及起重机械指挥操作证考试
题库来源:安全生产模拟考试一点通公众号小程序 2023年起重机械指挥考试试卷为正在备考起重机械指挥操作证的学员准备的理论考试专题,每个月更新的起重机械指挥操作证考试祝您顺利通过起重机械指挥考试。 1、【多选题】《中华人民共和国特种设备安全法》…...

组件的设计原则
目录 插槽的基本概念 基础用法 具名插槽 使用场景 布局控制 嵌套组件 组件的灵活性 高级用法 作用域插槽 总结 前言 Vue 的 slot 是一项强大的特性,用于组件化开发中。它允许父组件向子组件传递内容,使得组件更加灵活和可复用。通过 slot&…...

安卓编译命令mm和mmm的区别(mm编译当前工作目录,mmm可编译指定目录)
文章目录 1. mm示例 2. mmm示例 注意 在Android操作系统的源代码编译过程中, mm和 mmm是两个用于构建部分代码的常用命令。它们都属于Android build system提供的命令集合,但用途略有不同: 1. mm mm(make module)命…...

计算机毕业设计 基于Springboot的影院购票管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...

使用.net 构建 Elsa Workflow
对接过蓝凌OA 也基于泛微OA数据库原型重新研发上线过产品,自研的开源的也上线过 每个公司对OA流程引擎介绍 都不一样的, 比如Elsa 这款微软MVP开源组件,基于跨平台开发的技术含量高,专门做OA的同行推过对应文章。 直接看怎么用吧。…...

open clip论文阅读摘要
看下open clip论文 Learning Transferable Visual Models From Natural Language Supervision These results suggest that the aggregate supervision accessible to modern pre-training methods within web-scale collections of text surpasses that of high-quality crowd…...
Vue3像Vue2一样在prototype(原型)上挂载数据
Vue2的写法 import App from ./App import Vue from vue import ./uni.promisify.adaptor Vue.config.productionTip false App.mpType app import config from "./static/js/config/config.js" Vue.prototype.$configconfig; const app new Vue({...App }) app.…...

API接口自动化测试
本节介绍,使用python实现接口自动化实现。 思路:讲接口数据存放在excel文档中,读取excel数据,将每一行数据存放在一个个列表当中。然后获取URL,header,请求体等数据,进行请求发送。 结构如下 excel文档内容如下&#x…...

基于springboot实现驾校管理系统项目【项目源码】
基于springboot实现驾校管理系统演示 JAVA简介 JavaScript是一种网络脚本语言,广泛运用于web应用开发,可以用来添加网页的格式动态效果,该语言不用进行预编译就直接运行,可以直接嵌入HTML语言中,写成js语言࿰…...
稀疏数组的保存优化(java版本)
什么是稀疏矩阵? 矩阵中,若数值为 0 的元素数目远远多于非 0 元素的数目,并且非 0 元素分布没有规律时,则称该矩阵为稀疏矩阵;与之相反,若非 0 元素数目占大多数时,则称该矩阵为稠密矩阵。 …...

Git GUI、SSH协议和IDEA中的Git使用详解
目录 前言 一、Git GUI的使用 1. 什么是Git GUI 2. 常见的Git GUI工具 3.使用 4.使用Git GUI工具的优缺点 优点: 缺点: 二、SSH协议 1.什么是SSH协议 2.SSH的主要特点和作用 3.SSH密钥认证的原理和流程 4. SSH协议的使用 三、IEDA使用git …...

Linux下C++调用python脚本实现LDAP协议通过TNLM认证连接到AD服务器
1.前言 首先要实现这个功能,必须先搞懂如何通过C调用python脚本文件最为关键,因为两者的环境不同。本质上是在 c 中启动了一个 python 解释器,由解释器对 python 相关的代码进行执行,执行完毕后释放资源。 2 模块功能 2.1python…...

计算机毕业设计选题推荐-校园交流平台微信小程序/安卓APP-项目实战
✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…...
FlinK之检查点与保存点机制
检查点与保存点 检查点Checkpoint概述保存时机保存与恢复检查点算法 检查点配置启用检查点指定存储位置其它配置通用增量 保存点Savepoint概述使用保存点切换状态后端 SQL客户端中操作提交作业触发恢复 检查点Checkpoint 概述 在 Flink 中,检查点是用于实现状态一致…...

UE5 新特性 Nanite 开启
啥也不说,只能说,真的牛,在自己的项目上,从10几20的帧数,直接彪到了70 适用场景: 大场景,三角面足够多 在Project Setting里面 将这几个勾未true 勾上这个,放入场景即可...

仿写知乎日报第四周
本周主要修改了以往的一些bug,实现了一些遗漏的新功能。 无限右滑 无限右滑我听了学长的思路,首先在scrollView的画布大小设置多一个宽度的画布,然后每当滑到那个画布的时候,就调用一个通知,该通知会触发在首页的vie…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...