Skip to content

Commit 6449a78

Browse files
committed
Improve removing node with two children
Previously, if the node had two children we'd remove one of the children and copy its value into the node you called remove() on. The advantage of that approach is that calling remove() on the root of the tree doesn't invalidate the root node. However, this wasn't true for when you tried to delete a root node that has only one child. That was confusing. Now we always truly remove the node you called remove() on, and tell you what its replacement is. As a consequence, if you remove the root node you must make sure to use its replacement node as the new root from then on.
1 parent e3c921b commit 6449a78

File tree

5 files changed

+191
-77
lines changed

5 files changed

+191
-77
lines changed

Binary Search Tree/README.markdown

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -431,34 +431,62 @@ It returns the rightmost descendent of the node. We find it by following `right`
431431
Finally, we can write the code the remove a node from the tree:
432432

433433
```swift
434-
public func remove() {
434+
public func remove() -> BinarySearchTree? {
435+
let replacement: BinarySearchTree?
436+
435437
if let left = left {
436438
if let right = right {
437-
let successor = right.minimum() // 1
438-
value = successor.value
439-
successor.remove()
439+
replacement = removeNodeWithTwoChildren(left, right) // 1
440440
} else {
441-
reconnectParentToNode(left) // 2
441+
replacement = left // 2
442442
}
443-
} else if let right = right { // 3
444-
reconnectParentToNode(right)
443+
} else if let right = right { // 3
444+
replacement = right
445445
} else {
446-
reconnectParentToNode(nil) // 4
446+
replacement = nil // 4
447447
}
448+
449+
reconnectParentToNode(replacement)
450+
451+
parent = nil
452+
left = nil
453+
right = nil
454+
455+
return replacement
448456
}
449457
```
450458

451-
It doesn't look so scary after all. ;-) Here is what it does:
452-
453-
1. This node has two children. It must be replaced by the smallest child that is larger than this node's value, which is the leftmost descendent of the right child, i.e. `right.minimum()`.
459+
It doesn't look so scary after all. ;-) There are four situations to handle:
454460

461+
1. This node has two children.
455462
2. This node only has a left child. The left child replaces the node.
456-
457463
3. This node only has a right child. The right child replaces the node.
458-
459464
4. This node has no children. We just disconnect it from its parent.
460465

461-
The only tricky situation here is `// 1`. Rather than deleting the current node, we give it the successor's value and remove the successor node instead.
466+
First, we determine which node will replace the one we're removing and then we call `reconnectParentToNode()` to change the `left`, `right`, and `parent` pointers to make that happen. Since the current node is no longer part of the tree, we clean it up by setting its pointers to `nil`. Finally, we return the node that has replaced the removed one (or `nil` if this was a leaf node).
467+
468+
The only tricky situation here is `// 1` and that logic has its own helper method:
469+
470+
```swift
471+
private func removeNodeWithTwoChildren(left: BinarySearchTree, _ right: BinarySearchTree) -> BinarySearchTree {
472+
let successor = right.minimum()
473+
successor.remove()
474+
475+
successor.left = left
476+
left.parent = successor
477+
478+
if right !== successor {
479+
successor.right = right
480+
right.parent = successor
481+
} else {
482+
successor.right = nil
483+
}
484+
485+
return successor
486+
}
487+
```
488+
489+
If the node to remove has two children, it must be replaced by the smallest child that is larger than this node's value. That always happens to be the leftmost descendent of the right child, i.e. `right.minimum()`. We take that node out of its original position in the tree and put it into the place of the node we're removing.
462490

463491
Try it out:
464492

@@ -478,9 +506,9 @@ But after `remove()` you get:
478506

479507
((1) <- 5) <- 7 -> ((9) <- 10)
480508

481-
As you can see, node `5` has taken the place of `2`. In fact, if you do `print(node2)` you'll see that it now has the value `5`. We didn't actually remove the `node2` object, we just gave it a new value.
509+
As you can see, node `5` has taken the place of `2`.
482510

