JavaScript数据结构——树Tree

5.1 ——简述

二叉树、AVL树、红黑树
根节点: 位于数顶部的节点;
内部节点: 存在子元素的节点;
外部节点: 没有子元素的节点;
树的深度: 节点的深度取决于它的祖先节点的数量;
树的高度: 所有节点深度的最大值;
森林:多颗子树;
二叉树:最多只能有两个子节点,一个是左侧子节点,另一个是右侧子节点;
二叉搜索树(BST): 左侧节点存储比父节点小的值,右侧存储比有节点大的值;
对于树的操作,递归很nice,用递归则必须要清楚退出条件;

5.2 ——BinarySearchTree 二叉搜索树

5.3:树节点 Tree Node

树的节点通过指针(引用)来表示节点之间的关系,一个指向左子节点,另一个指向右子节点,默认是指向null的,表示没有子节点,使用element来存储值

class Node {
	constructor() {
		this.element= element; //值
		this.left = null; //左指针
		this.right = null; //右指针
	}
}

5.4:二叉搜索树 BinarySearchTree

声明一个变量来控制tree的根节点root

class BinarySearchTree {
	constructor() {
        this.root = null; //Node类型的根节点
	}
}

5.5:Methods

5.5.1:insert 插入节点

使用递归来操作树不要太香,
首先进行越界判断,如果根节点为空则表示树为空,传入的值可直接作为根节点插入,它的左右指针会自动设置为null,如果不为空这向下递归调用insertNode方法;
insertNode方法接收两个参数,当前节点以及要插入的值value;首先比较当前节点的值与value,由于二叉树左边的值始终小于当前节点,判断value的值是否小于当前的值,且当前值的左指针不为null,则继续向左查找,直到找到指针为空的节点,将value的节点至于该节点的左侧,如果value的值大于当前节点的值,同理往右查找放置节点;

insert(value) {
    if (this.root == null) {
        this.root = new Node(value); //创建一个节点存储元素
    } else {
        this.insertNode(this.root, value); //递归插入key
    }
}
insertNode(node, value) {
	//如果value比当前的值element小
    if (node.element > value) {
    	//左子节点不为空
        if (node.left == null) {
        	//连接左子节点
            node.left = new Node(element)
        } else {
        	//不为空继续向下遍历
            this.insertNode(node.left, element);
        }
    } else {
        if (node.right == null) {
            node.right = new Node(element)
        } else {
            this.insertNode(node.right, element);
        }
    }
}
5.5.2:traverse 遍历方法

这里需要传入一个回调函数callback用来处理数据,回调函数接收遍历的值;
树的遍历方法有三种,先序,中序,后序遍历;
先序遍历:以优先于后代节点的顺序访问每一个节点,它会先访问节点本身,然后在访问节点的左侧,左侧访问完再访问右侧;
中序遍历:从最小到最大的顺序访问所有节点,先访问左子节点,再对每个根节点执行回调函数,最后访问右节点;
后序遍历:先访问所有的左右节点,在访问节点本身;

traverse(callBack) {
	//传入根节点,以及cb回调函数
	this.traverseNode(this.root, callBack);
}
//先序遍历
traverseNode(node, callBack) {
	//越界判断
	if (node != null) {
		callBack(node.element);
		this.inOrderTraverseNode(node.left, callBack);
		this.inOrderTraverseNode(node.right, callBack);
	}
}
//中序遍历
traverseNode(node, callBack) {
	//越界判断
	if (node != null) {
		this.inOrderTraverseNode(node.left, callBack);
		callBack(node.element);
		this.inOrderTraverseNode(node.right, callBack);
	}
}
//后序遍历
traverseNode(node, callBack) {
	//越界判断
	if (node != null) {
		this.inOrderTraverseNode(node.left, callBack);
		this.inOrderTraverseNode(node.right, callBack);
		callBack(node.element);
	}
}
//callback函数
const cb = function(value) {
	console.log(value)
}
5.5.3:min/max 获取树中最小值和最小值

在二叉树中,最小值总是在树最后一层的最左边,最大值则在最右边;
要想获取树的最小值,只需从根节点一直往左遍历,直到找到node的左节点为null的节点,即目标节点,获取最大值同理

