背景
本文是《Java 后端从小白到大神》修仙系列第五篇
,正式进入Java后端
世界,本篇文章主要聊Java基础
。若想详细学习请点击首篇博文,我们开始把。
文章概览
- 数据结构
数据结构(续)
1. 堆(Heap)
- 定义:一种完全二叉树,满足父节点与子节点的特定顺序关系(最小堆/最大堆)。默认情况下,PriorityQueue是按照自然顺序(即元素的自然大小顺序)实现的,对于数值类型,这意味着它是一个最小堆。如果要实现最大堆,则需要提供一个自定义的Comparator。
- Java 实现:通过
PriorityQueue
类实现。
- 代码示例:
1
2
3
4
5
6
7
8
9
10
11
|
// 最小堆(默认)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
minHeap.add(5);
minHeap.add(3);
System.out.println(minHeap.poll()); // 输出 3
// 最大堆(需自定义 Comparator)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
maxHeap.add(5);
maxHeap.add(3);
System.out.println(maxHeap.poll()); // 输出 5
|
2. 树(Tree)
1.树结构(Tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
// 树节点定义
class TreeNode<T> {
T data;
List<TreeNode<T>> children; // 子节点列表
public TreeNode(T data) {
this.data = data;
this.children = new ArrayList<>();
}
}
// 遍历树 方法1:
public static <T> void traverseTree(TreeNode<T> root) {
if (root == null) {
return;
}
Stack<TreeNode<T>> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode<T> node = stack.pop();
System.out.print(node.data + " ");
for (int i = node.children.size() - 1; i >= 0; i--) {
stack.push(node.children.get(i));
}
}
}
// 递归遍历树 方法2:
public static <T> void traverseTree(TreeNode<T> node) {
if (node == null) {
return;
}
System.out.print(node.data + " ");
for (TreeNode<T> child : node.children) {
traverseTree(child);
}
}
// 创建树
TreeNode<String> root = new TreeNode<String>("Root");
TreeNode<String> child1 = new TreeNode<String>("Child1");
TreeNode<String> child2 = new TreeNode<String>("Child2");
TreeNode<String> grandchild1 = new TreeNode<String>("Grandchild1");
TreeNode<String> grandchild2 = new TreeNode<String>("Grandchild2");
root.children.add(child1);
root.children.add(child2);
child1.children.add(grandchild1);
child1.children.add(grandchild2);
|
2. 二叉树(Binary Tree)
- 定义:二叉树的每个节点最多有两个子节点,称为左子节点(Left Child)和右子节点(Right Child)。
- 二叉搜索树(BST):左子树所有节点值 < 根节点值 < 右子树所有节点值。
- 完全二叉树:除最后一层外,其他层节点数达到最大,且最后一层节点向左对齐。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// 二叉树节点定义
class BinaryTreeNode<T> {
T data;
BinaryTreeNode<T> left;
BinaryTreeNode<T> right;
public BinaryTreeNode(T data) {
this.data = data;
}
}
// 遍历二叉树
public static <T> void traverseBinaryTree(BinaryTreeNode<T> broot) {
if (broot == null) {
return;
}
System.out.print(broot.data + " ");
traverseBinaryTree(broot.left);
traverseBinaryTree(broot.right);
}
// 创建二叉树
BinaryTreeNode<Integer> root = new BinaryTreeNode<>(10);
root.left = new BinaryTreeNode<>(5);
root.right = new BinaryTreeNode<>(15);
root.left.right = new BinaryTreeNode<>(7);
|
3. 红黑树(Red-Black Tree)
定义:红黑树(Red-Black Tree)是一种自平衡二叉搜索树(Self-Balancing Binary Search Tree)。它通过引入一组规则来确保树的高度始终保持在对数级别(O(log n)),从而保证插入、删除和查找操作的时间复杂度为 O(log n)。
红黑树的性质:红黑树的每个节点都有一个颜色属性,可以是红色或黑色。为了保持树的平衡性,红黑树遵循以下规则:
- 根叶黑:
- 根节点和叶子节点必须是黑色,这里叶子节点是指树最底层的叶子节点。
- 黑路同:
- 每个结点到任意叶结点的所有路径上的黑色结点数量都是相同的。
- 左根右:
- 不红红:不存在两个连续的红色节点。
红黑树插入节点规则:
- 插入结点默认是红色结点
- 如果插入后性质被破坏,则根据下面三种情况做调整:
- 插入结点是根结点,颜色直接变黑。
- 插入结点的叔叔节点是
红色
,叔父爷
变色,爷爷变插入结点,继续判断爷爷是否满足红黑树性质,如果满足则结束,如果不满足则继续调整。
- 插入结点的叔叔节点是
黑色
,根据LL\RR\LR\RL四种情况调整。
- 若LL,则右旋:向右转爷爷节点,父亲为旋转中心点,将爷爷和父亲变色。
- 若RR,则左旋:向左转爷爷节点,父亲为旋转中心点,将爷爷和父亲变色。
- 若LR,则先左旋父亲节点,再右旋爷爷节点,父亲为旋转中心点,将爷爷和父亲变色。
- 若RL,则先右旋父亲节点,再左旋爷爷节点,父亲为旋转中心点,将爷爷和父亲变色。
红黑树的作用:红黑树的主要作用是提供一种高效的动态数据结构,用于存储和检索有序数据。相比于普通的二叉搜索树,红黑树通过自平衡机制避免了退化成链表的情况,从而保证了操作的高效性。它适用于需要高效动态数据管理的场景,如 Java 的 TreeMap 和 TreeSet
- Java 集合框架:
- TreeMap 和 TreeSet 的底层实现基于红黑树。
- 操作系统:
- 数据库:
- 语言运行时:
- Java 的 ConcurrentHashMap 在桶中使用红黑树优化链表性能。
红黑树的操作:
1. 插入操作:插入新节点时,可能会破坏红黑树的规则,因此需要通过一系列旋转和重新着色操作来恢复平衡。
-
步骤:
- 将新节点插入到树中,并将其颜色设置为红色。
- 检查是否违反红黑树规则。
- 如果违反规则,执行旋转和重新着色操作以恢复平衡。
-
旋转类型:
- 左旋(Left Rotation):将右子节点提升为父节点。
- 右旋(Right Rotation):将左子节点提升为父节点。
2. 删除操作:删除节点时,可能会导致树失去平衡,因此需要调整树的结构。
- 步骤:
- 找到要删除的节点。
- 如果删除的是黑色节点,可能会违反红黑树规则。
- 通过旋转和重新着色操作恢复平衡。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
class RedBlackTree {
private static final boolean RED = true;
private static final boolean BLACK = false;
class Node {
int key;
Node left, right;
boolean color;
Node(int key, boolean color) {
this.key = key;
this.color = color;
}
}
private Node root;
public void insert(int key) {
root = insert(root, key);
root.color = BLACK; // 根节点始终为黑色
}
private Node insert(Node node, int key) {
if (node == null) {
return new Node(key, RED); // 新插入的节点为红色
}
if (key < node.key) {
node.left = insert(node.left, key);
} else if (key > node.key) {
node.right = insert(node.right, key);
} else {
node.key = key; // 更新键值
}
// 平衡调整
if (isRed(node.right) && !isRed(node.left)) {
node = rotateLeft(node); // 左旋
}
if (isRed(node.left) && isRed(node.left.left)) {
node = rotateRight(node); // 右旋
}
if (isRed(node.left) && isRed(node.right)) {
flipColors(node); // 重新着色
}
return node;
}
private boolean isRed(Node node) {
if (node == null) return false;
return node.color == RED;
}
private Node rotateLeft(Node node) {
Node x = node.right;
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
private Node rotateRight(Node node) {
Node x = node.left;
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
private void flipColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
}
|
对比总结:
特性 |
树(Tree) |
二叉树(Binary Tree) |
红黑树(Red-Black Tree) |
子节点数量 |
任意 |
最多2个 |
最多2个(自平衡二叉搜索树) |
主要用途 |
分层数据(如目录结构) |
高效查找/插入(如BST) |
保证动态操作的高效性(O(log n)) |
Java实现 |
需自定义 |
需自定义 |
TreeMap , TreeSet |
选择建议:
- 需要有序键值对 → 红黑树(
TreeMap
)。
- 简单分层数据 → 普通树或二叉树。
- 高频动态插入/删除 → 红黑树(避免普通BST退化为链表)。
4. 树的遍历(Tree Traversal)
1. 前序遍历(Preorder):
- 顺序:根节点 → 左子树 → 右子树
- 应用场景:复制二叉树、前缀表达式(波兰表达式)
- 代码示例:
递归实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public void preorder(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " "); // 访问根节点
preorder(root.left); // 遍历左子树
preorder(root.right); // 遍历右子树
}
|
迭代实现(栈):
1
2
3
4
5
6
7
8
9
10
11
|
public void preorderIterative(TreeNode root) {
if (root == null) return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.print(node.val + " ");
if (node.right != null) stack.push(node.right); // 右子先入栈
if (node.left != null) stack.push(node.left); // 左子后入栈
}
}
|
2. 中序遍历(Inorder):
- 顺序:左子树 → 根节点 → 右子树
- 应用场景:二叉搜索树(BST)的有序输出
- 代码示例:
递归实现:
1
2
3
4
5
6
|
public void inorder(TreeNode root) {
if (root == null) return;
inorder(root.left); // 遍历左子树
System.out.print(root.val + " "); // 访问根节点
inorder(root.right); // 遍历右子树
}
|
迭代实现(栈):
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public void inorderIterative(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr); // 左子节点入栈
curr = curr.left;
}
curr = stack.pop();
System.out.print(curr.val + " ");
curr = curr.right; // 转向右子树
}
}
|
3. 后序遍历(Postorder):
- 顺序:左子树 → 右子树 → 根节点
- 应用场景:释放二叉树内存、后缀表达式(逆波兰表达式)
- 代码示例:
递归实现:
1
2
3
4
5
6
|
public void postorder(TreeNode root) {
if (root == null) return;
postorder(root.left); // 遍历左子树
postorder(root.right); // 遍历右子树
System.out.print(root.val + " "); // 访问根节点
}
|
迭代实现(双栈):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public void postorderIterative(TreeNode root) {
if (root == null) return;
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(root);
while (!stack1.isEmpty()) {
TreeNode node = stack1.pop();
stack2.push(node); // 根节点先压入 stack2
if (node.left != null) stack1.push(node.left);
if (node.right != null) stack1.push(node.right);
}
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().val + " ");
}
}
|
4. 层序遍历(Level Order):
- 顺序:按层从上到下、每层从左到右访问
- 应用场景:按层次处理数据(如二叉树深度、广度优先搜索)
- 代码示例(队列实现):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public void levelOrder(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
System.out.println(); // 按层换行(可选)
}
}
|
总结:
遍历方式 |
顺序 |
递归时间复杂度 |
迭代辅助结构 |
前序遍历 |
根 → 左 → 右 |
O(n) |
栈 |
中序遍历 |
左 → 根 → 右 |
O(n) |
栈 |
后序遍历 |
左 → 右 → 根 |
O(n) |
双栈 |
层序遍历 |
按层从左到右 |
O(n) |
队列 |
根据需求选择遍历方式:
- 前序:快速复制二叉树结构。
- 中序:BST 输出有序序列。
- 后序:释放内存避免野指针。
- 层序:按层次分析数据(如求二叉树深度)。
3. 图(Graphs)
4. 其他数据结构
1. 枚举(Enumeration)
-
定义:固定常量的集合。
-
Java 实现:通过 enum
关键字定义。
-
代码示例:
1
2
3
|
enum Day { MONDAY, TUESDAY, WEDNESDAY }
Day today = Day.MONDAY;
System.out.println(today); // 输出 MONDAY
|
2. 位集合(BitSet)
-
定义:动态大小的位数组,支持位运算。
-
Java 实现:BitSet
类。
-
代码示例:
1
2
3
4
|
BitSet bits = new BitSet(8);
bits.set(2); // 设置第2位为1
bits.set(4);
System.out.println(bits.get(2)); // 输出 true
|
3. 字典(Dictionary)
4. 哈希表(Hashtable)
5. 属性(Properties)
总结
数据结构 |
用途 |
典型实现类 |
堆 |
优先级任务调度 |
PriorityQueue |
树 |
有序数据存储(如排序、查找) |
TreeMap , TreeSet |
图 |
网络关系建模(如社交网络) |
自定义邻接表/邻接矩阵 |
枚举 |
定义固定常量集合 |
enum |
位集合 |
高效位操作(如权限管理) |
BitSet |
哈希表 |
线程安全的键值存储 |
Hashtable |
属性 |
配置文件读写 |
Properties |
根据需求选择合适的数据结构:
- 高频增删 →
LinkedList
(链表)。
- 快速查找 →
HashMap
(哈希表)。
- 有序数据 →
TreeMap
(红黑树)。
- 线程安全 →
ConcurrentHashMap
或 Hashtable
。