[Python] 2分木/binary tree

Pythonで2分木を実装します。

2分木


二分木binary tree二進木、バイナリツリー)は、データ構造の1つである。付き木構造の中で、あるノード(節点 node)が持つ子の数が高々2であるものをいう。典型的には2つの子はそれぞれ「左」「右」と呼ばれる。
たとえば、二分探索二分ヒープを実装するために使われる。


出典: フリー百科事典『ウィキペディア(Wikipedia)』

大きさ9、深さ3、根は値2を持つ 2分木。Wikipediaより。

ノードクラスの定義

木構造の中のノードを定義します。ノードは、値と左の子、右の子を持ちます。

class Node(object):
    def __init__(self, value):
        self.value = value 
        self.left = None 
        self.right = None 

2分木クラスの定義

class BinaryTree(object):
    def __init__(self, root):
        self.root = Node(root)

これにより、例えば以下のような形で、2分木を定義できます。

tree = BinaryTree(1)
tree.root.left = Node(2)
tree.root.right = Node(3)
tree.root.left.left = Node(4)
tree.root.left.right = Node(5)
tree.root.right.left = Node(6)
tree.root.right.right = Node(7)
tree.root.right.right.right = Node(8)

二分木を巡回する方法

行きがけ順、通りがけ順、帰りがけ順探索


二分木においてはあるノードとその子孫もまた二分木を構成する。これを部分木と呼ぶ。 従って二分木を部分木に分け、再帰を用いて探索する方法は自然である。


出典: フリー百科事典『ウィキペディア(Wikipedia)』

行きがけ順 (preorder)

行きがけ順 (preorder) は、根を調べてからそれにぶらさがる部分木を調べます。

2分木クラスのメソッドとして定義します。

 def preorder_print(self, start, traversal):
        """Root -> Left -> Right"""
        if start:
            traversal += (str(start.value) + "-")
            traversal = self.preorder_print(start.left, traversal)
            traversal = self.preorder_print(start.right, traversal)
        return traversal

上で定義した2分木で出力します。

print(tree.preorder_print(tree.root, ''))
# 出力 1-2-4-5-3-6-7-8-

通りがけ順 (in-order) 

通りがけ順 (in-order) では、片方の部分木を調べ、根を調べ、次いで反対の部分木を調べます。

2分木クラスのメソッドとして定義します。

def inorder_print(self, start, traversal):
        """Left -> Root -> Right """
        if start:
            traversal = self.inorder_print(start.left, traversal)
            traversal += (str(start.value) + "-")
            traversal = self.inorder_print(start.right, traversal)
        return traversal

上で定義した2分木で出力します。

print(tree.inorder_print(tree.root, ''))
# 出力 4-2-5-1-6-3-7-8-

帰りがけ順 (postorder)

帰りがけ順 (postorder) では、部分木を調べてからその根を調べます。

2分木クラスのメソッドとして定義します。

def postorder_print(self, start, traversal):
        """Left -> Right -> Root"""
        if start:
            traversal = self.postorder_print(start.left, traversal)
            traversal = self.postorder_print(start.right, traversal)
            traversal += (str(start.value) + '-')
        return traversal 

上で定義した2分木で出力します。

  print(tree.postorder_print(tree.root, ''))
# 出力 4-5-2-6-8-7-3-1-

幅優先探索


深さ優先探索と対照的に、未だに訪れていないノードを、根に近い方から探索する。


出典: フリー百科事典『ウィキペディア(Wikipedia)』

キューを用いることで幅優先探索を実装します。

まずはキューを定義します。

class Queue(object):
    def __init__(self):
        self.items = []

    def enqueue(self, item):
        self.items.insert(0, item)

    def dequeue(self):
        if not self.is_empty():
            return self.items.pop()

    def is_empty(self):
        return len(self.items) == 0

    def peek(self):
        if not self.is_empty():
            return self.items[-1].value 

    def __len__(self):
        return self.size()
    
    def size(self):
        return len(self.items)

幅優先探索を 2分木クラスのメソッドとして実装します。

def levelorder_print(self, start):
        if start is None:
            return 

        queue = Queue()
        queue.enqueue(start)

        traversal = ''
        while len(queue) > 0:
            traversal += str(queue.peek()) + "-"
            node = queue.dequeue()

            if node.left:
                queue.enqueue(node.left)
            if node.right:
                queue.enqueue(node.right)

        return traversal 

上で定義した2分木で出力します。

print(tree.levelorder_print(tree.root))
# 出力 1-2-3-4-5-6-7-8-

葉に近いほうからの幅優先探索

逆に、葉に近いほうからの幅優先探索を定義します。

スタックとキューを用いて実装します。

スタックを定義します。

class Stack(object):
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if not self.is_empty():
            return self.items.pop()

    def is_empty(self):
        return len(self.items) == 0

    def peek(self):
        if not self.is_empty():
            return self.items[-1]

    def size(self):
        return len(self.items)

    def __len__(self):
        return self.size()

葉に近いほうからの幅優先探索を 2分木クラスのメソッドとして実装します。

def reverse_levelorder_print(self, start):
        if start is None:
            return 

        queue = Queue()
        stack = Stack()
        queue.enqueue(start)

        traversal = ''
        while len(queue) > 0:
            node = queue.dequeue()
            stack.push(node)

            if node.right:
                queue.enqueue(node.right)
            if node.left:
                queue.enqueue(node.left)

        while len(stack) > 0:
            node = stack.pop()
            traversal += str(node.value) + '-'
        return traversal 

上で定義した2分木で出力します。

print(tree.reverse_levelorder_print(tree.root))
# 出力 8-4-5-6-7-2-3-1-

2分木の高さ

2分木の高さを再帰的に巡回することで求めます。

2分木クラスのメソッドとして実装します。

def height(self, node):
        if node is None:
            return -1
        left_height = self.height(node.left)
        right_height = self.height(node.right)
        
        return 1 + max(left_height, right_height)

上で定義した2分木で出力します。

print(tree.height(tree.root))
# 出力 3

ノードの数

2分木のノードの数を求めます。

スタックを用いてノードの数を求める

2分木クラスのメソッドとして実装します。

def size(self):
        if self.root is None:
            return 0 
        
        stack = Stack()
        stack.push(self.root)
        
        size = 1
        while stack:
            node = stack.pop()
            if node.left:
                size += 1
                stack.push(node.left)
            if node.right:
                size += 1
                stack.push(node.right)

        return size

上で定義した2分木で出力します。

print(tree.size())
# 出力 8

再帰でノードの数を求める

2分木クラスのメソッドとして実装します。

def size_recursive(self, node):
        if node is None:
            return 0 
        return 1 + self.size_recursive(node.left) + self.size_recursive(node.right)

上で定義した2分木で出力します。

print(tree.size_recursive(tree.root))
# 出力 8