Code archives/Algorithms/B-Trees

This code has been declared by its author to be Public Domain code.

Download source code

B-Trees by Warpy2009
A B-Tree is like a binary tree, but each node can have several children. They're good for storing things on disk, because they're very quick to search, and have lower depth than a binary tree.

My implementation works like a TMap, but you have to create it with btree.Create(order), where order is the maximum number of children each node can have.
Type btreeenumerator
	Field enum:bnodeenumerator
	Method objectenumerator:bnodeenumerator()
		Return enum
	End Method
End Type

Type bnodeenumerator
	Field b:bnode,i
	
	Method hasnext()
		Return b<>Null
	End Method

	Method nextobject:Object()
		res:bnode=b
		If i>0
			While b=res
				findnext
			Wend
			res=b
		EndIf
		While b=res
			findnext
		Wend
		Return res
	End Method	
	
	Method findnext()
		i:+1
		If b.leaf
			If i>=b.n
				If b.parent
					i=b.parent.childindex(b)
				EndIf
				b=b.parent
				While b And i=b.n
					If b.parent
						i=b.parent.childindex(b)
					EndIf
					b=b.parent
				Wend
			EndIf
		Else
			b=b.children[i]
			While Not b.leaf
				b=b.children[0]
			Wend
			i=0
		EndIf
	End Method
End Type

Type bkeyenumerator Extends bnodeenumerator
	
	Method nextobject:Object()
		res:Object=b.keys[i]
		findnext()
		Return res
	End Method
	
End Type

Type bvalueenumerator Extends bkeyenumerator
	Method nextobject:Object()
		res:Object=b.values[i]
		findnext()
		Return res
	End Method
End Type

