Pythonは二分探索木の削除機能を実装しています

Pythonは二分探索木の削除機能を実装しています

二分探索木(二分探索木)は、ソートされた二分木および順序付けられた二分木とも呼ばれます。

二分探索木の実装は、https//blog.csdn.net/weixin_43790276/article/details/105753543を参照できます。

この記事では、Pythonを使用してバイナリ検索ツリーの削除機能を実装します。その前に、まずバイナリ検索ツリーの特性を知っておく必要があります。

1.バイナリツリーの左側のサブツリーが空でない場合、左側のサブツリーのすべてのノードの値は、そのルートノードの値よりも小さくなります。

2.バイナリツリーの右側のサブツリーが空でない場合、右側のサブツリーのすべてのノードの値は、そのルートノードの値よりも大きくなります。

3.個別に見ると、左右のサブツリーも二分探索木です。

1つは、二分探索木クラスを準備する

二分探索木の削除機能を実装する前に、まず二分探索木のクラスSearchBinaryTreeを実装します。

# coding=utf-8
class Node(object):
    """节点类"""
    def __init__(self, data, left_child=None, right_child=None):
        self.data = data
        self.parent = None
        self.left_child = left_child
        self.right_child = right_child


class SearchBinaryTree(object):
    """二叉树类"""
    def __init__(self):
        self.__root = None
        self.prefix_branch = '├'
        self.prefix_trunk = '|'
        self.prefix_leaf = '└'
        self.prefix_empty = ''
        self.prefix_left = '─L─'
        self.prefix_right = '─R─'

    def is_empty(self):
        return not self.__root

    @property
    def root(self):
        return self.__root

    @root.setter
    def root(self, value):
        self.__root = value if isinstance(value, Node) else Node(value)

    def show_tree(self):
        if self.is_empty():
            print('空二叉树')
            return
        print('-' * 20)
        print(self.__root.data)
        self.__print_tree(self.__root)
        print('-' * 20)

    def insert(self, root, value):
        """二叉搜索树插入节点-递归"""
        node = value if isinstance(value, Node) else Node(value)
        if self.is_empty():
            self.root = node
            return
        if root is None:
            root = node
        elif node.data < root.data:
            root.left_child = self.insert(root.left_child, value)
            root.left_child.parent = root
        elif node.data > root.data:
            root.right_child = self.insert(root.right_child, value)
            root.right_child.parent = root
        return root

    def __print_tree(self, node, prefix=None):
        if prefix is None:
            prefix, prefix_left_child = '', ''
        else:
            prefix = prefix.replace(self.prefix_branch, self.prefix_trunk)
            prefix = prefix.replace(self.prefix_leaf, self.prefix_empty)
            prefix_left_child = prefix.replace(self.prefix_leaf, self.prefix_empty)
        if self.has_child(node):
            if node.right_child is not None:
                print(prefix + self.prefix_branch + self.prefix_right + str(node.right_child.data))
                if self.has_child(node.right_child):
                    self.__print_tree(node.right_child, prefix + self.prefix_branch + ' ')
            else:
                print(prefix + self.prefix_branch + self.prefix_right)
            if node.left_child is not None:
                print(prefix + self.prefix_leaf + self.prefix_left + str(node.left_child.data))
                if self.has_child(node.left_child):
                    prefix_left_child += '  '
                    self.__print_tree(node.left_child, self.prefix_leaf + prefix_left_child)
            else:
                print(prefix + self.prefix_leaf + self.prefix_left)

    def has_child(self, node):
        return node.left_child is not None or node.right_child is not None

    def __str__(self):
        return str(self.__class__)

上記のコードは、ノードクラスNodeとバイナリ検索ツリークラスSearchBinaryTreeを実装しています。SearchBinaryTreeでは、バイナリ検索ツリーが空かどうかを判断するためのis_empty()メソッド、呼び出すインスタンスオブジェクトのroot()メソッドのペア、ツリー構造でバイナリ検索ツリーを出力するためのshow_tree()メソッド、およびデータの追加バイナリ検索ツリーのinsert(root、value)メソッド。 

次に、後で削除する方法のために、最初にいくつかのノードをツリーに追加します。

