由一个根节点和之多两个互不相交、称为左子树和右子树的字二叉树构成。
2020-06-18 更新
- 新增二叉树的层序遍历实现、设计说明;
2022-03-03 更新
- 根据前序、中序构造二叉树
- 根据后续、中序构造二叉树
二叉树
二叉树不是树的特例。二叉树和树都是树的不同类型
概念定义
每个节点都由数据元素和指针域构成。
- 二叉树的初始化;
- 左插入节点;
- 右插入节点;
- 左删除子树;
- 右删除子树;
- 遍历二叉树;
- 树节点销毁;
常用术语:
节点的度: 指节点所拥有的子树的个数。
树的度: 指所有节点的度 最大的值。
性质
- 对于一个非空二叉树,如果叶节点个数为,度为2 的节点数为,则有:
- 对于具有个节点的完全二叉树,如果按照从上至下和从左至右的顺序对所有节点从0开始顺序编号。则对于序号为的节点,有以下规则:
条件 | 结果 | |
为根节点,无双亲节点 | ||
节点的双亲节点序号为 | ||
左孩子节点序号为; | ||
节点无左孩子 | ||
右孩子节点序号为 | ||
节点无右孩子 |
设计实现
包括:顺序存储结构、链式存储结构和仿真指针存储。
采用二叉链的方式实现二叉树。
说明:
- 二叉树节点结构
function TreeNode(){
this.val = null;
this.left = null;
this.right = null;
}
- 测试示例:
// 测试示例
let root = new TreeNode("F")
let Tree = new BiTree(root);
// 插入左子树
let leftNode = Tree.insertLeftNode(root,"B"),
rightNode = Tree.insertRightNode(root,"C"),
leftNode_1 = Tree.insertLeftNode(root,"D");
leftNode_1_2 = Tree.insertRightNode(leftNode_1,"E");
rightNode_1 = Tree.insertLeftNode(rightNode,"G");
rightNode_2 = Tree.insertRightNode(rightNode_1,"I");
let result = [root];
console.log(result);
// 测试节点删除
// Tree.deleteLeftNode(leftNode_1);
// Tree.deleteRightNode(leftNode_1);
- 生成的二叉树:
- 左/右节点插入时,将原节点左/右节点作为新插入的节点的左/右子节点.
- 节点销毁时,需要递归处理该节点的左右子树。
二叉树设计实现:
// 定义二叉树操作方法
function BiTree(root){
// root 为 TreeNode 对象
// 初始化时保证左右子树为 null;
root.left = null;
root.right = null;
}
BiTree.prototype = {
insertLeftNode(curr,val){
// 在当前 curr 左子树插入值 val
let preLeftNode;
if(!curr){
return null;
}
// 原节点的左子树作为新插入节点的左子树
preLeftNode = curr.left;
// 新建节点代替当前的左节点
let newLeftNode = new TreeNode(val);
newLeftNode.left = preLeftNode;
newLeftNode.right = null;
// 原节点指向新的左子节点
curr.left = newLeftNode;
// 返回新的左子树
return newLeftNode;
},
insertRightNode(curr,val){
let preRightNode;
if(!curr){
return null;
}
// 原节点右子树
preRightNode = curr.right;
// 新建节点
let newRigthNode = new TreeNode(val);
newRigthNode.right = preRightNode;
newRigthNode.left = null;
// 改变当前指向
curr.right = newRigthNode;
// 返回新的右子树
return newRigthNode;
},
deleteLeftNode(curr){
// 删除左子树
if(curr===null || curr.left === null){
return null;
}
// 销毁左子树
this.destoryNode(curr.left);
// 当前左子节点指向为空
curr.left = null;
return curr;
},
deleteRightNode(curr){
// 删除右子树
if(curr === null ||curr.right === null){
return null;
}
this.destoryNode(curr.right);
//
curr.right = null;
return right;
},
destoryNode(node){
// 节点销毁,遍历处理每个不是叶节点的指向
if(node!==null && node.left!==null){
this.destoryNode(node.left);
}
if(node!==null && node.right!==null){
this.destoryNode(node.right);
}
node = null;
}
}
二叉树遍历
包括:前序遍历、中序遍历、后序遍历。
示例图:
算法实现:
// 二叉树遍历,输出遍历后的值。
var traversal = function(root) {
let arr = [];
if(!root){
return [];
}
getChild(arr,root);
return arr;
function getChild(arr,p){
// 前序遍历时,首先需要输出当前节点值,再去处理左右子树
arr.push(p.val);
if(p.left!==null){
getChild(arr,p.left);
}
// 中序遍历时,则需要先处理左子树,在取根节点,再去处理右子树
// arr.push(p.val);
if(p.right!=null){
getChild(arr,p.right);
}
// 后序遍历时,则需先处理左右子树,在取根节点值。
}
};
应用
- 解决算法表达式的应用采用中序遍历。
- 计算表达式的值采用后续遍历,遇到数值则入栈,遇到运算符时则取栈顶两个元素计算后在入栈。
- 根据二叉树的前序遍历(或后序遍历)和中序遍历可以确定唯一的二叉树的结构。
前序、中序构造二叉树
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function(preorder, inorder) {
if(!preorder || !preorder.length){
return null
}
let root = new TreeNode(preorder[0])
// 按照根节点处理左右子树
let stack = [root]
// 中序遍历 街边下标
let inorderIndex = 0
for(let i =1;i<preorder.length;i++){
// 前序遍历第一个值
let preorderVal = preorder[i]
let node = stack[stack.length-1]
if(node.val!=inorder[inorderIndex]){
node.left = new TreeNode(preorderVal)
stack.push(node.left)
}else{
while(stack.length>0 &&stack[stack.length-1].val === inorder[inorderIndex]){
node = stack.pop()
inorderIndex++
}
node.right = new TreeNode(preorderVal)
stack.push(node.right)
}
}
return root
};
后序、中序构造二叉树
/**
* @param {number[]} inorder
* @param {number[]} postorder
* @return {TreeNode}
*/
var buildTree = function(inorder, postorder) {
if(!inorder || !inorder.length){
return null
}
let len = postorder.length
let root = new TreeNode(postorder[len - 1])
let stack = [root]
let inorderIndex = inorder.length - 1;
for(let i=len-2;i>=0;i--){
let postorderVal = postorder[i]
let node = stack[stack.length - 1]
if(node.val !== inorder[inorderIndex]){
node.right = new TreeNode(postorderVal)
stack.push(node.right)
}else{
while(stack.length && stack[stack.length - 1].val === inorder[inorderIndex]){
node = stack.pop();
inorderIndex--
}
node.left = new TreeNode(postorderVal)
stack.push(node.left)
}
}
return root
};
层序遍历
层序遍历即一层一层输出二叉树的节点;
取上图,遍历结果:F B G A D I C E H
说明:
- 初始化一个队列;
- 根节点加入到队列;
- 队列非空时,循环以下步骤,访问:
- 出队列,去节点值;
- 若该节点左子树非空,则该节点左子树入队列;
- 做该节点右子树非空,则该节点右子树入队列;
function LevelOrder(root){
// 队列;
let queue = [];
// 结果返回值
let result = [];
if(!root){
return result;
}
// 初始加进去根节点
queue.push(root);
while(queue.length>0){
let p = queue.shift();
// 访问节点,取值
result.push(p.val);
if(p.left) queue.push(p.left);
if(p.right) queue.push(p.right);
}
return result;
}