400 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			400 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|   | --- | |||
|  | id: 587d8258367417b2b2512c82 | |||
|  | title: 2 つの子を持つノードを二分探索木で削除する | |||
|  | challengeType: 1 | |||
|  | forumTopicId: 301639 | |||
|  | dashedName: delete-a-node-with-two-children-in-a-binary-search-tree | |||
|  | --- | |||
|  | 
 | |||
|  | # --description--
 | |||
|  | 
 | |||
|  | 2 つの子を持つノードを削除するのは、最も実装が難しいケースです。 このようなノードを削除すると、元のツリー構造との接続が切れた 2 つの部分木が生じます。 どうすれば再接続できるでしょうか? 一つの方法は、削除対象ノードの右部分木で最小の値を見つけ、対象ノードをこの値に置き換えることです。 このような方法で置き換えると、削除対象ノードは、それが新しい親として持つ、左部分木にあるすべてのノードよりも必ず大きくなると同時に、それが新しい親として持つ、右部分木にあるすべてのノードよりも必ず小さくなります。 この置換が行われたら、置換ノードは右部分木から削除されなければなりません。 この操作でさえ用心が必要です。置換ノードが葉ノードであったり、それ自体が右部分木の親であったりする可能性があるためです。 それが葉ノードである場合は、それに対する親の参照を削除する必要があります。 葉ノードでない場合は、それは削除対象ノードの右側の子でなければなりません。 この場合、削除対象ノードの値を置換値に置き換え、削除対象ノードが置換ノードの右側の子を参照するように設定しなければなりません。 | |||
|  | 
 | |||
|  | # --instructions--
 | |||
|  | 
 | |||
|  | 最後に、`remove` メソッドで 3 つ目のケースを処理しましょう。 最初の 2 つのケースのために、今回もコードが用意されています。 2 つの子を持つ対象ノードを処理するコードを追加してください。 意識すべきエッジケースがありますか? 木にノードが 3 つしかない場合はどうでしょうか? これを終えれば、二分探索木の削除操作が完了します。 よくできました、これはかなり難しい問題です! | |||
|  | 
 | |||
|  | # --hints--
 | |||
|  | 
 | |||
