阅读 193

JS数据结构之BST(二叉搜索树)

前言

二叉搜索树可以帮我们按照一定的规则存储数据,方便我们能够快速操作,最差的情况也是O(n)的时间复杂度。

代码实现

节点定义

class Node {
    constructor(key) {
    	this.key = key; //结点值
    	this.left = null; //左结点引用
    	this.right = null; //右结点引用
    }
}
复制代码

比较方法定义

const Compare = {
    LESS_THAN: -1,
    BIGGER_THAN: 1,
};
function defaultCompare(a, b) {
	if (a === b) {
	    return 0;
	}
	return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN; // {2}
}
复制代码

不同于在之前的其他数据结构中将节点本身称作节点或项,我们将会对二叉树中的结点称其为键(key),键是树相关的术语中对节点的称呼。

二叉树相关代码

class BinarySearchTree {
	constructor(compareFn = defaultCompare) {
		this.compareFn = compareFn; //用来比较节点值的函数
		this.root = null; //Node类型的根节点
	}
	//向二叉搜索树中插入一个键
        insert(key) {
            if (this.root == null) {
                this.root = new Node(key);
            } else {
                this.insertNode(this.root, key);
            }
        }
	insertNode(node, key) {
	        //要插入的键比当前遍历到的键小的情况,则开始在左子树中插入
		if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
			if (node.left == null) { 
			//如果左子树为空则插入到左子树否则继续遍历
				node.left = new Node(key);
			} else {
				this.insertNode(node.left, key);
			}
		} else {
		//否则在右子树插入
			if (node.right == null) {
			//如果右子树为空则插入到右子树否则继续遍历
				node.right = new Node(key);
			} else {
				this.insertNode(node.right, key);
			}
		}
	}
	//层次遍历
	levelOrder(root) { //从上到下bfs
		const queue = [];//bfs一般都是队列,因为不需要回溯
		const result = [];//保存结果的数组
		if (root != null) {
			queue.push(root);//把根节点存进去
		}
		while (queue.length != 0) {
			const level = [];//每一层的数据
			const len = queue.length;//当前层的元素个数
			for (let i = 0; i < len; i++) {
				const currentNode = queue.shift();
				level.push(currentNode.key);//添加当前层的键
				if (currentNode.left !== null) {
					queue.push(currentNode.left);//添加当前层的左子树
				}
				if (currentNode.right !== null) {
					queue.push(currentNode.right);//添加当前层的右子树
				}
				result.push(level);//把当前层的数据存到总数据中
            }
            return result//最后返回出去就是遍历后的结果
		}
	}
	//中序遍历[最小到最大的顺序访问所有节点]
	inOrderTraverse(node) {
		if (node != null) {
			this.inOrderTraverse(node.left);
			console.log(node.key);
			this.inOrderTraverse(node.right);
		}
	}
	//先序遍历 先访问自身结点 再去遍历左右
	preOrderTraverse(node) {
		if (node != null) {
			console.log(node.key);
			this.preOrderTraverse(node.left);
			this.preOrderTraverse(node.right);
		}
	}
	//后序遍历 先访问左子树,然后访问右子树, 再去遍历自身结点
	postOrderTraverseNode(node) {
		if (node != null) {
			this.postOrderTraverseNode(node.left);
			this.postOrderTraverseNode(node.right);
			console.log(node.key);
		}
	}
	//最小值
	min() {
		return this.minNode(this.root);
	}
	minNode(node) { //一直往最左侧寻找
		let current = node;
		while (current != null && current.left != null) {
			current = current.left;
		}
		return current;
	}
	max() {
		return this.maxNode(this.root);
	}
	maxNode(node) { //一直往最右侧寻找
		let current = node;
		while (current != null && current.right != null) {
			current = current.right;
		}
		return current;
	}
	//总结:寻找最大数就沿着最右边一直寻找 最小就沿着最左侧一直寻找
	//搜索特定的值
	search(key) {
		return this.searchNode(this.root, key);
	}
	searchNode(node, key) {
		if (node == null) {
			return false;
		}
		//如果要找的比当前遍历到的小,就去左子树找
		if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
			return this.searchNode(node.left, key);
		} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
		//如果要找的比当前遍历到的大,就去右子树找
			return this.searchNode(node.right, key);
		} else {
			return true;
		}
	}

	//remove 使二叉搜索树中最复杂的方法
	remove(key) {
		this.root = this.removeNode(this.root, key);
	}
	removeNode(node, key) {
		if (ndoe == null) { //如果正在检测的节点为 null ,那么说明键不存在于树中,所以返回 null 。
			return null;
		}
		if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
			/*如果要找的键比当前节点的值小
,就沿着树的左边找到下一个节点。*/
			node.left = this.removeNode(node.left, key); 
			return node; 
		} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
		//如果要找的键比当前节点的值大,
//那么就沿着树的右边找到下一个节点
			node.right = this.removeNode(node.right, key);
			return node; 
		} else {
		//如果我们找到了要找的键(键和 node.key 相等),就需要处理三种不同的情况    
            // 第一种情况 移除一个叶子节点
			if (node.left == null && node.right == null) {
				node = null; 
				return node; 
			}
			// 第二种情况 移除有一个左侧或右侧子节点的节点
			if (node.left == null) {
				node = node.right; 
				return node;
			} else if (node.right == null) {
				node = node.left; 
				return node;
			}
			// 第三种情况 移除有两个子节点的节点
			//当找到了要移除的节点后,需要找到它右边子树中最小的节点(它的后继 (当然也可以找他左子树中最大的结点,叫做他的前驱),这里我们找的是后继
			const aux = this.minNode(node.right); 
			//然后,用它右侧子树中最小节点的键去更新这个节点的值(行 {19} )。通过这一步,我们改变了这个节点的键,也就是说它被移除了。
			node.key = aux.key;
			// 但是,这样在树中就有两个拥有相同键的节点了,这是不行的。要继续把右侧子树中的最小节点移除,毕竟它已经被移至要移除的节点的位置了
			node.right = this.removeNode(node.right, aux.key); 
			//最后,向它的父节点返回更新后节点的引用
			return node; 
		}
	}
}
复制代码

结语

这样我们就实现了一个二叉搜索树BST

关注下面的标签,发现更多相似文章
评论