数据结构笔记——链表成环/反转 + 有序二叉树(BST)构建、遍历、删除

一、链表核心进阶算法

链表是线性表的核心,相较于数组,链表支持动态扩容、灵活插入删除。进阶面试重点考察:链表成环检测、入环点定位、原地反转

1.1 成环链表与快慢指针策略

1.1.1 成环检测核心逻辑

采用经典**快慢指针(双指针)**算法,空间复杂度 O(1),无需额外集合存储节点,是最优解法。

  • 指针规则:慢指针每次走 1 步,快指针每次走 2 步。

  • 判环逻辑 :若链表存在环,快慢指针最终必然在环内相遇;若链表无环,快指针会优先遍历到链表末尾(next = null)。

1.1.2 入环节点精准定位

判断有环后,需要精准找到环的入口节点,算法有严格数学推导支撑。

  • 操作步骤 :快慢指针环内相遇后,将任意一个指针重置至链表头节点,之后两个指针同步每次走 1 步,再次相遇的节点即为环入口节点。

  • 数学推导 :设 X = 头节点到入口距离,Y = 入口到相遇点距离,Z = 相遇点回到入口距离。根据快慢指针路程关系可推导出:X = n*(Y+Z) + Z,以此证明同步遍历必相遇于入口。

1.2 链表原地反转实现

1.2.1 双指针反转核心思想

采用 pre + index 双游标 实现链表原地反转,不创建新链表,仅修改原节点指针指向,空间复杂度 O(1)。

  • pre:前驱节点,初始值为 null

  • index:当前遍历节点,初始为头节点

  • 临时变量:记录下一个节点地址,防止断链

1.2.2 执行流程

  1. 临时变量保存当前节点的下一个节点地址;

  2. 修改当前节点 next 指向,指向前驱节点 pre;

  3. pre、index 整体后移,循环遍历至链表末尾。

1.2.3 内存管理核心注意点

核心避坑点:修改节点 next 域之前,必须先用变量持有后续节点地址。若直接修改指针,会丢失后续所有节点引用,造成节点丢失、内存泄漏。

二、有序二叉树(BST)构建与遍历

有序二叉树(二叉搜索树 BST)核心特性:左子树所有节点值 < 根节点值 < 右子树所有节点值。基于该特性,BST 拥有高效的查找、插入、删除性能。

2.1 二叉树物理结构与手动构建

2.1.1 节点存储模型

BST 最小单元为树节点,每个节点包含三个核心域,结合 Java 内存机制:

  • value:存储节点数据;

  • left:左子节点引用地址;

  • right:右子节点引用地址。

内存分配:节点对象在堆内存分配空间,栈内存的引用变量仅存储对象地址。

2.1.2 手动构建流程

通过 new Node() 创建零散独立节点,手动赋值 left、right 指针,严格遵循 左小右大 规则拼接为完整二叉树,适合理解底层结构。

2.2 自动构建与插入方法

手动构建效率极低,实际开发封装 insert 方法实现节点自动插入、自动构建 BST。

2.2.1 插入核心逻辑

  1. 若根节点为空,新节点直接作为根节点;

  2. 定义游标从根节点开始遍历,比较节点值大小;

  3. 小于当前节点向左遍历,大于等于当前节点向右遍历;

  4. 遍历到空位置时挂载新节点,始终维护 BST 有序性。

2.2.2 this 关键字作用

构造方法中 this.value 用于区分成员变量局部形参,解决变量命名冲突;无冲突场景下 this 可省略。

2.3 二叉树两大核心遍历方式

2.3.1 广度优先遍历(BFS 层序遍历)

基于 Queue 队列 实现,从上到下、从左到右逐层遍历节点。

流程:根节点入队 → 循环出队访问节点 → 非空左右子节点依次入队 → 队列为空遍历结束。

2.3.2 深度优先遍历(DFS 递归遍历)

基于递归栈帧实现,分为三种遍历规则,可通过栈帧图完整分析递归执行流程:

  • 前序遍历:根 → 左 → 右

  • 中序遍历:左 → 根 → 右(BST 中序遍历结果为升序有序序列)

  • 后序遍历:左 → 右 → 根

