Skip to content

Commit dcd1a0a

Browse files
Bo QianBo Qian
Bo Qian
authored and
Bo Qian
committed
Updated README.markdown for the new implementation of insert and remove. Added a remove example in BinarySearchTree.playground.
1 parent 942e3b1 commit dcd1a0a

File tree

4 files changed

+31
-104
lines changed

4 files changed

+31
-104
lines changed
Binary file not shown.
Loading

Binary Search Tree/README.markdown

+29-103
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,14 @@ If you traverse a binary search tree in-order, it looks at all the nodes as if t
6969

7070
## Deleting nodes
7171

72-
Removing nodes is kinda tricky. It is easy to remove a leaf node, you just disconnect it from its parent:
73-
74-
![Deleting a leaf node](Images/DeleteLeaf.png)
75-
76-
If the node to remove has only one child, we can link that child to the parent node. So we just pull the node out:
77-
78-
![Deleting a node with one child](Images/DeleteOneChild.png)
79-
80-
The gnarly part is when the node to remove has two children. To keep the tree properly sorted, we must replace this node by the smallest child that is larger than the node:
72+
Removing nodes is also easy. After removing a node, we replace the node with either its biggest child on the left or its smallest child on the right. That way the tree is still sorted after the removal. In following example, 10 is removed and replaced with either 9 (Figure 2), or 11 (Figure 3).
8173

8274
![Deleting a node with two children](Images/DeleteTwoChildren.png)
8375

84-
This is always the leftmost descendant in the right subtree. It requires an additional search of at most **O(h)** to find this child.
76+
Note the replacement needs to happen when the node has at least one child. If it has no child, you just disconnect it from its parent:
77+
78+
![Deleting a leaf node](Images/DeleteLeaf.png)
8579

86-
Most of the other code involving binary search trees is fairly straightforward (if you understand recursion) but deleting nodes is a bit of a headscratcher.
8780

8881
## The code (solution 1)
8982

@@ -158,23 +151,19 @@ A tree node by itself is pretty useless, so here is how you would add new nodes
158151

