JS–树、二叉树(深度优先、广度优先遍历、平衡树旋转)
一、树结构和特点
1、树结构
树由节点组成,从根节点出发,每个节点可以拥有子节点,没有子节点的节点叫做叶子节点。
节点的度:节点拥有子节点的个数,度为0表示叶子节点
树的高度:从根节点开始计算(1开始)到叶子节点,一共拥有的层数
树的度:树中所有节点中最大的节点度
树中特殊的计算:
- 一个二叉树第 i 层的最大结点数为:2^(i-1), i >= 1;
- 深度为k的二叉树有最大结点总数为: 2^k - 1, k >= 1;
- 对任何非空二叉树 T,若n0表示叶结点的个数、n2是度为2的 非叶结点个数,那么两者满足关系n0 = n2 + 1
特殊的树结构:
二叉树: 每一个节点最多可以有两个子节点(节点度为0-2的树)
满二叉树: 一棵二叉树的结点要么是叶子结点,要么它有两个子结点(如果一个二叉树的层数为K,且结点总数是(2^k) -1,则它就是满二叉树。)
完全二叉树: 若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。
平衡二叉树: 左右子树高度相差不超过1
不平衡二叉树: 左右子树高度相差大于等于2
2、树的特点
对于所有的树都具有以下特点:
- 子树之间不可以相交
- 除了根节点外,每个节点有且仅有一个父节点
- 一颗N个节点的树有n-1条边
对二叉树:
- 每个节点的度最大为2,节点的度为0-2
- 树的度可能为0-2
在实际运用场景中,使用得最多的是二叉树的特殊实例——二叉搜索树,它具有二叉树的全部特点,并且:
- 树中的节点一定有序,一般是升序
二、二叉搜索树的封装
二叉树常用数组或链表来实现,实际运用当中,使用链表是最好的,这里讲解使用链表封装二叉搜索树的详细过程:
1、创建二叉树节点:在节点中应该保存当前节点的数据、左边子节点、右边子节点
class Node{
constructor(element){
this.data = element;
//左子节点
this.left = null;
//右子节点
this.right = null;
}
}
2、插入节点:首先创建二叉搜索树类BST(Binary Sort Tree的简写),在构造方法中定义一个变量root用于保存根节点;插入节点时仅需要一个参数即插入的数据,因为儿茶搜索树是有序的,因此,使用递归的方式来找到合适的位置插入节点,在插入节点时,需要创建一个辅助方法来实现递归。
//插入节点
insert(element){
let node = new Node(element);
if(this.root == null){
//当树为空时,直接将插入的节点当做根节点
this.root = node;
}else{
//从根节点开始查找,在合适位置插入
this.insertNode(this.root, node);
}
}
//辅助方法
insertNode(node, newNode){
//根据大小决定插入在当前节点的左边还是右边
if(newNode.data < node.data){
if(node.left == null){
//小于时放在左边,当左边节点为空时直接放在左边作为左边的节点
node.left = newNode;
}else{
//当左边节点部位空时递归向下寻找
this.insertNode(node.left, newNode);
}
}else{
//当节点数据大于当前节点时,放在当前节点右边作为子节点
if(node.right == null){
//当前节点的右子节点为空时,直接插入作为右边子节点
node.right = newNode;
}else{
//当前节点右子节点部位空时,递归向下寻找
this.insertNode(node.right, newNode);
}
}
}
3、先序遍历:根——左——右
在树中为提高效率,遍历树中节点时使用的是递归方式
//先序遍历 根-左-右
preOderTraversal(callback){
//callback是一个回调函数,仅用于输出当前遍历到的节点数据
//data => {console.log(data)}
//由根节点开始遍历
this.preOderTraversalNode(this.root, callback);
}
//先序遍历节点(辅助方法)
preOderTraversalNode(node, callback){
if(node != null){
callback(node.data);
//递归遍历当前节点左子树
this.preOderTraversalNode(node.left, callback);
//递归遍历当前节点右子树
this.preOderTraversalNode(node.right, callback);
}
}
4、中序遍历:左——根——右
通过中序遍历方法遍历出来的顺序是递增的
//中序遍历(升序) 左-根-右
inOderTraversal(callback){
//callback是一个回调函数,仅用于输出当前遍历到的节点数据
//data => {console.log(data)}
//从根节点开始
this.inOderTraversalNode(this.root, callback);
}
//中序遍历节点(辅助方法)
inOderTraversalNode(node, callback){
if(node != null){
//由根节点的左边开始遍历
this.inOderTraversalNode(n