数据结构 六
承接单向链表基础增删改查,本文继续深入讲解链表高频面试算法,系统梳理栈与队列的底层实现与经典应用,并完整拆解二叉搜索树(BST)的插入、遍历、查找、删除全流程,所有代码均对齐你提供的源文件,补充完整注释与原理说明。
一、链表进阶高频算法题
链表算法的核心技巧是双指针思想,通过设置不同移动速度、不同起点的指针,在仅一次遍历的前提下完成复杂操作,是力扣热门考点。
1. 环形链表 II:寻找环的入口节点
题目描述
给定一个链表,判断是否存在环;如果有环,返回环的起始节点;无环则返回 null。
解题思路:快慢指针 + 数学推导
第一步:判断是否有环
定义快慢指针 fast 和 slow,同时从头节点出发:
slow每次走 1 步fast每次走 2 步
若 fast 最终走到链表末尾(指向 null),说明无环;若 fast 和 slow 相遇,则证明链表存在环。
第二步:定位环的入口
两指针相遇后,将其中一个指针重置回头节点,两个指针以**相同速度(每次1步)**同时移动,再次相遇的节点就是环的入口节点。
数学推导证明
设三个变量:
- 头节点到环入口的距离为
x - 环入口到两指针相遇点的距离为
y - 相遇点沿环回到入口的距离为
z - 环的周长 =
y + z
相遇时路程关系:
slow总路程:x + yfast总路程:x + n*(y+z) + y(n 为 fast 在环内绕的圈数,n ≥ 1)
因为 fast 速度是 slow 的 2 倍,路程也为 2 倍:
2(x+y)=x+n(y+z)+y 2(x + y) = x + n(y+z) + y 2(x+y)=x+n(y+z)+y
化简得:
x=(n−1)(y+z)+z x = (n-1)(y+z) + z x=(n−1)(y+z)+z
该式含义:头节点到入口的距离 x,等于从相遇点出发,绕 n-1 圈后再走 z 的距离。因此两个指针同速移动,一定会在环入口处精准相遇。
代码实现
java
/**
* 寻找环形链表的入口节点
* @return 环入口节点,无环返回null
*/
public Node detectCycle() {
if (root == null || root.next == null) {
return null;
}
Node fast = root;
Node slow = root;
// 第一步:快慢指针找相遇点
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
// 相遇,确认有环,开始定位入口
if (slow == fast) {
// 慢指针重置回头节点
slow = root;
// 同速移动,再次相遇即为入口
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
// fast走到终点,无环
return null;
}
2. 反转链表
题目描述
原地反转一个单链表,返回反转后的新头节点,要求不额外创建新链表。
解题思路:双指针迭代法
核心是逐个修改节点的 next 指针,让每个节点的 next 指向它的前驱节点:
- 定义两个指针:
pre指向前驱节点(初始为 null),cur指向当前节点(初始为头节点) - 遍历链表,每次先临时保存当前节点的后继节点(防止断链丢失)
- 将当前节点的 next 指向 pre,完成该节点的反转
- pre 和 cur 同时后移,直到 cur 遍历完整个链表
- 遍历结束后,pre 就是反转后的新头节点
代码实现
java
/**
* 原地反转链表
*/
public void reverseList() {
Node pre = null; // 前驱指针,初始为空
Node cur = root; // 当前指针,从头节点开始
Node nextTemp; // 临时保存后继节点,防止断链
while (cur != null) {
nextTemp = cur.next; // 先保存下一个节点
cur.next = pre; // 当前节点指向前驱,完成反转
pre = cur; // 前驱指针后移
cur = nextTemp; // 当前指针后移
}
// 遍历结束,pre成为新的头节点
root = pre;
}
核心注意点:必须先保存后继节点再修改 next 指针,否则一旦修改指向,后续节点会彻底丢失引用,导致链表断裂。
3. 完整链表工具类(整合所有功能)
对应你提供的 MyLinkedList.java,整合基础增删改查 + 进阶算法的完整版本:
java
public class MyLinkedList {
public Node root;
// 头插法
public void HeadInsert(int value){
Node newNode = new Node(value);
if(root == null){
root = newNode;
return;
}
newNode.next = root;
root = newNode;
}
// 尾插法
public void EndInsert(int value){
Node newNode = new Node(value);
if(root == null){
root = newNode;
return;
}
Node current = root;
while(current.next != null){
current = current.next;
}
current.next = newNode;
}
// 打印链表
public void Print(){
Node cur = root;
while(cur != null){
System.out.print(cur.data + "->");
cur = cur.next;
}
System.out.println("null");
}
// 获取链表长度
public int size(){
int count = 0;
Node cur = root;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
// 删除指定下标节点(虚拟头节点法,统一边界逻辑)
public void delete(int index){
if(index < 0 || index >= size()){
return;
}
Node dummy = new Node(0);
dummy.next = root;
Node cur = dummy;
// 定位到待删节点的前驱
for(int i = 0; i < index; i++){
cur = cur.next;
}
cur.next = cur.next.next;
root = dummy.next;
}
// 按值查找是否存在
public boolean find(int value){
Node cur = root;
while(cur != null){
if(cur.data == value){
return true;
}
cur = cur.next;
}
return false;
}
// 查找链表中间节点(快慢指针)
public Node findMiddle() {
Node fast = root;
Node slow = root;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// 查找倒数第k个节点
public Node findLastK(int k) {
if (k <= 0 || k > size()) return null;
Node fast = root;
Node slow = root;
// 快指针先走k步
for (int i = 0; i < k; i++) {
fast = fast.next;
}
// 同步移动
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
// 判断链表是否有环
public boolean hasCycle() {
Node fast = root;
Node slow = root;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
// 寻找环的入口节点
public Node detectCycle() {
if (root == null || root.next == null) return null;
Node fast = root;
Node slow = root;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
// 相遇,开始找入口
if (slow == fast) {
slow = root;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
// 原地反转链表
public void reverseList() {
Node pre = null;
Node cur = root;
Node nextTemp;
while (cur != null) {
nextTemp = cur.next;
cur.next = pre;
pre = cur;
cur = nextTemp;
}
root = pre;
}
}
二、栈与队列
栈和队列是两种操作受限的线性表:栈是「后进先出(LIFO)」,只能在栈顶操作;队列是「先进先出(FIFO)」,队尾入队、队头出队。二者底层均可通过数组或链表实现。
1. 栈的数组实现
数组实现栈非常简单,用一个 top 变量记录栈顶下标,初始为 -1 表示空栈,所有操作都在栈顶完成。
对应你提供的 StringStackDemo.java,泛型版本实现:
java
public class StringStackDemo<T> {
// 底层数组,模拟栈空间
T[] arr;
// 栈顶指针,初始为-1表示空栈
int top = -1;
/**
* 构造方法:初始化指定容量的栈
* @param size 栈的最大容量
*/
public StringStackDemo(int size) {
// 泛型数组的标准创建方式
arr = (T[]) new Object[size];
}
/**
* 入栈:向栈顶添加元素
* @param value 要入栈的元素
*/
public void push(T value){
// 栈满判断
if(arr.length - 1 == top){
System.out.println("栈已满,无法入栈");
return;
}
top++;
arr[top] = value;
}
/**
* 出栈:弹出栈顶元素并打印
*/
public void pop(){
// 栈空判断
if(top == -1){
System.out.println("栈为空,无法出栈");
return;
}
System.out.println(arr[top]);
top--;
}
/**
* 查看栈顶元素(不弹出)
* @return 栈顶元素
*/
public T peek() {
if(top == -1) return null;
return arr[top];
}
/**
* 判断栈是否为空
*/
public boolean isEmpty() {
return top == -1;
}
}
2. 队列的链表实现
队列需要同时操作队头和队尾,用链表实现时会同时维护 head 头指针和 tail 尾指针,让入队和出队都达到 O(1) 时间复杂度。
对应你提供的 LinkQueue.java,泛型版本链式队列:
java
public class LinkQueue<W> {
// 内部节点类
public static class Node<W>{
W data;
Node<W> next;
Node(W data){
this.data = data;
this.next = null;
}
}
Node<W> head; // 队头指针
Node<W> tail; // 队尾指针
int size = 0; // 当前元素个数
int maxSize; // 队列最大容量
/**
* 构造方法:创建指定最大容量的链式队列
* @param maxSize 最大容量
*/
public LinkQueue(int maxSize){
this.maxSize = maxSize;
head = null;
tail = null;
}
/**
* 入队:向队尾添加元素
* @param value 要添加的元素
*/
public void push(W value){
// 队列已满
if(size == maxSize){
System.out.println("队列满");
return;
}
Node<W> newNode = new Node<>(value);
// 空队列:头尾指针都指向新节点
if(head == null){
head = newNode;
tail = newNode;
}else{
// 非空:尾节点next指向新节点,尾指针后移
tail.next = newNode;
tail = newNode;
}
size++;
}
/**
* 出队:弹出队头元素并打印
*/
public void pop(){
if(size == 0){
System.out.println("队列空");
return;
}
System.out.println(head.data);
// 头指针后移
head = head.next;
// 出队后队列为空,尾指针也要置空,避免野指针
if (head == null) {
tail = null;
}
size--;
}
/**
* 查看队头元素
*/
public W peek() {
if (size == 0) return null;
return head.data;
}
/**
* 判断队列是否为空
*/
public boolean isEmpty() {
return size == 0;
}
}
3. 栈经典面试题:有效的括号
题目描述
给定一个只包含 '('、')'、'['、']'、'{'、'}' 的字符串,判断括号是否有效。要求左括号必须用相同类型的右括号闭合,且必须以正确的顺序闭合。
解题思路
利用栈「后进先出」的匹配特性:
- 遍历字符串,遇到左括号时,将对应的右括号压入栈
- 遇到右括号时:
- 栈为空或栈顶元素不匹配 → 直接返回 false
- 匹配成功 → 弹出栈顶
- 遍历结束后,栈为空说明所有括号都正确闭合
对应你提供的 StackTest1.java:
java
import java.util.Stack;
public class StackTest1 {
/**
* 判断括号字符串是否有效
* @param s 括号字符串,例如 "({})"
* @return 是否有效
*/
public static boolean isValid(String s){
Stack<Character> stack = new Stack<>();
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
// 左括号:压入对应的右括号
if(c == '('){
stack.push(')');
}else if(c == '['){
stack.push(']');
}else if(c == '{'){
stack.push('}');
}else if(stack.isEmpty() || stack.peek() != c){
// 右括号不匹配或栈已提前为空
return false;
}else{
// 匹配成功,弹出栈顶
stack.pop();
}
}
// 所有左括号都匹配完成,栈为空
return stack.isEmpty();
}
}
三、二叉搜索树(BST)
二叉搜索树(Binary Search Tree,也叫有序二叉树)是一种特殊二叉树,核心规则:任意节点的左子树所有节点值都小于该节点,右子树所有节点值都大于该节点。它的中序遍历结果是天然的升序序列,这是 BST 最核心的特性。
1. 树节点结构
二叉树每个节点包含三部分:数据域、左孩子指针、右孩子指针。
对应你提供的 TreeNode.java:
java
package tree;
public class TreeNode {
public Integer data; // 节点存储的数据
public TreeNode leftNode; // 左孩子指针
public TreeNode rightNode; // 右孩子指针
/**
* 带参构造方法:创建节点时直接赋值
* @param x 节点值
*/
public TreeNode(Integer x) {
this.data = x;
// 左右孩子默认初始化为null
}
}
2. 二叉搜索树的插入
核心思路
- 创建新节点
- 如果树为空,新节点直接作为根节点
- 树非空时,从根节点开始循环比较:
- 新值 < 当前节点值 → 往左子树走,左孩子为空则插入
- 新值 ≥ 当前节点值 → 往右子树走,右孩子为空则插入
- 找到空位置完成插入,结束
注意:你提供的
BinaryTree0.java存在经典 bug:cur 移动后直接 return,没有继续循环比较,导致只能插入两层节点。下面给出的是BinaryTree.java中的正确循环实现。
正确插入代码
java
/**
* 向二叉搜索树中插入节点
* @param x 要插入的值
*/
public void createBinaryTree(Integer x){
// 1. 创建新节点
TreeNode newNode = new TreeNode(x);
// 2. 树为空,新节点作为根
if (root == null){
root = newNode;
return;
}
// 3. 从根节点遍历,寻找插入位置
TreeNode cur = root;
while (cur != null){
if (x < cur.data){
// 往左子树查找
if(cur.leftNode != null){
cur = cur.leftNode;
}else {
// 左孩子为空,在此插入
cur.leftNode = newNode;
return;
}
}else{
// 往右子树查找
if (cur.rightNode != null){
cur = cur.rightNode;
}else {
// 右孩子为空,在此插入
cur.rightNode = newNode;
return;
}
}
}
}
3. 二叉树的四种遍历方式
二叉树遍历分为深度优先遍历(DFS)和广度优先遍历(BFS)。深度优先又分为先序、中序、后序三种,区别在于根节点的访问时机。
(1)先序遍历(根 → 左 → 右)
访问顺序:先打印当前节点,再递归遍历左子树,最后递归遍历右子树。
java
/**
* 先序遍历
* @param treeNode 当前遍历节点
*/
public void beforeOrder(TreeNode treeNode){
// 递归出口:节点为空则返回
if(treeNode == null){
return;
}
// 1. 访问根节点
System.out.println(treeNode.data);
// 2. 递归遍历左子树
beforeOrder(treeNode.leftNode);
// 3. 递归遍历右子树
beforeOrder(treeNode.rightNode);
}
(2)中序遍历(左 → 根 → 右)
访问顺序:先递归遍历左子树,再打印当前节点,最后递归遍历右子树。
核心特性:二叉搜索树的中序遍历结果是严格升序的序列。
java
/**
* 中序遍历
* @param treeNode 当前遍历节点
*/
public void middleOrder(TreeNode treeNode){
if(treeNode == null){
return;
}
// 1. 递归遍历左子树
middleOrder(treeNode.leftNode);
// 2. 访问根节点
System.out.println(treeNode.data);
// 3. 递归遍历右子树
middleOrder(treeNode.rightNode);
}
(3)后序遍历(左 → 右 → 根)
访问顺序:先递归遍历左子树,再递归遍历右子树,最后打印当前节点。
java
/**
* 后序遍历
* @param treeNode 当前遍历节点
*/
public void afterOrder(TreeNode treeNode){
if(treeNode == null){
return;
}
// 1. 递归遍历左子树
afterOrder(treeNode.leftNode);
// 2. 递归遍历右子树
afterOrder(treeNode.rightNode);
// 3. 访问根节点
System.out.println(treeNode.data);
}
(4)层序遍历(广度优先)
按层级从上到下、每层从左到右依次访问节点,需要借助队列实现:
- 根节点先入队
- 队列不为空则循环:出队一个节点并打印
- 将当前节点的左、右孩子依次入队(非空才入队)
- 队列为空时遍历结束
java
import java.util.LinkedList;
import java.util.Queue;
/**
* 层序遍历(广度优先遍历)
*/
public void levelOrder(){
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
// 访问当前节点
System.out.println(cur.data);
// 左孩子入队
if(cur.leftNode != null){
queue.offer(cur.leftNode);
}
// 右孩子入队
if(cur.rightNode != null){
queue.offer(cur.rightNode);
}
}
}
4. 查找操作
(1)查找目标节点
利用 BST 有序特性,从根节点开始比较大小,左小右大,直到找到目标或遍历到空。
java
/**
* 查找值为target的节点
* @param node 起始节点
* @param target 目标值
* @return 找到的节点,找不到返回null
*/
public TreeNode find(TreeNode node, int target){
if(node == null){
return null;
}
if(node.data == target){
// 找到目标节点
return node;
}else if(node.data > target){
// 目标值更小,去左子树找
return find(node.leftNode, target);
}else{
// 目标值更大,去右子树找
return find(node.rightNode, target);
}
}
(2)查找目标节点的父节点
删除节点时需要修改父节点的指针,因此需要单独实现查找父节点的方法。
java
/**
* 查找目标节点的父节点
* @param node 起始节点
* @param target 目标值
* @return 父节点,找不到返回null
*/
public TreeNode findParent(TreeNode node, int target){
if(node == null){
return null;
}
// 判断当前节点的左/右孩子是不是目标节点
if( (node.leftNode != null && node.leftNode.data == target)
|| (node.rightNode != null && node.rightNode.data == target) ){
return node;
}else {
// 目标值小,去左子树继续找
if(node.leftNode != null && node.data > target){
return findParent(node.leftNode, target);
}
// 目标值大,去右子树继续找
else if(node.rightNode != null && node.data < target){
return findParent(node.rightNode, target);
}else {
return null;
}
}
}
5. 二叉搜索树的删除
BST 删除是最复杂的操作,需要分三种场景处理:
- 叶子节点:直接删除,将父节点对应的指针置空
- 只有一棵子树:让父节点直接指向该节点的子树,跳过被删节点
- 有两棵子树:不能直接删除,用「右子树最小值」或「左子树最大值」替换当前节点的值,再删除那个最小/最大节点(该节点一定是叶子或单子树,不会递归触发双孩子场景)
辅助方法:查找右子树的最小值
右子树的最小值就是右子树最左侧的节点,用来替换被删节点的值,保证有序性。
java
/**
* 查找以node为根的子树的最小值,并删除该最小节点
* @param node 子树根节点
* @return 最小值
*/
public int findRightTreeMin(TreeNode node){
TreeNode cur = node;
// 一直往左走,最左节点就是最小值
while (cur.leftNode != null){
cur = cur.leftNode;
}
// 删除这个最小节点
delete(node, cur.data);
return cur.data;
}
完整删除方法
java
/**
* 删除值为target的节点
* @param node 起始节点
* @param target 要删除的值
*/
public void delete(TreeNode node, int target){
// 树为空
if(node == null){
System.out.println("树为null");
return;
}
// 1. 定位目标节点
TreeNode targetNode = find(node, target);
if(targetNode == null){
System.out.println("没有找到该节点");
return;
}
// 2. 定位目标节点的父节点
TreeNode parentNode = findParent(node, target);
// 情况一:目标节点是叶子节点(左右都为空)
if(targetNode.leftNode == null && targetNode.rightNode == null){
// 特殊情况:删除的是根节点,且整棵树只有一个节点
if(parentNode == null){
root = null;
return;
}
// 目标是父节点的左孩子
if(parentNode.leftNode != null && parentNode.leftNode.data == target){
parentNode.leftNode = null;
}
// 目标是父节点的右孩子
else if(parentNode.rightNode != null && parentNode.rightNode.data == target){
parentNode.rightNode = null;
}
}
// 情况二:目标节点有左右两棵子树
else if(targetNode.leftNode != null && targetNode.rightNode != null){
// 用右子树的最小值替换当前节点的值
targetNode.data = findRightTreeMin(targetNode.rightNode);
}
// 情况三:目标节点只有一棵子树(左或右)
else {
// 目标节点有左子树
if(targetNode.leftNode != null){
// 删除的是根节点
if(parentNode == null){
root = targetNode.leftNode;
return;
}
// 目标是父节点的左孩子
if(parentNode.leftNode.data == target){
parentNode.leftNode = targetNode.leftNode;
}
// 目标是父节点的右孩子
else {
parentNode.rightNode = targetNode.leftNode;
}
}
// 目标节点有右子树
else{
// 删除的是根节点
if(parentNode == null){
root = targetNode.rightNode;
return;
}
// 目标是父节点的左孩子
if(parentNode.leftNode.data == target){
parentNode.leftNode = targetNode.rightNode;
}
// 目标是父节点的右孩子
else {
parentNode.rightNode = targetNode.rightNode;
}
}
}
}
6. 完整 BST 类 + 测试代码
BinaryTree 完整类
java
package tree;
import java.util.LinkedList;
import java.util.Queue;
public class BinaryTree {
// 根节点指针
public TreeNode root;
/**
* 插入节点,构建二叉搜索树
*/
public void createBinaryTree(Integer x){
TreeNode newNode = new TreeNode(x);
if (root == null){
root = newNode;
return;
}
TreeNode cur = root;
while (cur != null){
if (x < cur.data){
if(cur.leftNode != null){
cur = cur.leftNode;
}else {
cur.leftNode = newNode;
return;
}
}else{
if (cur.rightNode != null){
cur = cur.rightNode;
}else {
cur.rightNode = newNode;
return;
}
}
}
}
/**
* 先序遍历
*/
public void beforeOrder(TreeNode treeNode){
if(treeNode == null) return;
System.out.println(treeNode.data);
beforeOrder(treeNode.leftNode);
beforeOrder(treeNode.rightNode);
}
/**
* 中序遍历
*/
public void middleOrder(TreeNode treeNode){
if(treeNode == null) return;
middleOrder(treeNode.leftNode);
System.out.println(treeNode.data);
middleOrder(treeNode.rightNode);
}
/**
* 后序遍历
*/
public void afterOrder(TreeNode treeNode){
if(treeNode == null) return;
afterOrder(treeNode.leftNode);
afterOrder(treeNode.rightNode);
System.out.println(treeNode.data);
}
/**
* 层序遍历
*/
public void levelOrder(){
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
System.out.println(cur.data);
if(cur.leftNode != null) queue.offer(cur.leftNode);
if(cur.rightNode != null) queue.offer(cur.rightNode);
}
}
/**
* 查找目标节点
*/
public TreeNode find(TreeNode node, int target){
if(root == null){
System.out.println("树为空");
return null;
}
if(node.data == target){
return node;
}else if(node.data > target){
if(node.leftNode == null) return null;
return find(node.leftNode, target);
}else{
if(node.rightNode == null) return null;
return find(node.rightNode, target);
}
}
/**
* 查找目标节点的父节点
*/
public TreeNode findParent(TreeNode node, int target){
if(root == null){
System.out.println("树为空");
return null;
}
if( (node.leftNode!=null && node.leftNode.data == target)
|| (node.rightNode!=null && node.rightNode.data == target) ){
return node;
}else {
if(node.leftNode!=null && node.data > target){
return findParent(node.leftNode, target);
}else if(node.rightNode!=null && node.data < target){
return findParent(node.rightNode, target);
}else {
return null;
}
}
}
/**
* 查找右子树最小值并删除该节点
*/
public int findRightTreeMin(TreeNode node){
TreeNode cur = node;
while (cur.leftNode != null){
cur = cur.leftNode;
}
delete(node, cur.data);
return cur.data;
}
/**
* 删除节点
*/
public void delete(TreeNode node, int target){
if(node == null){
System.out.println("树为null");
return;
}
// 整棵树只有一个节点
if(node.leftNode == null && node.rightNode == null){
node = null;
return;
}
TreeNode targetNode = find(node, target);
if(targetNode == null){
System.out.println("没有找到该节点");
return;
}
TreeNode parentNode = findParent(node, target);
// 情况1:叶子节点
if(targetNode.leftNode == null && targetNode.rightNode == null){
if(parentNode == null){
root = null;
return;
}
if(parentNode.leftNode != null && parentNode.leftNode.data == target){
parentNode.leftNode = null;
}else if(parentNode.rightNode!=null && parentNode.rightNode.data == target){
parentNode.rightNode = null;
}
}
// 情况2:有左右两棵子树
else if(targetNode.leftNode != null && targetNode.rightNode != null){
targetNode.data = findRightTreeMin(targetNode.rightNode);
}
// 情况3:只有一棵子树
else {
if(targetNode.leftNode !=null){
if(parentNode == null){
root = targetNode.leftNode;
return;
}
if(parentNode.leftNode.data == target){
parentNode.leftNode = targetNode.leftNode;
}else {
parentNode.rightNode = targetNode.leftNode;
}
}else{
if(parentNode == null){
root = targetNode.rightNode;
return;
}
if(parentNode.leftNode.data == target){
parentNode.leftNode = targetNode.rightNode;
}else {
parentNode.rightNode = targetNode.rightNode;
}
}
}
}
}
测试类 Test.java
java
package tree;
public class Test {
public static void main(String[] args) {
BinaryTree binaryTree = new BinaryTree();
// 构建二叉搜索树
binaryTree.createBinaryTree(5);
binaryTree.createBinaryTree(3);
binaryTree.createBinaryTree(7);
binaryTree.createBinaryTree(1);
binaryTree.createBinaryTree(4);
binaryTree.createBinaryTree(6);
binaryTree.createBinaryTree(9);
System.out.println("===== 先序遍历 =====");
binaryTree.beforeOrder(binaryTree.root);
System.out.println("===== 中序遍历 =====");
binaryTree.middleOrder(binaryTree.root);
System.out.println("===== 后序遍历 =====");
binaryTree.afterOrder(binaryTree.root);
System.out.println("===== 层序遍历 =====");
binaryTree.levelOrder();
// 测试删除
System.out.println("===== 删除节点7,中序遍历 =====");
binaryTree.delete(binaryTree.root, 7);
binaryTree.middleOrder(binaryTree.root);
}
}
本篇总结
- 链表进阶:环形链表入口、反转链表都是双指针思想的经典应用,通过调整指针的速度与起点,在一次遍历内完成多轮遍历才能实现的功能,是面试必背题型。
- 栈与队列:栈后进先出,适合括号匹配、表达式求值、回溯类场景;队列先进先出,适合层序遍历、任务排队场景。数组实现栈简单高效,链表实现队列可避免扩容开销。
- 二叉搜索树:核心特性是左小右大,中序遍历天然有序;插入、查找可通过循环或递归实现,删除需分叶子、单子树、双子树三种情况处理,双子树用右子树最小值替换是标准解法。