Pythonで二分探索木を使った探索アルゴリズムをつくった！

# 使ったもの
Python 3.6.0

# 二分探索木とは？
動的集合を取り扱うための多くの操作が利用できるデータ構造

今回扱う二分探索木
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/bb0b9b86-12d0-1a0e-1238-acd9e56484ad.png)

# 実装
```python:search.py
# 節点(key,parent,left,rightをメンバ変数に持つ)
class Node:
    def __init__(self, key, parent=None, left=None, right=None):
        self.key = key
        self.p = parent
        self.left = left
        self.right = right

    def __repr__(self):
        return 'Node(%s)' % repr(self.key)

    # 探索
    def search(self, k):
        if self is None or k == self.key:
            return self
        if k < self.key:
            return self.left.search(k)
        else:
            return self.right.search(k)

    # 最小値
    def minimum(self):
        while self.left:
            self = self.left
        return self

    # 最大値
    def maximum(self):
        while self.right:
            self = self.right
        return self

    # 次節点
    def successor(self):
        if self.right:
            return self.right.minimum()
        y = self.p
        while y and self == y.right:
            self = y
            y = y.p
        return y

    # 前節点
    def predecessor(self):
        if self.left:
            return self.left.maximum()
        y = self.p
        while y and self == y.left:
            self = y
            y = y.p
        return y

    # 二分探索木のルートを返すメソッド
    def root(self):
        while self.p:
            self = self.p
        return self

    # 挿入
    def insert(self, value):
        z = Node(value)
        y = None
        self = self.root()
        while self:
            y = self
            if z.key < self.key:
                self = self.left
            else:
                self = self.right
        z.p = y
        if y is None:
            pass
        elif z.key < y.key:
            y.left = z
        else:
            y.right = z

    # 節点(自分)を削除して、子の節点に差し替えるメソッド(deleteメソッドの一部)
    def transparent(self, v):  # selfは削除される節点、vは差し替える節点
        if self.p is None:
            pass
        elif self == self.p.left:  # 節点が親の左部分木に属する場合
            self.p.left = v
        else:  # 節点が親の右部分木に属する場合
            self.p.right = v
        if v:
            v.p = self.p

    # 削除
    def delete(self):
        if self.left is None:  # 右の子のみを持つ場合
            y = self.right
            self.transparent(y)
        elif self.right is None:  # 左の子のみを持つ場合
            y = self.left
            self.transparent(y)
        else:  # 子を二つ持つ場合
            y = self.right.minimum()
            if y.p != self:
                y.transparent(y.right)
                y.right = self.right
                y.right.p = y
            self.transparent(y)
            y.left = self.left
            y.left.p = y
        return y.root()  # 新しく構成した二分木を返す


def main():
    # データセット
    r = Node(15)
    x1 = Node(6, r)
    x2 = Node(18, r)
    x3 = Node(3, x1)
    x4 = Node(7, x1)
    x5 = Node(17, x2)
    x6 = Node(20, x2)
    x7 = Node(2, x3)
    x8 = Node(4, x3)
    x9 = Node(13, x4)
    x10 = Node(9, x9)

    # 後からleft,rightを代入
    r.left, r.right = x1, x2
    x1.left, x1.right = x3, x4
    x2.left, x2.right = x5, x6
    x3.left, x3.right = x7, x8
    x4.right = x9
    x9.left = x10

    print("探索")
    print(r.search(13))  # 13

    print("\n最小値")
    print(r.minimum())  # 2

    print("\n最大値")
    print(r.maximum())  # 20

    print("\n(6の)次節点")
    print(r.search(6).successor())  # 7

    print("\n(6の)前節点")
    print(r.search(6).predecessor())  # 4

    print("\n挿入")
    r.insert(10)  # 10を挿入
    print(r.search(10))  # 確認

    print("\n削除")
    # 子が一つの場合
    print("zが右の子のみをもつ場合")
    print("before:{}".format(r.left.right))
    r = r.search(7).delete()
    print("after:{}".format(r.left.right))

    # 子が二つの場合
    print("\nzが子を二つもつ場合")
    print("before:{}".format(r.left))
    r = r.search(6).delete()
    print("after:{}".format(r.left))

    # ルートの場合
    print("\nzがルートの場合")
    print("before:{}".format(r))
    r = r.delete()
    print("after:{}".format(r))


if __name__ == '__main__':
    main()


```

# 動的集合に対する6つの操作
###質問（query): 集合𝑆に関する情報を返す操作
- SEARCH(𝑆,𝑘): 指定したキーを保持する𝑆の要素を返す
- MINIMUM(𝑆): 最小のキーを保持する𝑆の要素を返す
- MAXIMUM(𝑆): 最大のキーを保持する𝑆の要素を返す
- SUCCESSOR(𝑆,𝑥)：要素𝑥を超える𝑆の最小要素を返す
- PREDECESSOR(𝑆,𝑥): 要素𝑥未満の𝑆の最大要素を返す

###変更操作 (modifying operation): 集合を変える操作
- INSERT(𝑆,𝑥): 集合𝑆に要素𝑥が指す要素を加える操作
- DELETE(𝑆,𝑥): 集合𝑆から要素𝑥が指す要素を取り除く操作

