树
树是一种典型的分支结构,由节点(Node)和边(Edge)组成,用于表示层次关系。树在计算机科学中有着广泛的应用,如文件系统、数据库索引、编译器语法分析等。
树的基本概念
- 节点(Node):树的基本单位,包含数据和指向子节点的引用
- 根节点(Root):树的顶层节点,没有父节点
- 叶子节点(Leaf):没有子节点的节点
- 内部节点(Internal Node):有子节点的节点
- 度(Degree):节点的子节点数量
- 深度(Depth):从根节点到当前节点的路径长度
- 高度(Height):从当前节点到最远叶子节点的路径长度
- 层(Level):根节点为第0层,其子节点为第1层,以此类推
树的特性
树具有以下特性:
- 树中任意两个结点之间存在唯一的路径。
- 树中没有环。
- 树中每个结点只有一个父结点(根结点除外)。
树的类型
树有很多类型,但是常见的树有:
二叉树(Binary Tree)
每个结点最多有两个子结点的树。
js
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinaryTree {
constructor() {
this.root = null;
}
// 插入节点
insert(value) {
const newNode = new TreeNode(value);
if (!this.root) {
this.root = newNode;
return;
}
const queue = [this.root];
while (queue.length > 0) {
const node = queue.shift();
if (!node.left) {
node.left = newNode;
return;
}
if (!node.right) {
node.right = newNode;
return;
}
queue.push(node.left);
queue.push(node.right);
}
}
// 删除节点
delete(value) {
if (!this.root) return null;
if (this.root.value === value) {
this.root = null;
return;
}
const queue = [this.root];
let targetNode = null;
let lastNode = null;
while (queue.length > 0) {
lastNode = queue.shift();
if (lastNode.value === value) {
targetNode = lastNode;
}
if (lastNode.left) queue.push(lastNode.left);
if (lastNode.right) queue.push(lastNode.right);
}
if (targetNode) {
targetNode.value = lastNode.value;
this.deleteDeepest(lastNode);
}
}
deleteDeepest(node) {
const queue = [this.root];
while (queue.length > 0) {
const current = queue.shift();
if (current.left === node) {
current.left = null;
return;
}
if (current.right === node) {
current.right = null;
return;
}
if (current.left) queue.push(current.left);
if (current.right) queue.push(current.right);
}
}
}
二叉搜索树(Binary Search Tree)
每个结点的左子树中的所有结点的值都小于该结点的值,右子树中的所有结点的值都大于该结点的值。
js
class BinarySearchTree extends BinaryTree {
insert(value) {
const newNode = new TreeNode(value);
if (!this.root) {
this.root = newNode;
return;
}
let current = this.root;
while (true) {
if (value < current.value) {
if (!current.left) {
current.left = newNode;
return;
}
current = current.left;
} else {
if (!current.right) {
current.right = newNode;
return;
}
current = current.right;
}
}
}
search(value) {
let current = this.root;
while (current) {
if (value === current.value) return current;
if (value < current.value) {
current = current.left;
} else {
current = current.right;
}
}
return null;
}
delete(value) {
this.root = this.deleteNode(this.root, value);
}
deleteNode(node, value) {
if (!node) return null;
if (value < node.value) {
node.left = this.deleteNode(node.left, value);
} else if (value > node.value) {
node.right = this.deleteNode(node.right, value);
} else {
if (!node.left) return node.right;
if (!node.right) return node.left;
const minRight = this.findMin(node.right);
node.value = minRight.value;
node.right = this.deleteNode(node.right, minRight.value);
}
return node;
}
findMin(node) {
while (node.left) {
node = node.left;
}
return node;
}
}
AVL树
一种自平衡的二叉搜索树,每个结点的左子树和右子树的高度差不能超过1。
js
class AVLNode extends TreeNode {
constructor(value) {
super(value);
this.height = 1;
}
}
class AVLTree extends BinarySearchTree {
getHeight(node) {
return node ? node.height : 0;
}
getBalanceFactor(node) {
return node ? this.getHeight(node.left) - this.getHeight(node.right) : 0;
}
updateHeight(node) {
node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
}
rotateRight(node) {
const left = node.left;
const leftRight = left.right;
left.right = node;
node.left = leftRight;
this.updateHeight(node);
this.updateHeight(left);
return left;
}
rotateLeft(node) {
const right = node.right;
const rightLeft = right.left;
right.left = node;
node.right = rightLeft;
this.updateHeight(node);
this.updateHeight(right);
return right;
}
balance(node) {
if (!node) return null;
this.updateHeight(node);
const balanceFactor = this.getBalanceFactor(node);
if (balanceFactor > 1) {
if (this.getBalanceFactor(node.left) < 0) {
node.left = this.rotateLeft(node.left);
}
return this.rotateRight(node);
}
if (balanceFactor < -1) {
if (this.getBalanceFactor(node.right) > 0) {
node.right = this.rotateRight(node.right);
}
return this.rotateLeft(node);
}
return node;
}
insert(value) {
this.root = this.insertNode(this.root, value);
}
insertNode(node, value) {
if (!node) return new AVLNode(value);
if (value < node.value) {
node.left = this.insertNode(node.left, value);
} else {
node.right = this.insertNode(node.right, value);
}
return this.balance(node);
}
delete(value) {
this.root = this.deleteNode(this.root, value);
}
deleteNode(node, value) {
if (!node) return null;
if (value < node.value) {
node.left = this.deleteNode(node.left, value);
} else if (value > node.value) {
node.right = this.deleteNode(node.right, value);
} else {
if (!node.left) return node.right;
if (!node.right) return node.left;
const minRight = this.findMin(node.right);
node.value = minRight.value;
node.right = this.deleteNode(node.right, minRight.value);
}
return this.balance(node);
}
}
红黑树
一种自平衡的二叉搜索树,通过颜色标记来保持平衡。
js
class RedBlackNode extends TreeNode {
constructor(value) {
super(value);
this.color = 'RED'; // 新节点默认为红色
}
}
class RedBlackTree extends BinarySearchTree {
insert(value) {
const node = new RedBlackNode(value);
this.root = this.insertNode(this.root, node);
this.root.color = 'BLACK'; // 根节点始终为黑色
}
insertNode(root, node) {
if (!root) return node;
if (node.value < root.value) {
root.left = this.insertNode(root.left, node);
} else {
root.right = this.insertNode(root.right, node);
}
// 修复红黑树性质
if (this.isRed(root.right) && !this.isRed(root.left)) {
root = this.rotateLeft(root);
}
if (this.isRed(root.left) && this.isRed(root.left.left)) {
root = this.rotateRight(root);
}
if (this.isRed(root.left) && this.isRed(root.right)) {
this.flipColors(root);
}
return root;
}
isRed(node) {
return node ? node.color === 'RED' : false;
}
rotateLeft(node) {
const right = node.right;
node.right = right.left;
right.left = node;
right.color = node.color;
node.color = 'RED';
return right;
}
rotateRight(node) {
const left = node.left;
node.left = left.right;
left.right = node;
left.color = node.color;
node.color = 'RED';
return left;
}
flipColors(node) {
node.color = 'RED';
node.left.color = 'BLACK';
node.right.color = 'BLACK';
}
}
B树和B+树
B树是一种多路平衡搜索树,B+树是B树的变种,常用于数据库索引。
js
class BTreeNode {
constructor(degree) {
this.degree = degree;
this.keys = [];
this.children = [];
this.isLeaf = true;
}
}
class BTree {
constructor(degree = 2) {
this.degree = degree;
this.root = new BTreeNode(degree);
}
insert(key) {
const root = this.root;
if (root.keys.length === 2 * this.degree - 1) {
const newRoot = new BTreeNode(this.degree);
newRoot.isLeaf = false;
newRoot.children.push(root);
this.root = newRoot;
this.splitChild(newRoot, 0);
}
this.insertNonFull(this.root, key);
}
insertNonFull(node, key) {
let i = node.keys.length - 1;
if (node.isLeaf) {
while (i >= 0 && key < node.keys[i]) {
node.keys[i + 1] = node.keys[i];
i--;
}
node.keys[i + 1] = key;
} else {
while (i >= 0 && key < node.keys[i]) {
i--;
}
i++;
if (node.children[i].keys.length === 2 * this.degree - 1) {
this.splitChild(node, i);
if (key > node.keys[i]) {
i++;
}
}
this.insertNonFull(node.children[i], key);
}
}
splitChild(parent, index) {
const child = parent.children[index];
const newChild = new BTreeNode(this.degree);
newChild.isLeaf = child.isLeaf;
for (let i = 0; i < this.degree - 1; i++) {
newChild.keys[i] = child.keys[i + this.degree];
}
if (!child.isLeaf) {
for (let i = 0; i < this.degree; i++) {
newChild.children[i] = child.children[i + this.degree];
}
}
child.keys.length = this.degree - 1;
if (!child.isLeaf) {
child.children.length = this.degree;
}
for (let i = parent.children.length; i > index + 1; i--) {
parent.children[i] = parent.children[i - 1];
}
parent.children[index + 1] = newChild;
for (let i = parent.keys.length; i > index; i--) {
parent.keys[i] = parent.keys[i - 1];
}
parent.keys[index] = child.keys[this.degree - 1];
}
}
还有一些特殊用途的树
- Trie树。也叫前缀树、单词查找树、字典树。
- 后缀树。
- 表达式树。
- Huffman树。
- 线段树。
- 并查集。
树的遍历
树的遍历是指访问树中每个结点的过程。常见的树遍历算法包括:
前序遍历
先访问根结点,然后访问左子树,最后访问右子树。
js
function preorderTraversal(node) {
if (node === null) return;
console.log(node.value); // 访问根节点
preorderTraversal(node.left); // 前序遍历左子树
preorderTraversal(node.right); // 前序遍历右子树
}
中序遍历
先访问左子树,然后访问根结点,最后访问右子树。
js
function inorderTraversal(node) {
if (node === null) return;
inorderTraversal(node.left); // 中序遍历左子树
console.log(node.value); // 访问根节点
inorderTraversal(node.right); // 中序遍历右子树
}
后序遍历
先访问左子树,然后访问右子树,最后访问根结点。
js
function postorderTraversal(node) {
if (node === null) return;
postorderTraversal(node.left); // 后序遍历左子树
postorderTraversal(node.right); // 后序遍历右子树
console.log(node.value); // 访问根节点
}
层序遍历
从根结点开始,逐层访问树中的结点。
js
function levelOrderTraversal(root) {
if (!root) return;
const queue = [root];
while (queue.length > 0) {
const node = queue.shift(); // 出队
console.log(node.value); // 访问节点
if (node.left) queue.push(node.left); // 左子节点入队
if (node.right) queue.push(node.right); // 右子节点入队
}
}
树的应用场景
- 文件系统:目录结构可以用树表示
- 数据库索引:B树和B+树用于数据库索引
- 编译器:语法分析树用于编译器
- 路由算法:决策树用于路由选择
- 游戏AI:决策树用于游戏AI
- XML/HTML解析:DOM树用于解析XML/HTML
- 机器学习:决策树用于分类和回归
- 网络协议:路由表可以用树表示
总结
树是一种重要的数据结构,能够表示层次关系。掌握树的基本概念、类型和操作,对于解决实际问题非常重要。在实际应用中,需要根据具体场景选择合适的树结构,并注意树的平衡性和效率。