赤黒木

概要

平衡探索二分木の一種で、以下を満たすように木を保つ。

  • 各ノードを赤と黒で(仮想的に)塗り分ける
  • 根は黒
  • 赤の子は黒(赤は2つ以上連続しない)
  • 根からそれぞれの葉までの経路上にある黒の個数は等しい

こんなことを決めて何がいいのかと、

  • これが満たされている木では、根から最短経路(黒黒黒……)と最長経路(黒赤黒赤……)の差は2倍を超えないので、ほどよい平衡が保たれることが保証される
  • データを追加したり削除したりするとき、親子で繋がった直近3~4ノードの色のパターンを見て塗り替えることで状態を維持できる
    • その中だけで状態を解消できないときは親に遡ることはあるが、再帰的に実装できる

ただ、パターン分けは結構種類があってややこしい。 詳細は参考の1つめのサイトが丁寧に場合分けを説明されている。

実装

再帰関数は極力無くした実装(1箇所だけ、多重再帰にはならない箇所で使用)。どうしても配列アクセスは多くなるので、Pythonだと遅い。PyPyなら……速いとは言えないが、まぁまぁ。

関数返値概要
insert(x)Nonexを追加(実装によっては重複する値を許さないこともできる。以下の実装では許す)
delete(x)Nonexを削除(無ければ何もしない)
upper_bound(x)y,ix<yを満たす最小の yと、その小さい方からのindex i(0-indexed)を取得
lower_bound(x)y,ixyを満たす最小の yと、その小さい方からのindex i(0-indexed)を取得
get_by_index(i)x小さい方から i番目の値を取得(0-indexed)
debug_print()None木を90°倒して左が根、上が小さい方となるように、“色,値,部分木以下の要素数”を描画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
"""
赤黒木
"""
 
 
class RedBlackTree:
    # For performance, nodes are implemented as lists.
    #   [left, right, data, color, count]
    #   color: 1:RED  0:BLACK
    #
    # It is useful to set the left/right index to 0/1 for boolean access.
    # The end nodes are BLACK and of which value is EOT (End Of Tree).
    #
    # Usage example:
    # rbt = RedBlackTree()
    # rbt.insert(x)
    # rbt.delete(x)
    # rbt.upper_bound(x)
    # rbt.lower_bound(x)
    # rbt.get_by_index(i)
    # rbt.debug_print()
     
 
    def __init__(self, EOT=-1):
        self.EOT= EOT
        self.root= self._leaf()
 
    def _leaf(self):
        return [None,None,self.EOT,0,0]
 
    def _rotate(self, node, r):
        child= node[r ^1]
        node[r ^1]= child[r]
        child[r]= node
        child[3]= node[3]
        node[3]= 1
        child_count= child[4]
        child[4]= node[4]
        node[4]-= child_count- node[r ^1][4]
        return child
 
    def insert(self, x):
        stack= []
        node= self.root
        while node[2] != self.EOT:
            # Variation: If the same value is not allowed, return when found.
 
            to_right= x >= node[2]
            stack.append((node, to_right))
            node= node[to_right]
 
        # Insert values to the end node
        node[0]= self._leaf()
        node[1]= self._leaf()
        node[2]= x
        node[3]= 1
        node[4]= 1
 
        # Increase count
        for parent, _in stack:
            parent[4]+= 1
 
        # Validate tree and rotate if needed.
        while stack:
            parent, r= stack.pop()
            if parent[3]== 1:
                node= parent
                continue
            parent[r]= node
            node, flag= self._balance_insert(parent, r)
 
            if stackand flag== True:
                parent, r= stack.pop()
                parent[r]= node
                break
 
        else:
            # If the root might have changed, update its color.
            self.root= node
            self.root[3]= 0
 
    def _balance_insert(self, node, r):
        flag= True
        if node[r][r ^1][3]== 1:
            node[r]= self._rotate(node[r], r)
        if node[r][r][3]== 1:
            if node[r ^1][3]== 1:
                node[3]= 1
                node[r ^1][3]= node[r][3]= 0
                flag= False
            else:
                node= self._rotate(node, r ^1)
        return node, flag
 
    def delete(self, x):
        node= self.root
        stack= []
        while node[2] != x:
            r= node[2] < x
            stack.append((node, r))
            node= node[r]
 
        # Not Found
        if node[2]== self.EOT:
            return
 
        # Node has 2 children: swap min node in right tree and delete min node
        if node[0][2] != self.EOTand node[1][2] != self.EOT:
            stack.append((node,1))
            min_node= self.get_min(node[1], stack)
            node[2]= min_node[2]
            node= min_node
 
        # Delete node is root
        if not stack:
            if node[0][2]== self.EOT:
                self.root= node[1]
            else:
                self.root= node[0]
            self.root[3]= 0
            return
 
        # Decrease count
        for parent, _in stack:
            parent[4]-= 1
 
        # Node has 0/1 child
        parent, r= stack[-1]
        if node[0][2]== self.EOT:
            parent[r]= node[1]
            node[1][3]= 0
            # Balance is only needed if both children are EOT and self color is black.
            if node[1][2] != self.EOTor node[3]== 1:
                return
        elif node[1][2]== self.EOT:
            parent[r]= node[0]
            node[0][3]= 0
            return
 
        # Validate tree and rotate if needed.
        while stack:
            parent, r= stack.pop()
            node, flag= self._balance_delete(parent, r)
 
            if stackand flag== True:
                parent, r= stack.pop()
                parent[r]= node
                break
        else:
            # If the root might have changed, update its color.
            self.root= node
            self.root[3]= 0
 
    def get_min(self, node, stack):
        while node[0][2] != self.EOT:
            stack.append((node,0))
            node= node[0]
        return node
 
    def _balance_delete(self, node, r):
        if node[r ^1][r][3]== 0 and node[r ^1][r ^1][3]== 0:
            if node[r ^1][3]== 0:
                node[r ^1][3]= 1
                if node[3]== 0:
                    return node,False
                node[3]= 0
            else:
                node= self._rotate(node, r)
                node[r], _= self._balance_delete(node[r], r)
        else:
            if node[r ^1][r][3]== 1:
                node[r ^1]= self._rotate(node[r ^1], r ^1)
            node= self._rotate(node, r)
            node[r][3]= 0
            node[r ^1][3]= 0
        return node,True
 
    def upper_bound(self, x):
        """
        :return Smallest y satisfying x < y and its leftmost index.
                If not exists, (EOT, length) will be returned.
        """
        node= self.root
        y= self.EOT
        i= node[4]
        j= 0
        while node[2] != self.EOT:
            if x < node[2]:
                y= node[2]
                i= j+ node[0][4]
                node= node[0]
            else:
                j+= node[0][4]+ 1
                node= node[1]
        return y, i
 
    def lower_bound(self, x):
        """
        :return Smallest y satisfying x <= y and its leftmost index.
                If not exists, (EOT, length) will be returned.
        """
        node= self.root
        y= self.EOT
        i= node[4]
        j= 0
        while node[2] != self.EOT:
            if x <= node[2]:
                y= node[2]
                i= j+ node[0][4]
                node= node[0]
            else:
                j+= node[0][4]+ 1
                node= node[1]
        return y, i
 
    def get_by_index(self, i):
        """
        :return (0-indexed) i-th smallest item.
                If i is greater than length, EOT will be returned.
        """
        node= self.root
        if node[4] <= i:
            return self.EOT
        j= i
        while node[2] != self.EOT:
            left_count= node[0][4]
            if left_count== j:
                return node[2]
            elif left_count > j:
                node= node[0]
            else:
                j-= left_count+ 1
                node= node[1]
 
    def debug_print(self):
        self._debug_print(self.root,0)
 
    def _debug_print(self, node, depth):
        if node[2] != self.EOT:
            self._debug_print(node[0], depth+ 1)
            print('      ' * depth,'BR'[node[3]], node[2], node[4])
            self._debug_print(node[1], depth+ 1)
 
 
def check():
    rbt= RedBlackTree()
    for xin [0,1,2,3,4,5,6,3,3,3,5,5,5,0,0,0]:
        print('------ INSERT', x,'------')
        rbt.insert(x)
        rbt.debug_print()
 
    for xin [0,1,2,3,4,5,6,7]:
        print('------ UPPER', x,'=', rbt.upper_bound(x))
        print('------ LOWER', x,'=', rbt.lower_bound(x))
 
    for xin [5,0,3,3,0,5]:
        print('------ DELETE', x,'------')
        rbt.delete(x)
        rbt.debug_print()
 
    for iin range(11):
        print('------ INDEX', i,'=', rbt.get_by_index(i))
 
 
if __name__== '__main__':
    check()

programming_algorithm/data_structure/balancing_binary_search_tree/redblacktree.txt · 最終更新: 2019/11/28 by ikatakos
CC Attribution 4.0 International
Driven by DokuWikiRecent changes RSS feedValid CSSValid XHTML 1.0