定义
定义:二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。
基本概念
1)结点的度:一个结点拥有子树的数目称为结点的度
2)树的度:树中所有结点的度的最大值
3)树的深度:也称为树的高度,树中所有结点的层次最大值称为树的深度
4)叶子结点:也称为终端结点,没有子树的结点或者度为零的结点
5)分支结点:也称为非终端结点,度不为零的结点称为非终端结点
树的分类和树的创建
1. 完全二叉树
特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部,完全二叉树如图:
2. 满二叉树
2.1 特点:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树
3. 二叉搜索树
也称二叉查找树,或二叉排序树,简单说就是左子树上的数值小于树根上的值,树根的值小于右子树的值,二叉查找树如图:
3.1 特点
(1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2) 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3) 任意节点的左、右子树也分别为二叉查找树;
(4) 没有键值相等的节点。
4. 平衡二叉树(AVL树)
4.1 特点:平衡二叉树首先必须是二叉查找树,然后每个节点的左子树和右子树的高度差至多为1。如图:
5. 红黑树
6. B树和B+树
树的遍历
树的遍历可以分为广度遍历和深度遍历,广度遍历可以理解为按层次遍历,深度遍历按照遍历根节点的顺序,可以分为前序遍历,中序遍历,后序遍历。
递归遍历【前序遍历、后序遍历、后序遍历】
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinaryTree {
constructor() {
this.root = null;
}
// 插入节点
insert(value) {
const newNode = new Node(value);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
if (newNode.value < node.value) {
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);
}
}
}
// 先序遍历
preorderTraversal(node, type) {
if (node !== null) {
console.log(node.value); // 输出当前节点值
this.preorderTraversal(node.left, 'left'); // 遍历左子树
this.preorderTraversal(node.right, 'right'); // 遍历右子树
}
}
// 中序遍历
inorderTraversal(node, type) {
if (node !== null) {
this.inorderTraversal(node.left, 'left'); // 遍历左子树
console.log(node.value); // 输出当前节点值
this.inorderTraversal(node.right, 'right'); // 遍历右子树
}
}
// 后序遍历
postorderTraversal(node) {
if (node !== null) {
this.postorderTraversal(node.left); // 遍历左子树
this.postorderTraversal(node.right); // 遍历右子树
console.log(node.value); // 输出当前节点值
}
}
levelOrderTraversal = function() {
var queue = [];
var result = [];
if (this.root !== null) {
queue.push(this.root);
}
while (queue.length > 0) {
var node = queue.shift();
result.push(node.value);
if (node.left !== null) {
queue.push(node.left);
}
if (node.right !== null) {
queue.push(node.right);
}
}
return result;
};
}
// 示例使用
const tree = new BinaryTree();
tree.insert(10);
tree.insert(5);
tree.insert(15);
tree.insert(3);
tree.insert(7);
tree.insert(13);
tree.insert(17);
console.log('tree:', tree);
console.log('Preorder traversal:');
tree.preorderTraversal(tree.root);
console.log('Inorder traversal:');
tree.inorderTraversal(tree.root);
console.log('Postorder traversal:');
tree.postorderTraversal(tree.root);
图解
前序遍历
常规操作:先根,再左,再右
确定了遍历整体结构:
确定了左子树中的整体结构
继续操作:
到此左子树中的遍历已经完成
确定右子树中的整体结构
前序结束
结果为:A B D F E C G I J H K
简单方法
从根结点出发向左开始绕二叉树一圈,经过的节点顺序即为先序遍历顺序
结果为:A B D F E C G I J H K
中序遍历
常规操作:先左,再根,再右
由此可确定根A在中间,下面分别确定左右子树中的顺序
左子树中:
由此确定了左子树中的顺序
右子树中:
对右子树进行整体划分
由此可以确定:
继续:
中序遍历结束
结果为:F D B E A I G J C H K
简单方法
以该二叉树为例
结果为:F D B E A I G J C H K
后序遍历
常规操作:先左,再右,再根
将整体结构化分出来,后序遍历时根节点永远在最后一位
继续对左子树的结构进行划分
自此,左子树中遍历结束
划分右子树结构
后序遍历结束
结果为:F D E B I J G K H C A
非递归遍历【前序遍历、后序遍历、后序遍历】
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinaryTree {
constructor() {
this.root = null;
}
// 添加节点的方法
insert(value) {
const newNode = new Node(value);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
if (newNode.value < node.value) {
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);
}
}
}
// 非递归前序遍历
preorderTraversal(callback) {
const stack = [];
let current = this.root;
if (current !== null) {
stack.push(current);
}
while (stack.length > 0) {
current = stack.pop();
callback(current.value);
if (current.right !== null) {
stack.push(current.right);
}
if (current.left !== null) {
stack.push(current.left);
}
}
}
}
// 使用示例
const tree = new BinaryTree();
tree.insert(10);
tree.insert(15);
tree.insert(5);
tree.insert(12);
tree.insert(18);
tree.insert(20);
tree.preorderTraversal((value) => console.log(value)); // 输出: 10, 5, 12, 15, 18, 20
相关算法延展
最小公共祖先
function lowestCommonAncestor(root, p, q) {
if (!root || root === p || root === q) return root;
let left = lowestCommonAncestor(root.left, p, q);
let right = lowestCommonAncestor(root.right, p, q);
let mid = root;
if (left && right) return mid; // 如果在左右子树均找到节点,则当前节点为最近公共祖先
if (left) return left; // 否则返回找到的一侧的节点
if (right) return right; // 否则返回找到的一侧的节点
return null; // 如果都没有找到,返回null
}