The problem with normal tree structures is that they are unsorted and very unwieldy to work with. If you run a search for a file on your computer it’ll generally take a pretty long time, this is because your files are unsorted and it has to search through EVERYTHING to get a result. We can heavily improve this by upgrading how we organize the values in our trees.
普通树结构的问题在于它们没有排序并且很难使用。 如果您在计算机上搜索文件,通常会花费很长时间,这是因为文件没有排序,并且必须搜索所有内容才能获得结果。 我们可以通过升级在树中组织价值的方式来大大改善这一点。
先决条件 (Prerequisites)
You’ll need to know the basic concepts of trees, which you can learn here, we’re also going to need to do some very basic tree traversal with a breadth-first search, which you can brush up on here.
您需要了解树木的基本概念,您可以在这里学习它们,我们还需要使用广度优先搜索进行一些非常基本的树木遍历,您可以在此处进行深入研究 。
概念 (Concept)
A binary tree is just a normal tree with the limitation of each node only being able to, at most, have two children. A binary search tree just has the additional rule that if there’s two values then they need to be ordered, in our case from the lower number on the left to the higher on the right.
二叉树只是普通树,每个节点的限制最多只能有两个孩子。 二叉搜索树还有一个附加规则,即如果有两个值,则需要对它们进行排序,在我们的例子中,是从左侧的较低数字到右侧的较高数字。
Searching on a binary search tree is a large improvement on our original O(n)
search speed since now to find something we just need to compare what we want to each parent node before moving left or right until we’re at what we want, giving us O(logn)
for all operations.
从现在开始,在二叉搜索树上进行搜索对我们原始的O(n)
搜索速度进行了很大的改进,从而找到了一些东西,我们只需要在向左或向右移动之前将我们想要的内容与每个父节点进行比较,直到达到所需的位置,为所有操作提供O(logn)
。
创造 (Create)
Very similar to linked lists we can use classes to generate our nodes and tree. Each node only really needs a pointer to the left/less and right/greater sides, the value, and I personally like to add a counter since repeated values can only exist once in the tree.
与链接列表非常相似,我们可以使用类来生成节点和树。 每个节点实际上只需要一个指向左侧/左侧和右侧/较大侧的指针,该值,而我个人喜欢添加一个计数器,因为重复的值在树中只能存在一次。
After checking if there’s anything already there, we can use a nice little utility function to put our value to a side. Very simply, we just need to loop through the tree, if our value is less than the current node move left else move right, if there’s nothing there become the new node in that position, if a matching value’s already there then we can increment its counter.
在检查是否已经有任何东西之后,我们可以使用一个不错的小实用函数将我们的价值放在一边。 非常简单,我们只需要遍历树,如果我们的值小于当前节点,请向左移动,否则请向右移动,如果没有任何东西在该位置上成为新节点,如果已经有匹配的值,那么我们可以增加其值计数器。
class Node {
constructor(val) {
this.val = val;
this.right = null;
this.left = null;
this.count = 0;
};
};
class BST {
constructor() {
this.root = null;
}
create(val) {
const newNode = new Node(val);
if (!this.root) {
this.root = newNode;
return this;
};
let current = this.root;
const addSide = side => {
if (!current[side]) {
current[side] = newNode;
return this;
};
current = current[side];
};
while (true) {
if (val === current.val) {
current.count++;
return this;
};
if (val < current.val) addSide('left');
else addSide('right');
};
};
};
let tree = new BST();
tree.add(10);
tree.add(4);
tree.add(4);
tree.add(12);
tree.add(2);
console.log(tree);
找 (Find)
Finding something is incredibly simple, just move left or right relative to the current value and return true
if we hit something that matches.
查找内容非常简单,只需相对于当前值向左或向右移动,如果我们找到匹配的内容,则返回true
。
find(val) {
if (!this.root) return undefined;
let current = this.root,
found = false;
while (current && !found) {
if (val < current.val) current = current.left;
else if (val > current.val) current = current.right;
else found = true;
};
if (!found) return 'Nothing Found!';
return current;
};
删除 (Delete)
Deletion are the most complicated operation since we’re not working with just leafs but having to restructure, or “rebalance”, all of a node’s children. There are 2 conditions we have to check for, whether the node is a leaf and if it has children.
删除是最复杂的操作,因为我们不仅要处理叶子,还必须重组或“重新平衡”节点的所有子节点。 我们必须检查2个条件,即节点是否为叶子以及是否有子节点。
First we need a utility function to collect all of the deleted node’s children. I’ll use a basic breadth-first search to push everything into an array which we can then loop through to re-add each item to the tree.
首先,我们需要一个实用程序功能来收集所有已删除节点的子级。 我将使用基本的广度优先搜索将所有内容推送到一个数组中,然后我们可以循环遍历该数组以将每个项目重新添加到树中。
The only difference from a normal search is that it needs to accept a different starting point, so we can limit our search only to the subtree of our deleted node’s children.
与常规搜索的唯一区别是它需要接受一个不同的起点,因此我们只能将搜索限制为已删除节点的子代的子树。
BFS(start) {
let data = [],
queue = [],
current = start ? this.find(start) : this.root;
queue.push(current);
while (queue.length) {
current = queue.shift();
data.push(current.val);
if (current.left) queue.push(current.left);
if (current.right) queue.push(current.right);
};
return data;
}
Since we can’t traverse back up to the parent we’ll use a variable to store the parent node to current
and use that to set current
to null
after we’ve saved the children.
既然我们不能移备份到父,我们将使用一个变量的父节点存储current
并用它来设定current
到null
后,我们已经拯救了孩子。
In deleteNode
we’ll collect our node’s children, set it on its parent to null
, then use create
on each of the children, restructuring them properly in the tree. Our children array will also include the deleted node, which we can just splice out.
在deleteNode
我们将收集节点的子代,将其父代设置为null
,然后对每个子代使用create
,以在树中正确地重构它们。 我们的子级数组还将包含已删除的节点,我们可以将其拼接出来。
delete(val) {
if (!this.root) return undefined;
let current = this.root,
parent;
const pickSide = side => {
if (!current[side]) return 'No node found!';
parent = current;
current = current[side];
};
const deleteNode = side => {
if (current.val === val && current.count > 1) current.count--;
else if (current.val === val) {
const children = this.BFS(current.val);
parent[side] = null;
children.splice(0, 1);
children.forEach(child => this.create(child));
};
};
while (current.val !== val) {
if (val < current.val) {
pickSide('left');
deleteNode('left');
};
else {
pickSide('right');
deleteNode('right');
};
};
return current;
}
结论 (Conclusion)
This would have been the full CRUD operations, as any update is essentially just deleting one node and creating a whole new one somewhere else, which doesn’t really need its own method.
这本来应该是完整的CRUD操作,因为任何更新实质上都只是删除一个节点并在其他位置创建一个全新的节点,而这实际上并不需要它自己的方法。
We’ll be able to do some cooler stuff with binary search trees as we move into binary heaps and priority queues.
当我们进入二进制堆和优先级队列时,我们将能够使用二进制搜索树做一些更酷的事情。
翻译自: https://www.digitalocean.com/community/tutorials/js-binary-search-trees