Skip to content

Commit 9a8d626

Browse files
make-github-pseudonymous-againAurélien Ooms
authored andcommitted
♻️ refactor(delete): Split "no-child" and "exactly-one-child" cases.
The coverage report seems to indicate that some additional pruning is possible in delete_one_child. Need to investigate.
1 parent f336edf commit 9a8d626

File tree

4 files changed

+86
-54
lines changed

4 files changed

+86
-54
lines changed

src/deletion/delete_no_child.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import assert from 'assert';
2+
import BLACK from '../color/BLACK.js';
3+
import RED from '../color/RED.js';
4+
import Node from '../types/Node.js';
5+
import Leaf from '../types/Leaf.js';
6+
7+
import replace_node from './replace_node.js';
8+
import delete_case2 from './delete_case2.js';
9+
10+
import prune_subtree from './prune_subtree.js';
11+
12+
/**
13+
* Delete a node <code>n</code> that has no non-leaf child.
14+
*
15+
* Precondition:
16+
* - n has no non-leaf child.
17+
* - n is not the root
18+
*
19+
* @param {Node} n - The node to delete.
20+
*/
21+
const delete_no_child = (n) => {
22+
assert(n instanceof Node);
23+
assert(n.parent !== null);
24+
assert(n.left === null);
25+
assert(n.right === null);
26+
27+
if (n._color === RED) {
28+
prune_subtree(n);
29+
return;
30+
}
31+
32+
assert(n._color === BLACK);
33+
34+
// Mock leaf since there is no left child
35+
const leaf = new Leaf(null);
36+
37+
// Replace n with the mocked leaf
38+
replace_node(n, leaf);
39+
40+
// If n is black, deleting it reduces the black-height of every path going
41+
// through it by 1. Leaf is black, so there are more things to fix.
42+
delete_case2(leaf);
43+
44+
// Delete mocked leaf
45+
prune_subtree(leaf);
46+
};
47+
48+
export default delete_no_child;

src/deletion/delete_one_child.js

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,50 @@ import assert from 'assert';
22
import BLACK from '../color/BLACK.js';
33
import RED from '../color/RED.js';
44
import Node from '../types/Node.js';
5-
import Leaf from '../types/Leaf.js';
65

76
import replace_node from './replace_node.js';
87
import delete_case2 from './delete_case2.js';
98