# 探索(SEARCH)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/b30d8a4a-f5d2-dd3c-1d4e-2f2bcb2bf26a.png)

```python:メソッド
# 探索
def search(self, k):
    if self is None or k == self.key:
        return self
    if k < self.key:
        return self.left.search(k)
    else:
        return self.right.search(k)

print("探索")              
print(r.search(13))  # 13
```

```python:結果
探索
Node(13)
```

# 最小値(MINIMUM)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/f24e9270-a6d6-9c85-98dd-f3b63d3d8d3d.png)

```python:メソッド
# 最小値  
def minimum(self):          
    while self.left:
        self = self.left    
    return self

print("\n最小値")         
print(r.minimum())  # 2             
```

```python:結果
最小値
Node(2)
```

# 最大値(MAXIMUM)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/41b1b611-5f84-6504-ef9b-8c6627a82f3d.png)

```python:メソッド
# 最大値                      
def maximum(self):           
    while self.right:
        self = self.right    
    return self

print("\n最大値")          
print(r.maximum())  # 20              
```

```python:結果
最大値
Node(20)
```

# 次節点(SUCCESSOR)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/036aaf8c-4b23-25e2-a584-692df4f3a782.png)

```python:メソッド
# 次節点                              
def successor(self):               
    if self.right:                 
        return self.right.minimum()
    y = self.p                     
    while y and self == y.right:   
        self = y                   
        y = y.p                    
    return y                       

print("\n(6の)次節点")                 
print(r.search(6).successor())  # 7                            
```

```python:結果
(6の)次節点
Node(7)
```

# 前節点(PREDECESSOR)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/acb3ae01-4fc7-7c3d-d7b3-f184326bce97.png)

```python:メソッド
# 前節点                             
def predecessor(self):            
    if self.left:                 
        return self.left.maximum()
    y = self.p                    
    while y and self == y.left:   
        self = y                  
        y = y.p                   
    return y                      

print("\n(6の)前節点")                   
print(r.search(6).predecessor())  # 4                           
```

```python:結果
(6の)前節点
Node(4)
```

# 挿入(INSERT)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/787c1eb5-5595-29b6-c6e5-b9c6308a8e5f.png)

```python:メソッド
# 二分探索木のルートを返すメソッド           
def root(self):              
    while self.p:            
        self = self.p        
    return self              
                             
# 挿入                         
def insert(self, value):     
    z = Node(value)          
    y = None                 
    self = self.root()       
    while self:              
        y = self             
        if z.key < self.key: 
            self = self.left 
        else:                
            self = self.right
    z.p = y                  
    if y is None:            
        pass                 
    elif z.key < y.key:      
        y.left = z           
    else:                    
        y.right = z          

print("\n挿入")            
r.insert(10)  # 10を挿入    
print(r.search(10))  # 確認          
```

```python:結果
挿入
Node(10)
```

# 削除(DELETE)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/94aeb02b-d8d4-580a-c0cb-6f6314043cd0.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/176349/2fe67d89-2435-eab5-e948-86b980b19185.png)

```python:メソッド
# 節点(自分)を削除して、子の節点に差し替えるメソッド(deleteメソッドの一部)        
def transparent(self, v):  # selfは削除される節点、vは差し替える節点
    if self.p is None:                             
        pass                                       
    elif self == self.p.left:  # 節点が親の左部分木に属する場合   
        self.p.left = v                            
    else:  # 節点が親の右部分木に属する場合                       
        self.p.right = v                           
    if v:                                          
        v.p = self.p                               
                                                   
# 削除                                               
def delete(self):                                  
    if self.left is None:  # 右の子のみを持つ場合            
        y = self.right                             
        self.transparent(y)                        
    elif self.right is None:  # 左の子のみを持つ場合         
        y = self.left                              
        self.transparent(y)                        
    else:  # 子を二つ持つ場合                              
        y = self.right.minimum()                   
        if y.p != self:                            
            y.transparent(y.right)                 
            y.right = self.right                   
            y.right.p = y                          
        self.transparent(y)                        
        y.left = self.left                         
        y.left.p = y                               
    return y.root()  # 新しく構成した二分木を返す                     

print("\n削除")                          
# 子が一つの場合                              
print("zが右の子のみをもつ場合")                  
print("before:{}".format(r.left.right))
r = r.search(7).delete()               
print("after:{}".format(r.left.right)) 
                                       
# 子が二つの場合                              
print("\nzが子を二つもつ場合")                  
print("before:{}".format(r.left))      
r = r.search(6).delete()               
print("after:{}".format(r.left))       
                                       
# ルートの場合                               
print("\nzがルートの場合")                    
print("before:{}".format(r))           
r = r.delete()                         
print("after:{}".format(r))                           
```

```python:結果
削除
zが右の子のみをもつ場合
before:Node(7)
after:Node(13)

zが子を二つもつ場合
before:Node(6)
after:Node(9)

zがルートの場合
before:Node(15)
after:Node(17)
```