三、有序二叉树(BST)删除操作详解

BST 删除是二叉树核心难点,需根据目标节点的子树情况分三种场景处理,核心原则:删除后不破坏 BST 左小右大的有序特性。

3.1 场景一:删除叶子节点(无左右子树)

  • 普通叶子节点:将父节点对应的 left/right 指针置空;

  • 特殊根节点:若整棵树仅有一个根节点,直接将 root 置为 null。

3.2 场景二:删除仅有一棵子树的节点

采用子树提升策略,让子树直接替代被删除节点的位置。

  • 普通节点:父节点指针跳过目标节点,直接指向其唯一的左/右子树;

  • 根节点:直接将根节点指向其唯一子树。

3.3 场景三:删除拥有两棵子树的节点

无法直接删除,采用 值替换法 保证树结构有序:

  1. 找到目标节点右子树的最小值节点(或左子树最大值节点);

  2. 用最值节点的值覆盖待删除节点的值;

  3. 删除原最值节点(该节点必为叶子/单子树节点,可复用前两种删除逻辑)。

3.4 核心辅助方法

为支撑增删逻辑,封装三大底层方法:

  • 目标节点查找:从根节点根据值大小左右遍历,精准定位节点;

  • 父节点查找:遍历过程记录父节点,用于指针修改;

  • 子树最值查找:最小值一直左遍历,最大值一直右遍历。

四、完整可运行 Java 源码

4.1 节点实体类 Node.java

java 复制代码
package com.qcby.有序二叉树;
public class Node {
	int value;
        Nt;
      right;
        
    e(int value) {
  this.value = value;
     Override
        public String toSt{
         "Node [value=" + value + ", left=" + left + ", right=" + right + "]";
   }
     }
       return ring()    }
        @                  public Nod  Node ode lef

4.2 二叉树核心功能类 BinaryTree.java

java 复制代码
package com.qcby.有序二叉树;
import java.util.LinkedList;
import java.util.Queue;

public class BinaryTree {
	Node root = null;
	
	// 二叉树节点插入、自动构建BST
	public void insert(int value) {
		Node node = new Node(value);
		if(root==null) {
			root=node;
			return;
		}
		Node index = root;
		while(true) {
			if(node.value &lt; index.value) {
				if(index.left==null) {
					index.left=node;
					return;
				}
				index=index.left;
			}else {
				if(index.right==null) {
					index.right=node;
					return;
				}
				index=index.right;
			}
		}
	}
	
	// 广度优先遍历(层序遍历)
	public void levelOrder() {
		Queue&lt;Node&gt; queue = new LinkedList&lt;Node&gt;();
		if(root!=null) {
			queue.add(root);
		}
		while(!queue.isEmpty()) {
			Node temp = queue.poll();
			System.out.print(temp.value + " ");
			if(temp.left!=null) {
				queue.add(temp.left);
			}
			if(temp.right!=null) {
				queue.add(temp.right);
			}
		}
		System.out.println();
	}
	
	// 深度优先-先序遍历
	public void beforeOrder(Node node) {
		if(node==null) {
			return;
		}
		System.out.print(node.value + " ");
		beforeOrder(node.left);
		beforeOrder(node.right);
	}
	
	// 深度优先-中序遍历
	public void inOrder(Node node) {
		if(node==null) {
			return;
		}
		inOrder(node.left);
		System.out.print(node.value + " ");
		inOrder(node.right);
	}
	
	// 深度优先-后序遍历
	public void afterOrder(Node node) {
		if(node==null) {
			return;
		}
		afterOrder(node.left);
		afterOrder(node.right);
		System.out.print(node.value + " ");
	}
	
	// 查找目标节点
	public Node find(int value) {
		Node index = root;
		while(index!=null) {
			if(index.value==value) {
				return index;
			}else if(index.value&gt;value) {
				index=index.left;
			}else {
				index=index.right;
			}
		}
		return null;
	}
	