10-
import prune_subtree from './prune_subtree.js';
11-
129
/**
13-
* Delete a node <code>n</code> that has at most a single non-leaf child.
10+
* Delete a node <code>n</code> with one non-leaf left child and one leaf right
11+
* child.
1412
*
1513
* Precondition:
16-
* - n has at most one non-leaf child.
14+
* - n has exactly one non-leaf child.
1715
* - n is not the root
18-
* - if n has a non-leaf child, then it is its left child.
16+
* - n's only non-leaf child is n's left child.
1917
* - hence, n's right child is a leaf
2018
*
2119
* @param {Node} n - The node to delete.
2220
*/
2321
const delete_one_child = (n) => {
2422
assert(n instanceof Node);
2523
assert(n.parent !== null);
26-
// Precondition: n's right child is a leaf.
27-
// The right child of n is always a LEAF because either n is a subtree
28-
// predecessor or it is the only child of its parent by the red-black tree
29-
// properties
24+
assert(n.left instanceof Node);
3025
assert(n.right === null);
3126

32-
if (n._color === RED) {
33-
// If n is red then its child can only be black. Replacing n with its
34-
// child suffices.
35-
const child = n.left;
36-
if (child === null) {
37-
prune_subtree(n);
38-
} else {
39-
assert(child._color === BLACK);
40-
replace_node(n, child);
41-
}
42-
43-
return;
44-
}
45-
46-
assert(n._color === BLACK);
47-
48-
// Mock leaf if there is no left child
49-
const child = n.left === null ? new Leaf(null) : n.left;
27+
const child = n.left;
5028

51-
// Replace n with its left child
29+
// Replace n with its only child
5230
replace_node(n, child);
5331

54-
// If n is black, deleting it reduces the black-height of every path going
55-
// through it by 1.
56-
// We can easily fix this when its left child is an
57-
// internal red node: change the color of the left child to black and
58-
// replace n with it.
59-
if (child._color === RED) child._color = BLACK;
60-
// Otherwise, there are more things to fix.
61-
else {
62-
delete_case2(child);
32+
if (n._color === BLACK) {
33+
// If n is black, deleting it reduces the black-height of every path going
34+
// through it by 1.
35+
// We can easily fix this when its only child is an
36+
// internal red node: change the color of the child to black and
37+
// replace n with it.
38+
if (child._color === RED) child._color = BLACK;
39+
// Otherwise, there are more things to fix.
40+
else {
41+
delete_case2(child);
42+
}
43+
} else {
44+
assert(n._color === RED);
45+
// If n is red then its child can only be black. Replacing n with its
46+
// child suffices. This has already been done above.
47+
assert(child._color === BLACK);
6348
}
64-
65-
// Delete mocked leaf
66-
if (child instanceof Leaf) prune_subtree(child);
6749
};
6850

6951
export default delete_one_child;

src/deletion/replace_node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Leaf from '../types/Leaf.js';
1111
const replace_node = (A, B) => {
1212
assert(A instanceof Node);
1313
assert(B instanceof Node || B instanceof Leaf);
14-
// We never apply delete_one_child on the root
14+
// We never apply delete_one_child or delete_no_child on the root
1515
assert(A.parent !== null);
1616

1717
if (A === A.parent.left) A.parent.left = B;

src/types/RedBlackTree.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import predecessor from '../family/predecessor.js';
66
import insert from '../insertion/insert.js';
77
import insert_case2 from '../insertion/insert_case2.js';
88
import delete_one_child from '../deletion/delete_one_child.js';
9+
import delete_no_child from '../deletion/delete_no_child.js';
910
import search from '../search/search.js';
1011
import inordertraversal from '../traversal/inordertraversal.js';
1112
import rangetraversal from '../traversal/rangetraversal.js';
@@ -21,6 +22,7 @@ export default class RedBlackTree {
2122
* @param {Function} compare - The comparison function for node keys.
2223
*/
2324
constructor(compare) {
25+
assert(compare instanceof Function);
2426
/** @member {Function} The comparison function for node keys. */
2527
this.compare = compare;
2628
/** @member {Node} The root of the tree. */
@@ -94,31 +96,31 @@ export default class RedBlackTree {
9496
* @param {Node} node - The input node to delete.
9597
*/
9698
_delete(node) {
99+
assert(node instanceof Node);
97100
if (node.left !== null) {
98101
// Replace node's key with predecessor's key
99102
const pred = predecessor(node);
100103
node.key = pred.key;
101104
// Delete predecessor node
102-
// note: this node can only have one non-leaf child
103-
// because the tree is a red-black tree
104-
delete_one_child(pred);
105+
// NOTE: this node can only have one non-leaf (left) child because
106+
// of red-black tree invariant.
107+
if (pred.left === null) {
108+
delete_no_child(pred);
109+
} else {
110+
delete_one_child(pred);
111+
}
105112
} else if (node.right !== null) {
106113
// Replace node's key with successor's key
107-
// If there is no left child, then there can only be one right
108-
// child.
114+
// NOTE: Since there is no left child, then there can only be one
115+
// right child by the red-black tree invariant.
109116
const succ = node.right;
110-
assert(succ instanceof Node);
111-
assert(succ.left === null);
112-
assert(succ.right === null);
113117
node.key = succ.key;
114118
// Delete successor node
115-
// note: this node can only have one non-leaf child
116-
// because the tree is a red-black tree
117-
delete_one_child(succ);
119+
delete_no_child(succ);
118120
} else if (node === this.root) {
119121
this.root = null;
120122
} else {
121-
delete_one_child(node);
123+
delete_no_child(node);
122124
}
123125
}
124126

0 commit comments

Comments
 (0)