|  | `BinarySearchTree` データ構造が存在する必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } | |||
|  |     return typeof test == 'object'; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | 二分探索木に `remove` というメソッドが必要です。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     return typeof test.remove == 'function'; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | 存在しない要素を削除しようとすると、`null` が返される必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     return typeof test.remove == 'function' ? test.remove(100) == null : false; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | 根ノードに子がない場合は、根ノードを削除すると根が `null` に設定される必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.add(500); | |||
|  |     test.remove(500); | |||
|  |     return typeof test.remove == 'function' ? test.inorder() == null : false; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | `remove` メソッドは、葉ノードを木から削除する必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.add(5); | |||
|  |     test.add(3); | |||
|  |     test.add(7); | |||
|  |     test.add(6); | |||
|  |     test.add(10); | |||
|  |     test.add(12); | |||
|  |     test.remove(3); | |||
|  |     test.remove(12); | |||
|  |     test.remove(10); | |||
|  |     return typeof test.remove == 'function' | |||
|  |       ? test.inorder().join('') == '567' | |||
|  |       : false; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | `remove` メソッドは、子を 1 つ持つノードを削除する必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     if (typeof test.remove !== 'function') { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.add(-1); | |||
|  |     test.add(3); | |||
|  |     test.add(7); | |||
|  |     test.add(16); | |||
|  |     test.remove(16); | |||
|  |     test.remove(7); | |||
|  |     test.remove(3); | |||
|  |     return test.inorder().join('') == '-1'; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | 2 つのノードを持つ木の根を削除すると、2 番目のノードが根として設定される必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     if (typeof test.remove !== 'function') { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.add(15); | |||
|  |     test.add(27); | |||
|  |     test.remove(15); | |||
|  |     return test.inorder().join('') == '27'; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | `remove` メソッドは二分探索木構造を維持しながら、2 つの子を持つノードを削除する必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     if (typeof test.remove !== 'function') { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.add(1); | |||
|  |     test.add(4); | |||
|  |     test.add(3); | |||
|  |     test.add(7); | |||
|  |     test.add(9); | |||
|  |     test.add(11); | |||
|  |     test.add(14); | |||
|  |     test.add(15); | |||
|  |     test.add(19); | |||
|  |     test.add(50); | |||
|  |     test.remove(9); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.remove(11); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.remove(14); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.remove(19); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.remove(3); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.remove(50); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.remove(15); | |||
|  |     if (!test.isBinarySearchTree()) { | |||
|  |       return false; | |||
|  |     } | |||
|  |     return test.inorder().join('') == '147'; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | 3 つのノードを持つ木において根を削除できる必要があります。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     var test = false; | |||
|  |     if (typeof BinarySearchTree !== 'undefined') { | |||
|  |       test = new BinarySearchTree(); | |||
|  |     } else { | |||
|  |       return false; | |||
|  |     } | |||
|  |     if (typeof test.remove !== 'function') { | |||
|  |       return false; | |||
|  |     } | |||
|  |     test.add(100); | |||
|  |     test.add(50); | |||
|  |     test.add(300); | |||
|  |     test.remove(100); | |||
|  |     return test.inorder().join('') == 50300; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | # --seed--
 | |||
|  | 
 | |||
|  | ## --after-user-code--
 | |||
|  | 
 | |||
|  | ```js | |||
|  | BinarySearchTree.prototype = Object.assign( | |||
|  |   BinarySearchTree.prototype, | |||
|  |   { | |||
|  |     add: function(value) { | |||
|  |       var node = this.root; | |||
|  |       if (node == null) { | |||
|  |         this.root = new Node(value); | |||
|  |         return; | |||
|  |       } else { | |||
|  |         function searchTree(node) { | |||
|  |           if (value < node.value) { | |||
|  |             if (node.left == null) { | |||
|  |               node.left = new Node(value); | |||
|  |               return; | |||
|  |             } else if (node.left != null) { | |||
|  |               return searchTree(node.left); | |||
|  |             } | |||
|  |           } else if (value > node.value) { | |||
|  |             if (node.right == null) { | |||
|  |               node.right = new Node(value); | |||
|  |               return; | |||
|  |             } else if (node.right != null) { | |||
|  |               return searchTree(node.right); | |||
|  |             } | |||
|  |           } else { | |||
|  |             return null; | |||
|  |           } | |||
|  |         } | |||
|  |         return searchTree(node); | |||
|  |       } | |||
|  |     }, | |||
|  |     inorder: function() { | |||
|  |       if (this.root == null) { | |||
|  |         return null; | |||
|  |       } else { | |||
|  |         var result = new Array(); | |||
|  |         function traverseInOrder(node) { | |||
|  |           if (node.left != null) { | |||
|  |             traverseInOrder(node.left); | |||
|  |           } | |||
|  |           result.push(node.value); | |||
|  |           if (node.right != null) { | |||
|  |             traverseInOrder(node.right); | |||
|  |           } | |||
|  |         } | |||
|  |         traverseInOrder(this.root); | |||
|  |         return result; | |||
|  |       } | |||
|  |     }, | |||
|  |     isBinarySearchTree() { | |||
|  |       if (this.root == null) { | |||
|  |         return null; | |||
|  |       } else { | |||
|  |         var check = true; | |||
|  |         function checkTree(node) { | |||
|  |           if (node.left != null) { | |||
|  |             var left = node.left; | |||
|  |             if (left.value > node.value) { | |||
|  |               check = false; | |||
|  |             } else { | |||
|  |               checkTree(left); | |||
|  |             } | |||
|  |           } | |||
|  |           if (node.right != null) { | |||
|  |             var right = node.right; | |||
|  |             if (right.value < node.value) { | |||
|  |               check = false; | |||
|  |             } else { | |||
|  |               checkTree(right); | |||
|  |             } | |||
|  |           } | |||
|  |         } | |||
|  |         checkTree(this.root); | |||
|  |         return check; | |||
|  |       } | |||
|  |     } | |||
|  |   } | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | ## --seed-contents--
 | |||
|  | 
 | |||
|  | ```js | |||
|  | var displayTree = tree => console.log(JSON.stringify(tree, null, 2)); | |||
|  | function Node(value) { | |||
|  |   this.value = value; | |||
|  |   this.left = null; | |||
|  |   this.right = null; | |||
|  | } | |||
|  | 
 | |||
|  | function BinarySearchTree() { | |||
|  |   this.root = null; | |||
|  |   this.remove = function(value) { | |||
|  |     if (this.root === null) { | |||
|  |       return null; | |||
|  |     } | |||
|  |     var target; | |||
|  |     var parent = null; | |||
|  |     // Find the target value and its parent | |||
|  |     (function findValue(node = this.root) { | |||
|  |       if (value == node.value) { | |||
|  |         target = node; | |||
|  |       } else if (value < node.value && node.left !== null) { | |||
|  |         parent = node; | |||
|  |         return findValue(node.left); | |||
|  |       } else if (value < node.value && node.left === null) { | |||
|  |         return null; | |||
|  |       } else if (value > node.value && node.right !== null) { | |||
|  |         parent = node; | |||
|  |         return findValue(node.right); | |||
|  |       } else { | |||
|  |         return null; | |||
|  |       } | |||
|  |     }.bind(this)()); | |||
|  |     if (target === null) { | |||
|  |       return null; | |||
|  |     } | |||
|  |     // Count the children of the target to delete | |||
|  |     var children = | |||
|  |       (target.left !== null ? 1 : 0) + (target.right !== null ? 1 : 0); | |||
|  |     // Case 1: Target has no children | |||
|  |     if (children === 0) { | |||
|  |       if (target == this.root) { | |||
|  |         this.root = null; | |||
|  |       } else { | |||
|  |         if (parent.left == target) { | |||
|  |           parent.left = null; | |||
|  |         } else { | |||
|  |           parent.right = null; | |||
|  |         } | |||
|  |       } | |||
|  |     } | |||
|  |     // Case 2: Target has one child | |||
|  |     else if (children == 1) { | |||
|  |       var newChild = target.left !== null ? target.left : target.right; | |||
|  |       if (parent === null) { | |||
|  |         target.value = newChild.value; | |||
|  |         target.left = null; | |||
|  |         target.right = null; | |||
|  |       } else if (newChild.value < parent.value) { | |||
|  |         parent.left = newChild; | |||
|  |       } else { | |||
|  |         parent.right = newChild; | |||
|  |       } | |||
|  |       target = null; | |||
|  |     } | |||
|  |     // Case 3: Target has two children | |||
|  |     // Only change code below this line | |||
|  |   }; | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | # --solutions--
 | |||
|  | 
 | |||
|  | ```js | |||
|  | // solution required | |||
|  | ``` |