Type btree
	Field order	'maximum number of children a tree node can have
	Field root:bnode
	
	Function Create:btree(order)
		If order<3 order=3
		
		bt:btree=New btree
		bt.order=order
		bt.root=bnode.Create(order,Null,True)
		Return bt
	End Function
	
	Method nodes:btreeenumerator()
		enum:bnodeenumerator=New bnodeenumerator
		b:bnode=root
		While Not b.leaf
			b=b.children[0]
		Wend
		enum.b=b
		tenum:btreeenumerator=New btreeenumerator
		tenum.enum=enum
		Return tenum
	End Method

	Method keys:btreeenumerator()
		enum:bnodeenumerator=New bkeyenumerator
		b:bnode=root
		While Not b.leaf
			b=b.children[0]
		Wend
		enum.b=b
		tenum:btreeenumerator=New btreeenumerator
		tenum.enum=enum
		Return tenum
	End Method
	
	Method values:btreeenumerator()
		enum:bnodeenumerator=New bvalueenumerator
		b:bnode=root
		While Not b.leaf
			b=b.children[0]
		Wend
		enum.b=b
		tenum:btreeenumerator=New btreeenumerator
		tenum.enum=enum
		Return tenum
	End Method
	
	Method contains(key:Object)	'does tree contain given key?
		u:bnode=search(key,i)
		If i<u.n And key.compare(u.keys[i])=0 Return True
	End Method
	
	Method valueforkey:Object(key:Object)
		u:bnode=search(key,i)
		If i=u.n Or key.compare(u.keys[i])<>0 Return Null
		Return u.values[i]
	End Method
	
	Method search:bnode(key:Object, i Var)	'find node which does/should stor given key, and put its index in parameter 'i'
		u:bnode=root
		While Not u.leaf
			i=u.index(key)
			If i<u.n And key.compare(u.keys[i])=0
				Return u
			EndIf
			u=u.children[i]
		Wend
		i=u.index(key)
		Return u
	End Method
	
	Method insert(key:Object,value:Object)
		u:bnode=search(key,i)	'find appropriate node and index to insert key in
		
		If i<u.n And key.compare(u.keys[i])=0	'key already in this leaf, so just change value
			u.values[i]=value
			Return
		EndIf
		
		For c=u.n To i+1 Step -1	'shift bigger keys along
			u.keys[c]=u.keys[c-1]
			u.values[c]=u.values[c-1]
		Next
		
		u.keys[i]=key	'insert new key
		u.values[i]=value
		u.n:+1
		
		If u.n=order	'if node is full, we need to split it
			t=(order+1)/2-1		'median index
			While u.n=order		'while node we're looking at is full
			
				v:bnode=bnode.Create(order,u.parent,u.leaf)	'create a new node to store the big half of the keys
				
				For c=t+1 To order-1	'fill in its keys
					v.keys[c-t-1]=u.keys[c]
					v.values[c-t-1]=u.values[c]
				Next
				For c=t+1 To order	'fill in its children
					v.children[c-t-1]=u.children[c]
					If u.children[c]
						u.children[c].parent=v
					EndIf
				Next
				
				u.n=t	'nodes now have order-1 keys in total, with left node having t keys
				v.n=order-1-t
				
				If Not u.parent	'if this is the root, it doesn't have a parent, so make one
					root=bnode.Create(order,Null,0)
					u.parent=root
					v.parent=root
					root.children[0]=u
				EndIf
				
				p:bnode=u.parent
				v.parent=p	'new node's parent is same as left node
				
				i=p.index(u.keys[t])	'find index to insert key which is being pushed up

				v._left=u		'update sibling pointers
				v._right=u._right
				u._right=v
				
				If Not u.leaf
					u.children[u.n]._right=Null
					v.children[0]._left=Null
				EndIf
				
				For c=p.n To i+1 Step -1	'shift keys along
					p.keys[c]=p.keys[c-1]
					p.values[c]=p.values[c-1]
					p.children[c+1]=p.children[c]
				Next
				
				p.keys[i]=u.keys[t]	'insert ascending key in parent
				p.values[i]=u.values[t]
				p.children[i+1]=v		'insert v as new child
				p.n:+1	'p has one more key
				p.leaf=0	'p is not a leaf any more, if it was one
				u=p	'now we want to check if p is full
			Wend
		EndIf
	End Method
	
	Method remove(key:Object)
		u:bnode=search(key,i)	'find node/index of key
		If i=u.n	'key is not in the tree
			Return
		EndIf
		
		If u.leaf
			removeleaf(u,i)	'can simply delete key
		Else
			ch:bnode=u.children[i+1]
			While Not ch.leaf 	'find successor
				ch=ch.children[0]
			Wend
			u.keys[i]=ch.keys[0]	'replace key by its successor
			u.values[i]=ch.values[0]
			removeleaf(ch,0)	'remove successor from child node
		EndIf
	End Method
	
	Method removeleaf(u:bnode,i)
		key:Object=u.keys[i]
		For c=i To u.n-2	'shift bigger keys left
			u.keys[c]=u.keys[c+1]
			u.values[c]=u.values[c+1]
		Next
		u.n:-1	'node has one fewer key

		t=(order+1)/2-1	'minimum size of a node
		
		While u<>root And u.n<t	'if node is too small
			p:bnode=u.parent
			i=p.childindex(u)		'find index of u in parent's children
			If i<p.n	'if u has a right sibling
				r:bnode=p.children[i+1]
				If r.n>t	'borrow a key from right sibling
					u.keys[u.n]=p.keys[i]	'add parent's separator value to the end of u
					u.values[u.n]=p.values[i]
					If Not r.leaf
						u.children[u.n+1]=r.children[0]	'u steals r's first child
						u.children[u.n+1].parent=u	'u is now stolen child's parent
						
						u.children[u.n]._right=u.children[u.n+1]	'update sibling pointers
						u.children[u.n+1]._left=u.children[u.n]
						u.children[u.n+1]._right=Null
						r.children[1]._left=Null
					EndIf
					u.n:+1	'u has one more key
					p.keys[i]=r.keys[0]	'insert right sibling's first key as new separator value
					p.values[i]=r.values[0]
					For c=1 To r.n-1	'shift right sibling's keys left
						r.keys[c-1]=r.keys[c]
						r.values[c-1]=r.values[c]
					Next
					For c=1 To r.n	'shift right sibling's children left
						r.children[c-1]=r.children[c]
					Next
					r.n:-1	'right sibling has one less key
					Return
				EndIf
			EndIf
			If i>0	'if u has a left sibling
				l:bnode=p.children[i-1]
				If l.n>t	'borrow a key from left sibling
					For c=u.n To 1 Step -1	'shift u's keys right
						u.keys[c]=u.keys[c-1]
						u.values[c]=u.values[c-1]
					Next
					For c=u.n+1 To 1 Step -1	'shift u's children right
						u.children[c]=u.children[c-1]
					Next
					u.n:+1
					u.keys[0]=p.keys[i-1]	'insert parent's separator value at the start of u
					u.values[0]=p.values[i-1]
					If Not u.leaf
						u.children[0]=l.children[l.n]	'u steals l's last child
						u.children[0].parent=u		'u is now stolen child's parent
						
						u.children[0]._left=Null	'update sibling pointers
						u.children[0]._right=u.children[1]
						u.children[1]._left=u.children[0]
						l.children[l.n-1]._right=Null
					EndIf
					p.keys[i-1]=l.keys[l.n-1]	'insert left sibling's last key as new separator value
					p.values[i-1]=l.values[l.n-1]
					l.n:-1	'left sibling has one less key
					Return
				EndIf
			EndIf
			
			If i=p.n	'if u does not have a right sibling, pretend we're merging the left sibling with u
				i:-1
				u=p.children[i]
			EndIf
			
			'no siblings have enough keys, so merge right
			u.keys[u.n]=p.keys[i]	'insert separator on the end of this node
			u.values[u.n]=p.values[i]
			u.n:+1
			
			r:bnode=p.children[i+1]
			For c=0 To r.n-1		'add right sibling's keys to the end of this node
				u.keys[u.n+c]=r.keys[c]
				u.values[u.n+c]=r.values[c]
			Next
			If Not r.leaf
				For c=0 To r.n	'add right sibling's children to this node
					u.children[u.n+c]=r.children[c]
					r.children[c].parent=u
				Next
				
				u.children[u.n-1]._right=u.children[u.n]	'update sibling pointers
				u.children[u.n]._left=u.children[u.n-1]
			EndIf
			u.n:+r.n
			
			u._right=r._right
			
			For c=i+1 To p.n-1	'shift parent's keys left
				p.keys[c-1]=p.keys[c]
				p.values[c-1]=p.values[c]
			Next
			For c=i+2 To p.n	'shift parent's children left
				p.children[c-1]=p.children[c]
			Next
			p.n:-1	'p has one less key
			

			u=p	'now we want to check if p is too small
			
		Wend
		If p=root And p.n=0
			root=p.children[0]
			root.parent=Null
		EndIf
	End Method
	
	Method repr$(u:bnode=Null,spaces$="")	'represent this tree in text form
		If Not u u=root
		out$=spaces+u.repr()

		If u.leaf=0
			For i=0 To u.n
				out:+"~n"+repr(u.children[i],spaces+" `")
				If u.children[i].parent<>u out:+"!!!!!!!"+u.parent.repr()
			Next
		EndIf
		Return out
	End Method