if __name__ == '__main__':
    tree = SearchBinaryTree()
    data = [50, 77, 55, 29, 10, 30, 66, 80, 51, 18, 90, 78, 79]
    for i in data:
        tree.insert(tree.root, i)
    tree.show_tree()

演算結果:

--------------------
50
├─R─77
| ├─R─80
| | ├─R─90
| | └─L─78
| |   ├─R─79
| |   └─L─
| └─L─55
|   ├─R─66
|   └─L─51
└─L─29
  ├─R─30
  └─L─10
    ├─R─18
    └─L─
--------------------

データ追加後の二分探索木は次のとおりです。

ノードを追加した後、ノードを削除するときに使用するメソッドの準備を続けます。二分探索木の高さを返すメソッドheight(root)は、ツリーのどのレベルが削除されたノードであるかを判別するために使用されます。二分探索木でノードを見つける、二分探索木からノードを削除する、メソッドsearch(root、data)は、最初にノードが二分探索木に属していることを確認します。したがって、最初にノードが二分探索木にあるかどうかを検索します。二分探索木。二分探索木で最大値のノードを返すメソッドget_max(root)と、最小値のノードを返すメソッドget_min(root)を使用して、削除されたノードの先行ノードまたは後続ノードを検索します。

    def height(self, root):
        """二叉树的深度"""
        if root.data is None:
            return 0
        if root.left_child is None and root.right_child is None:
            return 1
        if root.left_child is not None and root.right_child is None:
            return 1 + self.height(root.left_child)
        if root.left_child is None and root.right_child is not None:
            return 1 + self.height(root.right_child)
        if root.left_child is not None and root.right_child is not None:
            return 1 + max(self.height(root.left_child), self.height(root.right_child))

    def search(self, root, data):
        """二叉搜索树的查询操作"""
        if root is None:
            return
        if root.data == data:
            return root
        elif data < root.data:
            return self.search(root.left_child, data)
        elif data > root.data:
            return self.search(root.right_child, data)

    def get_max(self, root):
        """查找二叉搜索树值最大的节点"""
        if self.is_empty():
            return
        return self.get_max(root.right_child) if root.right_child else root

    def get_min(self, root):
        """查找二叉搜索树值最小的节点"""
        if self.is_empty():
            return
        return self.get_min(root.left_child) if root.left_child else root

二分、二分探索木削除ノード

削除するノードがリーフノードの場合は、扱いやすく、このノードを見つけて削除する方が簡単です。削除されたノードがリーフノードでない場合、ノードには左サブツリーまたは右サブツリー、あるいはその両方があります。ノードを削除した後、左サブツリーと右サブツリーはノードとともにツリーから「切り離され」ます。ノードを削除するだけでなく、すべてのサブツリーを削除します。したがって、非リーフノードを削除するときは、サブツリーからノードを選択して、削除されたノードの位置を埋めて、ツリーの破損を回避し、他のノードへの「影響」を回避し、ノードの後のバイナリツリーが削除されたものはまだ1つです。二分探索木は二分探索木の特性を満たします。

単純なものから複雑なものまで、削除されたノードに左右のサブツリーがあるかどうかに応じて、二分探索木の削除は3つの状況に分けることができます。削除されたノードはリーフノードであり、削除されたノードには1つのサブツリーしかなく、削除されたノードには2つのサブツリーがあります。

1.削除されたノードはリーフノードです。つまり、削除されたノードには左側のサブツリーと右側のサブツリーがありません。リーフノードが削除された後、ノードが検出され、ノードがバイナリツリーから「切断」されている(親ノードのポインタが空を指している)限り、ツリー全体の構造は破壊されません。上記のデータを追加した後の二分探索木で、66などのリーフノードをランダムに選択します。

    def _delete_no_child(self, node):
        """删除叶节点"""
        if node == self.root:
            self.root = None
            return
        if node == node.parent.left_child:
            node.parent.left_child = None
        else:
            node.parent.right_child = None
        node.parent = None

_delete_no_child(node):リーフノードを削除するメソッド。このメソッドは削除機能の一部にすぎないため、削除機能が完全に実装された直後に使用されないことを示すために、前にアンダースコアを追加します。

    node = tree.search(tree.root, 66)
    tree._delete_no_child(node)
    tree.show_tree()

演算結果:

