|
| 1 | +class node: |
| 2 | + def __init__(self,keys=None,isLeaf = True,children=None): |
| 3 | + if keys is None:keys=[] |
| 4 | + if children is None: children =[] |
| 5 | + self.keys = keys |
| 6 | + self.isLeaf = isLeaf |
| 7 | + self.children = [] |
| 8 | + def __getitem__(self,i): |
| 9 | + return self.keys[i] |
| 10 | + def __delitem__(self,i): |
| 11 | + del self.keys[i] |
| 12 | + def __setitem__(self,i,k): |
| 13 | + self.keys[i] = k |
| 14 | + def __len__(self): |
| 15 | + return len(self.keys) |
| 16 | + def __repr__(self): |
| 17 | + return str(self.keys) |
| 18 | + def __str__(self): |
| 19 | + children = ','.join([str(nd.keys) for nd in self.children]) |
| 20 | + return f'keys: {self.keys}\nchildren: {children}\nisLeaf: {self.isLeaf}' |
| 21 | + def getChd(self,i): |
| 22 | + return self.children[i] |
| 23 | + def delChd(self,i): |
| 24 | + del self.children[i] |
| 25 | + def setChd(self,i,chd): |
| 26 | + self.children[i] = chd |
| 27 | + def getChildren(self,begin=0,end=None): |
| 28 | + if end is None:return self.children[begin:] |
| 29 | + return self.children[begin:end] |
| 30 | + def findKey(self,key): |
| 31 | + for i,k in enumerate(self.keys): |
| 32 | + if k>=key: |
| 33 | + return i |
| 34 | + return len(self) |
| 35 | + def update(self,keys=None,isLeaf=None,children=None): |
| 36 | + if keys is not None:self.keys = keys |
| 37 | + if children is not None:self.children = children |
| 38 | + if isLeaf is not None: self.isLeaf = isLeaf |
| 39 | + def insert(self,i,key=None,nd=None): |
| 40 | + if key is not None:self.keys.insert(i,key) |
| 41 | + if not self.isLeaf and nd is not None: self.children.insert(i,nd) |
| 42 | + def isLeafNode(self):return self.isLeaf |
| 43 | + def split(self,prt,t): |
| 44 | + # form new two nodes |
| 45 | + k = self[t-1] |
| 46 | + nd1 = node() |
| 47 | + nd2 = node() |
| 48 | + nd1.keys,nd2.keys = self[:t-1], self[t:] # note that t is 1 bigger than key index |
| 49 | + nd1.isLeaf = nd2.isLeaf = self.isLeaf |
| 50 | + if not self.isLeaf: |
| 51 | + # note that children index is one bigger than key index, and all children included |
| 52 | + nd1.children, nd2.children = self.children[0:t], self.children[t:] |
| 53 | + # connect them to parent |
| 54 | + idx = prt.findKey(k) |
| 55 | + if prt.children !=[]: prt.children.remove(self) # remove the original node |
| 56 | + prt.insert(idx,k,nd2) |
| 57 | + prt.insert(idx,nd = nd1) |
| 58 | + return prt |
| 59 | + |
| 60 | + |
| 61 | +class bTree: |
| 62 | + def __init__(self,degree=2): |
| 63 | + self.root = node() |
| 64 | + self.degree=degree |
| 65 | + self.nodeNum = 1 |
| 66 | + self.keyNum = 0 |
| 67 | + def search(self,key,withpath=False): |
| 68 | + nd = self.root |
| 69 | + fathers = [] |
| 70 | + while True: |
| 71 | + i = nd.findKey(key) |
| 72 | + if i==len(nd): fathers.append((nd,i-1,i)) |
| 73 | + else: fathers.append((nd,i,i)) |
| 74 | + if i<len(nd) and nd[i]==key: |
| 75 | + if withpath:return nd,i,fathers |
| 76 | + else:return nd,i |
| 77 | + if nd.isLeafNode(): |
| 78 | + if withpath:return None,None,None |
| 79 | + else:return None,None |
| 80 | + nd = nd.getChd(i) |
| 81 | + def insert(self,key): |
| 82 | + if len(self.root)== self.degree*2-1: |
| 83 | + self.root = self.root.split(node(isLeaf=False),self.degree) |
| 84 | + self.nodeNum +=2 |
| 85 | + nd = self.root |
| 86 | + while True: |
| 87 | + idx = nd.findKey(key) |
| 88 | + if idx<len(nd) and nd[idx] == key:return |
| 89 | + if nd.isLeafNode(): |
| 90 | + nd.insert(idx,key) |
| 91 | + self.keyNum+=1 |
| 92 | + return |
| 93 | + else: |
| 94 | + chd = nd.getChd(idx) |
| 95 | + if len(chd)== self.degree*2-1: #ensure its keys won't excess when its chd split and u |
| 96 | + nd = chd.split(nd,self.degree) |
| 97 | + self.nodeNum +=1 |
| 98 | + else: |
| 99 | + nd = chd |
| 100 | + def delete(self,key):#to do |
| 101 | + '''search the key, delete it , and form down to up to rebalance it ''' |
| 102 | + nd,idx ,fathers= self.search(key,withpath=True) |
| 103 | + if nd is None : return |
| 104 | + del nd[idx] |
| 105 | + self.keyNum-=1 |
| 106 | + if not nd.isLeafNode(): |
| 107 | + chd = nd.getChd(idx) # find the predecessor key |
| 108 | + while not chd.isLeafNode(): |
| 109 | + fathers.append((chd,len(chd)-1,len(chd))) |
| 110 | + chd = chd.getChd(-1) |
| 111 | + fathers.append((chd,len(chd)-1,len(chd))) |
| 112 | + nd.insert(idx,chd[-1]) |
| 113 | + del chd[-1] |
| 114 | + if len(fathers)>1:self.rebalance(fathers) |
| 115 | + def rebalance(self,fathers): |
| 116 | + nd,keyIdx,chdIdx = fathers.pop() |
| 117 | + while len(nd)<self.degree-1: # rebalance tree from down to up |
| 118 | + prt,keyIdx,chdIdx = fathers[-1] |
| 119 | + lbro = [] if chdIdx==0 else prt.getChd(chdIdx-1) |
| 120 | + rbro = [] if chdIdx==len(prt) else prt.getChd(chdIdx+1) |
| 121 | + if len(lbro)<self.degree and len(rbro)<self.degree: # merge two deficient nodes |
| 122 | + beforeNode,afterNode = None,None |
| 123 | + if lbro ==[]: |
| 124 | + keyIdx = chdIdx |
| 125 | + beforeNode,afterNode = nd,rbro |
| 126 | + else: |
| 127 | + beforeNode,afterNode = lbro,nd |
| 128 | + keyIdx = chdIdx-1 # important, when choosing |
| 129 | + keys = beforeNode[:]+[prt[keyIdx]]+afterNode[:] |
| 130 | + children = beforeNode.getChildren() + afterNode.getChildren() |
| 131 | + isLeaf = beforeNode.isLeafNode() |
| 132 | + prt.delChd(keyIdx+1) |
| 133 | + del prt[keyIdx] |
| 134 | + nd.update(keys,isLeaf,children) |
| 135 | + prt.children[keyIdx]=nd |
| 136 | + self.nodeNum -=1 |
| 137 | + elif len(lbro)>=self.degree: # rotate when only one sibling is deficient |
| 138 | + keyIdx = chdIdx-1 |
| 139 | + nd.insert(0,prt[keyIdx]) # rotate keys |
| 140 | + prt[keyIdx] = lbro[-1] |
| 141 | + del lbro[-1] |
| 142 | + if not nd.isLeafNode(): # if not leaf, move children |
| 143 | + nd.insert(0,nd=lbro.getChd(-1)) |
| 144 | + lbro.delChd(-1) |
| 145 | + else: |
| 146 | + keyIdx = chdIdx |
| 147 | + nd.insert(len(nd),prt[keyIdx]) # rotate keys |
| 148 | + prt[keyIdx] = rbro[0] |
| 149 | + del rbro[0] |
| 150 | + if not nd.isLeafNode(): # if not leaf, move children |
| 151 | + #note that insert(-1,ele) will make the ele be the last second one |
| 152 | + nd.insert(len(nd),nd=rbro.getChd(0)) |
| 153 | + rbro.delChd(0) |
| 154 | + if len(fathers)==1: |
| 155 | + if len(self.root)==0: |
| 156 | + self.root = nd |
| 157 | + self.nodeNum -=1 |
| 158 | + break |
| 159 | + nd,i,j = fathers.pop() |
| 160 | + def __str__(self): |
| 161 | + head= '\n'+'-'*30+'B Tree'+'-'*30 |
| 162 | + tail= '-'*30+'the end'+'-'*30+'\n' |
| 163 | + lst = [[head],[f'node num: {self.nodeNum}, key num: {self.keyNum}']] |
| 164 | + cur = [] |
| 165 | + ndNum =0 |
| 166 | + ndTotal= 1 |
| 167 | + que = [self.root] |
| 168 | + while que!=[]: |
| 169 | + nd = que.pop(0) |
| 170 | + cur.append(repr(nd)) |
| 171 | + ndNum+=1 |
| 172 | + que+=nd.getChildren() |
| 173 | + if ndNum==ndTotal: |
| 174 | + lst.append(cur) |
| 175 | + cur = [] |
| 176 | + ndNum = 0 |
| 177 | + ndTotal =len(que) |
| 178 | + lst.append([tail]) |
| 179 | + lst = [','.join(li) for li in lst] |
| 180 | + return '\n'.join(lst) |
| 181 | + def __iter__(self,nd = None): |
| 182 | + if nd is None: nd = self.root |
| 183 | + que = [nd] |
| 184 | + while que !=[]: |
| 185 | + nd = que.pop(0) |
| 186 | + yield nd |
| 187 | + if nd.isLeafNode():continue |
| 188 | + for i in range(len(nd)+1): |
| 189 | + que.append(nd.getChd(i)) |
| 190 | + |
| 191 | + |
| 192 | +if __name__ =='__main__': |
| 193 | + bt = bTree() |
| 194 | + from random import shuffle,sample |
| 195 | + n = 20 |
| 196 | + lst = [i for i in range(n)] |
| 197 | + shuffle(lst) |
| 198 | + test= sample(lst,len(lst)//4) |
| 199 | + print(f'building b-tree with {lst}') |
| 200 | + for i in lst: |
| 201 | + bt.insert(i) |
| 202 | + #print(f'inserting {i}) |
| 203 | + #print(bt) |
| 204 | + print(bt) |
| 205 | + print(f'serching {test}') |
| 206 | + for i in test: |
| 207 | + nd,idx = bt.search(i) |
| 208 | + print(f'node: {repr(nd)}[{idx}]== {i}') |
| 209 | + for i in test: |
| 210 | + print(f'deleting {i}') |
| 211 | + bt.delete(i) |
| 212 | + print(bt) |
0 commit comments