483-
> **Note:** What would happen if you deleted the root node? Specifically, how would you know which node becomes the new root node? It turns out you don't have to worry about that because the root never actually gets deleted. We simply give it a new value.
511+
> **Note:** What would happen if you deleted the root node? In that case, `remove()` tells you which node has become the new root. Try it out: call `tree.remove()` and see what happens.
484512
485513
Like most binary search tree operations, removing a node runs in **O(h)** time, where **h** is the height of the tree.
486514

Binary Search Tree/Solution 1/BinarySearchTree.playground/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ tree.maximum()
2626

2727
if let node2 = tree.search(2) {
2828
node2.remove()
29-
node2.value // this is now node "5"
29+
node2
3030
print(tree)
3131
}
3232

Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,34 +101,67 @@ extension BinarySearchTree {
101101
extension BinarySearchTree {
102102
/*
103103
Deletes a node from the tree.
104+
105+
Returns the node that has replaced this removed one (or nil if this was a
106+
leaf node). That is primarily useful for when you delete the root node, in
107+
which case the tree gets a new root.
108+
104109
Performance: runs in O(h) time, where h is the height of the tree.
105110
*/
106-
public func remove() {
111+
public func remove() -> BinarySearchTree? {
112+
let replacement: BinarySearchTree?
113+
107114
if let left = left {
108115
if let right = right {
109-
// This node has two children. It must be replaced by the smallest
110-
// child that is larger than this node's value, which is the leftmost
111-
// descendent of the right child.
112-
let successor = right.minimum()
113-
114-
// Rather than deleting the current node (which is problematic for the
115-
// root node) we give it the successor's value and remove the successor.
116-
value = successor.value
117-
118-
// If this in-order successor has a right child of its own (it cannot
119-
// have a left child by definition), then that must take its place.
120-
successor.remove()
116+
replacement = removeNodeWithTwoChildren(left, right)
121117
} else {
122118
// This node only has a left child. The left child replaces the node.
123-
reconnectParentToNode(left)
119+
replacement = left
124120
}
125121
} else if let right = right {
126122
// This node only has a right child. The right child replaces the node.
127-
reconnectParentToNode(right)
123+
replacement = right
128124
} else {
129125
// This node has no children. We just disconnect it from its parent.
130-
reconnectParentToNode(nil)
126+
replacement = nil
127+
}
128+
129+
reconnectParentToNode(replacement)
130+
131+
// The current node is no longer part of the tree, so clean it up.
132+
parent = nil
133+
left = nil
134+
right = nil
135+
136+
return replacement
137+
}
138+
139+
private func removeNodeWithTwoChildren(left: BinarySearchTree, _ right: BinarySearchTree) -> BinarySearchTree {
140+
// This node has two children. It must be replaced by the smallest
141+
// child that is larger than this node's value, which is the leftmost
142+
// descendent of the right child.
143+
let successor = right.minimum()
144+
145+
// If this in-order successor has a right child of its own (it cannot
146+
// have a left child by definition), then that must take its place.
147+
successor.remove()
148+
149+
// Connect our left child with the new node.
150+
successor.left = left
151+
left.parent = successor
152+
153+
// Connect our right child with the new node. If the right child does
154+
// not have any left children of its own, then the in-order successor
155+
// *is* the right child.
156+
if right !== successor {
157+
successor.right = right
158+
right.parent = successor
159+
} else {
160+
successor.right = nil
131161
}
162+
163+
// And finally, connect the successor node to our parent.
164+
return successor
132165
}
133166

134167
private func reconnectParentToNode(node: BinarySearchTree?) {

Binary Search Tree/Solution 1/BinarySearchTree.swift

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,34 +101,67 @@ extension BinarySearchTree {
101101
extension BinarySearchTree {
102102
/*
103103
Deletes a node from the tree.
104+
105+
Returns the node that has replaced this removed one (or nil if this was a
106+
leaf node). That is primarily useful for when you delete the root node, in
107+
which case the tree gets a new root.
108+
104109
Performance: runs in O(h) time, where h is the height of the tree.
105110
*/
106-
public func remove() {
111+
public func remove() -> BinarySearchTree? {
112+
let replacement: BinarySearchTree?
113+
107114
if let left = left {
108115
if let right = right {
109-
// This node has two children. It must be replaced by the smallest
110-
// child that is larger than this node's value, which is the leftmost
111-
// descendent of the right child.
112-
let successor = right.minimum()
113-
114-
// Rather than deleting the current node (which is problematic for the
115-
// root node) we give it the successor's value and remove the successor.
116-
value = successor.value
117-
118-
// If this in-order successor has a right child of its own (it cannot
119-
// have a left child by definition), then that must take its place.
120-
successor.remove()
116+
replacement = removeNodeWithTwoChildren(left, right)
121117
} else {
122118
// This node only has a left child. The left child replaces the node.
123-
reconnectParentToNode(left)
119+
replacement = left
124120
}
125121
} else if let right = right {
126122
// This node only has a right child. The right child replaces the node.
127-
reconnectParentToNode(right)
123+
replacement = right
128124
} else {
129125
// This node has no children. We just disconnect it from its parent.
130-
reconnectParentToNode(nil)
126+
replacement = nil
127+
}
128+
129+
reconnectParentToNode(replacement)
130+
131+
// The current node is no longer part of the tree, so clean it up.
132+
parent = nil
133+
left = nil
134+
right = nil
135+
136+
return replacement
137+
}
138+
139+
private func removeNodeWithTwoChildren(left: BinarySearchTree, _ right: BinarySearchTree) -> BinarySearchTree {
140+
// This node has two children. It must be replaced by the smallest
141+
// child that is larger than this node's value, which is the leftmost
142+
// descendent of the right child.
143+
let successor = right.minimum()
144+
145+
// If this in-order successor has a right child of its own (it cannot
146+
// have a left child by definition), then that must take its place.
147+
successor.remove()
148+
149+
// Connect our left child with the new node.
150+
successor.left = left
151+
left.parent = successor
152+
153+
// Connect our right child with the new node. If the right child does
154+
// not have any left children of its own, then the in-order successor
155+
// *is* the right child.
156+
if right !== successor {
157+
successor.right = right
158+
right.parent = successor
159+
} else {
160+
successor.right = nil
131161
}
162+
163+
// And finally, connect the successor node to our parent.
164+
return successor
132165
}
133166

134167
private func reconnectParentToNode(node: BinarySearchTree?) {

Binary Search Tree/Solution 1/Tests/BinarySearchTreeTests.swift

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,17 @@ class BinarySearchTreeTest: XCTestCase {
139139
XCTAssertTrue(node5.left === node4)
140140
XCTAssertNil(node5.right)
141141

142-
node4.remove()
142+
let replacement1 = node4.remove()
143143
XCTAssertNil(node5.left)
144+
XCTAssertNil(replacement1)
144145

145-
node5.remove()
146+
let replacement2 = node5.remove()
146147
XCTAssertNil(tree.left)
148+
XCTAssertNil(replacement2)
147149

148-
node10.remove()
150+
let replacement3 = node10.remove()
149151
XCTAssertNil(tree.right)
152+
XCTAssertNil(replacement3)
150153

151154
XCTAssertEqual(tree.count, 1)
152155
XCTAssertEqual(tree.toArray(), [8])
@@ -223,15 +226,18 @@ class BinarySearchTreeTest: XCTestCase {
223226
XCTAssertTrue(node5 === node4.parent)
224227
XCTAssertTrue(node5 === node6.parent)
225228

226-
node5.remove()
227-
XCTAssertTrue(tree.left === node5)
228-
XCTAssertTrue(tree === node5.parent)
229-
XCTAssertTrue(node5.left === node4)
230-
XCTAssertTrue(node5 === node4.parent)
229+
let replacement1 = node5.remove()
230+
XCTAssertTrue(replacement1 === node6)
231+
XCTAssertTrue(tree.left === node6)
232+
XCTAssertTrue(tree === node6.parent)
233+
XCTAssertTrue(node6.left === node4)
234+
XCTAssertTrue(node6 === node4.parent)
235+
XCTAssertNil(node5.left)
231236
XCTAssertNil(node5.right)
237+
XCTAssertNil(node5.parent)
232238
XCTAssertNil(node4.left)
233239
XCTAssertNil(node4.right)
234-
XCTAssertEqual(node5.value, 6)
240+
XCTAssertNotNil(node4.parent)
235241
XCTAssertEqual(tree.count, 6)
236242
XCTAssertEqual(tree.toArray(), [4, 6, 8, 9, 10, 11])
237243

@@ -243,15 +249,18 @@ class BinarySearchTreeTest: XCTestCase {
243249
XCTAssertTrue(node10 === node9.parent)
244250
XCTAssertTrue(node10 === node11.parent)
245251

246-
node10.remove()
247-
XCTAssertTrue(tree.right === node10)
248-
XCTAssertTrue(tree === node10.parent)
249-
XCTAssertTrue(node10.left === node9)
250-
XCTAssertTrue(node10 === node9.parent)
252+
let replacement2 = node10.remove()
253+
XCTAssertTrue(replacement2 === node11)
254+
XCTAssertTrue(tree.right === node11)
255+
XCTAssertTrue(tree === node11.parent)
256+
XCTAssertTrue(node11.left === node9)
257+
XCTAssertTrue(node11 === node9.parent)
258+
XCTAssertNil(node10.left)
251259
XCTAssertNil(node10.right)
260+
XCTAssertNil(node10.parent)
252261
XCTAssertNil(node9.left)
253262
XCTAssertNil(node9.right)
254-
XCTAssertEqual(node10.value, 11)
263+
XCTAssertNotNil(node9.parent)
255264
XCTAssertEqual(tree.count, 5)
256265
XCTAssertEqual(tree.toArray(), [4, 6, 8, 9, 11])
257266
}
@@ -273,28 +282,39 @@ class BinarySearchTreeTest: XCTestCase {
273282
XCTAssertTrue(node11.right === node15)
274283
XCTAssertTrue(node11 === node15.parent)
275284

276-
node10.remove()
277-
XCTAssertTrue(tree.right === node10)
278-
XCTAssertTrue(tree === node10.parent)
279-
XCTAssertTrue(node10.left === node9)
280-
XCTAssertTrue(node10 === node9.parent)
281-
XCTAssertTrue(node10.right === node20)
282-
XCTAssertTrue(node10 === node20.parent)
285+
let replacement = node10.remove()
286+
XCTAssertTrue(replacement === node11)
287+
XCTAssertTrue(tree.right === node11)
288+
XCTAssertTrue(tree === node11.parent)
289+
XCTAssertTrue(node11.left === node9)
290+
XCTAssertTrue(node11 === node9.parent)
291+
XCTAssertTrue(node11.right === node20)
292+
XCTAssertTrue(node11 === node20.parent)
283293
XCTAssertTrue(node20.left === node15)
284294
XCTAssertTrue(node20 === node15.parent)
285295
XCTAssertNil(node20.right)
286-
XCTAssertEqual(node10.value, 11)
296+
XCTAssertNil(node10.left)
297+
XCTAssertNil(node10.right)
298+
XCTAssertNil(node10.parent)
287299
XCTAssertEqual(tree.count, 8)
288300
XCTAssertEqual(tree.toArray(), [4, 5, 8, 9, 11, 13, 15, 20])
289301
}
290302

291303
func testRemoveRoot() {
292304
let tree = BinarySearchTree(array: [8, 5, 10, 4, 9, 20, 11, 15, 13])
293-
tree.remove()
294-
295-
XCTAssertEqual(tree.value, 9)
296-
XCTAssertEqual(tree.count, 8)
297-
XCTAssertEqual(tree.toArray(), [4, 5, 9, 10, 11, 13, 15, 20])
305+
306+
let node9 = tree.search(9)!
307+
308+
let newRoot = tree.remove()
309+
XCTAssertTrue(newRoot === node9)
310+
XCTAssertEqual(newRoot!.value, 9)
311+
XCTAssertEqual(newRoot!.count, 8)
312+
XCTAssertEqual(newRoot!.toArray(), [4, 5, 9, 10, 11, 13, 15, 20])
313+
314+
// The old root is a subtree of a single element.
315+
XCTAssertEqual(tree.value, 8)
316+
XCTAssertEqual(tree.count, 1)
317+
XCTAssertEqual(tree.toArray(), [8])
298318
}
299319

300320
func testPredecessor() {

0 commit comments

Comments
 (0)