Pythonで二分木がヒープ(最大ヒープ)かどうかを判定する方法
この記事では、与えられた二分木がヒープ(最大ヒープ)であるかどうかをPythonで判定するアルゴリズムを解説します。再帰処理を使って「完全二分木であること」と「親子間の大小関係」を効率的にチェックする方法を、実装例とともに見ていきましょう。
ヒープの条件とは?
ある二分木がヒープとみなされるためには、次の2つの性質を満たしている必要があります。
- 完全二分木であること:最後のレベルを除くすべての階層がノードで埋まっている状態になっている
- 最大ヒープの性質を持つこと:すべての親ノードの値が、その子ノードの値以上である
たとえば、次のような木構造が入力として与えられた場合、これらの条件をすべて満たしているため、出力は True になります。
判定アルゴリズムの手順
判定は、次の3つの補助関数を組み合わせて行います。
1. ノード総数を数える(number_of_nodes)
- 引数
rootを受け取ります。 rootがnullの場合は 0 を返します。- それ以外の場合は、
1 + number_of_nodes(root.left) + number_of_nodes(root.right)を返します(自分自身+左部分木+右部分木)。
2. ヒープの性質を確認する(has_heap_property)
- 左右の子がどちらも存在しない(葉ノード)場合は
Trueを返します。 - 右の子だけが存在しない場合は、
root.val >= root.left.valが成り立てばTrueを返します。 - 両方の子が存在する場合は、
root.val >= root.left.valかつroot.val >= root.right.valが成り立つときに限り、左右の部分木に対して再帰的にhas_heap_propertyを呼び出した結果を返します。条件を満たさない場合はFalseを返します。
3. 完全二分木かどうかを確認する(is_complete_tree)
- 引数として
root、index、node_countを受け取ります。 rootがnullの場合はTrueを返します。index >= node_countの場合はFalseを返します(ノードが詰め切れていない=完全二分木ではないことを意味します)。- それ以外の場合は、左の子をインデックス
2 * index + 1、右の子をインデックス2 * index + 2として再帰的に判定した結果の AND を返します。
メイン処理の流れ
node_count := number_of_nodes()でノード総数を取得します。is_complete_tree(root, 0, node_count)とhas_heap_property(root)がどちらも真であればTrueを返します。- それ以外の場合は
Falseを返します。
Pythonでの実装例
理解を深めるために、実際のコードを見てみましょう。
class TreeNode:
def __init__(self, value):
self.val = value
self.left = None
self.right = None
def number_of_nodes(self, root):
if root is None:
return 0
else:
return (1 + self.number_of_nodes(root.left) + self.number_of_nodes(root.right))
def has_heap_property(self, root):
if (root.left is None and root.right is None):
return True
if root.right is None:
return root.val >= root.left.val
else:
if (root.val >= root.left.val and
root.val >= root.right.val):
return (self.has_heap_property(root.left) and self.has_heap_property(root.right))
else:
return False
def is_complete_tree(self, root,index, node_count):
if root is None:
return True
if index >= node_count:
return False
return (self.is_complete_tree(root.left, 2 * index + 1, node_count) and self.is_complete_tree(root.right, 2 * index + 2, node_count))
def is_heap(self):
node_count = self.number_of_nodes(self)
if (self.is_complete_tree(self, 0, node_count) and self.has_heap_property(self)):
return True
else:
return False
root = TreeNode(99)
root.left = TreeNode(46)
root.right = TreeNode(39)
root.left.left = TreeNode(14)
root.left.right = TreeNode(5)
root.right.left = TreeNode(9)
root.right.right = TreeNode(33)
root.left.left.left = TreeNode(7)
root.left.left.right = TreeNode(12)
print(root.is_heap())
入力
root = TreeNode(99) root.left = TreeNode(46) root.right = TreeNode(39) root.left.left = TreeNode(14) root.left.right = TreeNode(5) root.right.left = TreeNode(9) root.right.right = TreeNode(33) root.left.left.left = TreeNode(7) root.left.left.right = TreeNode(12)
出力
True
まとめ
このように、ノード数のカウント・完全二分木の判定・ヒープ性質の再帰チェックという3つのステップに分解することで、二分木が最大ヒープかどうかを簡潔に判定できます。計算量はいずれも木のノード数 n に対して O(n) となり、実用上も十分な効率です。優先度キューなどのデータ構造を実装する際の基礎知識として、ぜひ押さえておきましょう。
-
Pythonで二分木の最小共通祖先(LCA)を求める方法
二分木が与えられたとき、指定した2つのノードの最小共通祖先(Lowest Common Ancestor:LCA)を求める問題を考えてみましょう。ノード p と q の LCA とは、p と q の両方を子孫として持つノードの中で、最も深い位置にあるノードのことです。 例えば、二分木が [3,5,1,6,2,0,8,null,null,7,4] という形式で表されている場合、木の構造は次のようになります。 この場合、ノード 5 と ノード 1 の LCA は 3 となります。 解法のアプローチ この問題は、再帰を使って次の手順で解くことができます。 木が空(None)の場合は、None
-
Pythonで二分木を反転する方法:再帰を使った実装を解説
二分木の反転とは二分木が与えられたとき、その左右の子ノードを入れ替えて「鏡像」のような木を作ることを二分木の反転(Invert Binary Tree)と呼びます。これはアルゴリズムの学習やコーディング面接でも頻出のトピックです。例えば、次のような二分木があったとします。これを反転すると、すべてのノードの左部分木と右部分木が入れ替わり、以下のような木になります。解き方:再帰的アプローチこの問題は再帰を使うと非常にシンプルに解けます。考え方は以下の3ステップです。ルートが None(null)であれば、そのまま返す(ベースケース)現在のノードの左ポインタと右ポインタを入れ替える左部分木と右部分木