End Type

Type bnode
	Field leaf			'is it a leaf?
	Field n			'number of keys
	Field parent:bnode	'parent node
	Field keys:Object[]	'array of keys (separation values)
	Field values:Object[]	'array of values corresponding to keys
	Field children:bnode[]	'array of child nodes
	
	Field _left:bnode,_right:bnode	'pointers to siblings
	
	Function Create:bnode(order,parent:bnode,leaf)	'order=2t-1
		bn:bnode=New bnode
		bn.keys=New Object[order]
		bn.values=New Object[order]
		bn.children=New bnode[order+1]
		bn.parent=parent
		bn.leaf=leaf
		Return bn
	End Function
	
	Method index(key:Object)	'key index corresponding to given key
		i=0
		While i<n And key.compare(keys[i])>0
			i:+1
		Wend
		Return i
	End Method
	
	Method childindex(b:bnode)
		For i=0 To n
			If children[i]=b Return i
		Next
	End Method
	
	Method repr$()	'represent keys list as a nice string
		out$="["
		For i=0 To n-1
			If i out:+", "
			out:+String(keys[i])+":"+String(values[i])
		Next
		out:+"]"
		Return out
	End Method
	
End Type



'EXAMPLE

'create a tree of order 4
bt:btree=btree.Create(4)

'fill the tree with the letters of the alphabet
For i=0 To 25
	ch$=Chr(Asc("a")+i)
	bt.insert ch,Upper(Ch)
Next

'remove some keys at random
Print "-----------------------------~n"
For i=0 To 6
	c=Rand(0,25)
	ch$=Chr(Asc("a")+c)
	bt.remove ch
Next

Print "TREE"
Print bt.repr()+"~n~n"

Print "KEYS: VALUES"
For ch$=EachIn bt.keys()
	Print ch+": "+String(bt.valueforkey(ch))
Next

Print "ALL VALUES"
For ch=EachIn bt.values()
	Print ch
Next

Print "ALL NODES"
For b:bnode=EachIn bt.nodes()
	Print b.repr()
Next

Comments

slenkar2009
does this have advantages over Tmap?


Warpy2009
It has lower depth than a tmap, so is useful if accessing nodes takes a long time, for example if they're stored on a disk.

So not really for this implementation. I just did it as a precursor to writing my own database.


Beaker2009
Does this have a bug, or is it just me?

If you reduce the number of nodes randomly deleted (in the example) to 15 or 5 it seems to have a few keys without corresponding values. Am I missing something?


Warpy2009
Yep, there was a bug. In btree.removeleaf, change lines 291 and 292 to
					u.keys[0]=p.keys[i-1]	'insert parent's separator value at the start of u
					u.values[0]=p.values[i-1]


and lines 302 and 303 to
					p.keys[i-1]=l.keys[l.n-1]	'insert left sibling's last key as new separator value
					p.values[i-1]=l.values[l.n-1]


(or just copy the code again, I've edited it)


Code Archives Forum