Java数据结构核心解析

7 阅读20分钟

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&lt;T&gt; newNode = new TreeNode<>(val);
        if (root == null) { // 空树,根节点设为新节点
            root = newNode;
            return;
        }

        // 层序遍历找到第一个空位置,插入新节点
        java.util.Queue<TreeNode<T>&gt; 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&gt;&gt; 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&lt;Integer&gt; 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&lt;Integer&gt; 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&lt;Integer&gt; 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种数据结构,核心重点如下:

  1. 线性结构(数组、链表、栈、队列):核心关注效率差异(随机访问vs增删效率);

  2. 键值对结构(HashMap):底层实现(数组+链表/红黑树)、哈希冲突、扩容机制;

  3. 树结构(二叉树、BST、堆):遍历方式、核心特性、堆化操作;

  4. 图结构:存储方式、DFS/BFS遍历、核心应用场景。