属性数据结构遍历详解
概述
属性数据结构 是指具有特定属性或特征的数据结构,如有序性、层级性、连通性等。遍历是访问数据结构中所有元素的基本操作,不同的数据结构有不同的遍历策略。
💡 提示: 核心思想
遍历的本质是:按照数据结构的特性,用合适的方式访问每个元素一次,且仅一次。
一、数组遍历
1.1 基本特性
| 特性 |
说明 |
| 存储方式 |
连续内存,随机访问 |
| 访问复杂度 |
O(1) |
| 遍历复杂度 |
O(n) |
| 属性 |
有序、固定大小、可重复 |
1.2 遍历思路
数组遍历的核心思路是线性扫描:从第一个元素开始,依次访问每个元素,直到最后一个元素。
1 2 3 4 5 6
| 思路流程: 1. 初始化指针/索引 i = 0 2. 检查 i < 数组长度 3. 访问 array[i] 4. i++ 5. 回到步骤2
|
1.3 实现方式
方式1:传统 for 循环(最直接)
1 2 3 4 5 6 7 8 9
| int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }
|
思路分析:
- 显式控制索引,可以随意访问任何位置
- 适合需要索引的场景(如修改元素、计算位置等)
方式2:增强 for 循环(简洁)
1 2 3 4 5 6 7 8 9
| int[] arr = {1, 2, 3, 4, 5};
for (int num : arr) { System.out.println(num); }
|
思路分析:
- 隐藏索引细节,代码更简洁
- 本质上还是顺序访问
- 不能修改数组元素(对基本类型)
方式3:迭代器遍历(通用)
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer num = iterator.next(); if (num == 3) { iterator.remove(); } }
|
思路分析:
- 迭代器维护遍历状态
- 支持在遍历中安全删除
- 适合集合框架
方式4:流式遍历(函数式)
1 2 3 4 5 6 7 8 9 10 11 12
| int[] arr = {1, 2, 3, 4, 5};
Arrays.stream(arr) .forEach(System.out::println);
IntStream.range(0, arr.length) .forEach(i -> System.out.println(arr[i]));
|
思路分析:
- 声明式编程,表达意图清晰
- 支持链式操作和函数组合
- 可能有额外开销
1.4 二维数组遍历
按行遍历(最常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int[][] matrix = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { System.out.println(matrix[i][j]); } }
|
思路分析:
- 外层控制行,内层控制列
- 按行优先顺序访问(cache 友好)
- 适合大多数矩阵操作
按列遍历
1 2 3 4 5 6 7 8 9
| for (int j = 0; j < matrix[0].length; j++) { for (int i = 0; i < matrix.length; i++) { System.out.println(matrix[i][j]); } }
|
思路分析:
- 适合需要按列处理的场景
- 可能导致 cache miss(不如按行遍历高效)
对角线遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
for (int diag = 0; diag < matrix.length + matrix[0].length - 1; diag++) { int startRow = Math.max(0, diag - matrix[0].length + 1); int endRow = Math.min(diag, matrix.length - 1); for (int i = startRow; i <= endRow; i++) { int j = diag - i; System.out.println(matrix[i][j]); } }
|
思路分析:
- 对角线编号:i + j = diag
- 需要计算每条对角线的起点和终点
- 适合特定的矩阵问题
1.5 遍历变种
反向遍历
1 2 3 4 5 6 7 8
| for (int i = arr.length - 1; i >= 0; i--) { System.out.println(arr[i]); }
|
跳跃遍历
1 2 3 4 5 6 7 8 9
| int k = 2; for (int i = 0; i < arr.length; i += k) { System.out.println(arr[i]); }
|
条件遍历
1 2 3 4 5 6 7 8 9 10 11
| for (int i = 0; i < arr.length; i++) { if (arr[i] > 5) { System.out.println(arr[i]); } }
Arrays.stream(arr) .filter(x -> x > 5) .forEach(System.out::println);
|
二、链表遍历
2.1 基本特性
| 特性 |
说明 |
| 存储方式 |
离散内存,通过指针连接 |
| 访问复杂度 |
O(n) |
| 遍历复杂度 |
O(n) |
| 属性 |
有序、动态大小、可重复 |
2.2 链表节点定义
1 2 3 4 5 6 7 8 9
| class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; this.next = null; } }
|
2.3 遍历思路
链表遍历的核心思路是指针跟踪:从头节点开始,通过 next 指针逐个访问每个节点。
1 2 3 4 5 6
| 思路流程: 1. 初始化指针 current = head 2. 检查 current != null 3. 访问 current.val 4. current = current.next 5. 回到步骤2
|
2.4 实现方式
方式1:while 循环(最常用)
1 2 3 4 5 6 7 8 9 10 11 12 13
| ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3);
ListNode current = head; while (current != null) { System.out.println(current.val); current = current.next; }
|
思路分析:
- 最直接的遍历方式
- 通过 null 检查确定遍历结束
- 不修改链表结构
方式2:递归遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void traverse(ListNode node) { if (node == null) { return; } System.out.println(node.val); traverse(node.next); }
traverse(head);
|
思路分析:
- 递归天然适合链表结构
- 空间复杂度较高(栈深度)
- 适合需要反向处理的场景
方式3:反向遍历(递归)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void reverseTraverse(ListNode node) { if (node == null) { return; } reverseTraverse(node.next); System.out.println(node.val); }
reverseTraverse(head);
|
思路分析:
- 利用递归栈实现反向遍历
- 不需要额外的反向链表
- 空间复杂度 O(n)
方式4:快慢指针遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ListNode slow = head; ListNode fast = head;
while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }
System.out.println("中点值:" + slow.val);
|
思路分析:
- 快指针速度是慢指针的两倍
- 当快指针到达末尾时,慢指针在中点
- 适合检测环、找中点等
方式5:迭代器遍历
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3);
Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
|
思路分析:
2.5 双向链表遍历
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 DoublyListNode { int val; DoublyListNode prev; DoublyListNode next; DoublyListNode(int val) { this.val = val; } }
DoublyListNode current = head; while (current != null) { System.out.println(current.val); current = current.next; }
current = tail; while (current != null) { System.out.println(current.val); current = current.prev; }
|
思路分析:
- 双向链表支持双向遍历
- 反向遍历不需要递归
- 适合需要双向访问的场景
三、树的遍历
3.1 基本特性
| 特性 |
说明 |
| 存储方式 |
层级结构,父子关系 |
| 访问复杂度 |
O(n) |
| 遍历复杂度 |
O(n) |
| 属性 |
有序、非线性、无环 |
3.2 树节点定义
二叉树节点
1 2 3 4 5 6 7 8 9 10 11
| class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int val) { this.val = val; this.left = null; this.right = null; } }
|
多叉树节点
1 2 3 4 5 6 7 8 9
| class NaryTreeNode { int val; List<NaryTreeNode> children; NaryTreeNode(int val) { this.val = val; this.children = new ArrayList<>(); } }
|
特点:
- 每个节点可以有任意数量的子节点
- 适合表示文件系统、组织结构等
- 遍历逻辑更通用
3.3 深度优先遍历(DFS)
前序遍历(Pre-order)
遍历顺序:根 → 左子树 → 右子树
1 2 3 4
| 思路流程: 1. 访问当前节点 2. 递归遍历左子树 3. 递归遍历右子树
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void preorder(TreeNode node) { if (node == null) return; System.out.println(node.val); preorder(node.left); preorder(node.right); }
|
思路分析:
- 最先访问根节点
- 适合需要先处理父节点的场景
- 如:复制树、序列化树
迭代实现(使用栈):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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.println(node.val); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } }
|
思路分析:
- 栈模拟递归调用
- 压栈顺序很关键(右先压,左后压)
- 避免递归栈溢出
中序遍历(In-order)
遍历顺序:左子树 → 根 → 右子树
1 2 3 4
| 思路流程: 1. 递归遍历左子树 2. 访问当前节点 3. 递归遍历右子树
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void inorder(TreeNode node) { if (node == null) return; inorder(node.left); System.out.println(node.val); inorder(node.right); }
|
思路分析:
- 对于二叉搜索树,中序遍历得到有序序列
- 适合需要有序处理的场景
- 最常用的遍历方式
迭代实现(使用栈):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void inorderIterative(TreeNode root) { Stack<TreeNode> stack = new Stack<>(); TreeNode current = root; while (current != null || !stack.isEmpty()) { while (current != null) { stack.push(current); current = current.left; } current = stack.pop(); System.out.println(current.val); current = current.right; } }
|
思路分析:
- 关键:先一直向左,直到 null
- 然后处理栈顶,再向右
- 比前序遍历更复杂
后序遍历(Post-order)
遍历顺序:左子树 → 右子树 → 根
1 2 3 4
| 思路流程: 1. 递归遍历左子树 2. 递归遍历右子树 3. 访问当前节点
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void postorder(TreeNode node) { if (node == null) return; postorder(node.left); postorder(node.right); System.out.println(node.val); }
|
思路分析:
- 最后访问根节点
- 适合需要先处理子节点的场景
- 如:删除树、计算树的高度
迭代实现(使用两个栈):
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
| 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); if (node.left != null) { stack1.push(node.left); } if (node.right != null) { stack1.push(node.right); } } while (!stack2.isEmpty()) { System.out.println(stack2.pop().val); } }
|
思路分析:
- 使用两个栈实现
- 第一个栈生成反向后序
- 第二个栈反转得到正确顺序
3.4 多叉树遍历
多叉树前序遍历
遍历顺序:根 → 所有子节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void preorderNary(NaryTreeNode node) { if (node == null) return; System.out.println(node.val); for (NaryTreeNode child : node.children) { preorderNary(child); } }
|
思路分析:
- 与二叉树前序类似,先访问根
- 遍历所有子节点而不是只有左右
- 时间复杂度 O(n),空间复杂度 O(h)
迭代实现(使用栈):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void preorderNaryIterative(NaryTreeNode root) { if (root == null) return; Stack<NaryTreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { NaryTreeNode node = stack.pop(); System.out.println(node.val); for (int i = node.children.size() - 1; i >= 0; i--) { stack.push(node.children.get(i)); } } }
|
思路分析:
- 反向压栈是关键(从最后一个子节点开始)
- 这样弹出时,第一个子节点先出
- 保证了正确的前序遍历顺序
多叉树后序遍历
遍历顺序:所有子节点 → 根
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void postorderNary(NaryTreeNode node) { if (node == null) return; for (NaryTreeNode child : node.children) { postorderNary(child); } System.out.println(node.val); }
|
思路分析:
- 先处理所有子节点,最后处理根
- 适合需要先处理子树的场景(如删除树)
- 时间复杂度 O(n),空间复杂度 O(h)
迭代实现(使用两个栈):
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
| void postorderNaryIterative(NaryTreeNode root) { if (root == null) return; Stack<NaryTreeNode> stack1 = new Stack<>(); Stack<NaryTreeNode> stack2 = new Stack<>(); stack1.push(root); while (!stack1.isEmpty()) { NaryTreeNode node = stack1.pop(); stack2.push(node); for (NaryTreeNode child : node.children) { stack1.push(child); } } while (!stack2.isEmpty()) { System.out.println(stack2.pop().val); } }
|
思路分析:
- 与二叉树后序类似,使用两个栈
- 第一个栈生成反向后序
- 第二个栈反转得到正确顺序
多叉树层序遍历
遍历顺序:按层从上到下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void levelorderNary(NaryTreeNode root) { if (root == null) return; Queue<NaryTreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { NaryTreeNode node = queue.poll(); System.out.println(node.val); for (NaryTreeNode child : node.children) { queue.add(child); } } }
|
思路分析:
- 队列保证层序访问
- 遍历所有子节点而不是只有左右
- 时间复杂度 O(n),空间复杂度 O(w)
分层遍历(返回每层的列表)
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
| List<List<Integer>> levelorderNaryList(NaryTreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) return result; Queue<NaryTreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int levelSize = queue.size(); List<Integer> currentLevel = new ArrayList<>(); for (int i = 0; i < levelSize; i++) { NaryTreeNode node = queue.poll(); currentLevel.add(node.val); for (NaryTreeNode child : node.children) { queue.add(child); } } result.add(currentLevel); } return result; }
|
思路分析:
- 关键:记录每层的节点数
- 在处理当前层时,只处理 levelSize 个节点
- 这样可以清晰地分离各层
3.5 多叉树 vs 二叉树对比
| 维度 |
二叉树 |
多叉树 |
| 子节点数 |
最多 2 个 |
任意个 |
| 前序遍历 |
根→左→右 |
根→所有子 |
| 后序遍历 |
左→右→根 |
所有子→根 |
| 中序遍历 |
左→根→右 |
不适用 |
| 适用场景 |
搜索树、堆 |
文件系统、组织结构 |
| 实现复杂度 |
简单 |
稍复杂(循环遍历子节点) |
3.6 广度优先遍历(BFS)
层序遍历(Level-order)
遍历顺序:按层从上到下,同层从左到右
1 2 3 4 5 6
| 思路流程: 1. 初始化队列,放入根节点 2. 队列不为空时: a. 弹出队列前端节点 b. 访问该节点 c. 将其子节点加入队列
|
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
| void levelorder(TreeNode root) { if (root == null) return; Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { TreeNode node = queue.poll(); System.out.println(node.val); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } }
|
思路分析:
- 队列保证了层序访问
- 先进先出的特性很关键
- 适合需要按层处理的场景
分层遍历(返回每层的列表)
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
| List<List<Integer>> levelOrderList(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) return result; Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int levelSize = queue.size(); List<Integer> currentLevel = new ArrayList<>(); for (int i = 0; i < levelSize; i++) { TreeNode node = queue.poll(); currentLevel.add(node.val); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } result.add(currentLevel); } return result; }
|
思路分析:
- 关键:记录每层的节点数
- 在处理当前层时,只处理 levelSize 个节点
- 这样可以清晰地分离各层
3.5 DFS vs BFS 对比
| 维度 |
DFS(递归/栈) |
BFS(队列) |
| 实现 |
递归或栈 |
队列 |
| 空间复杂度 |
O(h)(高度) |
O(w)(最大宽度) |
| 适用场景 |
路径问题、深度问题 |
最短路径、层级问题 |
| 遍历顺序 |
深度优先 |
广度优先 |
四、图的遍历
4.1 基本特性
| 特性 |
说明 |
| 存储方式 |
邻接表或邻接矩阵 |
| 访问复杂度 |
O(V+E) |
| 遍历复杂度 |
O(V+E) |
| 属性 |
可能有环、可能不连通 |
4.2 图的表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Map<Integer, List<Integer>> graph = new HashMap<>(); graph.put(1, Arrays.asList(2, 3)); graph.put(2, Arrays.asList(1, 4)); graph.put(3, Arrays.asList(1)); graph.put(4, Arrays.asList(2));
int[][] adjMatrix = { {0, 1, 1, 0}, {1, 0, 0, 1}, {1, 0, 0, 0}, {0, 1, 0, 0} };
|
4.3 深度优先遍历(DFS)
思路:从起点开始,沿着一条路径尽可能深地探索,直到无法继续,然后回溯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void dfs(int node, Map<Integer, List<Integer>> graph, Set<Integer> visited) { visited.add(node); System.out.println(node); for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) { if (!visited.contains(neighbor)) { dfs(neighbor, graph, visited); } } }
Set<Integer> visited = new HashSet<>(); dfs(1, graph, visited);
|
思路分析:
- 使用 visited 集合防止重复访问(处理环)
- 递归自然地实现了深度优先
- 适合检测环、拓扑排序等
迭代实现(使用栈):
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
| void dfsIterative(int start, Map<Integer, List<Integer>> graph) { Set<Integer> visited = new HashSet<>(); Stack<Integer> stack = new Stack<>(); stack.push(start); while (!stack.isEmpty()) { int node = stack.pop(); if (visited.contains(node)) { continue; } visited.add(node); System.out.println(node); for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) { if (!visited.contains(neighbor)) { stack.push(neighbor); } } } }
|
思路分析:
4.4 广度优先遍历(BFS)
思路:从起点开始,逐层探索所有邻接节点,然后探索下一层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void bfs(int start, Map<Integer, List<Integer>> graph) { Set<Integer> visited = new HashSet<>(); Queue<Integer> queue = new LinkedList<>(); queue.add(start); visited.add(start); while (!queue.isEmpty()) { int node = queue.poll(); System.out.println(node); for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) { if (!visited.contains(neighbor)) { visited.add(neighbor); queue.add(neighbor); } } } }
|
思路分析:
- 队列保证了广度优先
- 访问时就标记,避免重复加入队列
- 适合最短路径问题
4.5 DFS vs BFS 对比
| 维度 |
DFS |
BFS |
| 数据结构 |
栈 |
队列 |
| 空间复杂度 |
O(V) |
O(V) |
| 适用场景 |
连通性、环检测、拓扑排序 |
最短路径、分层 |
| 遍历顺序 |
深度优先 |
广度优先 |
五、特殊数据结构遍历
5.1 二叉搜索树(BST)遍历
特性:左子树 < 根 < 右子树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void inorderBST(TreeNode node) { if (node == null) return; inorderBST(node.left); System.out.println(node.val); inorderBST(node.right); }
|
思路分析:
- 中序遍历的特殊应用
- 利用 BST 的有序性
- 时间复杂度 O(n)
5.2 堆的遍历
1 2 3 4 5 6 7 8 9 10 11 12
| int[] heap = {10, 8, 9, 4, 5, 6, 7};
for (int i = 0; i < heap.length; i++) { System.out.println(heap[i]); }
|
思路分析:
- 堆用数组表示,按层序存储
- 遍历就是按数组顺序访问
- 时间复杂度 O(n)
5.3 哈希表遍历
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
| Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3);
for (String key : map.keySet()) { System.out.println(key); }
for (Integer value : map.values()) { System.out.println(value); }
for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); System.out.println(entry.getKey() + ": " + entry.getValue()); }
|
思路分析:
- 遍历 entry 最高效(避免重复查询)
- 哈希表无序,遍历顺序不确定
- 遍历中删除需要用迭代器
六、遍历的通用思路总结
6.1 遍历的三个核心要素
| 要素 |
说明 |
示例 |
| 访问顺序 |
按什么顺序访问元素 |
前序、中序、后序、层序 |
| 状态管理 |
如何跟踪已访问的元素 |
visited 集合、指针位置 |
| 终止条件 |
何时停止遍历 |
null、队列为空、visited 完全 |
6.2 选择遍历方式的决策树
1 2 3 4 5 6 7 8 9 10 11 12 13
| 需要遍历数据结构 ↓ 是否需要特定顺序? ├─ 是 → 选择对应的遍历方式 │ ├─ 前序:先处理父节点 │ ├─ 中序:有序处理(BST) │ ├─ 后序:先处理子节点 │ └─ 层序:按层处理 │ └─ 否 → 选择最简单的方式 ├─ 线性结构 → for 循环 ├─ 树 → DFS 或 BFS └─ 图 → DFS 或 BFS
|
6.3 递归 vs 迭代
| 维度 |
递归 |
迭代 |
| 代码简洁性 |
简洁 |
复杂 |
| 空间复杂度 |
O(h)(栈) |
O(h)(栈) |
| 栈溢出风险 |
高(深度大) |
低 |
| 理解难度 |
容易 |
较难 |
| 性能 |
略低(函数调用) |
略高 |
七、常见遍历问题
7.1 如何在遍历中删除元素?
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
| List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); for (int i = 0; i < list.size(); i++) { if (list.get(i) == 3) { list.remove(i); } }
Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { if (iterator.next() == 3) { iterator.remove(); } }
for (int i = list.size() - 1; i >= 0; i--) { if (list.get(i) == 3) { list.remove(i); } }
List<Integer> toRemove = new ArrayList<>(); for (int num : list) { if (num == 3) { toRemove.add(num); } } list.removeAll(toRemove);
|
7.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
|
Set<ListNode> visited = new HashSet<>(); ListNode current = head; while (current != null) { if (visited.contains(current)) { System.out.println("检测到环"); break; } visited.add(current); current = current.next; }
ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { System.out.println("检测到环"); break; } }
|
7.3 如何处理不连通的图?
1 2 3 4 5 6 7 8 9 10 11 12 13
|
void traverseAllComponents(Map<Integer, List<Integer>> graph) { Set<Integer> visited = new HashSet<>(); for (int node : graph.keySet()) { if (!visited.contains(node)) { System.out.println("--- 新的连通分量 ---"); dfs(node, graph, visited); } } }
|
八、性能对比总结
| 数据结构 |
遍历方式 |
时间复杂度 |
空间复杂度 |
适用场景 |
| 数组 |
for 循环 |
O(n) |
O(1) |
顺序访问 |
| 链表 |
while 循环 |
O(n) |
O(1) |
顺序访问 |
| 树 |
DFS(递归) |
O(n) |
O(h) |
深度优先 |
| 树 |
BFS(队列) |
O(n) |
O(w) |
广度优先 |
| 图 |
DFS |
O(V+E) |
O(V) |
连通性检测 |
| 图 |
BFS |
O(V+E) |
O(V) |
最短路径 |
| 哈希表 |
entry 遍历 |
O(n) |
O(1) |
键值对访问 |
相关笔记
- 数组操作详解
- 链表操作详解
- 树的操作详解
- 图的操作详解