展示一下实际效果:
import sys
from PySide6.QtWidgets import (QApplication, QWidget, QLabel, QLineEdit,
QPushButton, QVBoxLayout, QFormLayout,
QSizePolicy, QMessageBox)
from PySide6.QtGui import QPixmap, Qt,QImage
from PySide6.QtCore import Slot
from graphviz import Digraph,Graph
import tempfile
import os
import uuid
# ------------------- 二叉树核心逻辑 -------------------
class TreeNode:
"""二叉树节点类"""
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def build_tree(preorder: list, inorder: list) -> TreeNode | None:
"""根据先序和中序遍历序列重建二叉树"""
if not preorder or not inorder:
return None
root_val = preorder[0]
root = TreeNode(root_val)
try:
inorder_root_index = inorder.index(root_val)
except ValueError:
raise ValueError("序列不匹配或包含重复值")
inorder_left_subtree = inorder[:inorder_root_index]
inorder_right_subtree = inorder[inorder_root_index + 1:]
preorder_left_subtree = preorder[1 : 1 + len(inorder_left_subtree)]
preorder_right_subtree = preorder[1 + len(inorder_left_subtree):]
root.left = build_tree(preorder_left_subtree, inorder_left_subtree)
root.right = build_tree(preorder_right_subtree, inorder_right_subtree)
return root
def tree_to_graphviz(root: TreeNode) -> Digraph:
# 使用有向图并设置布局属性
dot = Graph('BinaryTree', format='png',
graph_attr={'ordering': 'out', # 强制水平排列
'nodesep': '0.4', # 节点水平间距
'ranksep': '0.6', # 层级垂直间距
'bgcolor': 'transparent',
'dpi':'512'},
node_attr={'shape': 'circle',
'style': 'filled',
'fillcolor': 'lightblue',
'fontname': 'Helvetica',
'fontsize': '12'},
edge_attr={'arrowsize': '0.8',
'color': '#606060'})
# 递归添加节点和边的逻辑优化
def add_nodes_edges(node):
if not node:
return
# 始终创建左右子节点占位符(即使为空)
left_exists = node.left is not None
right_exists = node.right is not None
# 当前节点样式
dot.node(str(node.val), label=str(node.val))
if left_exists or right_exists:
# 处理左子树
if left_exists:
add_nodes_edges(node.left)
dot.edge(str(node.val), str(node.left.val), label='L')
else:
# 创建不可见左节点占位,后续可根据depth调整空结点大小
null_id_1 = f"null_{node.val}_left_1"
dot.node(null_id_1, label="", shape="point", width="0.0", height="0.0",style="invis")
dot.edge(str(node.val), null_id_1, style="invis")
null_id_2 = f"null_{node.val}_left_2"
dot.node(null_id_2, label="", shape="point", width="0.0", height="0.0",style="invis")
dot.edge(str(node.val), null_id_2, style="invis")
# 处理右子树
if right_exists:
add_nodes_edges(node.right)
dot.edge(str(node.val), str(node.right.val), label='R')
else:
# 创建不可见右节点占位
null_id = f"null_{node.val}_right"
dot.node(null_id, label="", shape="point", width="0.0", height="0.0",style="invis")
dot.edge(str(node.val), null_id, style="invis")
add_nodes_edges(root)
return dot
def save_tree_image(root: TreeNode, filepath: str):
dot = tree_to_graphviz(root)
dot.render(filepath, format='png', cleanup=True)
# ------------------- PySide6 界面 -------------------
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("二叉树可视化")
self.setMinimumSize(800, 600)
self._setup_ui()
self._original_image = None
def _setup_ui(self):
"""初始化界面组件"""
main_layout = QVBoxLayout()
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(15)
# 输入区域
form_layout = QFormLayout()
form_layout.setVerticalSpacing(15)
self.pre_edit = QLineEdit()
self.pre_edit.setPlaceholderText("输入先序序列,用空格或逗号分隔")
self.in_edit = QLineEdit()
self.in_edit.setPlaceholderText("输入中序序列,用空格或逗号分隔")
form_layout.addRow("先序遍历序列:", self.pre_edit)
form_layout.addRow("中序遍历序列:", self.in_edit)
# 绘制按钮
self.btn_draw = QPushButton("生成二叉树")
self.btn_draw.clicked.connect(self._on_draw_clicked)
self.btn_draw.setFixedHeight(40)
# 图像显示区域
self.img_label = QLabel()
self.img_label.setAlignment(Qt.AlignCenter)
self.img_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.img_label.setStyleSheet("background-color: #F0F0F0; border-radius: 8px;")
# 状态标签
self.status_label = QLabel()
self.status_label.setAlignment(Qt.AlignCenter)
# 组装布局
main_layout.addLayout(form_layout)
main_layout.addWidget(self.btn_draw)
main_layout.addWidget(self.img_label, 1)
main_layout.addWidget(self.status_label)
self.setLayout(main_layout)
# 样式设置
self.setStyleSheet("""
QWidget {
font-family: Segoe UI;
font-size: 14px;
}
QLineEdit {
padding: 8px;
border: 1px solid #CCCCCC;
border-radius: 4px;
min-width: 300px;
}
QPushButton {
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 14px;
}
QPushButton:hover {
background-color: #45A049;
}
QLabel#status_label {
color: #666666;
font-size: 13px;
}
""")
self.status_label.setObjectName("status_label")
@Slot()
def _on_draw_clicked(self):
"""处理绘制按钮点击事件"""
self.status_label.clear()
self.img_label.clear()
# 获取输入
pre_str = self.pre_edit.text().strip()
in_str = self.in_edit.text().strip()
# 输入验证
if not pre_str or not in_str:
self._show_error("请输入先序和中序遍历序列")
return
# 解析输入
try:
pre_list = [x.strip() for x in pre_str.replace(',', ' ').split()]
in_list = [x.strip() for x in in_str.replace(',', ' ').split()]
except Exception as e:
self._show_error(f"输入格式错误: {str(e)}")
return
# 验证长度
if len(pre_list) != len(in_list):
self._show_error("先序与中序序列长度不一致")
return
if len(set(pre_list)) != len(pre_list) or len(set(in_list))!=len(in_list):
self._show_error("先序或中序序列存在名称一致的结点")
return
# 构建二叉树
try:
root = build_tree(pre_list, in_list)
except Exception as e:
self._show_error(str(e))
return
# 生成临时文件路径
tmp_path = os.path.join(tempfile.gettempdir(), f"tree_{uuid.uuid4().hex}")
try:
save_tree_image(root, tmp_path)
img_path = tmp_path + ".png"
# 加载并显示图片
self._original_image = QPixmap(img_path)
if self._original_image.isNull():
raise ValueError("生成的图像文件无效")
screen_ratio = QApplication.primaryScreen().devicePixelRatio()
self._original_image.setDevicePixelRatio(screen_ratio)
self.img_label.setPixmap(self._original_image)
#self.img_label.setScaledContents(True)
self._resize_image()
self.status_label.setText("二叉树生成成功")
self.status_label.setStyleSheet("color: #4CAF50;")
except Exception as e:
self._show_error(f"图像生成失败: {str(e)}")
finally:
# 清理临时文件
if os.path.exists(img_path):
os.remove(img_path)
def _resize_image(self):
"""调整图片尺寸适应窗口"""
if self._original_image:
scaled_pixmap = self._original_image.scaledToHeight(
self.img_label.height() + 80,
#Qt.IgnoreAspectRatio,
#Qt.KeepAspectRatio,
#Qt.FastTransformation
Qt.SmoothTransformation
)
self.img_label.setPixmap(scaled_pixmap)
#self.img_label.setScaledContents(True)
def resizeEvent(self, event):
"""窗口大小改变事件"""
super().resizeEvent(event)
self._resize_image()
def _show_error(self, message: str):
"""显示错误信息"""
self.status_label.setText(message)
self.status_label.setStyleSheet("color: #FF4444;")
QMessageBox.critical(self, "错误", message)
if __name__ == "__main__":
# 设置高DPI支持
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
评论 (0)