--------------------
50
├─R─77
| ├─R─80
| | ├─R─90
| | └─L─78
| |   ├─R─79
| |   └─L─
| └─L─55
|   ├─R─
|   └─L─51
└─L─29
  ├─R─30
  └─L─10
    ├─R─18
    └─L─
--------------------

ノード66を削除した後の二分探索木の構造は次の図のようになります。

2.削除されたノードにはサブツリーが1つだけあります。つまり、削除されたノードには左側のサブツリーのみ、または右側のサブツリーのみがあります。ノードを削除した後、ノードのサブツリーが「暗示」されていないことを確認するために、ノードのサブツリーを「埋める」必要があります。下図のノード10を削除すると、削除されたノードはその親ノードの左側の子ノードになり、削除されたノードには右側の子ツリーがあります。ノードを削除すると、削除されたノードの右側の子ツリーが直接埋められます。親ノードのLeftサブツリーになります。

    def _delete_one_child(self, node):
        """删除有一个子节点的节点"""
        if node.left_child:
            if node.parent and node.parent.left_child == node:
                node.left_child.parent = node.parent
                node.parent.left_child = node.left_child
            elif node.parent and node.parent.right_child == node:
                node.left_child.parent = node.parent
                node.parent.right_child = node.left_child
            else:
                self.root = node.left_child
                node.left_child.parent = None
                node.left_child = None
        else:
            if node.parent and node.parent.left_child == node:
                node.right_child.parent = node.parent
                node.parent.left_child = node.right_child
            elif node.parent and node.parent.right_child == node:
                node.right_child.parent = node.parent
                node.parent.right_child = node.right_child
            else:
                self.root = node.right_child
                node.right_child.parent = None
                node.right_child = None
        node.left_child = None
        node.parent = None

_delete_one_child(node):子ツリーが1つしかないノードを削除します。同様に、このメソッドは削除機能の一部にすぎないため、削除機能が完全に実現された直後に削除機能が使用されないことを示すために、前にアンダースコアを追加します。

    node = tree.search(tree.root, 10)
    tree._delete_one_child(node)
    tree.show_tree()

演算結果:

--------------------
50
├─R─77
| ├─R─80
| | ├─R─90
| | └─L─78
| |   ├─R─79
| |   └─L─
| └─L─55
|   ├─R─66
|   └─L─51
└─L─29
  ├─R─30
  └─L─18
--------------------

ノード10を削除した後の二分探索木の構造は次の図のようになります。

3.削除されたノードには2つのサブツリーがあります。つまり、削除されたノードには左側のサブツリーと右側のサブツリーの両方があります。ノードを削除した後、2つのサブツリーがツリーから「壊れる」のを防ぐために、削除されたノードの位置を満たすノードを見つける必要があります。次の図のノード77を削除する場合は、入力するノードを見つける必要があります。そうしないと、2つのサブツリーも削除されます。

ノードが削除された後もバイナリツリーがバイナリ検索ツリーであることを確認するには、補完ノードを選択する2つの方法があります。削除されたノードの後続ノードまたは後続ノードを選択します。後続ノードは左側のサブツリーに存在する必要があります。後継ノードが存在する必要があります。右側のサブツリーでは、どちらの方法も選択できます。この記事では、後継ノードを選択します。

では、後続ノードと先行ノードとは何ですか?二分探索木のノードが格納された値の昇順で配置されている場合、現在のノードの後のノードが後続ノードであり、現在のノードの前のノードが先行ノードです。ノード77の後続ノードは78です。

ノードを削除した後、後続ノードを見つけて、削除されたノードの位置まで後続ノードを埋めます。これにより、削除されたノードの2つのサブツリーが壊れないようにします。ただし、まだ終わっていません。サクセサノードがいっぱいになると、サクセサノードを元の位置から削除するのと同じです。ノード78が77の位置に追加された後、それはノード78を元の位置から削除することと同等である。

したがって、サクセサノードを処理する必要があります。サクセサノードはリーフノードの場合があります。ケース1を使用できます。サクセサノードには右サブツリーがあります。ノード78などのケース2を使用できます。サクセサノードに左のサブツリーなので、考慮しないでください。