	// 查找目标节点的父节点
	public Node findParentNode(int value) {
		Node index = root;
		while (index!=null) {
			if((index.left!=null&amp;&amp;index.left.value==value)||(index.right!=null&amp;&amp;index.right.value==value)) {
				return index;
			}else if(index.value&gt;value) {
				index=index.left;
			}else {
				index=index.right;
			}
		}
		return null;
	}
	
	// 查找子树最小值
	public int rightMin(Node node) {
		Node index = node;
		while(index.left!=null) {
			index=index.left;
		}
		return index.value;
	}
	
	// BST节点删除核心方法
	public void delete(int value) {
		Node target = find(value);
		if(target==null) {
			System.out.println("没有这个节点");
			return;
		}
		Node parent = findParentNode(value);
		
		// 1. 删除叶子节点
		if(target.left==null&amp;&amp;target.right==null) {
			if(parent==null) {
				root = null;
				return;
			}
			if(parent.left==target) {
				parent.left = null;
			}else {
				parent.right=null;
			}
		}
		// 2. 删除拥有两棵子树的节点
		else if(target.left!=null&amp;&amp;target.right!=null) {
			int min = rightMin(target.right);
			delete(min);
			target.value=min;
		}
		// 3. 删除仅有一棵子树的节点
		else {
			if(parent==null) {
				if(target.left!=null) {
					root = root.left;
				}else {
					root = root.right;
				}
				return;
			}
			if(parent.left==target) {
				if(target.left!=null) {
					parent.left=target.left;
				}else {
					parent.left=target.right;
				}
			}else {
				if(target.left!=null) {
					parent.right=target.left;
				}else {
					parent.right=target.right;
				}
			}
		}
	}
}

4.3 测试类 Test.java

java 复制代码
package com.qcby.有序二叉树;

public class Test {
	public static void main(String[] args) {
		BinaryTree tree = new BinaryTree();
		// 插入节点构建BST
		tree.insert(5);
		tree.insert(7);
		tree.insert(4);
		tree.insert(2);
		tree.insert(6);
		tree.insert(10);
		
		System.out.println("中序遍历结果(有序):");
		tree.inOrder(tree.root);
	}
}

五、核心知识点总结

  1. 链表进阶:快慢指针高效解决链表成环检测与入环点定位,双指针原地反转实现无额外空间翻转,核心避坑点为防止断链内存泄漏;

  2. BST 核心特性:左小右大的有序规则是所有增、删、查、遍历逻辑的基础;

  3. 遍历方式:BFS 队列实现层序遍历,DFS 递归实现三序遍历,中序遍历可输出有序数组;

  4. BST 删除重难点:分叶子节点、单子树、双子树三种场景,双子树节点采用「右子树最小值替换法」。

相关推荐
只会写代码1 小时前
一套开箱即用实体反射Lambda链式工具,彻底告别原生反射样板代码
java·程序员·源码
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第151题】【06_Spring篇】第11题:说一下 Spring Bean 的生命周期?
java·开发语言·后端·spring·面试
骑士雄师1 小时前
java面试题:jvm ,mybatis
java·jvm·mybatis
广州浮点FLOATLIC2 小时前
Creo 许可证利用率怎么优化:制造企业该先看共享规则,还是先看模块占用结构
java·开发语言
2601_962440842 小时前
计算机毕业设计之jsp教室管理系统
java·开发语言·笔记·分布式·算法·课程设计·推荐算法
带刺的坐椅3 小时前
用 ChatModel 构建 LLM 驱动的 Java 应用
java·ai·llm·solon·rag·chatmodel
用户3721574261355 小时前
Java 将 Word 文档转换为 Markdown:基础转换与导出选项详解
java
行者全栈架构师5 小时前
PolarDB + Spring Boot 实战:从自建MySQL到云原生数据库的零停机迁移
java·后端·架构
karry_k21 小时前
MyBatis批量insert-select踩坑:useGeneratedKeys=true 可能让PostgreSQL返回大量插入结果
java·后端