一种非顺序数据结构--树。
树是一种分层数据的抽象模型,一个树结构包含一系列存在父子关系的节点,每个节点都有一个父节点(除了顶部第一个节点)以及零个或多个子节点,位于树顶部的节点叫根节点。
二叉树和二叉搜索树
二叉树中的节点最多只能有两个节点,一个左侧的节点,一个右侧节点。
二叉搜索树(BST)是二叉树的一种,但是只允许在左侧储存比父节点小的数据,在右侧储存比父节点大的数据。
和链表一样,通过引用来表示节点之间的关系,在双向链表里,每个节点有俩引用,一个指向上一个节点,一个指向下一个节点,对于二叉搜索树也使用同样方式,不同的地方是一个指向左侧节点,一个指向右侧节点(树中会称节点为键)。
- insert(key) 树里插入键
- search(key) 树里查找一个键
- inOrderTraverse() 中序遍历所有(上行顺序遍历,先左,自身,右)
- preOrderTraverse() 先序遍历所有(先自身,左,右)
- postOrderTraverse() 后序遍历所有(先左,右,自身)
- min() 获取树最小值
- max() 获取树最大值
- remove(key) 删除树里某个键
中序,先序,后序,都是递归实现,描述可能不太具体,具体还是看代码把。
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;
}
class Node{
constructor(key){
this.key = key;
this.left = null;
this.right = null;
}
}
class BinarySearchTree{
constructor(compareFn = defaultCompare){
this.compareFn = compareFn;
this.root = null;
}
insert(key){
if(!this.root){
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){
node.left = new Node(key);
}else{
this.insertNode(node.left,key);
}
} else {
if(!node.right){
node.right = new Node(key);
}else{
this.insertNode(node.right,key);
}
}
}
search(key){
return this.searachNode(this.root,key);
}
searachNode(node,key){
if(!node){
return false;
}
if(this.compareFn(key,node.key)===Compare.LESS_THAN){
return this.searachNode(node.left,key);
}else if(this.compareFn(key,node.key)===Compare.BIGGER_THAN){
return this.searachNode(node.right,key);
}else{
return true;//可以根据自己需要返回
}
}
min(){
return this.minNode(this.root);
}
minNode(node){
let current = node;
while(current&¤t.left){
current = current.left;
}
return current;
}
max(){
return this.maxNode(this.root);
}
maxNode(node){
let current = node;
while(current&¤t.right){
current = current.right;
}
return current;
}
remove(key){
this.root = this.removeNode(this.root,key)
}
removeNode(node,key){
if(!node){
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{//找到了
if(!node.left&&!node.right){
node = null;
return node;
}
if(!node.left){
node = node.right;
return node;
}else if(!node.right){
node = node.left;
return node;
}
//第三种情况,移除的节点有两个子节点,先找到要移除节点右边子树里最小的节点,然后移到当前删除位置,然后这会会有两个相同的键(当前删除位置===右侧子树最小节点),所以把右侧子树最小节点删掉。
const aux = this.minNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right,aux.key);
return node;
}
}
inOrderTraverse(cb){
this.inOrderTraverseNode(this.root,cb);
}
inOrderTraverseNode(node,cb){
if(node){
this.inOrderTraverseNode(node.left,cb);
cb(node.key);
this.inOrderTraverseNode(node.right,cb);
}
}
preOrderTraverse(cb){
this.preOrderTraverseNode(this.root,cb)
}
preOrderTraverseNode(node,cb){
if(node){
cb(node.key);
this.preOrderTraverseNode(node.left,cb);
this.preOrderTraverseNode(node.right,cb);
}
}
postOrderTraverse(cb){
this.postOrderTraverseNode(this.root,cb);
}
postOrderTraverseNode(node,cb){
if(node){
this.postOrderTraverseNode(node.left,cb);
this.postOrderTraverseNode(node.right,cb);
cb(node.key);
}
}
}
const printNode = (value)=>console.log(value);//可以用此做回掉测试
二叉搜索树(BST)存在一个问题,在添加或删除后,可能会某一侧会很深,有的节点可能就几层,有的可能有很多层。
自平衡二叉树(AVL树)
AVL是一颗自平衡树,在添加或者移除节点的时候会尝试保证自平衡,任何一个节点(不论深度),左侧字树和右侧字数最多相差1。
AVL树需要根据节点高度来计算平衡因子,根据平衡因子来判断,是否需要旋转平衡。
计算一个节点高度的方法:getNodeHeight(node)
计算平衡因子方法:getBalanceFactor(node)
平衡因子的概念:对每个节点计算左子树和右子树的高度的差值,该值(left-right)应为0,1,-1,如果结果不是这三个值之一,则需要平衡AVL树。
LL:向右的单旋转
RR:向左的单旋转
LR:先左后右,先RR转再LL转,这种情况出现于不平衡节点的左侧节点高度大于右侧节点高度,并且左侧节点右侧较重,这种情况可以先对左侧节点进行左旋转修复,然后在对不平衡节点进行右旋转修复。
RL:先右后左,先LL转再RR转,这种情况出现于不平衡节点的右侧节点高度大于左侧节点高度,并且右侧节点左侧较重,这种情况可以先对右侧节点进行右旋转修复,然后对不平衡节点进行一个左旋转修修复。
const BalanceFactor = {//平衡因子的常量
UNBALANCED_RIGHT:1,
SLIGHTLY_UNBALANCED_RIGHT:2,
BALANCED:3,
SLTGHTLY_UNBALANCED_LEFT:4,
UNBALANCED_LEFT:5
}
class AVLTree extends BinarySearchTree{
constructor(compareFn=defaultCompare){
super(compareFn);
this.compareFn=compareFn;
this.root = null;
}
getNodeHeight(node){
if(!node){
return -1
}
return Math.max(this.getNodeHeight(node.left),
this.getNodeHeight(node.right))+1
}
getBalanceFactor(node){
const heightDifference = this.getNodeHeight(node.left)-this.getNodeHeight(node.right);
switch(heightDifference){
case -2:
return BalanceFactor.UNBALANCED_RIGHT;
case -1:
return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT;
case 1:
return BalanceFactor.SLTGHTLY_UNBALANCED_LEFT;
case 2:
return BalanceFactor.UNBALANCED_LEFT;
default:
return BalanceFactor.BALANCED;
}
}
rotationLL(node){//右单旋转
const tmp = node.left;
node.left = tmp.right;
tmp.right = node;
return node;
}
rotationRR(node){//左单旋转
const tmp = node.right;
node.right = tmp.left;
tmp.left = node;
return tmp;
}
rotationLR(node){
node.left = this.rotationRR(node.left);
return this.rotationLL(node);
}
rotationRL(node){
node.right = this.rotationLL(node.right);
return this.rotationRR(node);
}
insert(key){
this.root = this.insertNode(this.root,key)
}
insertNode(node,key){
if(!node){
return new Node(key);
}else if(this.compareFn(key,node.key)===Compare.LESS_THAN){
//向左继续
node.left = this.insertNode(node.left,key);
}elso if(this.compareFn(key,node.key)===Compare.BIGGER_THAN){
//向右继续
node.right = this.insertNode(node.right,key);
}else{//不比当前key大也不比当前key小,等于当前重复
return node;//重复的键
}
//递归插入完成后,根据函数依次弹栈,来计算当前的节点的平衡因子,查看是否失衡
const balanceFactor = this.getBalanceFactor(node);
if(balanceFactor === BalanceFactor.UNBALANCED_LEFT){//等于2,左侧子节点失衡
if(this.compareFn(key,node.left.key)===Compare.LESS_THAN){//查看插入的节点是插到了当前节点左子节点的下左侧,符合LL,详细可以配上面图看
node = this.rotationLL(node);
}else {//右,符合LR
return this.rotationLR(node);
}
}
if(balanceFactor === BalanceFactor.UNBALANCED_RIGHT){//-2右侧失衡
if(this.compareFn(key,node.right.key)===Compare.BIGGER_THAN){//查看插入的节点是插到了当前节点右子节点的下右侧,符合RR,详细可以配上面图看
node = this.rotationRR(node);
}else{//下左,符合RL
return this.rotationRL(node);
}
}
return node;
}
removeNode(node){
node = super.removeNode(node,key);
if(!node){
return node;
}
const balanceFactor = this.getBalanceFactor(node);
if(balanceFactor === BalanceFactor.UNBALANCED_LEFT){//左子节点失衡
const balanceFactorLeft = this.getBalanceFactor(node.left);
//判断左边是那种情况,是进行LL转还是LR转
if(balanceFactorLeft===BalanceFactor.SLTGHTLY_UNBALANCED_LEFT||balanceFactorLeft.BALANCED){//差值为1或者其他情况的时候进行符合LL
return this.rotationLL(node);
}
if(balanceFactorLeft===BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT){//左侧差值为-1,符合LR
return this.rotationLR(node);
}
}
if(balanceFactor === BalanceFactor.UNBALANCED_RIGHT){//差值为-2,右侧子节点不平衡
const balanceFactorRight = this.getBalanceFactor(node.right);
if(balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT||balanceFactorRight===BalanceFactor.BALANCED){//和左相反,差值-1或其他符合RR
return return this.rotationRR(node);
}
if(balanceFactorRight === BalanceFactor.SLTGHTLY_UNBALANCED_LEFT){//差值为1符合RL
return this.rotationRL(node);
}
}
return node;
}
}
红黑树
最先发明的平衡二叉查找树是AVL树(它严格符合平衡二叉查找树的定义,即任何节点的左右子树高度相差不超过 1,是一种高度平衡的二叉查找树。
)但是在工程中,我们经常听到的通常是红黑树
,而不是AVL树.那么为什么工程中都喜欢用红黑树,而不是其他平衡二叉查找树呢?
其实在这里,我们应该能有一些想法了.既然他严格按照规定执行,每次的插入,删除,都就会引发树的调整.调整的多了,自然会影响树的效率.那么红黑树又是怎样解决这个问题的呐?
其实,平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些。
所以红黑树就是这种设计思路(近似平衡)了.
如果我们需要一个包含多次插入和删除的自平衡树,红黑树是比较好的,如果插入和删除频率较低,更多是进行搜索操作,那么AVL树比红黑树更好。
在红黑树中,每个节点遵循以下规则。
- 每个节点不是红的就是黑的。
- 树的根节点都是黑的。
- 所有叶节点都是黑的([注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!])
- 如果一个节点是红的,那么他的两个子节点都是黑的。
- 不能有两个相邻的红节点,一个红节点不能有红的父节点或子节点也就是说,红色节点是被黑色节点隔开的。
- 从给定的节点到他的后代节点(null节点)的所有路径包含相同数量的黑色节点。
和AVL树一样,在进行节点的插入和删除时,就会破坏红黑树的一些规则
.在红黑树中主要破坏的是以下两点:
- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;
- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;
插入
红黑树规定,插入的节点必须是红色的。而且,二叉查找树中新插入的节点都是放在叶子节点上
。所以,关于插入操作的平衡调整,有这样两种特殊情况,但是也都非常好处理。
- 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。
- 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。
- 其他:都会违背红黑树的定义,于是我们就需要进行调整,调整的过程包含两种基础的操作:左右旋转和改变颜色。
const Color = {
RED:'red',
BLACK:'black'
}
class RedBlackNode extends Node{
constructor(key){
this.key = key;
this.color = Color.RED;
this.parent = null;
}
isrRed(){
return this.color === Color.RED;
}
}
class RedBlackTree extends BinarySearchTree{
constructor(compareFn = defaultCompare){
super(compareFn);
this.compareFn = compareFn;
this.root = null;
}
insert(key){
if(!this.root){
this.root = new RedBlackNode(key);
this.root.color = Colors.BLACK;
}else{
const newNode = this.insertNode(this.root,key);
this.fixTreeProperties(newNode);
}
}
insertNode(node,key){
if(this.compareFn(key,node.key)===Compare.LESS_THAN){
if(!node.left){
node.left = new RedBlackNode(key);
node.left.parent = node;
return node.left;
}else{
return this.insertNode(node.left,key);
}
}else if(this.compareFn(key,node.key)===Compare.BIGGER_THAN){
if(!node.right){
node.right = new RedBlackNode(key);
node.right.parent = node;
return node.right;
}else{
return this.insertNode(node.right,key);
}
}else{//相同
node.key = key; //key万一是复杂对象。
return node;
}
}
fixTreeProperties(node){
while(node&&node.parent&&node.parent.isRed()&&node.color===!==Color.BLACK){
let parent = node.parent;
const grandParent = parent.parent;
if(grandParent&&grandParent.left===parent){
//父节点是左侧子节点
const uncle = grandParent.right;
if(uncle&&uncle.color === Color.RED){
//左侧叔父节点是红色,重新填色,隔开颜色
grandParent.color = Color.RED;
uncle.color = Color.BALCK;
parent.color= Color.BALCK;
node = grandParent;
}else{
//节点是右侧子节点-左旋转
if(node === parent.right){
this.rotationRR(parent);
node = parent;
parent = node.parent;
}
//节点是左侧子节点-右旋转
this.rotationLL(grandParent);
parent.color = Color.BLACK;
grandParent.color = Color.RED;
node = parent;
}
}else{//父节点是右侧节点
const uncle = grandParent.left;
//左侧叔父节点是红色,重新填色,隔开颜色
if(uncle&&uncle.color === Color.RED){
grandParent.color = Color.RED;
uncle.color = Color.BALCK;
parent.color= Color.BALCK;
node = grandParent;
}else{
if(node === parent.left){
//节点是左侧子节点-右旋转
this.rotationLLarent);
node = parent;
parent = node.parent;
}
//节点是右侧子节点旋转
this.rotationRRrandParent);
parent.color = Color.BLACK;
grandParent.color = Color.RED;
node = parent;
}
}
}
this.root.color = Color.Black;
}
rotationLL(node){//多了一步设置对应父级
const tmp = node.left;
node.left = tmp.right;
if(tmp.right&&tmp.right.key){
tmp.right.parent = node;
}
tmp.parent = node.parent;
if(!node.parent){//顶级了
this.root =tmp;
}else{
if(node === node.parent.left){
node.parent.left = tmp;
}else{
node.parent.right = tmp;
}
}
tmp.right = node;
node.parent = tmp;
}
rotationRR(node){//左旋转
const tmp = node.right;
node.right = tmp.left;
if(tmp.left&&tmp.left.key){
tmp.left.parent = node;
}
tmp.parent = node.parent;
if(!node.parent){
this.root = node;
}else{
if(node === node.parent.left){
node.parent.left=tmp;
}else{
node.parent.right = tmp;
}
}
tmp.left = node;
node.parent = tmp;
}
}
做个笔记,欢迎指出错误,该内容借鉴与学习javascript数据结构与算法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。