python-PyQt项目实战案例:制作一个视频播放器
文章目录
- 1. 关键问题描述
- 2. 通过OpenCV读取视频/打开摄像头抓取视频
- 3. 通过PyQt 中的 QTimer定时器实现视频播放
- 4. PyQt 视频播放器实现代码
- 参考文献
1. 关键问题描述
在前面的文章中已经分享了pyqt制作图像处理工具的文章,也知道pyqt通过使用label控件显示图像的方式。在此,对于视频的显示,其本质上一帧一帧的图像,因此也可以使用同样的方式对其显示。但是,有两个关键的问题需要解决,也即:
a. 如何读取视频/或通过摄像头抓取视频
b. 如果连续显示视频图像且不会造成界面假死
2. 通过OpenCV读取视频/打开摄像头抓取视频
主要函数:
cv.VideoCapture.isOpened(),检查视频捕获是否初始化成功
cv.VideoCapture.read(),捕获视频文件、视频流或捕获的视频设备
cv.VideoCapture.release(),关闭视频文件或设备,释放对象
cv.VideoWriter.release(),关闭视频写入,释放对象
抓取视频
import cv2
import timecap = cv2.VideoCapture(0, cv2.CAP_DSHOW) # 使用cv2.CAP_DSHOW后启动快,但帧率慢了
cap.set(6, cv2.VideoWriter.fourcc('M', 'J', 'P', 'G'))
# cap.set(6, cv2.VideoWriter.fourcc('Y', 'U', 'V', '2'))# 使用cv2.CAP_DSHOW后大小为640
cap.set(3, 2560) # 640 1280 1920 2560# 宽
# cap.set(4, 720) # 720 1080 # 高
# cap.set(5, 60) # 帧数
# cap.set(10, 150) # 亮度
# cap.set(14, 150) # 增益
# cap.set(11, 90) # 对比度
# cap.set(15, 150) # 曝光print(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
print(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(cap.get(cv2.CAP_PROP_FPS))num = 0
start = time.time()
fps = '0'
while(True):ret, frame = cap.read()num = num + 1frame = cv2.flip(frame, 1)# frame = beauty_face(frame)if (time.time() - start) > 1:fps = num / (time.time() - start)fps = str(round(fps, 2))num = 0start = time.time()cv2.putText(frame, "FPS:" + fps, (20, 20), 1, 1.5, (255, 255, 255), 2)cv2.imshow("DST", frame)stop = time.time()elapsed = stop - start# print('time=', elapsed)if 1 < elapsed < 1.5:print('time=', elapsed)print('frame num:', num)if cv2.waitKey(1) & 0xFF == ord('q'):break
cap.release()
cv2.destroyAllWindows()
读取视频
import cv2 as cvif __name__ == '__main__':# 创建视频读取/捕获对象vedioRead = "../images/test.mp4" # 读取视频文件的路径capRead = cv.VideoCapture(vedioRead) # 实例化 VideoCapture 类# 读取视频文件frameNum = 0 # 视频帧数初值while capRead.isOpened(): # 检查视频捕获是否成功ret, frame = capRead.read() # 读取下一帧视频图像if ret is True:cv.imshow(vedioRead, frame) # 播放视频图像if cv.waitKey(1) & 0xFF == ord('q'): # 按 'q' 退出breakelse:print("Can't receive frame at frameNum {}".format(frameNum))breakcapRead.release() # 关闭读取视频文件capWrite.release() # 关闭视频写入对象cv.destroyAllWindows() # 关闭显示窗口
3. 通过PyQt 中的 QTimer定时器实现视频播放
使用 OpenCV 对视频文件进行解码获得图像帧以后,可以使用 QTime 定时器来控制 QLabel 控件中的图像更新,实现视频播放。
PyQt5 中的 QTimer类提供了重复的和单次的定时器,为计时器提供了高级编程接口。
要使用定时器,需要先创建一个QTimer实例,将定时器的timeout信号连接到相应的槽函数,并调用start(),定时器就会以设定的间隔发出timeout信号。
QTimer类中的常用方法:
start(milliseconds):启动或重新启动定时器,时间间隔为毫秒。如果定时器已经运行,它将被停止并重新启动。如果singleShot信号为真,定时器将仅被激活一次。
stop():停止定时器
QTimer类中的常用信号:
singleShot:在给定的时间间隔后调用一个槽函数时发射此信号。
timeout:当定时器超时时发射此信号。
注意:可以设置槽函数的执行次数,默认为定时器开启后周期性调用槽函数。如果设置了setSingleShot(True),则槽函数仅执行一次。
关键代码:
# 初始化定时器,并绑定触发信号
self.timerCam = QtCore.QTimer() # 定时器,毫秒
self.timerCam.timeout.connect(self.refreshFrame) # 计时器结束时调用槽函数刷新当前帧# 开始定时
if self.cap.isOpened(): # 检查视频捕获是否成功self.timerCam.start(20) # 设置计时间隔并启动,定时结束将触发刷新当前帧
# 暂停播放
self.timerCam.blockSignals(True) # 信号阻塞,暂停定时器
# 继续播放
self.timerCam.blockSignals(False) # 取消信号阻塞,恢复定时器
4. PyQt 视频播放器实现代码
import os
import sys
import cv2 as cv
import numpy as np
from PyQt5 import QtCore
from PyQt5.QtCore import QObject, pyqtSignal, QPoint, QRect, qDebug, Qt
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from ui_VideoProV1 import Ui_MainWindow # 导入 uiDemo8.py 中的 Ui_MainWindow 界面类class MyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow 类和 Ui_MainWindow 界面类def __init__(self, parent=None):super(MyMainWindow, self).__init__(parent) # 初始化父类self.setupUi(self) # 继承 Ui_MainWindow 界面类self.timerCam = QtCore.QTimer() # 定时器,毫秒self.cap = Noneself.frameNum = 1 # 视频帧数初值self.edge_flag = Falseself.smooth_flag = False# 添加可点击执行的菜单self.menu_file = QMenu("文件", self)temp = self.menuBar()temp.addMenu(self.menu_file)t = self.menu_fileself.action_open = QAction("打开", self)self.action_open.triggered.connect(self.openVideo)t.addAction(self.action_open)self.action_save = QAction("保存", self)t.addAction(self.action_save)t.addAction('其他')self.mTest = QAction("帮助", self)self.mTest.triggered.connect(self.trigger_actHelp)temp = self.menuBar()temp.addAction(self.mTest)self.action_close = QAction("退出", self)self.action_close.triggered.connect(self.close)temp = self.menuBar()temp.addAction(self.action_close)## # 菜单栏self.action_save.triggered.connect(self.saveSlot) # 连接并执行 openSlot 子程序# 通过 connect 建立信号/槽连接,点击按钮事件发射 triggered 信号,执行相应的子程序 click_pushButtonself.pushButton.clicked.connect(self.openVideo)self.pushButton_2.clicked.connect(self.playVideo)self.pushButton_3.clicked.connect(self.pauseVideo)self.pushButton_4.clicked.connect(self.click_pushButton_5) # # 按钮触发:边缘检测self.pushButton_5.clicked.connect(self.click_pushButton_6) # 点击 # 按钮触发:双边self.timerCam.timeout.connect(self.refreshFrame) # 计时器结束时调用槽函数刷新当前帧# 初始化self.frame = np.ndarray(())self.videoPath = 'test'self.textEdit_log.append("欢迎回来!")returndef openVideo(self): # 读取视频文件,点击 pushButton_1 触发try:self.videoPath, _ = QFileDialog.getOpenFileName(self, "Open Video", "../images/", "*.mp4 *.avi *.flv")print("Open Video: ", self.videoPath)except:print("Open video failed.")returndef playVideo(self): # 播放视频文件,点击 pushButton_2 触发if self.timerCam.isActive() == False:if self.videoPath.endswith(('avi', 'mp4')):self.cap = cv.VideoCapture(self.videoPath)self.textEdit_log.append('open video successfully')else:self.cap = cv.VideoCapture(0)self.textEdit_log.append('open camera successfully')if self.cap.isOpened(): # 检查视频捕获是否成功self.timerCam.start(20) # 设置计时间隔并启动,定时结束将触发刷新当前帧else: #self.timerCam.stop() # 停止定时器self.cap.release() # 关闭读取视频文件self.label_1.clear() # 清除显示内容returndef pauseVideo(self):self.timerCam.blockSignals(False) # 取消信号阻塞,恢复定时器if self.timerCam.isActive() and self.frameNum % 2 == 1:self.timerCam.blockSignals(True) # 信号阻塞,暂停定时器self.pushButton_3.setText("继续") # 点击"继续",恢复播放print("信号阻塞,暂停播放。", self.frameNum)self.textEdit_log.append("信号阻塞,暂停播放。" + str(self.frameNum))else:self.pushButton_3.setText("暂停") # 点击"暂停",暂停播放print("取消阻塞,恢复播放。", self.frameNum)self.textEdit_log.append("取消阻塞,恢复播放。" + str(self.frameNum))self.frameNum = self.frameNum + 1def refreshFrame(self): # 刷新视频图像ret, self.frame = self.cap.read() # 读取下一帧视频图像self.frame = cv.flip(self.frame, 1)if self.smooth_flag:self.frame = cv.bilateralFilter(self.frame, 15, 30, 40, None)if self.edge_flag:# self.frame = cv.Canny(self.frame, 10, 60)self.frame = self.apply_heat_effect(self.frame)qImg = self.cvToQImage(self.frame) # OpenCV 转为 PyQt 图像格式self.label_1.setScaledContents(True)self.label_1.setPixmap((QPixmap.fromImage(qImg))) # 加载 PyQt 图像# self.frameNum = self.frameNum + 1# print("视频帧数:", self.frameNum)returndef cvToQImage(self, image):if image.dtype == np.uint8:channels = 1 if len(image.shape) == 2 else image.shape[2]if channels == 3:qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_RGB888)return qImg.rgbSwapped()elif channels == 1:qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_Indexed8)return qImgelse:QtCore.qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])return QImage()def click_pushButton_5(self):if self.edge_flag:self.edge_flag = Falseself.textEdit_log.append('close edge mode successfully')else:self.edge_flag = Trueself.textEdit_log.append('open edge mode successfully')returndef click_pushButton_6(self):if self.smooth_flag:self.smooth_flag = Falseself.textEdit_log.append('close smooth mode successfully')else:self.smooth_flag = Trueself.textEdit_log.append('open smooth mode successfully')returndef saveSlot(self): # 保存图像文件# 选择存储文件 dialogtry:fileName, tmp = QFileDialog.getSaveFileName(self, "Save Image", "../images/", '*.png; *.jpg; *.tif')if self.frame.size == 1:return# OpenCV 写入图像文件ret = cv.imwrite(fileName, self.frame)if ret:print(fileName, self.frame.shape)except:print('save failed.')returndef cvToQImage(self, image):# 8-bits unsigned, NO. OF CHANNELS=1if image.dtype == np.uint8:channels = 1 if len(image.shape) == 2 else image.shape[2]if channels == 3: # CV_8UC3# Create QImage with same dimensions as input MatqImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_RGB888)return qImg.rgbSwapped()elif channels == 1:# Create QImage with same dimensions as input MatqImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_Indexed8)return qImgelse:qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])return QImage()def qPixmapToCV(self, qPixmap): # PyQt图像 转换为 OpenCV图像qImg = qPixmap.toImage() # QPixmap 转换为 QImageshape = (qImg.height(), qImg.bytesPerLine() * 8 // qImg.depth())shape += (4,)ptr = qImg.bits()ptr.setsize(qImg.byteCount())image = np.array(ptr, dtype=np.uint8).reshape(shape) # 定义 OpenCV 图像image = image[..., :3]return imagedef trigger_actHelp(self): # 动作 actHelp 触发QMessageBox.about(self, "About", """打开视频或摄像头 v1.0""")returndef closeEvent(self):self.timerCam.stop() # 停止定时器self.cap.release() # 关闭读取视频文件self.label_1.clear() # 清除显示内容if __name__ == '__main__':app = QApplication(sys.argv)myWin = MyMainWindow()myWin.show()sys.exit(app.exec_())
参考文献
[1] OpenCV-PyQT项目实战(9)项目案例04:视频播放
相关文章:
python-PyQt项目实战案例:制作一个视频播放器
文章目录 1. 关键问题描述2. 通过OpenCV读取视频/打开摄像头抓取视频3. 通过PyQt 中的 QTimer定时器实现视频播放4. PyQt 视频播放器实现代码参考文献 1. 关键问题描述 在前面的文章中已经分享了pyqt制作图像处理工具的文章,也知道pyqt通过使用label控件显示图像的…...
反向传播的微积分原理 | Chapter 4 | Deep Learning | 3Blue1Brown
目录 前言1. 简介2. 神经网络中的链式法则3. 微积分的计算4. 公式含义5. 代价函数对权重偏置的敏感度6. 多个神经元的情形7. 回顾相关资料结语 前言 3Blue1Brown 视频笔记,仅供自己参考 这个章节主要来深度讲解反向传播中的一些微积分理论 官网:https://…...
matlab读取excel表格
使用matlab读取excel表格中的数据 使用推荐代码读取excel表格中的数据 path "C:\Users\24975\Desktop\503\GUI展示案例\Tx_20_0_Rx_40_90_0.1_95_L.xlsx";%文件路径 data readtable(path,Sheet,Sheet1,ReadRowNames,false,ReadVariableNames,false,Ra…...
基于springboot+vue实现的助学兼职系统(源码+L文+ppt)4-092
基于springbootvue实现的助学兼职系统(源码L文ppt)4-092 第4章 系统设计 4.1 总体功能设计 一般学生、招聘公司和管理者都需要登录才能进入助学兼职系统,使用者登录时会在后台判断使用的权限类型,包括一般使用者和管理者,一般使…...
⌈ 传知代码 ⌋ 农作物病害分类(Web端实现)
💛前情提要💛 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间,对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…...
CMU生成式人工智能大模型:从入门到放弃(九)
引言 在前面的系列博客中,我们深入探讨了生成式对抗网络(GANs)和变分自编码器(VAEs)等生成式模型。今天,我们将探索扩散模型(Diffusion Models)的进一步应用,并讨论在上…...
HTML基础总结
一、简介 HTML(HyperText Markup Language)即超文本标记语言,是用于创建网页的标准标记语言。它通过使用各种标签来定义网页的结构和内容,告诉浏览器如何显示网页。HTML 文档由标签和文本组成,标签用于描述文本的性质…...
EXCELL中如何两条线画入一张图中,标记坐标轴标题?
1,打开excel,左击选中两列, 2,菜单栏>“插入”>”二维折线图”选中一个 3,选中出现的两条线中的一条右击>最下一行,“设置数据系列格式” 4,右测“系列选项中”>点击“次坐标轴” 5…...
Zabbix企业级分布式监控环境部署
“运筹帷幄之中,决胜千里之外”。在IT运维中,监控占据着重要的地位,按比例来算,说占30%一点也不为过。对IT运维工程师来说,构建一个真正可用的监控告警系统是一项艰巨的任务。在监控系统的开源软件中,可供选…...
水轮发电机油压自动化控制系统解决方案介绍
在现代水电工程中,水轮机组油压自动化控制系统,不仅直接关系到水轮发电机组的安全稳定运行,还影响着整个水电站的生产效率和经济效益。 一、系统概述 国科JSF油压自动控制系统,适用于水轮发电机组调速器油压及主阀(蝶…...
今天不分享技术,分享秋天的故事
引言 这个爱情故事好像是个悲剧,你说的是婚姻。爱情没有悲剧,对爱者而言,爱情怎么会是悲剧呢。对春天而言,秋天是它的悲剧吗。结尾是什么,等待,之后呢,没有之后。或者说,等待的结果…...
转录组上游分析流程(三)
环境部署——数据下载——查看数据(非质控)——数据质控——数据过滤(过滤低质量数据) 测序得到的原始序列含有接头序列和低质量序列,为了保证信息分析的准确性,需要对原始数据进行质量控制,得到高质量序列(Clean Reads),原始序列…...
excel判断某一列(A列)中的数据是否在另一列(B列)中
如B列如果有7个元素,在A列右边的空白列中,输入如下公式: COUNTIF($B$1:$B$7,A1), 其中,$B$1:$B$7代表A列中的所有数据即绝对范围,A1代表B列中的一个单元格....
[环境配置]macOS上怎么查看vscode的commit id
macOS的commit id和windows上有点不一样,windows可以在帮助-关于查看 macOS则需要再左边第一个查看...
.net framework 3.5sp1组件安装进度条不动启动错误怎么解决
安装.NET Framework 3.5 SP1通常需要管理员权限。这是因为安装过程可能需要修改系统文件和注册表项,这些操作通常需要管理员权限才能执行。在Windows系统上,安装.NET Framework 3.5 SP1通常通过控制面板中的“启用或关闭Windows功能”选项进行࿰…...
学习threejs,利用THREE.ExtrudeGeometry拉伸几何体实现svg的拉伸
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.ExtrudeGeometry拉伸…...
大模型之三十二-语音合成TTS(coqui) 之二 fine-tune
在 大模型之三十-语音合成TTS(coqui)[shichaog CSDN]中提到了xttsv2的fine-tune。 数据情况: 我是从bilibili up主小Lin说提取了一些视频,然后进行了重新的fine-tune。 训练结果 如下图所示,上面波形幅度较大的是xttsv2原始模型的结果&am…...
JVM的内存模型是什么,每个区域的作用是什么,以及面试题(含答案)
JVM(Java 虚拟机)内存模型定义了 Java 程序在运行时如何分配、管理和优化内存。JVM 内存模型主要分为几个关键区域,每个区域有特定的作用: JVM 内存模型 堆内存(Heap): 作用:用于存…...
《设计模式三》Java代理模式实现
Java代理模式实现 静态代理实现 // Subject.java // 主题接口,定义了请求方法 public interface Subject {void request(); }// RealSubject.java // 真实主题实现类,实现了Subject接口 public class RealSubject implements Subject {Overridepublic …...
vue3中计算属性的用法以及使用场景
在 Vue 3 中,计算属性(computed properties)是一种基于依赖项动态计算并缓存的响应式数据。它与 Vue 2 中的计算属性类似,但在组合式 API 中使用 computed 函数来定义。计算属性的核心优势在于能够自动缓存计算结果,仅…...
pytorh学习笔记——cifar10(六)MobileNet V1网络结构
基础知识储备: 一、深度可分离卷积(Depthwise Separable Convolution) MobileNet的核心是深度可分离卷积(Depthwise Separable Convolution),深度可分离卷积是卷积神经网络(CNN…...
报表系统-连接数据库操作
本专栏用于解析自己开源的项目代码,作为复盘和学习使用。欢迎大家一起交流 本样例说明源码开源在: ruoyi-reoprt gitee仓库 ruoyi-report github仓库 欢迎大家到到项目中多给点star支持,对项目有建议或者有想要了解的欢迎一起讨论 连接数据库…...
[计算机网络] 常见端口号
前言 常见的端口号是指互联网协议(如TCP/IP)中预留给特定服务使用的数字范围。它们主要用于标识网络应用程序和服务,并帮助数据包在网络中找到正确的接收方。 按协议类型划分 TCP协议端口: 21:FTP文件传输协议2…...
Linux系统块存储子系统分析记录
1 Linux存储栈 通过网址Linux Storage Stack Diagram - Thomas-Krenn-Wiki-en,可以获取多个linux内核版本下的存储栈概略图,下面是kernel-4.0的存储栈概略图: 2 存储接口、传输速度 和 协议 2.1 硬盘 《深入浅出SSD:固态存储核心…...
大数据——本地威胁检测的全球方法
大数据似乎是众多专业人士关注的话题,从在自然灾害发生时帮助挽救生命,到帮助营销团队设计更有针对性的策略以接触新客户。 对于安全工程师来说,大数据分析被证明是抵御不断演变的网络入侵的有效防御手段,这得益于基于大量不同网…...
使用postman接口测试
一 、postman断言 1、什么是断言 postman 断言借助JavaScript -js 语言编写代码,自动判断预期结果与实际结果是否一致。 断言代码写在 Tests 的标签中。(新版本在Scripts标签中) 2、断言工作原理 3、常用断言 断言响应状态码 // 断言响应状态码 是否为 200 pm.…...
Ubuntu24.04双系统安装(Linux/windows共存一文打通)
他向远方望去,无法看到高山背后的矮山,只能看到一座座更高的山峰。 目录 编辑 一.前言 二.虚拟机和双系统比较 三.Windows/Linux双系统安装 1.Rufus-制作U盘启动盘系统工具安装 2.Ubuntu24.04下载 3.Ubuntu-u盘启动盘制作 4.压缩磁盘留足安装空…...
C++ - deque
博客主页:【夜泉_ly】 本文专栏:【C】 欢迎点赞👍收藏⭐关注❤️ 文章目录 💡双端队列简介1. 基本特性2. 与其他容器的比较与 vector与 list 3. 中控数组的设计4. 优缺点优点缺点 5. 应用场景6. 结论 💡双端队列简…...
国产!瑞芯微米尔RK357核心板革新AIoT设备,8核6T高算力
随着科技的快速发展,AIoT智能终端对嵌入式模块的末端计算能力、数据处理能力等要求日益提高。近日,米尔电子发布了一款基于瑞芯微RK3576核心板和开发板。核心板提供4GB/8GB LPDDR4X、32GB/64GB eMMC等多个型号供选择。瑞芯微RK3576核心优势主要包括高性能…...
中国人寿财险青岛市分公司践行绿色金融,助力可持续发展
中国人寿财险青岛市分公司积极响应国家绿色发展战略,大力推进绿色金融实践。在保险产品创新方面,推出一系列绿色保险产品。如新能源汽车保险,为新能源汽车产业发展提供风险保障,促进交通领域的节能减排。环境污染责任保险则助力企…...
做emu对网站有什么要求/网站搜索引擎拓客
点击蓝字关注我们哦~公众号文章的图片点击后分块消失隐藏,而且可以图片跳动哦,这个特效可以用来做连连看效果、节日收礼物效果,非常棒,文章会详细介绍,而且制作很简单哦。看过本喵以前的教程知道怎么点击出…...
郑州网站推广技术/学历提升
导读:AIoT产业是多种技术融合,赋能各行业的产业,整体市场潜在空间超十万亿元。根据IDC的数据与预测,2019年全球AIoT市场规模达到2264亿美元,预计到2022年达到4820亿美元,2019-2022年复合增长率为28.65%。AI…...
哈尔滨自主建站模板/怎么在百度发布自己的文章
20190411 从路径名中分离文件名 https://zhidao.baidu.com/question/1430003887373444979.html给字符数组赋值的方法 https://www.cnblogs.com/mfrbuaa/p/3767189.html 数组初始化以后,不能直接对数组赋值,可以通过函数strcpy()和memcpy()拷贝赋值。str…...
网站的数据库选择/优秀网站
在上个星期面试一家公司的笔试题上面的最后一道题就是写程序查找一个字符串的最长重复子串。当时想了很长时间没想出什么好方法,就把一个算法复杂度比较高的算法写上去了。回来上机把那个算法写了一遍测试没问题,然后自己又到网上面查查还有什么方法&…...
如何查企业做网站是否备案过/五种网络营销推广方法
高额收息、暴力催收……即便被央视“315晚会”曝光,监管部门多方围剿,“714高炮”依然在夹缝中顽强求生。 与监管趋严前高举高打,毫无顾忌相比,如今的“714高炮”开始转入“地下”,并且形成了一条包括引流、支付、催收…...
网站设计公司如何做好网站建设/怎么注册网站平台
一个数据结构程序用于求解一个数据结构问题,其设计的一般步骤如下。 第一步:分析求解问题的数据和求解功能,采用抽象数据类型来描述求解问题,主要包括数据逻辑结构和运算定义。 第二步:设计逻辑结构对应的存储结构。 第…...