二叉搜索树
BinaryTreeNode
import Comparator from '../utils/Comparator.js';
import HashTable from '../hash-table/HashTable.js';
export default class BinaryTreeNode {
constructor(value = null) {
this.left = null;
this.right = null;
this.parent = null;
this.value = value;
this.nodeComparator = new Comparator();
this.meta = new HashTable();
}
/**
* 左视高
* @return {number}
*/
get leftHeight() {
if (!this.left) {
return 0;
}
return this.left.height + 1;
}
/**
* 右视高
* @return {number}
*/
get rightHeight() {
if (!this.right) {
return 0;
}
return this.right.height + 1;
}
/**
* @return {number}
*/
get height() {
return Math.max(this.leftHeight, this.rightHeight);
}
/**
* 平衡因子: 某个结点的左子树的高度减去右子树的高度得到的差值。
* in AVL Tree
* @return {number}
*/
get balanceFactor() {
return this.leftHeight - this.rightHeight;
}
/**
* Get parent's sibling if it exists.
* @return {BinaryTreeNode}
*/
get uncle() {
if (!this.parent) {
return null;
}
if (!this.parent.parent) {
return null;
}
if (this.nodeComparator.equal(this.parent.parent.left, this.parent)) {
return this.parent.parent.right;
}
if (this.nodeComparator.equal(this.parent.parent.right, this.parent)) {
return this.parent.parent.left;
}
}
/**
* @param {BinaryTreeNode} sourceNode
* @param {BinaryTreeNode} targetNode
*/
static copyNode(sourceNode, targetNode) {
targetNode.value = sourceNode.value;
targetNode.setLeft(sourceNode.left);
targetNode.setRight(sourceNode.right);
}
setLeft(node) {
// Reset parent for left node since it is going to be detached.
if (this.left) {
this.left.parent = null;
}
// Attach new node to the left.
this.left = node;
// Make current node to be a parent for new left one.
if (this.left) {
this.left.parent = this;
}
return this;
}
setRight(node) {
if (this.right) {
this.right.parent = null;
}
this.right = node;
if (this.right) {
this.right.parent = this;
}
return this;
}
/**
* @param {BinaryTreeNode} nodeToRemove
* @return {boolean}
*/
removeChild(nodeToRemove) {
if (this.left && this.nodeComparator.equal(this.left, nodeToRemove)) {
this.left = null;
nodeToRemove.parent = null;
return true;
}
if (this.right && this.nodeComparator.equal(this.right, nodeToRemove)) {
this.right = null;
nodeToRemove.parent = null;
return true;
}
return false;
}
/**
* @param {BinaryTreeNode} nodeToReplace
* @param {BinaryTreeNode} replacementNode
* @return {boolean}
*/
replaceChild(nodeToReplace, replacementNode) {
if (!nodeToReplace || !replacementNode) {
return false;
}
if (this.left && this.nodeComparator.equal(this.left, nodeToReplace)) {
this.left = replacementNode;
replacementNode.parent = this;
nodeToReplace.parent = null;
return true;
}
if (this.right && this.nodeComparator.equal(this.right, nodeToReplace)) {
this.right = replacementNode;
replacementNode.parent = this;
nodeToReplace.parent = null;
return true;
}
return false;
}
/**
* 中序遍历(inorder)
* 即 left -> root -> right
* @return {*[]}
*/
traverseInOrder() {
let traverse = [];
// 左
if (this.left) {
traverse = traverse.concat(this.left.traverseInOrder());
}
// 中
traverse.push(this.value);
// 右
if (this.right) {
traverse = traverse.concat(this.right.traverseInOrder());
}
return traverse;
}
/**
* 先序遍历(preorder)
* 即 root -> left -> right
* @return {*[]}
*/
traversePreOrder() {}
/**
* 后序遍历(postorder)
* 即 left -> right -> root
* @return {*[]}
*/
traversePostOrder() {}
toString() {
return this.traverseInOrder().toString();
}
}
BinarySearchTreeNode
- tree/binary-search-tree/BinarySearchTreeNode.js
import BinaryTreeNode from '../BinaryTreeNode.js';
import Comparator from '../../utils/Comparator.js';
export default class BinarySearchTreeNode extends BinaryTreeNode {
constructor(value = null, compareFunction = undefined) {
super(value);
// This comparator is used to compare node values with each other.
this.compareFunction = compareFunction;
this.nodeValueComparator = new Comparator(compareFunction);
}
/**
* @param {*} value
* @return {BinarySearchTreeNode}
*/
insert(value) {
if (this.nodeValueComparator.equal(this.value, null)) {
// Insert root node
this.value = value;
return this;
}
if (this.nodeValueComparator.lessThan(value, this.value)) {
// Insert to the left.
if (this.left) {
return this.left.insert(value);
}
const newNode = new BinarySearchTreeNode(value, this.compareFunction);
this.setLeft(newNode);
return newNode;
}
if (this.nodeValueComparator.greaterThan(value, this.value)) {
// Insert to the right.
if (this.right) {
return this.right.insert(value);
}
const newNode = new BinarySearchTreeNode(value, this.compareFunction);
this.setRight(newNode);
return newNode;
}
}
/**
* @param {*} value
* @return {BinarySearchTreeNode}
*/
find(value) {
if (this.nodeValueComparator.equal(value, this.value)) {
return this;
}
if (this.nodeValueComparator.lessThan(value, this.value) && this.left) {
return this.left.find(value);
}
if (this.nodeValueComparator.greaterThan(value, this.value) && this.right) {
return this.right.find(value)
}
return null;
}
/**
* @param {*} value
* @return {boolean}
*/
contains(value) {
return !!this.find(value);
}
/**
* 1. 若为叶节点,即无孩子,其父指向它的指针设为 null;
* 2. 若一个孩子,其父指向它的指针改指向其孩子;
* 3. 若俩孩子,首先,要么找到其左子树的最大值,要么找到其右子树的最小值。
* 本文采用后者,后续步骤转到4;
* 4. 将该节点值替换为最小值,`递归地删除`右子树中最小值节点。
*
* @param {*} value
* @return {boolean}
*/
remove(value) {
const nodeToRemove = this.find(value);
if (!nodeToRemove) {
throw new Error('找不到节点 In the tree');
}
const { parent } = nodeToRemove;
if (!nodeToRemove.left && !nodeToRemove.right) {
// 1. 无孩子:Node is leaf & has no child
if (parent) {
parent.removeChild(nodeToRemove);
} else {
nodeToRemove.value = undefined;
}
return true;
} else if (!nodeToRemove.left || !nodeToRemove.right) {
// 2. 只有一个:孩子Node has only one child
const childNode = nodeToRemove.left || nodeToRemove.right;
if (parent) {
parent.replaceChild(nodeToRemove, childNode);
} else {
BinaryTreeNode.copyNode(childNode, nodeToRemove); // 根节点
}
} else {
// 3. 有两个孩子:Node has two children
const nextBiggerNode = nodeToRemove.right.findMin();
if (this.nodeComparator.equal(nextBiggerNode, nodeToRemove.right)) {
nodeToRemove.value = nodeToRemove.right.value;
nodeToRemove.setRight(nodeToRemove.right.right);
} else {
// 这里递归
this.remove(nextBiggerNode.value);
nodeToRemove.value = nextBiggerNode.value;
}
}
nodeToRemove.parent = null;
return true;
}
/**
* @return {BinarySearchTreeNode}
*/
findMin() {
if (!this.left) {
return this;
}
return this.left.findMin();
}
}
BinarySearchTree
- tree/binary-search-tree/BinarySearchTree.js
import BinarySearchTreeNode from './BinarySearchTreeNode.js';
/**
* 二叉查找树(BST)
* “A binary search tree is a binary tree in
* which data with lesser values are stored in left nodes
* and data with greater values are stored in right nodes.”
*/
export default class BinarySearchTree {
constructor(nodeValueCompareFunction) {
this.root = new BinarySearchTreeNode(null, nodeValueCompareFunction);
this.nodeValueComparator = this.root.nodeValueComparator;
}
insert(value) {
return this.root.insert(value);
}
remove(value) {
return this.root.remove(value);
}
toString() {
return this.root.toString();
}
/**
* Traverse by level
* @param {BinarySearchTreeNode} node
* @param {Function} visit
* @return {*[]}
*/
bfs(node, visit) {
const queue = [];
queue.push(node);
while(queue.length) {
const head = queue.shift();
visit && visit(head);
if (head.left) {
queue.push(head.left);
}
if (head.right) {
queue.push(head.right);
}
}
}
print() {
this.bfs(this.root, node => {
if (node.height > 0) {
const nodeVal = `${node.value}(${node.meta.get('color')})`;
const leftVal = node.left
? `${node.left.value}(${node.left.meta.get('color')})`
: 'NIL';
const rightVal = node.right
? `${node.right.value}(${node.right.meta.get('color')})`
: 'NIL';
console.log(`${nodeVal} -> ${leftVal} | ${rightVal}`);
}
});
}
}