用 Java 深入研究树,你了解多少?("Java 树结构深度解析:你掌握的程度有多深?")
原创
一、引言
在计算机科学中,树是一种非常常见的数据结构,它模拟了一种层级关系,类似于自然界中的树。树结构在许多领域都有广泛应用,如数据库索引、优先队列、排序等。Java 提供了多种树结构的实现,如二叉树、平衡树(AVL树)、红黑树等。本文将深入探讨 Java 中的树结构,帮助读者更好地领会和掌握这一重要数据结构。
二、Java 树结构概述
Java 中的树结构核心分为两类:二叉树和非二叉树。二叉树是最常见的树结构,每个节点最多有两个子节点,分别为左子节点和右子节点。而非二叉树中的节点可以有多个子节点。下面分别介绍这两种树结构。
三、二叉树
二叉树是一种非常基础的树结构,下面将详细介绍二叉树的基本概念、存储结构以及常见操作。
3.1 基本概念
二叉树是一种节点间具有父子关系的树结构,每个节点最多有两个子节点。二叉树具有以下特点:
- 每个节点最多有两个子节点,分别为左子节点和右子节点;
- 每个子节点都有唯一的父节点;
- 根节点没有父节点。
3.2 存储结构
二叉树通常使用数组或链表进行存储。下面分别介绍这两种存储结构。
3.2.1 数组存储结构
使用数组存储二叉树时,通常按照层次遍历的顺序存储节点。对于任意节点 i,其左子节点的索引为 2i+1,右子节点的索引为 2i+2。下面是一个使用数组存储二叉树的示例代码:
public class BinaryTreeArray {
private int[] data;
public BinaryTreeArray(int[] data) {
this.data = data;
}
public void printTree() {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
System.out.println();
}
}
3.2.2 链表存储结构
使用链表存储二叉树时,每个节点包含三个部分:值、左子节点指针和右子节点指针。下面是一个使用链表存储二叉树的示例代码:
class TreeNode {
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value) {
this.value = value;
this.left = null;
this.right = null;
}
}
public class BinaryTree {
private TreeNode root;
public BinaryTree(int value) {
root = new TreeNode(value);
}
public void insert(int value) {
TreeNode newNode = new TreeNode(value);
if (root == null) {
root = newNode;
} else {
TreeNode current = root;
while (true) {
if (value < current.value) {
if (current.left == null) {
current.left = newNode;
break;
} else {
current = current.left;
}
} else {
if (current.right == null) {
current.right = newNode;
break;
} else {
current = current.right;
}
}
}
}
}
}
3.3 常见操作
二叉树的常见操作包括遍历、查找、插入和删除等。下面分别介绍这些操作。
3.3.1 遍历
二叉树的遍历分为三种:前序遍历、中序遍历和后序遍历。下面是这三种遍历的示例代码:
public void preOrder(TreeNode node) {
if (node != null) {
System.out.print(node.value + " ");
preOrder(node.left);
preOrder(node.right);
}
}
public void inOrder(TreeNode node) {
if (node != null) {
inOrder(node.left);
System.out.print(node.value + " ");
inOrder(node.right);
}
}
public void postOrder(TreeNode node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.print(node.value + " ");
}
}
3.3.2 查找
二叉树的查找操作通常使用递归实现。下面是一个查找二叉树中值为指定值的节点的示例代码:
public TreeNode search(TreeNode node, int value) {
if (node == null) {
return null;
}
if (node.value == value) {
return node;
} else if (value < node.value) {
return search(node.left, value);
} else {
return search(node.right, value);
}
}
3.3.3 插入
二叉树的插入操作通常遵循二叉搜索树的规则。下面是一个插入节点的示例代码:
public void insert(int value) {
TreeNode newNode = new TreeNode(value);
if (root == null) {
root = newNode;
} else {
TreeNode current = root;
while (true) {
if (value < current.value) {
if (current.left == null) {
current.left = newNode;
break;
} else {
current = current.left;
}
} else {
if (current.right == null) {
current.right = newNode;
break;
} else {
current = current.right;
}
}
}
}
}
3.3.4 删除
二叉树的删除操作相对繁复,需要考虑多种情况。下面是一个删除节点的示例代码:
public void delete(int value) {
root = deleteRecursive(root, value);
}
private TreeNode deleteRecursive(TreeNode node, int value) {
if (node == null) {
return null;
}
if (value == node.value) {
if (node.left == null && node.right == null) {
return null;
}
if (node.left == null) {
return node.right;
}
if (node.right == null) {
return node.left;
}
int smallestValue = findSmallestValue(node.right);
node.value = smallestValue;
node.right = deleteRecursive(node.right, smallestValue);
return node;
}
if (value < node.value) {
node.left = deleteRecursive(node.left, value);
} else {
node.right = deleteRecursive(node.right, value);
}
return node;
}
private int findSmallestValue(TreeNode node) {
return node.left == null ? node.value : findSmallestValue(node.left);
}
四、平衡树(AVL树)
平衡树是一种特殊的二叉搜索树,它确保任何节点的左右子树高度差不超过1。下面将介绍平衡树的基本概念、插入和删除操作。
4.1 基本概念
平衡树(AVL树)是一种自平衡的二叉搜索树,它通过旋转操作来保持树的平衡。平衡树的每个节点包含一个额外的信息:平衡因子(balance factor),描述该节点的左子树高度与右子树高度的差。
4.2 插入操作
平衡树的插入操作与二叉搜索树类似,但在插入后需要检查平衡因子,并选择平衡因子进行旋转操作。下面是一个插入节点的示例代码:
public void insert(int value) {
root = insertRecursive(root, value);
}
private TreeNode insertRecursive(TreeNode node, int value) {
if (node == null) {
return new TreeNode(value);
}
if (value < node.value) {
node.left = insertRecursive(node.left, value);
} else if (value > node.value) {
node.right = insertRecursive(node.right, value);
} else {
return node;
}
updateHeight(node);
int balanceFactor = getBalanceFactor(node);
if (balanceFactor > 1 && value < node.left.value) {
return rightRotate(node);
}
if (balanceFactor < -1 && value > node.right.value) {
return leftRotate(node);
}
if (balanceFactor > 1 && value > node.left.value) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
if (balanceFactor < -1 && value < node.right.value) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
private void updateHeight(TreeNode node) {
if (node != null) {
node.height = Math.max(height(node.left), height(node.right)) + 1;
}
}
private int height(TreeNode node) {
if (node == null) {
return 0;
}
return node.height;
}
private int getBalanceFactor(TreeNode node) {
if (node == null) {
return 0;
}
return height(node.left) - height(node.right);
}
private TreeNode rightRotate(TreeNode y) {
TreeNode x = y.left;
TreeNode T2 = x.right;
x.right = y;
y.left = T2;
updateHeight(y);
updateHeight(x);
return x;
}
private TreeNode leftRotate(TreeNode x) {
TreeNode y = x.right;
TreeNode T2 = y.left;
y.left = x;
x.right = T2;
updateHeight(x);
updateHeight(y);
return y;
}
4.3 删除操作
平衡树的删除操作与插入操作类似,也需要在删除后检查平衡因子,并进行相应的旋转操作。下面是一个删除节点的示例代码:
public void delete(int value) {
root = deleteRecursive(root, value);
}
private TreeNode deleteRecursive(TreeNode node, int value) {
if (node == null) {
return null;
}
if (value < node.value) {
node.left = deleteRecursive(node.left, value);
} else if (value > node.value) {
node.right = deleteRecursive(node.right, value);
} else {
if (node.left == null || node.right == null) {
TreeNode temp = node.left != null ? node.left : node.right;
if (temp == null) {
temp = node;
node = null;
} else {
node = temp;
}
} else {
int smallestValue = findSmallestValue(node.right);
node.value = smallestValue;
node.right = deleteRecursive(node.right, smallestValue);
}
}
if (node == null) {
return null;
}
updateHeight(node);
int balanceFactor = getBalanceFactor(node);
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
return rightRotate(node);
}
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return leftRotate(node);
}
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
五、红黑树
红黑树是一种自平衡的二叉搜索树,它通过确保节点的颜色和子树的黑高相等来保持树的平衡。下面将介绍红黑树的基本概念和插入操作。
5.1 基本概念
红黑树是一种特殊的二叉搜索树,它具有以下特性:
- 每个节点或者是红的,或者是黑的;
- 根节点是黑的;
- 所有叶子节点(NIL节点)是黑的;
- 如果一个节点是红的,那么它的两个子节点都是黑的;
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
5.2 插入操作
红黑树的插入操作相对繁复,需要考虑多种情况并进行相应的旋转和颜色调整。下面是一个插入节点的示例代码:
public void insert(int value) {
TreeNode newNode = new TreeNode(value);
newNode.color = RED;
root = insertRecursive(root, newNode);
fixInsertion(root, newNode);
}
private TreeNode insertRecursive(TreeNode node, TreeNode newNode) {
if (node == null) {
return newNode;
}
if (newNode.value < node.value) {
node.left = insertRecursive(node.left, newNode);
} else if (newNode.value > node.value) {
node.right = insertRecursive(node.right, newNode);
}
return node;
}
private void fixInsertion(TreeNode root, TreeNode node) {
TreeNode parent = null;
TreeNode grandParent = null;
while (node != root && node.color == RED && node.parent.color == RED) {
parent = node.parent;
grandParent = parent.parent;
if (parent == grandParent.left) {
TreeNode uncle = grandParent.right;
if (uncle != null && uncle.color == RED) {
uncle.color = BLACK;
parent.color = BLACK;
grandParent.color = RED;
node = grandParent;
} else {
if (node == parent.right) {
parent = leftRotate(parent);
node = node.left;
}
parent.color = BLACK;
grandParent.color = RED;
rightRotate(grandParent);
}
} else {
TreeNode uncle = grandParent.left;
if (uncle != null && uncle.color == RED) {
uncle.color = BLACK;
parent.color = BLACK;
grandParent.color = RED;
node = grandParent;
} else {
if (node == parent.left) {
parent = rightRotate(parent);
node = node.right;
}
parent.color = BLACK;
grandParent.color = RED;
leftRotate(grandParent);
}
}
}
root.color = BLACK;
}
private TreeNode leftRotate(TreeNode node) {
TreeNode rightChild = node.right;
node.right = rightChild.left;
if (node.right != null) {
node.right.parent = node;
}
rightChild.parent = node.parent;
if (node.parent == null) {
root = rightChild;
} else if (node == node.parent.left) {
node.parent.left = rightChild;
} else {
node.parent.right = rightChild;
}
rightChild.left = node;
node.parent = rightChild;
return rightChild;
}
private TreeNode rightRotate(TreeNode node) {
TreeNode leftChild = node.left;
node.left = leftChild.right;
if (node.left != null) {
node.left.parent = node;
}
leftChild.parent = node.parent;
if (node.parent == null) {
root = leftChild;
} else if (node == node.parent.right) {
node.parent.right = leftChild;
} else {
node.parent.left = leftChild;
}
leftChild.right = node;
node.parent = leftChild;
return leftChild;
}
六、总结
本文详细介绍了 Java 中的树结构,包括二叉树、平衡树(AVL树)和红黑树。通过对这些树结构的深入解析,我们可以更好地领会树的基本概念、存储结构以及常见操作。掌握这些树结构对于领会 Java 数据结构和算法具有重要意义,有助于我们在实际开发中更加高效地解决问题。