/**
* @获取最小值
* */
min() {
    return this.minNode(this.root);
}
minNode(node) {
    let min = node; //标记当前节点
    //遍历tree,如果当前节点不为空,或left指针不为空
    while (min != null && min.left != null) {
        min = min.left; //继续查找
    }
    //退出循环即找到最后一个节点,直接返回出去供调用者接收
    return min;
}
/**
* @获取最大值
* */
max() {
    return this.maxNode(this.root);
}
maxNode(node) {
    let max = node;
    while (max != null && max.right != null) {
        max = max.right;
    }
    return max;
}
5.5.4:searchNode 搜索特定的值

search方法接收一个值value,在树种查找value,如果存在则返回true,不存在返回false;
当遍历到节点的指针为null表示已经找到了树的最后一层。此时返回false没有找到;

search(value) {
    return this.searchNode(this.root, value);
}
searchNode(node, value) {
	//如果当前节点为空(传进来的node.left == null)
    if (node == null) {
    	//查找完毕依旧没有找到,返回false
        return false;
    }
    //对比当前节点的值,是否从左或右查找
    if (node.element > value) {
    	//如果遍历到最后一层,node.left会传入null
        return this.searchNode(node.left, value);
    } else if (node.element< value) {
        return this.searchNode(node.right, value);
    } else {
    	//返回每一次查找的结果,一旦找到直接返回true
        return true;
    }
}
5.5.5:remove 移除节点

移除操作需要画图看得清…
首先定义递归退出条件,当遍历完整颗树依然没有发现value,则返回null;
递归调用removeNode,依次从根节点左右查找,

  1. 如果目标节点无子节点,直接将node置空;
  2. 如果目标节点只有一侧有子节点(左子节点或右子节点),直接将目标节点的引用改为它子节点的引用,并返回更新后的节点
  3. 如果目标节点同时存在左右节点,则需要以目标节点作为根节点,找到该树右侧的最小节点替换目标节点,然后移除那个最小节点
remove(value) {
    this.root = this.removeNode(this.root, value);
}
removeNode(node, value) {
    //未找到要移除的节点 返回null
    if (node == null) {
        return null;
    }
    //compare
    if (node.element> value) {
        //当前节点的值比目标值大, 继续往左找
        node.left = this.removeNode(node.left, value);
        return node;
    } else if (node.element< value) {
        //当前节点的key比目标key小, 继续往右找
        node.right = this.removeNode(node.right, value);
        return node;
    } else {
        //找到了 判断目标节点是否存在子节点
        //无子节点
        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;
        }
        //同时存在左右子节点
        //找到右子节点树的最小节点,更新当前节点的key为最小节点的key
        const aux = this.minNode(node.right);
        node.key = aux.key;
        //移除最小节点
        node.right = this.removeNode(node.right, aux.element);
        return node;
    }
}

5.6:AVL 自平衡树

接下来直接写注释了,不画图很绕

5.6.1:工具箱
//计数器,用来处理平衡因子的数值
 const BalanceFactor = {
    UNBALANCED_RIGHT: 1,
    SLIGHTLY_UNBALANCED_RIGHT: 2,
    BALANCED: 3,
    SLIGHTLY_UNBALANCED_LEFT: 4,
    UNBALANCED_LEFT: 5
}
5.6.2:Adelson-Velskii-Landi
class AVLTree extends BinarySearchTree {
    constructor() {
        this.root = null;
    }
}
5.6.3:getNodeHeight 或许AVL树的高度

