From 1fa93415a6e14acc24e90443b2e9bdf053c4c983 Mon Sep 17 00:00:00 2001 From: Adrian Mejia Date: Fri, 23 Aug 2019 16:17:21 -0400 Subject: [PATCH] fix(tree-rotations): prevent losing nodes LL Rotation, lose the newParent previous right node; RR Rotation too Fixes: #30 --- src/data-structures/trees/avl-tree.spec.js | 19 ++++++ src/data-structures/trees/tree-rotations.js | 65 +++++++++++++------ .../trees/tree-rotations.spec.js | 63 +++++++++++++++++- 3 files changed, 125 insertions(+), 22 deletions(-) diff --git a/src/data-structures/trees/avl-tree.spec.js b/src/data-structures/trees/avl-tree.spec.js index de25a0b1..db049bbe 100644 --- a/src/data-structures/trees/avl-tree.spec.js +++ b/src/data-structures/trees/avl-tree.spec.js @@ -144,4 +144,23 @@ describe('AvlTree', () => { expect(tree.root).toBe(n); }); }); + + describe('balance without loosing nodes', () => { + beforeEach(() => { + tree.add(16); + tree.add(4); + tree.add(32); + tree.add(8); + tree.add(2); + }); + + it('should have all nodes', () => { + expect(tree.toArray()).toEqual([16, 4, 32, 2, 8, null, null, null, null, null, null]); + }); + + it('should rebalance and keep all nodes', () => { + tree.add(1); + expect(tree.toArray()).toEqual([4, 2, 16, 1, null, 8, 32, null, null, null, null, null, null]); + }); + }); }); diff --git a/src/data-structures/trees/tree-rotations.js b/src/data-structures/trees/tree-rotations.js index 8f2c6338..235c3691 100644 --- a/src/data-structures/trees/tree-rotations.js +++ b/src/data-structures/trees/tree-rotations.js @@ -2,7 +2,6 @@ /** * Swap parent's child * - * * @example Child on the left side (it also work for the right side) * * p = parent @@ -34,7 +33,7 @@ function swapParentChild(oldChild, newChild, parent) { /** * Single Left Rotation (LL Rotation) * - * @example: tree with values 1-2-3-4 + * @example: #1 tree with values 1-2-3-4 * * 1 1 * \ \ @@ -43,21 +42,34 @@ function swapParentChild(oldChild, newChild, parent) { * 3 2* 4 * \ * 4 - * @param {TreeNode} node - * @returns {TreeNode} new parent after the rotation + * + * @example: #2 left rotation + * + * 1 1 + * \ \ + * 4* 16 + * / \ / \ + * 2 16 -- left-rotation(4) -> 4 32 + * / \ / \ \ + * 8 32 2 8 64 + * \ + * 64 + * @param {TreeNode} node current node to rotate (e.g. 4) + * @returns {TreeNode} new parent after the rotation (e.g. 16) */ function leftRotation(node) { - const newParent = node.right; // E.g., node 3 + const newParent = node.right; // E.g., node 16 const grandparent = node.parent; // E.g., node 1 + const previousLeft = newParent.left; // E.g., node 8 - // swap node 1 left child from 2 to 3. + // swap parent of node 4 from node 1 to node 16 swapParentChild(node, newParent, grandparent); - // Update node 3 left child to be 2, and - // updates node 2 parent to be node 3 (instead of 1). + // Update node 16 left child to be 4, and + // updates node 4 parent to be node 16 (instead of 1). newParent.setLeftAndUpdateParent(node); - // remove node 2 left child (previouly was node 3) - node.setRightAndUpdateParent(null); + // set node4 right child to be previousLeft (node 8) + node.setRightAndUpdateParent(previousLeft); return newParent; } @@ -66,8 +78,7 @@ function leftRotation(node) { // tag::rightRotation[] /** * Single Right Rotation (RR Rotation) - * - * @example rotate node 3 to the right +* @example: #1 rotate node 3 to the right * * 4 4 * / / @@ -77,22 +88,34 @@ function leftRotation(node) { * / * 1 * + * @example: #2 rotate 16 to the right and preserve nodes + * 64 64 + * / / + * 16* 4 + * / \ / \ + * 4 32 -- right-rotation(16) --> 2 16 + * / \ / / \ + * 2 8 1 8 32 + * / + * 1 + * * @param {TreeNode} node - * this is the node we want to rotate to the right. (E.g., node 3) - * @returns {TreeNode} new parent after the rotation (E.g., node 2) + * this is the node we want to rotate to the right. (E.g., node 16) + * @returns {TreeNode} new parent after the rotation (E.g., node 4) */ function rightRotation(node) { - const newParent = node.left; // E.g., node 2 - const grandparent = node.parent; // E.g., node 4 + const newParent = node.left; // E.g., node 4 + const grandparent = node.parent; // E.g., node 64 + const previousRight = newParent.right; // E.g., node 8 - // swap node 4 left children (node 3) with node 2. + // swap node 64's left children (node 16) with node 4 (newParent). swapParentChild(node, newParent, grandparent); - // update right child on node 2 to be node 3, - // also make node 2 the new parent of node 3. + // update node 4's right child to be node 16, + // also make node 4 the new parent of node 16. newParent.setRightAndUpdateParent(node); - // remove node 3 left child (so it doesn't point to node 2) - node.setLeftAndUpdateParent(null); + // Update 16's left child to be the `previousRight` node. + node.setLeftAndUpdateParent(previousRight); return newParent; } diff --git a/src/data-structures/trees/tree-rotations.spec.js b/src/data-structures/trees/tree-rotations.spec.js index bb02039f..3a826532 100644 --- a/src/data-structures/trees/tree-rotations.spec.js +++ b/src/data-structures/trees/tree-rotations.spec.js @@ -96,6 +96,39 @@ describe('Tree rotations', () => { value: 3, left: 2, right: null, parent: 4, }); }); + + it('should not lose nodes on LL', () => { + // 1 1 + // \ \ + // 4* 16 + // / \ / \ + // 2 16 -- left-rotation(4) -> 4 32 + // / \ / \ \ + // 8 32 2 8 64 + // \ + // 64 + const n8 = new BinaryTreeNode(8); + const n16 = new BinaryTreeNode(16); + const n32 = new BinaryTreeNode(32); + const n64 = new BinaryTreeNode(64); + + n1.setRightAndUpdateParent(n4); + n4.setLeftAndUpdateParent(n2); + n4.setRightAndUpdateParent(n16); + n16.setLeftAndUpdateParent(n8); + n16.setRightAndUpdateParent(n32); + n32.setLeftAndUpdateParent(n64); + + const newParent = leftRotation(n4); + + expect(newParent).toBe(n16); + expect(n8.toValues()).toMatchObject({ + value: 8, left: null, right: null, parent: 4, + }); + expect(n4.toValues()).toMatchObject({ + value: 4, left: 2, right: 8, parent: 16, + }); + }); }); describe('#rightRotation (RR Rotation)', () => { @@ -145,7 +178,7 @@ describe('Tree rotations', () => { }); }); - it('should last two', () => { + it('should RR last two', () => { // 1 // \ // 3* @@ -167,6 +200,34 @@ describe('Tree rotations', () => { value: 3, left: null, right: null, parent: 2, }); }); + + it('should not lose nodes on RR', () => { + // 16* 4 + // / \ / \ + // 4 32 -- right-rotation(16) --> 2 16 + // / \ / / \ + // 2 8 1 8 32 + // / + // 1 + const n8 = new BinaryTreeNode(8); + const n16 = new BinaryTreeNode(16); + const n32 = new BinaryTreeNode(32); + n16.setLeftAndUpdateParent(n4); + n16.setRightAndUpdateParent(n32); + n4.setLeftAndUpdateParent(n2); + n4.setRightAndUpdateParent(n8); + n2.setLeftAndUpdateParent(n1); + + const newParent = rightRotation(n16); + + expect(newParent).toBe(n4); + expect(n8.toValues()).toMatchObject({ + value: 8, left: null, right: null, parent: 16, + }); + expect(n16.toValues()).toMatchObject({ + value: 16, left: 8, right: 32, parent: 4, + }); + }); }); describe('#leftRightRotation (LR Rotation)', () => {