-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBTree.ts
280 lines (227 loc) · 9.58 KB
/
BTree.ts
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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import { BTreeNode } from './BTreeNode'
/**
* A class representation of a generic b-tree
*/
export class BTree<Type> {
/**
* The root node of the tree
*/
root: BTreeNode<Type>
/**
* The order of the tree which restricts the minimum and maximum number of keys in a node
*/
order: number
constructor (order: number) {
this.root = new BTreeNode<Type>()
this.order = order
}
/**
* Steps through the tree to calculate its height
*/
getHeight () {
let currentNode = this.root
let depth = 1
while (currentNode.children[0]) {
depth += 1
currentNode = currentNode.children[0]
}
return depth
}
/**
* Helper function to check for the presence of a key
* @param key A key to be searched for
* @returns Whether the key is in the tree
*/
contains (key: Type) {
if (this.findNode(key)) return true
else return false
}
/**
* Searches the tree recursively for the given value
* @param value The searched for value
* @param node The Node to start search with (default = root)
* @returns The Node containing the value or null
*/
findNode (value: Type, node: BTreeNode<Type> = this.root): BTreeNode<Type> | null {
// Node contains no elements
if (node.keys.length === 0) return null
// Node contains the target value => return this node
if (node.keys.some(key => key === value)) return node
// Has no children to continue search with => end search
if (node.isLeaf()) return null
// Continue search with next suitable child
let nextChildIndex = node.keys.findIndex(key => value < key)
if (nextChildIndex === -1) nextChildIndex = node.keys.length
return this.findNode(value, node.children[nextChildIndex])
}
/**
* Inserts a new key into the tree and splits all overfull nodes accordingly
* @param newKey New key to be inserted into the tree
*/
insert (newKey: Type): void {
// Find an appropriate leaf node to insert into
const leafNode = this.getAppropriateLeafNode(newKey)
// Insert if the node is not full
if (!leafNode.isFull(this.order)) {
leafNode.insertKey(newKey)
return
}
// Node has to be split since it is now overfull
leafNode.insertKey(newKey)
this.splitNode(leafNode)
}
/**
* Returns the node in which the key should be inserted
* @param newKey New key to be inserted
* @param nodeToCheck current node to be checked
* @returns The appropriate node
*/
getAppropriateLeafNode (newKey: Type, nodeToCheck: BTreeNode<Type> = this.root): BTreeNode<Type> {
// Return the node once a leafnode is reached
if (nodeToCheck.isLeaf()) return nodeToCheck
// Find the appropriate next child to check
let nextChildIndex = nodeToCheck.keys.findIndex(key => newKey < key)
if (nextChildIndex === -1) nextChildIndex = nodeToCheck.keys.length
// Traverse down the tree recursively
return this.getAppropriateLeafNode(newKey, nodeToCheck.children[nextChildIndex])
}
/**
* Splits a node along its median. The median gets transfered up into the parent while the parts to its left and right are the contents of the new child nodes.
* @param nodeToSplit The node that should be split
*/
splitNode (nodeToSplit: BTreeNode<Type>) {
// Get the parent node to insert into or new node if the node to be split is the root
const parentNode = nodeToSplit.parent ? nodeToSplit.parent : new BTreeNode<Type>()
// New neighbor which will receive the right half of the keys and children
const neighborNode = new BTreeNode<Type>()
const median = nodeToSplit.keys[this.order]
// Put keys and children after the median into the new neighbor
neighborNode.keys = nodeToSplit.keys.slice(this.order + 1)
neighborNode.children = nodeToSplit.children.slice(this.order + 1)
neighborNode.children.forEach(child => {
child.parent = neighborNode
})
// Keep keys and children before the median in the old node
nodeToSplit.keys = nodeToSplit.keys.slice(0, this.order)
nodeToSplit.children = nodeToSplit.children.slice(0, this.order + 1) // Probably?
// Check whether parent has to be split later on
const parentNodeIsAlreadyFull = parentNode.isFull(this.order)
// Move median up into parent and link to new children
parentNode.insertKey(median)
// Only add node to parent if the parent was newly generated
if (!nodeToSplit.parent) parentNode.addChild(nodeToSplit)
parentNode.addChild(neighborNode)
if (!parentNode.parent) this.root = parentNode
// Split parent if necessary
if (parentNodeIsAlreadyFull) this.splitNode(parentNode)
}
/**
* Removes the target key from the tree (or subtree if given a node)
* @param target key to be removed
* @param nodeToCheck the node and its subsequent subtree in which the key is searched for. (default = root, but convenient for smart duplicate removal)
*/
remove (target: Type, nodeToCheck: BTreeNode<Type> = this.root) {
// Find the node with the key
const containingNode = this.findNode(target, nodeToCheck)
// Return immediately if key does not exist
if (!containingNode) return
// Case 1: Key in internal node
if (!containingNode.isLeaf()) {
this.removeKeyFromInternalNode(target, containingNode)
return
}
// Case 2: Key in leaf
// Subcase 1: No Underflow -> Key can simply be removed
if (containingNode.keys.length > this.order) {
containingNode.keys = containingNode.keys.filter(key => key !== target)
return
}
// Subcase 2: Underflow after removal -> Steal key
// Remove key
containingNode.keys = containingNode.keys.filter(key => key !== target)
this.handleUnderflow(containingNode)
}
/**
* Removes the key from the node and rebalances the tree
* @param target key to be removed
* @param node node containing the key
*/
removeKeyFromInternalNode (target: Type, node: BTreeNode<Type>) {
// Remove key
const keyPosition = node.keys.findIndex(key => key === target)
node.keys.splice(keyPosition, 1)
let tempChild = node.children[keyPosition]
// Traverse to max value
while (!tempChild.isLeaf()) {
tempChild = tempChild.children[tempChild.children.length - 1]
}
// Take max value from max child and insert it in parent
const replaceValue = tempChild.keys[tempChild.keys.length - 1]
node.insertKey(replaceValue)
this.remove(replaceValue, tempChild)
}
/**
* Recursively fixes underflow issues from the node and its parent chain
* @param node the underflowing node
*/
handleUnderflow (node: BTreeNode<Type>) {
const leftNeighborNode = node.getLeftNeighborNode()
const rightNeighborNode = node.getRightNeighborNode()
if (!node.parent) return // TODO Value in Root
const positionInParent = node.parent.children.findIndex(child => node === child)
// Check neighbors and parent to steal from
if (leftNeighborNode && leftNeighborNode.keys.length > this.order) { // Steal from left neighbor
// Move key from parent into containing node
node.insertKey(node.parent.keys[positionInParent - 1])
// Move key from neighbor into parent overwriting the duplicate in the parent
node.parent.keys[positionInParent - 1] = leftNeighborNode.keys[leftNeighborNode.keys.length - 1]
// Remove duplicate in the neighbor
leftNeighborNode.keys.splice(leftNeighborNode.keys.length - 1, 1)
} else if (rightNeighborNode && rightNeighborNode.keys.length > this.order) { // Steal from right neighbor
// Move key from parent into containing node
node.insertKey(node.parent.keys[positionInParent])
// Move key from neighbor into parent overwriting the duplicate in the parent
node.parent.keys[positionInParent] = rightNeighborNode.keys[0]
// Remove duplicate in the neighbor
rightNeighborNode.keys.splice(0, 1)
} else if (leftNeighborNode) { // Merge with left neighbor
// Move key from parent into containing node
node.insertKey(node.parent.keys[positionInParent - 1])
// Remove duplicate in parent
node.parent.keys.splice(positionInParent - 1, 1)
// Move all remaining keys from node into neighbor to merge
node.keys.forEach(key => leftNeighborNode.insertKey(key))
// Move all remaining children from node to neighbor
node.children.forEach(child => leftNeighborNode.addChild(child))
// Remove the node after merging
node.parent.children.splice(positionInParent, 1)
// If Parent Node empty now, delete
if (node.parent.keys.length === 0 && node.parent.parent == null) {
leftNeighborNode.parent = null
this.root = leftNeighborNode
}
} else if (rightNeighborNode) { // Merge with right neighbor
// Move key from parent into containing node
node.insertKey(node.parent.keys[positionInParent])
// Remove duplicate in parent
node.parent.keys.splice(positionInParent, 1)
// Move all remaining keys from node into neighbor to merge
node.keys.forEach(key => rightNeighborNode.insertKey(key))
// Move all remaining children from node to neighbor
node.children.forEach(child => rightNeighborNode.addChild(child))
// Remove the node after merging
node.parent.children.splice(positionInParent, 1)
// If Parent Node empty now, delete
if (node.parent.keys.length === 0 && !node.parent.parent) {
rightNeighborNode.parent = null
this.root = rightNeighborNode
}
}
// Handle underflow in next parent
if (node.parent) {
if (node.parent.keys.length < this.order && node.parent.parent) {
this.handleUnderflow(node.parent)
}
}
}
}