JavaScript树(二) 二叉树搜索
TypeScript方式实现源码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // 二叉树与二叉树搜索 2 class Node { 3 key; 4 left; 5 right; 6 constructor(key) { 7 this.key = key; 8 this.left = null; 9 this.right = null; 10 } 11 } 12 class BinarySearchTree { 13 root = null; 14 public insert(key) { 15 let newNode = new Node(key); 16 17 if (this.root === null) { 18 this.root = newNode; 19 } else { 20 this.insertNode(this.root, newNode); 21 } 22 } 23 private insertNode(node, newNode) { 24 if (newNode.key < node.key) { 25 if (node.left === null) { 26 node.left = newNode; 27 } else { 28 this.insertNode(node.left, newNode); 29 } 30 } else { 31 if (node.right === null) { 32 node.right = newNode; 33 } else { 34 this.insertNode(node.right, newNode); 35 } 36 } 37 } 38 public search(key) { 39 return this.searchNode(this.root, key); 40 } 41 private searchNode(node, key) { 42 if (node === null) { 43 return false; 44 } 45 if (key < node.key) { 46 return this.searchNode(node.left, key); 47 } else if (key > node.key) { 48 return this.searchNode(node.right, key); 49 } else { 50 return true; 51 } 52 } 53 /** 54 * 中序遍历 55 */ 56 public inOrderTraverse(callback) { 57 this.inOrderTraverseNode(this.root, callback); 58 } 59 private inOrderTraverseNode(node, callback) { 60 if (node !== null) { 61 this.inOrderTraverseNode(node.left, callback); 62 callback(node.key); 63 this.inOrderTraverseNode(node.right, callback); 64 } 65 } 66 /** 67 * 先序遍历 68 */ 69 public preOrderTraverse(callback) { 70 this.preOrderTraverseNode(this.root, callback) 71 } 72 private preOrderTraverseNode(node, callback) { 73 if (node !== null) { 74 callback(node.key); 75 this.preOrderTraverseNode(node.left, callback); 76 this.preOrderTraverseNode(node.right, callback); 77 } 78 } 79 /** 80 * 后序遍历 81 */ 82 public postOrderTraverse(callback) { 83 this.postOrderTranverseNode(this.root, callback) 84 } 85 private postOrderTranverseNode(node, callback) { 86 if (node !== null) { 87 this.postOrderTranverseNode(node.left, callback); 88 this.postOrderTranverseNode(node.right, callback); 89 callback(node.key); 90 } 91 } 92 public min() { 93 94 } 95 private minNode(node) { 96 if (node) { 97 while (node && node.left !== null) { 98 node = node.left; 99 } 100 return node.key; 101 } 102 return null; 103 } 104 public max(node) { 105 if (node) { 106 while (node && node.right !== null) { 107 node = node.right; 108 } 109 return node.key; 110 } 111 return null; 112 } 113 public remove(key) { 114 this.root = this.removeNode(this.root, key); 115 } 116 private removeNode(node, key) { 117 if (node === null) { 118 return null; 119 } 120 if (key < node.key) { 121 node.left = this.removeNode(node.left, key); 122 return node; 123 } else if (key > node.key) { 124 node.right = this.removeNode(node.right, key); 125 return node; 126 } else { // 建等于node.key 127 // 第一种情况——一个叶节点 128 if (node.left === null && node.right === null) { 129 node = null; 130 return node; 131 } 132 133 // 第二种情况——一个只有一个子节点的节点 134 if (node.left === null) { 135 node = node.right; 136 return node; 137 } else if (node.right === null) { 138 node = node.left; 139 return node; 140 } 141 142 // 第三种情况——一个只有两个子节点的节点 143 let aux = findMinNode(node.right); 144 node.key = aux.key; 145 node.right = this.removeNode(node.right, aux.key); 146 return node; 147 } 148 } 149 } 150 function printNode(value) { 151 console.log(value); 152 } 153 let tree = new BinarySearchTree(); 154 tree.insert(11); 155 tree.insert(7); 156 tree.insert(15); 157 tree.insert(5); 158 tree.insert(3); 159 tree.insert(9); 160 tree.insert(8); 161 tree.insert(10); 162 163 tree.insert(13); 164 tree.insert(12); 165 tree.insert(14); 166 tree.insert(20); 167 tree.insert(18); 168 tree.insert(25); 169 tree.insert(6); 170 171 // tree.inOrderTraverse(printNode); 172 // tree.preOrderTraverse(printNode); 173 tree.postOrderTraverse(printNode); 174 175 console.log(tree.search(1) ? 'Key 1 found.' : 'Key 1 not found.'); 176 console.log(tree.search(8) ? 'Key 8 found.' : 'Key 8 not found.');
二叉树和二叉搜索树
二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。这些定
义有助于我们写出更高效的向/从树中插入、查找和删除节点的算法。二叉树在计算机科学中的
应用非常广泛。
二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值,
在右侧节点存储(比父节点)大(或者等于)的值。上一节的图中就展现了一棵二叉搜索树。
二叉搜索树将是我们在本章中要研究的数据结构。
下图展现了二叉搜索树数据结构的组织方式:
和链表一样,将通过指针来表示节点之间的关系(术语称其为边) 。在双向链表中,每个节
点包含两个指针,一个指向下一个节点,另一个指向上一个节点。对于树,使用同样的方式(也
使用两个指针) 。但是,一个指向左侧子节点,另一个指向右侧子节点。因此,将声明一个Node
类来表示树中的每个节点(行{1}) 。值得注意的一个小细节是,不同于在之前的章节中将节点
本身称作节点或项,我们将会称其为键。键是树相关的术语中对节点的称呼。
我们将会遵循和LinkedList类中相同的模式(第5章) ,这表示也将声明一个变量以控制此
数据结构的第一个节点。在树中,它不再是头节点,而是根元素(行{2}) 。
然后,我们需要实现一些方法。下面是将要在树类中实现的方法。
insert(key):向树中插入一个新的键。
search(key):在树中查找一个键,如果节点存在,则返回true;如果不存在,则返回false。
inOrderTraverse:通过中序遍历方式遍历所有节点。
preOrderTraverse:通过先序遍历方式遍历所有节点。
min:返回树中最小的值/键。
max:返回树中最大的值/键。
remove(key):从树中移除某个键。
我们将在后面的小节中实现每个方法。
向树中插入一个键
要向树中插入一个新的节点(或项) ,要经历三个步骤。
第一步是创建用来表示新节点的Node类实例(行{1}) 。只需要向构造函数传递我们想用来
插入树的节点值,它的左指针和右指针的值会由构造函数自动设置为null。
第二步要验证这个插入操作是否为一种特殊情况。 这个特殊情况就是我们要插入的节点是树
的第一个节点(行{2}) 。如果是,就将根节点指向新节点。
第三步是将节点加在非根节点的其他位置。
树的遍历
遍历一棵树是指访问树的每个节点并对它们进行某种操作的过程。但是我们应该怎么去做
呢?应该从树的顶端还是底端开始呢?从左开始还是从右开始呢?访问树的所有节点有三种方
式:中序、先序和后序。
中序遍历
中序遍历是一种以上行顺序访问BST所有节点的遍历方式, 也就是以从最小到最大的顺序访
问所有节点。中序遍历的一种应用就是对树进行排序操作。
this.inOrderTraverse = function(callback){ inOrderTraverseNode(root, callback); //{1} };
inOrderTraverse方法接收一个回调函数作为参数。回调函数用来定义我们对遍历到的每
个节点进行的操作(这也叫作访问者模式,要了解更多关于访问者模式的信息,请参考http://en.
wikipedia.org/wiki/Visitor_pattern) 。由于我们在BST中最常实现的算法是递归,这里使用了一个
私有的辅助函数,来接收一个节点和对应的回调函数作为参数(行{1}) 。
var inOrderTraverseNode = function (node, callback) { if (node !== null) { //{2} inOrderTraverseNode(node.left, callback); //{3} callback(node.key); //{4} inOrderTraverseNode(node.right, callback); //{5} } };
要通过中序遍历的方法遍历一棵树,首先要检查以参数形式传入的节点是否为null(这就
是停止递归继续执行的判断条件——行{2}——递归算法的基本条件) 。
然后,递归调用相同的函数来访问左侧子节点(行{3}) 。接着对这个节点进行一些操作
(callback) ,然后再访问右侧子节点(行{5}) 。
我们试着在之前展示的树上执行下面的方法:
function printNode(value){ //{6} console.log(value); } tree.inOrderTraverse(printNode); //{7}
但首先,需要创建一个回调函数(行{6}) 。我们要做的,是在浏览器的控制台上输出节点
的值。然后,调用inOrderTraverse方法并将回调函数作为参数传入(行{7}) 。当执行上面的
代码后,下面的结果将会在控制台上输出(每个数字将会输出在不同的行) :
3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
下面的图描绘了inOrderTraverse方法的访问路径: