Java数据结构核心解析
本文聚焦Java中常用数据结构,结合Java代码实战,讲解数组、链表、栈、队列、哈希表的核心原理、底层实现及使用场景,兼顾基础入门与实用技巧,适配学习与开发参考。
一、数组(Array)—— 基础线性结构
1.1 核心定义与特征
数组是Java中最基础的线性数据结构,用于存储相同数据类型的有序集合,底层占用连续的内存空间,通过索引(下标)快速访问元素。
核心特征:
-
固定长度:初始化时必须指定长度,一旦创建无法动态扩容(需手动实现扩容逻辑);
-
有序性:元素存入顺序与取出顺序一致,索引从0开始;
-
随机访问:通过索引访问元素,时间复杂度O(1);
-
增删低效:插入/删除元素需移动后续元素,时间复杂度O(n)。
1.2 Java数组实战(基础操作)
public class ArrayDemo {
public static void main(String[] args) {
// 1. 数组初始化(三种方式)
// 方式1:指定长度,默认初始化(基本类型为0,引用类型为null)
int[] arr1 = new int[5];
// 方式2:直接赋值,自动推断长度
int[] arr2 = {1, 2, 3, 4, 5};
// 方式3:指定长度并手动赋值
int[] arr3 = new int[5];
for (int i = 0; i< arr3.length; i++) {
arr3[i] = i + 1; // 赋值1-5
}
// 2. 访问元素(通过索引)
System.out.println("arr2的第3个元素(索引2):" + arr2[2]); // 输出3
// 3. 修改元素
arr2[2] = 10;
System.out.println("修改后arr2的第3个元素:" + arr2[2]); // 输出10
// 4. 遍历数组(三种方式)
// 方式1:普通for循环(推荐,可获取索引)
System.out.println("普通for循环遍历arr3:");
for (int i = 0; i < arr3.length; i++) {
System.out.print(arr3[i] + " ");
}
System.out.println();
// 方式2:增强for循环(简洁,无需索引)
System.out.println("增强for循环遍历arr2:");
for (int num : arr2) {
System.out.print(num + " ");
}
System.out.println();
// 5. 数组扩容(手动实现,核心:创建新数组,复制原数组元素)
int[] newArr = new int[arr2.length * 2]; // 扩容为原长度2倍
System.arraycopy(arr2, 0, newArr, 0, arr2.length); // 复制原数组元素
newArr[5] = 20; // 新增元素
System.out.println("扩容后新数组的第6个元素:" + newArr[5]); // 输出20
}
}
1.3 注意事项(避坑重点)
-
索引越界异常:访问索引超出[0, 长度-1]范围,会抛出ArrayIndexOutOfBoundsException;
-
数组长度不可变:若需动态增删元素,推荐使用ArrayList(底层封装了数组扩容逻辑);
-
数组存储引用类型时,存储的是对象引用,而非对象本身,需注意空指针异常。
二、链表(LinkedList)—— 非连续线性结构
2.1 核心定义与特征
链表是一种非连续的线性数据结构,底层由节点(Node)组成,每个节点包含数据域(存储元素)和指针域(指向其他节点),Java中LinkedList底层实现为双向链表。
核心特征:
-
动态长度:无需指定初始长度,可动态增删节点,无需扩容;
-
非连续内存:节点分散存储,通过指针关联,节省内存(无预留空间);
-
增删高效:插入/删除节点仅需修改指针,时间复杂度O(1)(查找节点除外);
-
查询低效:需从头/尾遍历节点,时间复杂度O(n)。
2.2 Java双向链表手动实现(核心源码级)
// 1. 定义双向链表节点类
class ListNode {
int val; // 数据域
ListNode prev; // 前驱节点指针
ListNode next; // 后继节点指针
// 构造方法
public ListNode(int val) {
this.val = val;
this.prev = null;
this.next = null;
}
}
// 2. 实现双向链表的核心操作
public class MyLinkedList {
private ListNode head; // 头节点
private ListNode tail; // 尾节点
private int size; // 链表长度
// 构造方法:初始化空链表
public MyLinkedList() {
this.head = null;
this.tail = null;
this.size = 0;
}
// 1. 向链表末尾添加节点
public void addLast(int val) {
ListNode newNode = new ListNode(val);
if (size == 0) { // 链表为空,头节点和尾节点都指向新节点
head = newNode;
tail = newNode;
} else { // 链表非空,尾节点的next指向新节点,新节点的prev指向原尾节点
tail.next = newNode;
newNode.prev = tail;
tail = newNode; // 更新尾节点
}
size++;
}
// 2. 向链表头部添加节点
public void addFirst(int val) {
ListNode newNode = new ListNode(val);
if (size == 0) {
head = newNode;
tail = newNode;
} else {
head.prev = newNode;
newNode.next = head;
head = newNode; // 更新头节点
}
size++;
}
// 3. 根据索引获取节点值
public int get(int index) {
// 校验索引合法性
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界");
}
// 优化:根据索引位置,选择从头或从尾遍历
ListNode curr;
if (index < size / 2) {
curr = head;
for (int i = 0; i < index; i++) {
curr = curr.next;
}
} else {
curr = tail;
for (int i = size - 1; i > index; i--) {
curr = curr.prev;
}
}
return curr.val;
}
// 4. 删除指定索引的节点
public void remove(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界");
}
ListNode curr = getNode(index); // 获取要删除的节点
// 分三种情况:删除头节点、删除尾节点、删除中间节点
if (curr == head) { // 删除头节点
head = head.next;
if (head != null) {
head.prev = null;
} else {
tail = null; // 链表只剩一个节点,删除后为空
}
} else if (curr == tail) { // 删除尾节点
tail = tail.prev;
if (tail != null) {
tail.next = null;
} else {
head = null;
}
} else { // 删除中间节点
curr.prev.next = curr.next;
curr.next.prev = curr.prev;
}
curr.prev = null; // 帮助GC回收
curr.next = null;
size--;
}
// 辅助方法:根据索引获取节点(内部使用)
private ListNode getNode(int index) {
ListNode curr;
if (index < size / 2) {
curr = head;
for (int i = 0; i < index; i++) {
curr = curr.next;
}
} else {
curr = tail;
for (int i = size - 1; i > index; i--) {
curr = curr.prev;
}
}
return curr;
}
// 遍历链表
public void printList() {
ListNode curr = head;
while (curr != null) {
System.out.print(curr.val + " ");
curr = curr.next;
}
System.out.println();
}
// 测试方法
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.addLast(1);
list.addLast(2);
list.addFirst(0);
System.out.println("链表遍历:");
list.printList(); // 输出:0 1 2
System.out.println("索引1的元素:" + list.get(1)); // 输出1
list.remove(1);
System.out.println("删除索引1后遍历:");
list.printList(); // 输出:0 2
}
}
2.3 Java自带LinkedList使用(开发常用)
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
// 1. 创建LinkedList对象
LinkedList<Integer> list = new LinkedList<>();
// 2. 核心操作
list.add(1); // 末尾添加
list.addFirst(0); // 头部添加
list.addLast(2); // 末尾添加
list.add(2, 10); // 索引2位置添加
// 3. 访问元素
System.out.println("头节点:" + list.getFirst()); // 0
System.out.println("尾节点:" + list.getLast()); // 2
System.out.println("索引2的元素:" + list.get(2)); // 10
// 4. 删除元素
list.removeFirst(); // 删除头节点
list.removeLast(); // 删除尾节点
list.remove(1); // 删除索引1的元素
// 5. 遍历
System.out.println("遍历链表:");
for (int num : list) {
System.out.print(num + " "); // 输出:1
}
}
}
三、栈(Stack)—— 先进后出(LIFO)结构
3.1 核心定义与特征
栈是一种特殊的线性结构,遵循**先进后出(LIFO)**原则,仅允许在栈顶(top)进行插入(push)和删除(pop)操作,底层可通过数组或链表实现。
核心特征:
-
操作受限:仅栈顶可进行增删查操作;
-
先进后出:先存入的元素最后取出,后存入的元素最先取出;
-
常用场景:表达式计算、括号匹配、方法调用栈等。
3.2 Java栈手动实现(数组版)
public class MyStack {
private int[] stack; // 底层数组存储元素
private int top; // 栈顶指针(指向栈顶元素,初始为-1,代表空栈)
private int capacity; // 栈的容量
// 构造方法:指定栈容量
public MyStack(int capacity) {
this.capacity = capacity;
this.stack = new int[capacity];
this.top = -1;
}
// 1. 入栈(push):向栈顶添加元素
public void push(int val) {
if (isFull()) {
throw new RuntimeException("栈已满,无法入栈");
}
top++; // 栈顶指针上移
stack[top] = val; // 存入元素
}
// 2. 出栈(pop):删除并返回栈顶元素
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈为空,无法出栈");
}
int val = stack[top]; // 获取栈顶元素
top--; // 栈顶指针下移
return val;
}
// 3. 查看栈顶元素(peek):不删除,仅返回
public int peek() {
if (isEmpty()) {
throw new RuntimeException("栈为空");
}
return stack[top];
}
// 4. 判断栈是否为空
public boolean isEmpty() {
return top == -1;
}
// 5. 判断栈是否已满
public boolean isFull() {
return top == capacity - 1;
}
// 6. 获取栈的大小
public int size() {
return top + 1;
}
// 测试
public static void main(String[] args) {
MyStack stack = new MyStack(5);
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("栈顶元素:" + stack.peek()); // 3
System.out.println("出栈元素:" + stack.pop()); // 3
System.out.println("栈的大小:" + stack.size()); // 2
// 遍历栈(需出栈才能遍历,会改变栈结构)
System.out.println("栈元素遍历:");
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " "); // 输出:2 1
}
}
}
3.3 Java自带Stack使用(注意:已过时,推荐Deque)
import java.util.Stack;
public class StackDemo {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 入栈
stack.push(1);
stack.push(2);
stack.push(3);
// 查看栈顶
System.out.println("栈顶元素:" + stack.peek()); // 3
// 出栈
System.out.println("出栈:" + stack.pop()); // 3
// 判断是否为空
System.out.println("栈是否为空:" + stack.isEmpty()); // false
// 遍历
System.out.println("栈遍历:");
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " "); // 2 1
}
}
}
注意:Java自带的Stack类继承自Vector,效率较低,开发中推荐使用Deque接口的实现类(如LinkedList)替代,用法一致且更高效。
四、队列(Queue)—— 先进先出(FIFO)结构
4.1 核心定义与特征
队列是一种特殊的线性结构,遵循**先进先出(FIFO)**原则,仅允许在队尾(rear)插入元素,在队头(front)删除元素,底层可通过数组或链表实现。
核心特征:
-
操作受限:队尾入队(offer),队头出队(poll);
-
先进先出:先存入的元素最先取出,后存入的元素最后取出;
-
常用场景:任务调度、消息队列、排队系统等。
4.2 Java队列手动实现(链表版)
// 队列节点类(复用双向链表节点,也可使用单向链表)
class QueueNode {
int val;
QueueNode next;
public QueueNode(int val) {
this.val = val;
this.next = null;
}
}
// 单向链表实现队列
public class MyQueue {
private QueueNode front; // 队头(出队端)
private QueueNode rear; // 队尾(入队端)
private int size; // 队列大小
public MyQueue() {
this.front = null;
this.rear = null;
this.size = 0;
}
// 1. 入队(offer):队尾添加元素
public void offer(int val) {
QueueNode newNode = new QueueNode(val);
if (isEmpty()) { // 队列为空,队头和队尾都指向新节点
front = newNode;
rear = newNode;
} else { // 队尾next指向新节点,更新队尾
rear.next = newNode;
rear = newNode;
}
size++;
}
// 2. 出队(poll):队头删除并返回元素
public int poll() {
if (isEmpty()) {
throw new RuntimeException("队列为空,无法出队");
}
int val = front.val; // 获取队头元素
front = front.next; // 更新队头
if (front == null) { // 队列只剩一个元素,出队后为空
rear = null;
}
size--;
return val;
}
// 3. 查看队头元素(peek):不删除,仅返回
public int peek() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
return front.val;
}
// 4. 判断队列是否为空
public boolean isEmpty() {
return size == 0;
}
// 5. 获取队列大小
public int size() {
return size;
}
// 遍历队列
public void printQueue() {
QueueNode curr = front;
while (curr != null) {
System.out.print(curr.val + " ");
curr = curr.next;
}
System.out.println();
}
// 测试
public static void main(String[] args) {
MyQueue queue = new MyQueue();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println("队头元素:" + queue.peek()); // 1
System.out.println("出队元素:" + queue.poll()); // 1
System.out.println("队列大小:" + queue.size()); // 2
System.out.println("队列遍历:");
queue.printQueue(); // 输出:2 3
}
}
4.3 Java自带Queue使用(开发常用)
import java.util.LinkedList;
import java.util.Queue;
public class QueueDemo {
public static void main(String[] args) {
// Queue是接口,常用实现类:LinkedList(推荐)、ArrayDeque
Queue<Integer> queue = new LinkedList<>();
// 入队
queue.offer(1);
queue.offer(2);
queue.offer(3);
// 查看队头
System.out.println("队头元素:" + queue.peek()); // 1
// 出队
System.out.println("出队:" + queue.poll()); // 1
// 判断是否为空
System.out.println("队列是否为空:" + queue.isEmpty()); // false
// 遍历队列(不改变队列结构)
System.out.println("队列遍历:");
for (int num : queue) {
System.out.print(num + " "); // 2 3
}
}
}
五、哈希表(HashMap)—— 键值对存储结构
5.1 核心定义与特征
哈希表(HashMap)是Java中最常用的键值对存储结构,底层基于数组+链表/红黑树实现(JDK 8+),通过哈希函数将键(key)映射到数组索引,实现快速查找、插入、删除。
核心特征:
-
键值对存储:每个元素是一个键值对(key-value),key唯一,value可重复;
-
高效操作:查询、插入、删除时间复杂度接近O(1)(哈希冲突较少时);
-
哈希冲突:不同key通过哈希函数得到相同索引,通过链表/红黑树解决;
-
无序性:元素存储顺序与插入顺序无关(JDK 8+ LinkedHashMap可保证有序)。
5.2 Java HashMap核心使用(开发高频)
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapDemo {
public static void main(String[] args) {
// 1. 创建HashMap对象(key为String,value为Integer)
Map<String, Integer> map = new HashMap<>();
// 2. 插入键值对(put)
map.put("Java", 100);
map.put("数据结构", 90);
map.put("算法", 85);
// 重复key会覆盖value
map.put("Java", 95);
// 3. 获取value(get)
System.out.println("Java的分数:" + map.get("Java")); // 95
// 获取不存在的key,返回null
System.out.println("Python的分数:" + map.get("Python")); // null
// 4. 判断key是否存在(containsKey)
System.out.println("是否包含key:数据结构?" + map.containsKey("数据结构")); // true
// 5. 删除键值对(remove)
map.remove("算法");
System.out.println("删除算法后,map大小:" + map.size()); // 2
// 6. 遍历HashMap(三种方式)
// 方式1:遍历key(keySet)
System.out.println("遍历key:");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key + ":" + map.get(key));
}
// 方式2:遍历键值对(entrySet,推荐,效率高)
System.out.println("遍历键值对:");
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
for (Map.Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 方式3:遍历value(values)
System.out.println("遍历value:");
for (Integer value : map.values()) {
System.out.print(value + " "); // 95 90
}
}
}
5.3 核心注意事项(面试重点)
-
key的要求:key必须重写equals()和hashCode()方法,否则会导致哈希冲突无法正确解决,key无法正常查找、删除;
-
哈希冲突解决:JDK 8+ 中,当链表长度超过8时,转为红黑树;长度小于6时,转回链表,提升效率;
-
线程安全:HashMap线程不安全,多线程场景推荐使用ConcurrentHashMap;
-
扩容机制:初始容量16,负载因子0.75,当元素个数超过16*0.75=12时,扩容为原容量的2倍。
六、二叉树(Binary Tree)—— 非线性核心结构
6.1 核心定义与特征
二叉树是每个节点最多拥有两个子节点(左孩子、右孩子)的非线性数据结构,底层用于实现树结构、图结构、查找算法等。
核心特征:
-
节点关系:父节点→左孩子、右孩子;
-
空节点:空树用
null表示; -
递归结构:二叉树的子树也是二叉树;
-
常用分类:满二叉树、完全二叉树、平衡二叉树、二叉搜索树。
6.2 Java二叉树手动实现(核心源码级)
6.2.1 节点类定义
// 二叉树节点类(泛型支持任意类型)
class TreeNode<T> {
T val; // 节点值
TreeNode<T> left; // 左孩子
TreeNode<T> right; // 右孩子
// 构造方法
public TreeNode(T val) {
this.val = val;
this.left = null;
this.right = null;
}
@Override
public String toString() {
return val.toString();
}
}
6.2.2 二叉树核心实现(含遍历、插入、删除)
public class MyBinaryTree<T> {
private TreeNode<T> root; // 根节点
// 构造方法:初始化空树
public MyBinaryTree() {
this.root = null;
}
// 1. 插入节点(按层序插入,构建完全二叉树)
public void insert(T val) {
TreeNode<T> newNode = new TreeNode<>(val);
if (root == null) { // 空树,根节点设为新节点
root = newNode;
return;
}
// 层序遍历找到第一个空位置,插入新节点
java.util.Queue<TreeNode<T>> queue = new java.util.LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode<T> curr = queue.poll();
// 左孩子为空,插入左子树
if (curr.left == null) {
curr.left = newNode;
return;
}
// 右孩子为空,插入右子树
if (curr.right == null) {
curr.right = newNode;
return;
}
// 左右孩子都不为空,加入队列继续遍历
queue.offer(curr.left);
queue.offer(curr.right);
}
}
// 2. 层序遍历(广度优先,BFS)—— 核心面试考点
public void levelOrderTraversal() {
if (root == null) {
System.out.println("二叉树为空");
return;
}
java.util.Queue<TreeNode<T>> queue = new java.util.LinkedList<>();
queue.offer(root);
System.out.print("层序遍历:");
while (!queue.isEmpty()) {
TreeNode<T> curr = queue.poll();
System.out.print(curr.val + " "); // 访问当前节点
// 左孩子入队
if (curr.left != null) {
queue.offer(curr.left);
}
// 右孩子入队
if (curr.right != null) {
queue.offer(curr.right);
}
}
System.out.println();
}
// 3. 前序遍历(递归版:根→左→右)
public void preOrderTraversal() {
System.out.print("前序遍历:");
preOrderRecursive(root);
System.out.println();
}
private void preOrderRecursive(TreeNode<T> node) {
if (node == null) {
return;
}
System.out.print(node.val + " "); // 根
preOrderRecursive(node.left); // 左
preOrderRecursive(node.right); // 右
}
// 4. 中序遍历(递归版:左→根→右)—— 二叉搜索树常用
public void inOrderTraversal() {
System.out.print("中序遍历:");
inOrderRecursive(root);
System.out.println();
}
private void inOrderRecursive(TreeNode<T> node) {
if (node == null) {
return;
}
inOrderRecursive(node.left); // 左
System.out.print(node.val + " "); // 根
inOrderRecursive(node.right); // 右
}
// 5. 后序遍历(递归版:左→右→根)
public void postOrderTraversal() {
System.out.print("后序遍历:");
postOrderRecursive(root);
System.out.println();
}
private void postOrderRecursive(TreeNode<T> node) {
if (node == null) {
return;
}
postOrderRecursive(node.left); // 左
postOrderRecursive(node.right); // 右
System.out.print(node.val + " "); // 根
}
// 测试方法
public static void main(String[] args) {
MyBinaryTree<Integer> tree = new MyBinaryTree<>();
// 插入节点:构建如下结构
// 1
// / \
// 2 3
// / \ /
// 4 5 6
tree.insert(1);
tree.insert(2);
tree.insert(3);
tree.insert(4);
tree.insert(5);
tree.insert(6);
tree.levelOrderTraversal(); // 输出:1 2 3 4 5 6
tree.preOrderTraversal(); // 输出:1 2 4 5 3 6
tree.inOrderTraversal(); // 输出:4 2 5 1 6 3
tree.postOrderTraversal(); // 输出:4 5 2 6 3 1
}
}
6.3 二叉搜索树(BST)—— 有序二叉树
6.3.1 核心定义与特征
二叉搜索树(Binary Search Tree)是有序二叉树,满足:
-
左子树所有节点值 < 根节点值;
-
右子树所有节点值 > 根节点值;
-
左右子树也为二叉搜索树;
-
中序遍历结果为升序序列(核心特性)。
6.3.2 Java二叉搜索树实现(核心操作)
public class BSTree<T extends Comparable<T>> {
private TreeNode<T> root; // 根节点
public BSTree() {
this.root = null;
}
// 1. 插入节点(保持BST特性)
public void insert(T val) {
root = insertRecursive(root, val);
}
private TreeNode<T> insertRecursive(TreeNode<T> node, T val) {
if (node == null) { // 空位置,创建新节点
return new TreeNode<>(val);
}
// 比较值,决定插入左/右子树
int compareResult = val.compareTo(node.val);
if (compareResult < 0) { // 插入左子树
node.left = insertRecursive(node.left, val);
} else if (compareResult > 0) { // 插入右子树
node.right = insertRecursive(node.right, val);
}
// 重复值,不插入(可根据需求修改为覆盖)
return node;
}
// 2. 查找节点(核心:BST查找效率O(logn))
public boolean contains(T val) {
return containsRecursive(root, val);
}
private boolean containsRecursive(TreeNode<T> node, T val) {
if (node == null) {
return false; // 未找到
}
int compareResult = val.compareTo(node.val);
if (compareResult == 0) {
return true; // 找到
} else if (compareResult < 0) {
return containsRecursive(node.left, val); // 左子树查找
} else {
return containsRecursive(node.right, val); // 右子树查找
}
}
// 3. 中序遍历(验证BST有序性)
public void inOrderTraversal() {
System.out.print("BST中序遍历(升序):");
inOrderRecursive(root);
System.out.println();
}
private void inOrderRecursive(TreeNode<T> node) {
if (node == null) {
return;
}
inOrderRecursive(node.left);
System.out.print(node.val + " ");
inOrderRecursive(node.right);
}
// 测试方法
public static void main(String[] args) {
BSTree<Integer> bst = new BSTree<>();
// 插入节点:构建如下结构
// 5
// / \
// 3 7
// / \ / \
// 2 4 6 8
bst.insert(5);
bst.insert(3);
bst.insert(7);
bst.insert(2);
bst.insert(4);
bst.insert(6);
bst.insert(8);
bst.inOrderTraversal(); // 输出:2 3 4 5 6 7 8(升序)
System.out.println("是否包含6:" + bst.contains(6)); // true
System.out.println("是否包含9:" + bst.contains(9)); // false
}
}
6.4 核心面试考点总结
-
遍历方式:前序、中序、后序(递归+非递归)、层序遍历,必考;
-
BST特性:左小右大,中序遍历升序,查找/插入效率O(logn);
-
完全二叉树:用数组存储,索引公式(左孩子2i+1、右孩子2i+2);
-
平衡二叉树:左右子树高度差不超过1,避免BST退化为链表;
-
红黑树:自平衡BST,Java中TreeMap、HashMap(JDK8+)底层实现。
七、堆(Heap)—— 优先队列核心结构
7.1 核心定义与特征
堆是完全二叉树结构的优先队列,底层用数组实现,分为:
-
大顶堆:父节点值 ≥ 子节点值,根节点为最大值;
-
小顶堆:父节点值 ≤ 子节点值,根节点为最小值。
核心特征:
-
完全二叉树:用数组存储,索引有明确映射;
-
堆化操作:调整堆结构,保持堆特性;
-
常用场景:TopK问题、任务调度、优先级队列。
7.2 Java大顶堆手动实现(核心源码级)
public class MaxHeap<T extends Comparable<T>> {
private java.util.List<T> heap; // 底层数组存储堆元素
// 构造方法:初始化空堆
public MaxHeap() {
heap = new java.util.ArrayList<>();
}
// 1. 插入元素(大顶堆)
public void insert(T val) {
heap.add(val); // 先添加到数组末尾
siftUp(heap.size() - 1); // 向上堆化,保持大顶堆特性
}
// 2. 向上堆化(核心:比较父节点,交换位置)
private void siftUp(int index) {
// 父节点索引:(index - 1) / 2
int parentIndex = (index - 1) / 2;
// 递归终止条件:到达根节点,或父节点值大于当前节点
if (index <= 0 || heap.get(parentIndex).compareTo(heap.get(index)) >= 0) {
return;
}
// 交换当前节点与父节点
swap(index, parentIndex);
// 继续向上堆化
siftUp(parentIndex);
}
// 3. 交换数组元素
private void swap(int i, int j) {
T temp = heap.get(i);
heap.set(i, heap.get(j));
heap.set(j, temp);
}
// 4. 获取堆顶元素(最大值,大顶堆)
public T getMax() {
if (isEmpty()) {
throw new RuntimeException("堆为空");
}
return heap.get(0);
}
// 5. 删除堆顶元素(大顶堆)
public T extractMax() {
if (isEmpty()) {
throw new RuntimeException("堆为空");
}
T max = heap.get(0);
// 用最后一个元素替换根节点
heap.set(0, heap.get(heap.size() - 1));
heap.remove(heap.size() - 1);
siftDown(0); // 向下堆化,保持大顶堆特性
return max;
}
// 6. 向下堆化(核心:比较子节点,交换位置)
private void siftDown(int index) {
int leftChildIndex = 2 * index + 1;
int rightChildIndex = 2 * index + 2;
int largestIndex = index; // 记录最大值索引
// 找到最大值索引
if (leftChildIndex < heap.size() && heap.get(leftChildIndex).compareTo(heap.get(largestIndex)) > 0) {
largestIndex = leftChildIndex;
}
if (rightChildIndex < heap.size() && heap.get(rightChildIndex).compareTo(heap.get(largestIndex)) > 0) {
largestIndex = rightChildIndex;
}
// 递归终止条件:当前节点已是最大值
if (largestIndex == index) {
return;
}
// 交换当前节点与最大值节点
swap(index, largestIndex);
// 继续向下堆化
siftDown(largestIndex);
}
// 7. 判断堆是否为空
public boolean isEmpty() {
return heap.isEmpty();
}
// 8. 获取堆大小
public int size() {
return heap.size();
}
// 测试方法
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<>();
maxHeap.insert(3);
maxHeap.insert(1);
maxHeap.insert(4);
maxHeap.insert(2);
maxHeap.insert(5);
System.out.println("堆顶最大值:" + maxHeap.getMax()); // 5
System.out.println("删除最大值:" + maxHeap.extractMax()); // 5
System.out.println("新堆顶最大值:" + maxHeap.getMax()); // 4
}
}
7.3 Java自带堆(PriorityQueue)使用(开发常用)
Java中java.util.PriorityQueue默认是小顶堆,可通过Comparator实现大顶堆。
import java.util.PriorityQueue;
import java.util.Comparator;
public class HeapDemo {
public static void main(String[] args) {
// 1. 小顶堆(默认)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
minHeap.offer(3);
minHeap.offer(1);
minHeap.offer(4);
System.out.println("小顶堆堆顶:" + minHeap.peek()); // 1
// 2. 大顶堆(自定义Comparator)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
maxHeap.offer(3);
maxHeap.offer(1);
maxHeap.offer(4);
System.out.println("大顶堆堆顶:" + maxHeap.peek()); // 4
}
}
7.4 核心面试考点总结
-
堆的特性:大顶堆/小顶堆,完全二叉树结构;
-
堆化操作:siftUp(向上)、siftDown(向下),时间复杂度O(logn);
-
TopK问题:用小顶堆求前K大元素,大顶堆求前K小元素,效率O(nlogk);
-
堆排序:利用堆特性实现排序,时间复杂度O(nlogn)。
八、图(Graph)—— 复杂非线性结构
8.1 核心定义与特征
图是由**顶点(Vertex)和边(Edge)**组成的非线性数据结构,用于表示多对多的关系。
核心分类:
-
有向图:边有方向,A→B 不等于 B→A;
-
无向图:边无方向,A↔B 等价于 B↔A;
-
加权图:边带有权重(如距离、成本);
-
常用存储方式:邻接矩阵、邻接表(Java常用)。
8.2 Java无向图邻接表实现(核心源码级)
import java.util.ArrayList;
import java.util.List;
// 无向图邻接表实现
public class UndirectedGraph {
private List<List<Integer>> adjList; // 邻接表:索引为顶点,值为相邻顶点集合
private int vertexCount; // 顶点数量
// 构造方法:初始化指定顶点数的图
public UndirectedGraph(int vertexCount) {
this.vertexCount = vertexCount;
adjList = new ArrayList<>(vertexCount);
// 初始化每个顶点的相邻顶点集合
for (int i = 0; i < vertexCount; i++) {
adjList.add(new ArrayList<>());
}
}
// 1. 添加边(无向图:A-B 等价于 B-A,需添加两次)
public void addEdge(int v1, int v2) {
// 校验顶点合法性
if (v1 < 0 || v1 >= vertexCount || v2 < 0 || v2 >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
// 无向图,双向添加边
adjList.get(v1).add(v2);
adjList.get(v2).add(v1);
}
// 2. 删除边(无向图:删除双向边)
public void removeEdge(int v1, int v2) {
if (v1 < 0 || v1 >= vertexCount || v2< 0 || v2 >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
adjList.get(v1).remove(Integer.valueOf(v2));
adjList.get(v2).remove(Integer.valueOf(v1));
}
// 3. 深度优先遍历(DFS,递归版)—— 核心面试考点
public void dfs(int startVertex) {
if (startVertex < 0 || startVertex >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
boolean[] visited = new boolean[vertexCount]; // 标记顶点是否被访问
System.out.print("DFS遍历:");
dfsRecursive(startVertex, visited);
System.out.println();
}
private void dfsRecursive(int vertex, boolean[] visited) {
// 标记当前顶点为已访问
visited[vertex] = true;
System.out.print(vertex + " ");
// 遍历当前顶点的所有相邻顶点,递归访问未被访问的顶点
for (int neighbor : adjList.get(vertex)) {
if (!visited[neighbor]) {
dfsRecursive(neighbor, visited);
}
}
}
// 4. 广度优先遍历(BFS)—— 核心面试考点
public void bfs(int startVertex) {
if (startVertex < 0 || startVertex >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
boolean[] visited = new boolean[vertexCount];
java.util.Queue<Integer> queue = new java.util.LinkedList<>();
// 初始化:标记起始顶点为已访问,加入队列
visited[startVertex] = true;
queue.offer(startVertex);
System.out.print("BFS遍历:");
while (!queue.isEmpty()) {
int currVertex = queue.poll();
System.out.print(currVertex + " ");
// 遍历当前顶点的相邻顶点,未访问则标记并加入队列
for (int neighbor : adjList.get(currVertex)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}
System.out.println();
}
// 5. 获取指定顶点的相邻顶点
public List<Integer> getNeighbors(int vertex) {
if (vertex < 0 || vertex >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
return new ArrayList<>(adjList.get(vertex)); // 返回副本,避免外部修改
}
// 测试方法
public static void main(String[] args) {
// 构建无向图:
// 0 - 1 - 2
// | /
// 3 ---
UndirectedGraph graph = new UndirectedGraph(4);
graph.addEdge(0, 1);
graph.addEdge(0, 3);
graph.addEdge(1, 2);
graph.addEdge(2, 3);
graph.dfs(0); // 输出:0 1 2 3(或其他DFS顺序,取决于遍历顺序)
graph.bfs(0); // 输出:0 1 3 2
System.out.println("顶点0的相邻顶点:" + graph.getNeighbors(0)); // [1, 3]
}
}
8.3 有向图简单实现(补充)
import java.util.ArrayList;
import java.util.List;
// 有向图邻接表实现(仅边的添加为单向,其余逻辑与无向图类似)
public class DirectedGraph {
private List<List<Integer>> adjList;
private int vertexCount;
public DirectedGraph(int vertexCount) {
this.vertexCount = vertexCount;
adjList = new ArrayList<>(vertexCount);
for (int i = 0; i < vertexCount; i++) {
adjList.add(new ArrayList<>());
}
}
// 有向图添加边:仅添加v1→v2,不添加v2→v1
public void addEdge(int v1, int v2) {
if (v1 < 0 || v1 >= vertexCount || v2 < 0 || v2 >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
adjList.get(v1).add(v2);
}
// DFS遍历(与无向图一致)
public void dfs(int startVertex) {
if (startVertex< 0 || startVertex >= vertexCount) {
throw new IndexOutOfBoundsException("顶点索引越界");
}
boolean[] visited = new boolean[vertexCount];
System.out.print("有向图DFS遍历:");
dfsRecursive(startVertex, visited);
System.out.println();
}
private void dfsRecursive(int vertex, boolean[] visited) {
visited[vertex] = true;
System.out.print(vertex + " ");
for (int neighbor : adjList.get(vertex)) {
if (!visited[neighbor]) {
dfsRecursive(neighbor, visited);
}
}
}
// 测试
public static void main(String[] args) {
// 有向图:0→1,1→2,0→3,3→2
DirectedGraph graph = new DirectedGraph(4);
graph.addEdge(0, 1);
graph.addEdge(1, 2);
graph.addEdge(0, 3);
graph.addEdge(3, 2);
graph.dfs(0); // 输出:0 1 2 3(或0 3 2 1,取决于遍历顺序)
}
}
8.4 核心面试考点总结
-
存储方式:邻接矩阵(适合稠密图,查询快、占用空间大)、邻接表(适合稀疏图,节省空间);
-
遍历方式:DFS(深度优先,递归/栈实现)、BFS(广度优先,队列实现),必考;
-
核心应用:最短路径(Dijkstra算法)、拓扑排序(有向无环图DAG)、连通性判断;
-
无向图与有向图区别:边的方向性,添加/删除边的逻辑差异。
九、Java数据结构核心总结(面试必备)
本文涵盖Java开发与面试中最常用的8种数据结构,核心重点如下:
-
线性结构(数组、链表、栈、队列):核心关注效率差异(随机访问vs增删效率);
-
键值对结构(HashMap):底层实现(数组+链表/红黑树)、哈希冲突、扩容机制;
-
树结构(二叉树、BST、堆):遍历方式、核心特性、堆化操作;
-
图结构:存储方式、DFS/BFS遍历、核心应用场景。