159152
```swift
160153
public func insert(value: T) {
161-
insert(value, parent: self)
162-
}
163-
164-
private func insert(value: T, parent: BinarySearchTree) {
165154
if value < self.value {
166155
if let left = left {
167-
left.insert(value, parent: left)
156+
left.insert(value: value)
168157
} else {
169158
left = BinarySearchTree(value: value)
170-
left?.parent = parent
159+
left?.parent = self
171160
}
172161
} else {
173162
if let right = right {
174-
right.insert(value, parent: right)
163+
right.insert(value: value)
175164
} else {
176165
right = BinarySearchTree(value: value)
177-
right?.parent = parent
166+
right?.parent = self
178167
}
179168
}
180169
}
@@ -379,7 +368,7 @@ As an exercise for yourself, see if you can implement filter and reduce.
379368

380369
### Deleting nodes
381370

382-
You've seen that deleting nodes can be tricky. We can make the code much more readable by defining some helper functions.
371+
We can make the code much more readable by defining some helper functions.
383372

384373
```swift
385374
private func reconnectParentToNode(node: BinarySearchTree?) {
@@ -396,7 +385,7 @@ You've seen that deleting nodes can be tricky. We can make the code much more re
396385

397386
Making changes to the tree involves changing a bunch of `parent` and `left` and `right` pointers. This function helps with that. It takes the parent of the current node -- that is `self` -- and connects it to another node. Usually that other node will be one of the children of `self`.
398387

399-
We also need a function that returns the leftmost descendent of a node:
388+
We also need a function that returns the minimum and maximum of a node:
400389

401390
```swift
402391
public func minimum() -> BinarySearchTree {
@@ -406,112 +395,49 @@ We also need a function that returns the leftmost descendent of a node:
406395
}
407396
return node
408397
}
409-
```
410-
411-
To see how this works, take the following tree:
412-
413-
![Example](Images/MinimumMaximum.png)
414-
415-
For example, if we look at node `10`, its leftmost descendent is `7`. We get there by following all the `left` pointers until there are no more left children to look at. The leftmost descendent of the root node `6` is `1`. Therefore, `1` is the minimum value in the entire tree.
416-
417-
We won't need it for deleting, but for completeness' sake, here is the opposite of `minimum()`:
418-
419-
```swift
398+
420399
public func maximum() -> BinarySearchTree {
421400
var node = self
422401
while case let next? = node.right {
423402
node = next
424403
}
425404
return node
426405
}
427-
```
428406

429-
It returns the rightmost descendent of the node. We find it by following `right` pointers until we get to the end. In the above example, the rightmost descendent of node `2` is `5`. The maximum value in the entire tree is `11`, because that is the rightmost descendent of the root node `6`.
407+
```
430408

431-
Finally, we can write the code that removes a node from the tree:
409+
The rest of the code is pretty self-explanatory:
432410

433411
```swift
434-
public func remove() -> BinarySearchTree? {
412+
@discardableResult public func remove() -> BinarySearchTree? {
435413
let replacement: BinarySearchTree?
436-
414+
415+
// Replacement for current node can be either biggest one on the left or
416+
// smallest one on the right, whichever is not nil
437417
if let left = left {
438-
if let right = right {
439-
replacement = removeNodeWithTwoChildren(left, right) // 1
440-
} else {
441-
replacement = left // 2
442-
}
443-
} else if let right = right { // 3
444-
replacement = right
418+
replacement = left.maximum()
419+
} else if let right = right {
420+
replacement = right.minimum()
445421
} else {
446-
replacement = nil // 4
422+
replacement = nil;
447423
}
448424

449-
reconnectParentToNode(replacement)
425+
replacement?.remove();
450426

427+
// Place the replacement on current node's position
428+
replacement?.right = right;
429+
replacement?.left = left;
430+
reconnectParentTo(node:replacement);
431+
432+
// The current node is no longer part of the tree, so clean it up.
451433
parent = nil
452434
left = nil
453435
right = nil
454-
455-
return replacement
456-
}
457-
```
458-
459-
It doesn't look so scary after all. ;-) There are four situations to handle:
460-
461-
1. This node has two children.
462-
2. This node only has a left child. The left child replaces the node.
463-
3. This node only has a right child. The right child replaces the node.
464-
4. This node has no children. We just disconnect it from its parent.
465-
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-
}
484436

485-
return successor
437+
return replacement;
486438
}
487439
```
488440

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.
490-
491-
Try it out:
492-
493-
```swift
494-
if let node2 = tree.search(2) {
495-
print(tree) // before
496-
node2.remove()
497-
print(tree) // after
498-
}
499-
```
500-
501-
First you find the node that you want to remove with `search()` and then you call `remove()` on that object. Before the removal, the tree printed like this:
502-
503-
((1) <- 2 -> (5)) <- 6 -> ((9) <- 10)
504-
505-
But after `remove()` you get:
506-
507-
((1) <- 5) <- 6 -> ((9) <- 10)
508-
509-
As you can see, node `5` has taken the place of `2`.
510-
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.
512-
513-
Like most binary search tree operations, removing a node runs in **O(h)** time, where **h** is the height of the tree.
514-
515441
### Depth and height
516442

517443
Recall that the height of a node is the distance to its lowest leaf. We can calculate that with the following function:

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ tree.insert(value: 10)
77
tree.insert(value: 9)
88
tree.insert(value: 1)
99

10+
let toDelete = tree.search(value: 1)
11+
toDelete?.remove()
1012
tree
11-
tree.debugDescription
1213

1314
let tree2 = BinarySearchTree<Int>(array: [7, 2, 5, 10, 9, 1])
1415

0 commit comments

Comments
 (0)