def CalculateBinarySearchTreeHeight(dataCount): """指定されたノード数の相異なる2分探索木の高さの平均と分散の計算""" # 2分探索木の高さの平均と分散を計算するためのプログラム # ノード数がnの相異なる2分木の個数はカタラン数Cnに一致するので, # 生成する2分探索木の形状のパターン数もカタラン数Cnに一致する # 同じ形状(高さ)の2分探索木を複数回処理しないために, 個々の形状が1回ずつ現れるような入力データ列を生成する # 入力データ列の生成 dataSequences = GenerateDataSequences(dataCount) # 2分探索木 binarySearchTree = BinarySearchTree() # 高さと平均のデータ numOfBinarySearchTree = 0 numOfAVLTree = 0 # 個々の形状の2分探索木とAVL木の高さ heightOfBinarySearchTree = [] heightOfAVLTree = [] # 個々の形状の2分探索木を1つずつ調べる for dataSequence in dataSequences: # データを2分探索木に挿入 binarySearchTree.Insert(dataSequence) treeHeight = binarySearchTree.Height() heightOfBinarySearchTree.append(treeHeight) numOfBinarySearchTree += 1 if binarySearchTree.IsAVLTree(): heightOfAVLTree.append(treeHeight) numOfAVLTree += 1 # 2分探索木の持つ全てのノードを破棄 binarySearchTree.Destroy() # 2分探索木の高さの平均と分散 averageHeightOfBinarySearchTree = np.average(heightOfBinarySearchTree) varianceOfHeightOfBinarySearchTree = np.var(heightOfBinarySearchTree) # AVL木の高さの平均と分散 averageHeightOfAVLTree = np.average(heightOfAVLTree) varianceOfHeightOfAVLTree = np.var(heightOfAVLTree) return (numOfBinarySearchTree, averageHeightOfBinarySearchTree, varianceOfHeightOfBinarySearchTree, numOfAVLTree, averageHeightOfAVLTree, varianceOfHeightOfAVLTree)
class MainWindow(QMainWindow): """メインウィンドウ""" def __init__(self): super(MainWindow, self).__init__() self.mCentralWidget = QWidget() self.setCentralWidget(self.mCentralWidget) self.mVerticalBoxLayout = QVBoxLayout() self.mCentralWidget.setLayout(self.mVerticalBoxLayout) self.mFont = QFont() self.mFont.setFamily("Meiryo UI") self.mFont.setPointSize(9) self.mFont.setStyle(QFont.StyleNormal) self.setFont(self.mFont) self.setWindowTitle( "Binary Search Tree Visualization using PyQt5, Matplotlib, NetworkX" ) self.resize(QSize(960, 640)) # 2分探索木の描画領域 self.mFigure = plt.figure() self.mFigureCanvas = FigureCanvas(self.mFigure) self.mVerticalBoxLayout.addWidget(self.mFigureCanvas) self.mHorizontalLayout = QHBoxLayout() self.mVerticalBoxLayout.addLayout(self.mHorizontalLayout) # キーの入力用テキストボックス self.mInputBoxFont = QFont() self.mInputBoxFont.setFamily("Consolas") self.mInputBoxFont.setPointSize(9) self.mInputBoxFont.setStyle(QFont.StyleNormal) self.mInputBox = QLineEdit() self.mInputBox.setAlignment(Qt.AlignLeft) self.mInputBox.setFont(self.mInputBoxFont) self.mInputBox.returnPressed.connect(self.OnInputBoxReturnPressed) self.mHorizontalLayout.addWidget(self.mInputBox) # 2分探索木の操作用ボタン self.mInsertButton = QPushButton("Insert") self.mInsertButton.clicked.connect(self.OnInsertButtonClicked) self.mHorizontalLayout.addWidget(self.mInsertButton) self.mRemoveButton = QPushButton("Remove") self.mRemoveButton.clicked.connect(self.OnRemoveButtonClicked) self.mHorizontalLayout.addWidget(self.mRemoveButton) self.mDestroyButton = QPushButton("Destroy") self.mDestroyButton.clicked.connect(self.OnDestroyButtonClicked) self.mHorizontalLayout.addWidget(self.mDestroyButton) self.mInfoButton = QPushButton("Info") self.mInfoButton.clicked.connect(self.OnInfoButtonClicked) self.mHorizontalLayout.addWidget(self.mInfoButton) # メニューバー self.mMenuBar = self.menuBar() self.mOperationMenu = self.mMenuBar.addMenu("操作(&O)") # メニュー項目 self.mInsertAction = QAction("ノードの挿入(&I)", self) self.mInsertAction.setStatusTip("指定されたキーを持つノードを作成して2分探索木に追加します.") self.mInsertAction.triggered.connect(self.OnInsertButtonClicked) self.mRemoveAction = QAction("ノードの削除(&R)", self) self.mRemoveAction.setStatusTip("指定されたキーを持つノードを2分探索木から削除します.") self.mRemoveAction.triggered.connect(self.OnRemoveButtonClicked) self.mDestroyAction = QAction("2分探索木の破棄(&D)", self) self.mDestroyAction.setStatusTip("2分探索木のノードを全て削除します.") self.mDestroyAction.triggered.connect(self.OnDestroyButtonClicked) self.mInfoAction = QAction("2分探索木の情報...(&I)", self) self.mInfoAction.setStatusTip("2分探索木に関する情報を表示します.") self.mInfoAction.triggered.connect(self.OnInfoButtonClicked) self.mOperationMenu.addAction(self.mInsertAction) self.mOperationMenu.addAction(self.mRemoveAction) self.mOperationMenu.addAction(self.mDestroyAction) self.mOperationMenu.addAction(self.mInfoAction) # ツールバー self.addToolBar(NavigationToolbar(self.mFigureCanvas, self)) # ステータスバー self.statusBar() # 2分探索木 self.mBinarySearchTree = BinarySearchTree() def OnInputBoxReturnPressed(self): self.OnInsertButtonClicked() self.mInputBox.clear() def OnInsertButtonClicked(self): """指定されたキーを持つノードを作成して2分探索木に追加""" if not self.mInputBox.text().isdigit(): QMessageBox.warning(self, "Binary Search Tree Visualization", "キーの形式が不適切です.", QMessageBox.Ok) return else: try: self.mBinarySearchTree.Insert(int(self.mInputBox.text())) except ValueError as e: QMessageBox.warning(self, "Binary Search Tree Visualization", "既にキーが登録されています.", QMessageBox.Ok) return # グラフの描画 self.DrawBinarySearchTree() def OnRemoveButtonClicked(self): """指定されたキーを持つノードを2分探索木から削除""" if not self.mInputBox.text().isdigit(): QMessageBox.warning(self, "Binary Search Tree Visualization", "キー値の形式が不適切です.", QMessageBox.Ok) return else: try: self.mBinarySearchTree.Remove(int(self.mInputBox.text())) except ValueError as e: QMessageBox.warning(self, "Binary Search Tree Visualization", "指定されたキーを持つノードが見つかりません.", QMessageBox.Ok) return # グラフの描画 self.DrawBinarySearchTree() def OnDestroyButtonClicked(self): """2分探索木のノードを全て削除""" self.mBinarySearchTree.Destroy() self.DrawBinarySearchTree() def OnInfoButtonClicked(self): """2分探索木に関する情報の表示""" if self.mBinarySearchTree.mRoot is None: QMessageBox.warning(self, "Binary Search Tree Visualization", "2分探索木が空です.", QMessageBox.Ok) return else: # メッセージ文字列の構築 infoMsg = "Information about Current Binary Search Tree:\n" infoMsg += "String Representation: {0}\n".format( self.mBinarySearchTree.ToString()) infoMsg += "Node Count: {0}\n".format( self.mBinarySearchTree.NodeCount()) infoMsg += "Max Node Value: {0}\n".format( self.mBinarySearchTree.MaxNodeValue()) infoMsg += "Min Node Value: {0}\n".format( self.mBinarySearchTree.MinNodeValue()) infoMsg += "Height: {0}\n".format(self.mBinarySearchTree.Height()) infoMsg += "IsAVLTree: {0}".format( self.mBinarySearchTree.IsAVLTree()) # メッセージの表示 QMessageBox.information(self, "Binary Search Tree Visualization", infoMsg, QMessageBox.Ok) def DrawBinarySearchTree(self): """グラフの描画""" # 描画領域のクリア self.mFigure.clf() # グラフの作成 graph = self.CreateNetworkXGraph() # グラフのレイアウトの調整 pos = self.CalculateGraphLayout() # グラフの描画 nx.draw(graph, pos, with_labels=True, arrows=False, node_color="w", node_shape="s", edge_color="k", width=1, font_color="b", font_size=16, font_weight="normal", font_family="sans-serif") self.mFigureCanvas.draw() def CreateNetworkXGraph(self): """グラフの作成""" graph = nx.Graph() def CreateNetworkXGraphHelper(graph, parentNode, currentNode): if currentNode is None: return if parentNode is None: graph.add_node(currentNode.mKey) if currentNode.mLeft is not None: graph.add_node(currentNode.mLeft.mKey) graph.add_edge(currentNode.mKey, currentNode.mLeft.mKey) CreateNetworkXGraphHelper(graph, currentNode, currentNode.mLeft) if currentNode.mRight is not None: graph.add_node(currentNode.mRight.mKey) graph.add_edge(currentNode.mKey, currentNode.mRight.mKey) CreateNetworkXGraphHelper(graph, currentNode, currentNode.mRight) return CreateNetworkXGraphHelper(graph, None, self.mBinarySearchTree.mRoot) return graph def CalculateGraphLayout(self): """グラフのレイアウトの調整""" def CalculateGraphLayoutHelper(parentNode, currentNode, left, right, depth, height, pos): if currentNode is None: return if parentNode is None: pos[currentNode.mKey] = (0.5, 1.0) else: pos[currentNode.mKey] = ((left + right) / 2.0, 1.0 - 1.0 / (height + 1) * depth) if currentNode.mLeft is not None: CalculateGraphLayoutHelper(currentNode, currentNode.mLeft, left, (left + right) / 2.0, depth + 1, height, pos) if currentNode.mRight is not None: CalculateGraphLayoutHelper(currentNode, currentNode.mRight, (left + right) / 2.0, right, depth + 1, height, pos) return graphLayout = {} CalculateGraphLayoutHelper(None, self.mBinarySearchTree.mRoot, 0.0, 1.0, 0, self.mBinarySearchTree.Height(), graphLayout) return graphLayout