後続ノードに左サブツリーがないのはなぜですか?後続ノードに左側のサブツリーがあると仮定すると、バイナリ検索ツリーの特性に従って、後続ノードの値は現在のノードよりも大きく、後続ノードは現在のノードの右側のサブツリーにある必要があり、値は後継ノードの左側のサブツリーにあるノードの値が後継ノードの値よりも小さいこのように、現在のノードよりも大きく後継ノードよりも小さいノードがあり、後継ノードの定義と矛盾します。仮説は成り立たない。

もちろん、これは、二分探索木のすべてのノードの後続ノードに左サブツリーがないということではありませんが、ノードに2つのサブツリーがある場合、その後続ノードに左サブツリーを含めることはできません。

これまで、ノード77の削除は2つのステップに分かれています。最初のステップは後続ノード78を見つけて、ノード78の位置を77の位置まで埋めることであり、2番目のステップはノード78を削除することです。

    def delete(self, value):
        """二叉搜索树删除节点"""
        if self.height(self.root) >= 1:
            node_delete = self.search(self.root, value)
            if node_delete:
                self._delete(node_delete)
            else:
                raise KeyError("Error, value not in this tree")
        else:
            raise KeyError("Error, value not in this tree")

    def _delete(self, node):
        """删除节点"""
        if not node.left_child and not node.right_child:
            self._delete_no_child(node)
        elif node.left_child and not node.right_child or not node.left_child and node.right_child:
            self._delete_one_child(node)
        else:
            rear_node = self.get_min(node.right_child)
            node.data = rear_node.data
            self._delete(rear_node)

_delete(node):ノードを削除するメソッド。このメソッドでは、現在のノードの後続ノードであるrear_nodeがget_min()メソッドを介して検出され、ビット充填操作が実行されます。後続ノードを削除する方法は前の2つのケースのいずれかに属している必要があるため、後続ノードを削除する操作は1回だけ再帰的に行う必要があります。

delete(node):ノードを削除する一般的な方法。前述のように、ノードを削除するには、最初にノードが現在の二分探索木に属している必要があります。したがって、削除するノードを指定することはできません。ノードに格納されている値に基づいてツリーを検索し、見つかったときに削除操作を実行することしかできません。

    tree.delete(77)
    tree.show_tree()

演算結果:

--------------------
50
├─R─78
| ├─R─80
| | ├─R─90
| | └─L─79
| └─L─55
|   ├─R─66
|   └─L─51
└─L─29
  ├─R─30
  └─L─10
    ├─R─18
    └─L─
--------------------

ノード77を削除した後の二分探索木の構造を次の図に示します。

3、完全なコード

# coding=utf-8
class Node(object):
    """节点类"""
    def __init__(self, data, left_child=None, right_child=None):
        self.data = data
        self.parent = None
        self.left_child = left_child
        self.right_child = right_child