左子树的最大深度L 与 右子树的最大深度R 获取最大深度满足公式:
`max(L, R) + 1;

getNodeHeight(node) {
    //递归 子节点为null退出
    if (node == null) {
        return 0;
    }
    return Math.max(
        //左右两边比较,取最大值
        this.getNodeHeight(node.left), 
        this.getNodeHeight(node.right)
    ) + 1;
}
5.6.4:getBalanceFactor

获取一个节点node的平衡因子,计算左右节点树的高度差

getBalanceFactor(node) {
    const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
    switch(heightDifference) {
        case -2:
            return BalanceFactor.UNBALANCED_RIGHT; //右节点多于左节点 1
        case -1:
            return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT; // 2
        case 1:
            return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT; //4
        case 2:
            return BalanceFactor.UNBALANCED_LEFT; // 5
        default:
            return BalanceFactor.BALANCED; // 3
    }
}
5.6.5:rotationLL 左-左旋转
/**
 * @desc 左-左旋转 LL:向右的单旋转
 * @param { root } 根节点
 * @return { Node } 新的根节点
 */
rotationLL(node) {
    const tmp = node.left; //标记root的左侧节点 (新的根节点)
    node.left = tmp.right; //将root的右节点替换为左子节点的右节点
    tmp.right = node; //将标记的右节点连接node
    return tmp; //标记的节点作为根节点返回
}
5.6.6:rotationRR 右-右旋转
/**
 * @desc 右-右 RR: 向右的单旋转
 * @param { root } 根节点
 * @return { node } 新的根节点
 */
rotationRR(node) {
    const temp = node.right;
    node.right = temp.left;
    temp.left = node;
    return temp;
}
5.6.7:rotationLR 左-右旋转
/**
 * @desc 左-右 LR: 向右的双旋转 (左侧的高度大于右侧的高度)
 * @param { root } 根节点
 * @return { Node } 新的根节点
 */
rotationLR(node) {
    node.left = this.rotationRR(node.left);
    return this.rotationLL(node);
}
5.6.8:rotationLR 左-右旋转
/**
 * @desc 右-左 RL: 向左的双循环 (右侧的高度大于左侧的高度)
 * @param { root  } 根节点
 * @return { Node } 新的根节点
 */
rotationRL(node) {
    node.right = this.rotationLL(node.right);
    return this.rotationRR(node);
}
5.6.9:insert AVL树插入节点

AVL树的插入操作与二叉树一致,不同在于,AVL树插入节点后需要检查树是否平衡,不平衡需要进行旋转操作

/**
 * @param { key } 要插入的值
 * @return { Node } 
 */
insert(key) {
    this.root = this.insertNode(this.root, key);
};
insertNode(node, key) {
    //遍历插入节点
    if (node == null) {
        return new Node(key); //如果数空,直接返回要插入值的节点
    } else if (node.element > key) {
        node.left = this.insertNode(node.left, key);
    } else if (node.element < key) {
        node.right = this.insertNode(node.right, key);
    } else {
        return node;
    }
    const balanceFactor = this.getBalanceFactor(node); //计算左右节点的高度差
    //如果左侧高
    if (balanceFactor == BalanceFactor.UNBALANCED_LEFT) {
        //判断 如果要插入的值小于左子节点,则说明往左插 左侧多了,进行左左单循环,右侧多了则进行左-右双循环
        if (node.left.element > key) {
            node = this.rotationLL(node);
        } else {
            return  this.rotationLR(node);
        }
    };
    //右侧高
    if (balanceFactor = BalanceFactor,UNBALANCED_RIGHT) {
        if (node.right.element < key) {
            node = this.rotationRR(node);
        } else {
            return this.rotationRL(node);
        }
    }
    return node;
}
5.6.10:removeNode AVL树移除元素

移除操作与二叉树也是一样的,移除后检查树是否平衡

/**
 * @desc 移除节点
 * @param { Node }
 * @return { Node }
 */
removeNode(node, key) {
    node = super.removeNode(Node, key); //继承二叉树的移除操作的方法
    if (node == null) {
        return node;
    };
    //检查树是否平衡
    //获取节点左右差值
    const balanceFactor = this.getBalanceFactor(node);
    // 如果左边多2
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
        //让根节点的左子节点作为根节点继续计算差值
        const balanceFactorLeft = this.getBalanceFactor(node.left);
        //如果是左子节点的左侧多,则进行左-左单旋转
        if (balanceFactorLeft === BalanceFactor.BALANCED || balanceFactorLeft === BalanceFactorSLIGHTLY_UNBALANCED_LEFT) {
            return this.rotationLL(node);
        }
        // 如果右侧多,则进行左-右双循环
        if (balanceFactorLeft === balanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
            return this.rotationLR(node.left);
        }
    }
    
    if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
        const balanceFactorRight = this.getBalanceFactor(node.right);
        if (balanceFactorRight === BalanceFactor.BALANCED || balanceFactorRight === BalanceFactorSLIGHTLY_UNBALANCED_RIGHT) {
            return this.rotationLR(node);
        }
        if (balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
            return this.rotationRL(node.right);
        }
    }
};

5.7:RedBlackTree 红黑树

红黑树规则:
1.每个节点不是黑色就是红色
2. 树的根节点是黑的
3. 所有叶子节点都是黑色的
4. 如果一个节点是红的,那么它的两个子节点都是黑的
5. 不能有两个相邻的红节点,一个红节点不能有红的父节点或子节点
6. 从给定的节点到它的后代节点的所有路径包含相同数量的黑色节点

5.7.1:RedBlackNode 红黑树节点

相对于二叉树,红黑树节点多了一个指向父节点的指针, 还有一个颜色属性,默认都为红色

class RedBlackNode {
    constructor(val) {
        this.val = val;
        this.left = null;
        this.right = null;
        this.color = Colors.RED; //创建的节点默认为红色
        this.parent = null; //指向父节点的引用
    }
    //验证是否为红节点,返回boolean
    isRed() {
        return this.color === Colors.RED;
    }
}
5.7.1:RedBlackTree 红黑树
class RedBlackTree {
    constructor() {
        this.root = null;
    };
}
5.7.2:fixTreeProperties 修复操作

插入节点后,要进行两个操作:重新填色,旋转 (旋转操作使用AVL树的方法)
如果要插入节点的颜色与其父节点相同,则需要 依次向上修改
如果要插入节点父节点的颜色为红色,需要改变父节点,祖父节点和父节点的相邻节点

fixTreeProperties(node) {
    while (node && node.parent && node.parent.color.isRed() && node.color !== Colors.BLACK) {
        //标记父节点以及父节点的上一级节点
        let parent = node.parent;
        const grandParent = parent.parent;
        //父节点是上一级节点的左子节点
        if (grandParent && grandParent.left === parent) {
            //标记其右节点
            const uncleR = grandParent.right;
            //如果目标节点的上一级节点都是红色的
            if (uncleR && uncleR.color === Colors.RED) {
                grandParent.color = Colors.RED;
                parent.color = Colors.BLACK;
                uncleR.color = Colors.BLACK;
                node = grandParent; //将当前节点的引用指向parent,继续检查树是否有其他冲突;
            } else {
                //如果上一层节点颜色不相同,进行相对应的旋转操作
                //当前节点在父节点的右侧
                if(node === parent.right) {
                    this.rotationRR(parent);
                    node = parent;
                    parent = node.parent;
                }
                this.rotationLL(grandParent);
                parent.color = Colors.BLACK;
                grandParent.color = Colors.RED;
                node = parent;
            }
        } else if (grandParent && grandParent.left === parent) {
            //父节点是上一级节点的右子节点, 标记其左节点
            const uncleL = grandParent.left;
            if (uncleL && uncleL.color === Colors.RED) {
                grandParent.color = Colors.RED;
                parent.color = Colors.BLACK;
                uncleL.color = Colors.BLACK;
                node = grandParent;
            } else {
                if (node === parent.left) {
                    this.rotationLL(parent);
                    node = parent;
                    parent = node.parent;
                } else if (node === parent.right) {
                    this.rotationRR(grandParent);
                    parent.color = Colors.BLACK;
                    grandParent.color = Colors.RED;
                    node = parent;
                }
            }
        }
    }
    this.root.color = Colors.BLACK;
}

#####5.7.3:insert 红黑树的插入操作

/**
 * @desc 插入操作
 * @param { key } 传入值
 * @return { void }
 */ 
insert(key) {
    if (!this.root) {
        //如果树空
        this.root = new RedBlackNode(key); //定义一个node节点
        //数的根节点是黑的
        this.root.color = Colors.BLACK;
    } else {
        //如果树不为空,继续向下查找执行插入操作
        const newNode = this.insertNode(this.root, key);
        //验证红黑树规则是否满足
        this.fixTreeProperties(newNode);
    }
};
/**
 * @des 插入节点
 * @param { Node, Key } node节点对象, 要插入的值
 * @return { Node } 返回插入值的节点
 */
insertNode(node, key) {
    if (key < node.key) {
        if (node.left == null) {
            node.left = new RedBlackNode(key);
            node.left.parent = node;
            return node.left;
        } else {
            return this.insertNode(node.left, key);
        }
    }
    if (key > node.key) {
        if (node.right == null) {
            node.right = new RedBlackNode(key);
            node.right.parent = node;
            return node.right;
        } else {
            return this.insertNode(node.right, key);
        }
    }
}  

你可能感兴趣的:(javascript,前端,javascript,数据结构,前端,算法)