class SearchBinaryTree(object):
    """二叉树类"""
    def __init__(self):
        self.__root = None
        self.prefix_branch = '├'
        self.prefix_trunk = '|'
        self.prefix_leaf = '└'
        self.prefix_empty = ''
        self.prefix_left = '─L─'
        self.prefix_right = '─R─'

    def is_empty(self):
        return not self.__root

    @property
    def root(self):
        return self.__root

    @root.setter
    def root(self, value):
        self.__root = value if isinstance(value, Node) else Node(value)

    def show_tree(self):
        if self.is_empty():
            print('空二叉树')
            return
        print('-' * 20)
        print(self.__root.data)
        self.__print_tree(self.__root)
        print('-' * 20)

    def insert(self, root, value):
        """二叉搜索树插入节点-递归"""
        node = value if isinstance(value, Node) else Node(value)
        if self.is_empty():
            self.root = node
            return
        if root is None:
            root = node
        elif node.data < root.data:
            root.left_child = self.insert(root.left_child, value)
            root.left_child.parent = root
        elif node.data > root.data:
            root.right_child = self.insert(root.right_child, value)
            root.right_child.parent = root
        return root

    def delete(self, value):
        """二叉搜索树删除节点"""
        if self.height(self.root) >= 1:
            node_delete = self.search(self.root, value)
            if node_delete:
                self._delete(node_delete)
            else:
                raise KeyError("Error, value not in this tree")
        else:
            raise KeyError("Error, value not in this tree")

    def _delete(self, node):
        """删除节点"""
        if not node.left_child and not node.right_child:
            self._delete_no_child(node)
        elif node.left_child and not node.right_child or not node.left_child and node.right_child:
            self._delete_one_child(node)
        else:
            rear_node = self.get_min(node.right_child)
            node.data = rear_node.data
            self._delete(rear_node)

    def _delete_no_child(self, node):
        """删除叶节点"""
        if node == self.root:
            self.root = None
            return
        if node == node.parent.left_child:
            node.parent.left_child = None
        else:
            node.parent.right_child = None
        node.parent = None

    def _delete_one_child(self, node):
        """删除有一个子节点的节点"""
        if node.left_child:
            if node.parent and node.parent.left_child == node:
                node.left_child.parent = node.parent
                node.parent.left_child = node.left_child
            elif node.parent and node.parent.right_child == node:
                node.left_child.parent = node.parent
                node.parent.right_child = node.left_child
            else:
                self.root = node.left_child
                node.left_child.parent = None
                node.left_child = None
        else:
            if node.parent and node.parent.left_child == node:
                node.right_child.parent = node.parent
                node.parent.left_child = node.right_child
            elif node.parent and node.parent.right_child == node:
                node.right_child.parent = node.parent
                node.parent.right_child = node.right_child
            else:
                self.root = node.right_child
                node.right_child.parent = None
                node.right_child = None
        node.left_child = None
        node.parent = None

    def height(self, root):
        """二叉树的深度"""
        if root.data is None:
            return 0
        if root.left_child is None and root.right_child is None:
            return 1
        if root.left_child is not None and root.right_child is None:
            return 1 + self.height(root.left_child)
        if root.left_child is None and root.right_child is not None:
            return 1 + self.height(root.right_child)
        if root.left_child is not None and root.right_child is not None:
            return 1 + max(self.height(root.left_child), self.height(root.right_child))

    def search(self, root, data):
        """二叉搜索树的查询操作"""
        if root is None:
            return
        if root.data == data:
            return root
        elif data < root.data:
            return self.search(root.left_child, data)
        elif data > root.data:
            return self.search(root.right_child, data)

    def get_max(self, root):
        """查找二叉搜索树值最大的节点"""
        if self.is_empty():
            return
        return self.get_max(root.right_child) if root.right_child else root

    def get_min(self, root):
        """查找二叉搜索树值最小的节点"""
        if self.is_empty():
            return
        return self.get_min(root.left_child) if root.left_child else root

    def __print_tree(self, node, prefix=None):
        if prefix is None:
            prefix, prefix_left_child = '', ''
        else:
            prefix = prefix.replace(self.prefix_branch, self.prefix_trunk)
            prefix = prefix.replace(self.prefix_leaf, self.prefix_empty)
            prefix_left_child = prefix.replace(self.prefix_leaf, self.prefix_empty)
        if self.has_child(node):
            if node.right_child is not None:
                print(prefix + self.prefix_branch + self.prefix_right + str(node.right_child.data))
                if self.has_child(node.right_child):
                    self.__print_tree(node.right_child, prefix + self.prefix_branch + ' ')
            else:
                print(prefix + self.prefix_branch + self.prefix_right)
            if node.left_child is not None:
                print(prefix + self.prefix_leaf + self.prefix_left + str(node.left_child.data))
                if self.has_child(node.left_child):
                    prefix_left_child += '  '
                    self.__print_tree(node.left_child, self.prefix_leaf + prefix_left_child)
            else:
                print(prefix + self.prefix_leaf + self.prefix_left)

    def has_child(self, node):
        return node.left_child is not None or node.right_child is not None

    def __str__(self):
        return str(self.__class__)


if __name__ == '__main__':
    tree = SearchBinaryTree()
    data = [50, 77, 55, 29, 10, 30, 66, 80, 51, 18, 90, 78, 79]
    for i in data:
        tree.insert(tree.root, i)
    # tree.show_tree()

    # node = tree.search(tree.root, 66)
    # tree._delete_no_child(node)
    # tree.show_tree()

    # node = tree.search(tree.root, 10)
    # tree._delete_one_child(node)
    # tree.show_tree()

    tree.delete(80)
    tree.show_tree()

 

 

おすすめ

転載: blog.csdn.net/weixin_43790276/article/details/105777348