数据结构与算法笔试核心知识点详解
前言
数据结构与算法是计算机科学的核心基础,也是技术笔试面试中的必考内容。本文将系统梳理链表、树、图、哈希表、排序算法等核心知识点,结合典型面试题型进行深入解析,帮助读者全面掌握笔试必备技能。
第一章:链表(Linked List)
1.1 链表基础概念
链表是一种线性数据结构,由一系列节点组成,每个节点包含数据域和指针域。相比数组,链表具有动态扩容、插入删除高效的优势。
1.1.1 链表分类
单链表(Singly Linked List)
- 每个节点只有一个指向下一节点的指针
- 只能单向遍历
- 结构简单,内存占用少
双链表(Doubly Linked List)
- 每个节点有两个指针:prev和next
- 支持双向遍历
- 插入删除操作更灵活
循环链表(Circular Linked List)
- 尾节点指针指向头节点
- 可以循环遍历
- 常用于实现队列等数据结构
1.2 链表核心操作
1.2.1 单链表节点定义
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | class ListNode {int val;
 ListNode next;
 
 ListNode(int val) {
 this.val = val;
 this.next = null;
 }
 }
 
 | 
1.2.2 链表反转(经典面试题)
迭代法实现:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public ListNode reverseList(ListNode head) {ListNode prev = null;
 ListNode curr = head;
 
 while (curr != null) {
 ListNode nextTemp = curr.next;
 curr.next = prev;
 prev = curr;
 curr = nextTemp;
 }
 
 return prev;
 }
 
 | 
递归法实现:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | public ListNode reverseListRecursive(ListNode head) {if (head == null || head.next == null) {
 return head;
 }
 
 ListNode reversed = reverseListRecursive(head.next);
 head.next.next = head;
 head.next = null;
 
 return reversed;
 }
 
 | 
1.2.3 链表环检测(快慢指针法)
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 
 | public boolean hasCycle(ListNode head) {if (head == null || head.next == null) {
 return false;
 }
 
 ListNode slow = head;
 ListNode fast = head.next;
 
 while (slow != fast) {
 if (fast == null || fast.next == null) {
 return false;
 }
 slow = slow.next;
 fast = fast.next.next;
 }
 
 return true;
 }
 
 
 public ListNode detectCycle(ListNode head) {
 if (head == null || head.next == null) {
 return null;
 }
 
 ListNode slow = head;
 ListNode fast = head;
 
 
 while (fast != null && fast.next != null) {
 slow = slow.next;
 fast = fast.next.next;
 if (slow == fast) {
 break;
 }
 }
 
 if (fast == null || fast.next == null) {
 return null;
 }
 
 
 slow = head;
 while (slow != fast) {
 slow = slow.next;
 fast = fast.next;
 }
 
 return slow;
 }
 
 | 
1.2.4 合并两个有序链表
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);
 ListNode curr = dummy;
 
 while (l1 != null && l2 != null) {
 if (l1.val <= l2.val) {
 curr.next = l1;
 l1 = l1.next;
 } else {
 curr.next = l2;
 l2 = l2.next;
 }
 curr = curr.next;
 }
 
 curr.next = (l1 != null) ? l1 : l2;
 return dummy.next;
 }
 
 | 
1.3 链表高频面试题
- 删除链表的倒数第N个节点
- 链表的中间节点
- 回文链表判断
- 链表相交节点查找
- 复杂链表的复制
第二章:树结构(Tree)
2.1 树结构基础概念
树是一种非线性数据结构,由节点和边组成,具有层次关系。在计算机科学中广泛应用,如文件系统、数据库索引等。
2.1.1 二叉树分类
二叉树(Binary Tree)
- 每个节点最多有两个子节点
- 子节点分为左孩子和右孩子
二叉搜索树(BST)
- 左子树所有节点值小于根节点值
- 右子树所有节点值大于根节点值
- 中序遍历结果为有序序列
平衡二叉树(AVL Tree)
- 任意节点的左右子树高度差不超过1
- 通过旋转操作保持平衡
- 查找、插入、删除时间复杂度O(log n)
2.1.2 二叉树节点定义
| 12
 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;
 }
 }
 
 | 
2.2 二叉树遍历算法
2.2.1 前序遍历(根-左-右)
| 12
 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
 
 | public void preorderTraversal(TreeNode root) {
 if (root == null) return;
 
 System.out.print(root.val + " ");
 preorderTraversal(root.left);
 preorderTraversal(root.right);
 }
 
 
 public List<Integer> preorderTraversalIterative(TreeNode root) {
 List<Integer> result = new ArrayList<>();
 if (root == null) return result;
 
 Stack<TreeNode> stack = new Stack<>();
 stack.push(root);
 
 while (!stack.isEmpty()) {
 TreeNode node = stack.pop();
 result.add(node.val);
 
 if (node.right != null) {
 stack.push(node.right);
 }
 if (node.left != null) {
 stack.push(node.left);
 }
 }
 
 return result;
 }
 
 | 
2.2.2 中序遍历(左-根-右)
| 12
 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
 
 | public void inorderTraversal(TreeNode root) {
 if (root == null) return;
 
 inorderTraversal(root.left);
 System.out.print(root.val + " ");
 inorderTraversal(root.right);
 }
 
 
 public List<Integer> inorderTraversalIterative(TreeNode root) {
 List<Integer> result = new ArrayList<>();
 Stack<TreeNode> stack = new Stack<>();
 TreeNode curr = root;
 
 while (curr != null || !stack.isEmpty()) {
 while (curr != null) {
 stack.push(curr);
 curr = curr.left;
 }
 
 curr = stack.pop();
 result.add(curr.val);
 curr = curr.right;
 }
 
 return result;
 }
 
 | 
2.2.3 后序遍历(左-右-根)
| 12
 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
 32
 33
 34
 35
 36
 
 | public void postorderTraversal(TreeNode root) {
 if (root == null) return;
 
 postorderTraversal(root.left);
 postorderTraversal(root.right);
 System.out.print(root.val + " ");
 }
 
 
 public List<Integer> postorderTraversalIterative(TreeNode root) {
 List<Integer> result = new ArrayList<>();
 if (root == null) return result;
 
 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()) {
 result.add(stack2.pop().val);
 }
 
 return result;
 }
 
 | 
2.3 二叉搜索树操作
2.3.1 插入操作
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public TreeNode insertIntoBST(TreeNode root, int val) {if (root == null) {
 return new TreeNode(val);
 }
 
 if (val < root.val) {
 root.left = insertIntoBST(root.left, val);
 } else {
 root.right = insertIntoBST(root.right, val);
 }
 
 return root;
 }
 
 | 
2.3.2 查找操作
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | public TreeNode searchBST(TreeNode root, int val) {if (root == null || root.val == val) {
 return root;
 }
 
 if (val < root.val) {
 return searchBST(root.left, val);
 } else {
 return searchBST(root.right, val);
 }
 }
 
 | 
2.4 平衡二叉树(AVL Tree)
2.4.1 AVL树节点定义
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | class AVLTreeNode {int val;
 int height;
 AVLTreeNode left;
 AVLTreeNode right;
 
 AVLTreeNode(int val) {
 this.val = val;
 this.height = 1;
 this.left = null;
 this.right = null;
 }
 }
 
 | 
2.4.2 旋转操作
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 
 | private AVLTreeNode rightRotate(AVLTreeNode y) {
 AVLTreeNode x = y.left;
 AVLTreeNode T2 = x.right;
 
 
 x.right = y;
 y.left = T2;
 
 
 y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
 x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
 
 return x;
 }
 
 
 private AVLTreeNode leftRotate(AVLTreeNode x) {
 AVLTreeNode y = x.right;
 AVLTreeNode T2 = y.left;
 
 
 y.left = x;
 x.right = T2;
 
 
 x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
 y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
 
 return y;
 }
 
 private int getHeight(AVLTreeNode node) {
 return (node == null) ? 0 : node.height;
 }
 
 private int getBalance(AVLTreeNode node) {
 return (node == null) ? 0 : getHeight(node.left) - getHeight(node.right);
 }
 
 | 
2.5 B树与B+树
2.5.1 B树特点
- 多路平衡查找树
- 每个节点可以有多个子节点
- 所有叶子节点在同一层
- 常用于文件系统和数据库索引
2.5.2 B+树特点
- B树的变种,所有数据存储在叶子节点
- 叶子节点通过指针连接,支持顺序访问
- 非叶子节点只存储索引信息
- 更适合范围查询和磁盘存储
2.5.3 B树与B+树对比
| 特性 | B树 | B+树 | 
| 数据存储 | 所有节点 | 仅叶子节点 | 
| 范围查询 | 效率低 | 效率高 | 
| 磁盘IO | 较多 | 较少 | 
| 应用场景 | 内存存储 | 磁盘存储 | 
第三章:图结构(Graph)
3.1 图基础概念
图是由顶点(Vertex)和边(Edge)组成的非线性数据结构,用于表示对象之间的关系。
3.1.1 图分类
无向图(Undirected Graph)
有向图(Directed Graph)
加权图(Weighted Graph)
3.2 图的表示方法
3.2.1 邻接矩阵
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | class GraphAdjMatrix {private int[][] adjMatrix;
 private int numVertices;
 
 public GraphAdjMatrix(int numVertices) {
 this.numVertices = numVertices;
 this.adjMatrix = new int[numVertices][numVertices];
 }
 
 public void addEdge(int i, int j) {
 adjMatrix[i][j] = 1;
 adjMatrix[j][i] = 1;
 }
 
 public void removeEdge(int i, int j) {
 adjMatrix[i][j] = 0;
 adjMatrix[j][i] = 0;
 }
 
 public boolean isEdge(int i, int j) {
 return adjMatrix[i][j] == 1;
 }
 }
 
 | 
3.2.2 邻接表
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | import java.util.*;
 class GraphAdjList {
 private Map<Integer, List<Integer>> adjList;
 
 public GraphAdjList() {
 adjList = new HashMap<>();
 }
 
 public void addVertex(int vertex) {
 adjList.put(vertex, new ArrayList<>());
 }
 
 public void addEdge(int src, int dest) {
 adjList.computeIfAbsent(src, k -> new ArrayList<>()).add(dest);
 adjList.computeIfAbsent(dest, k -> new ArrayList<>()).add(src);
 }
 
 public List<Integer> getNeighbors(int vertex) {
 return adjList.getOrDefault(vertex, new ArrayList<>());
 }
 }
 
 | 
3.3 图遍历算法
3.3.1 深度优先搜索(DFS)
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | class DFS {private boolean[] visited;
 
 public void dfs(GraphAdjList graph, int start) {
 visited = new boolean[graph.size()];
 dfsHelper(graph, start);
 }
 
 private void dfsHelper(GraphAdjList graph, int vertex) {
 visited[vertex] = true;
 System.out.print(vertex + " ");
 
 for (int neighbor : graph.getNeighbors(vertex)) {
 if (!visited[neighbor]) {
 dfsHelper(graph, neighbor);
 }
 }
 }
 }
 
 
 public void dfsIterative(GraphAdjList graph, int start) {
 boolean[] visited = new boolean[graph.size()];
 Stack<Integer> stack = new Stack<>();
 
 stack.push(start);
 
 while (!stack.isEmpty()) {
 int vertex = stack.pop();
 
 if (!visited[vertex]) {
 visited[vertex] = true;
 System.out.print(vertex + " ");
 
 
 List<Integer> neighbors = graph.getNeighbors(vertex);
 for (int i = neighbors.size() - 1; i >= 0; i--) {
 int neighbor = neighbors.get(i);
 if (!visited[neighbor]) {
 stack.push(neighbor);
 }
 }
 }
 }
 }
 
 | 
3.3.2 广度优先搜索(BFS)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | class BFS {public void bfs(GraphAdjList graph, int start) {
 boolean[] visited = new boolean[graph.size()];
 Queue<Integer> queue = new LinkedList<>();
 
 visited[start] = true;
 queue.offer(start);
 
 while (!queue.isEmpty()) {
 int vertex = queue.poll();
 System.out.print(vertex + " ");
 
 for (int neighbor : graph.getNeighbors(vertex)) {
 if (!visited[neighbor]) {
 visited[neighbor] = true;
 queue.offer(neighbor);
 }
 }
 }
 }
 }
 
 | 
3.4 最短路径算法
3.4.1 Dijkstra算法
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 
 | class Dijkstra {public int[] dijkstra(int[][] graph, int start) {
 int n = graph.length;
 int[] dist = new int[n];
 boolean[] visited = new boolean[n];
 
 Arrays.fill(dist, Integer.MAX_VALUE);
 dist[start] = 0;
 
 for (int i = 0; i < n - 1; i++) {
 int u = findMinDistance(dist, visited);
 visited[u] = true;
 
 for (int v = 0; v < n; v++) {
 if (!visited[v] && graph[u][v] != 0 &&
 dist[u] != Integer.MAX_VALUE &&
 dist[u] + graph[u][v] < dist[v]) {
 dist[v] = dist[u] + graph[u][v];
 }
 }
 }
 
 return dist;
 }
 
 private int findMinDistance(int[] dist, boolean[] visited) {
 int min = Integer.MAX_VALUE;
 int minIndex = -1;
 
 for (int i = 0; i < dist.length; i++) {
 if (!visited[i] && dist[i] <= min) {
 min = dist[i];
 minIndex = i;
 }
 }
 
 return minIndex;
 }
 }
 
 | 
第四章:哈希表(Hash Table)
4.1 哈希表基础概念
哈希表通过哈希函数将键映射到数组索引,实现O(1)时间复杂度的查找、插入和删除操作。
4.1.1 哈希函数设计原则
- 确定性:相同输入产生相同输出
- 高效性:计算速度快
- 均匀性:均匀分布,减少冲突
- 雪崩效应:输入微小变化导致输出巨大变化
4.1.2 冲突解决方法
链地址法(Separate Chaining)
- 每个桶使用链表存储冲突元素
- 简单实现,动态扩容容易
- 内存利用率较低
开放地址法(Open Addressing)
- 冲突时寻找下一个可用位置
- 包括线性探测、二次探测、双重哈希
- 内存利用率高,但删除操作复杂
4.2 Java中的哈希表实现
4.2.1 HashMap源码分析
| 12
 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
 32
 33
 34
 35
 36
 37
 
 | public class HashMap<K,V> extends AbstractMap<K,V>
 implements Map<K,V>, Cloneable, Serializable {
 
 
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
 
 
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 
 
 static final int TREEIFY_THRESHOLD = 8;
 
 
 static final int UNTREEIFY_THRESHOLD = 6;
 
 
 static final int MIN_TREEIFY_CAPACITY = 64;
 
 
 transient Node<K,V>[] table;
 
 
 static class Node<K,V> implements Map.Entry<K,V> {
 final int hash;
 final K key;
 V value;
 Node<K,V> next;
 
 Node(int hash, K key, V value, Node<K,V> next) {
 this.hash = hash;
 this.key = key;
 this.value = value;
 this.next = next;
 }
 }
 }
 
 | 
4.2.2 ConcurrentHashMap线程安全实现
JDK 1.7实现:分段锁
- 将数据分成多个段(Segment)
- 每个段相当于小的HashMap
- 不同段可以并发操作
JDK 1.8实现:CAS + synchronized
- 使用CAS操作保证线程安全
- 链表长度超过8时转为红黑树
- 性能更优,并发度更高
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 
 | final V putVal(K key, V value, boolean onlyIfAbsent) {
 if (key == null || value == null) throw new NullPointerException();
 int hash = spread(key.hashCode());
 int binCount = 0;
 
 for (Node<K,V>[] tab = table;;) {
 Node<K,V> f; int n, i, fh;
 if (tab == null || (n = tab.length) == 0)
 tab = initTable();
 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
 break;
 }
 else if ((fh = f.hash) == MOVED)
 tab = helpTransfer(tab, f);
 else {
 V oldVal = null;
 synchronized (f) {
 if (tabAt(tab, i) == f) {
 if (fh >= 0) {
 binCount = 1;
 for (Node<K,V> e = f;; ++binCount) {
 K ek;
 if (e.hash == hash &&
 ((ek = e.key) == key ||
 (ek != null && key.equals(ek)))) {
 oldVal = e.val;
 if (!onlyIfAbsent)
 e.val = value;
 break;
 }
 Node<K,V> pred = e;
 if ((e = e.next) == null) {
 pred.next = new Node<K,V>(hash, key, value, null);
 break;
 }
 }
 }
 else if (f instanceof TreeBin) {
 Node<K,V> p;
 binCount = 2;
 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
 oldVal = p.val;
 if (!onlyIfAbsent)
 p.val = value;
 }
 }
 }
 }
 if (binCount != 0) {
 if (binCount >= TREEIFY_THRESHOLD)
 treeifyBin(tab, i);
 if (oldVal != null)
 return oldVal;
 break;
 }
 }
 }
 addCount(1L, binCount);
 return null;
 }
 
 | 
4.3 哈希冲突解决策略详解
4.3.1 链地址法实现
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 
 | public class ChainingHashMap<K, V> {private static final int INITIAL_CAPACITY = 16;
 private static final float LOAD_FACTOR = 0.75f;
 
 private Node<K, V>[] table;
 private int size;
 private int threshold;
 
 @SuppressWarnings("unchecked")
 public ChainingHashMap() {
 table = (Node<K, V>[]) new Node[INITIAL_CAPACITY];
 threshold = (int) (INITIAL_CAPACITY * LOAD_FACTOR);
 }
 
 static class Node<K, V> {
 final K key;
 V value;
 Node<K, V> next;
 final int hash;
 
 Node(int hash, K key, V value, Node<K, V> next) {
 this.hash = hash;
 this.key = key;
 this.value = value;
 this.next = next;
 }
 }
 
 public V put(K key, V value) {
 int hash = hash(key);
 int index = indexFor(hash, table.length);
 
 
 for (Node<K, V> e = table[index]; e != null; e = e.next) {
 if (e.hash == hash && (e.key == key || key.equals(e.key))) {
 V oldValue = e.value;
 e.value = value;
 return oldValue;
 }
 }
 
 
 addNode(hash, key, value, index);
 return null;
 }
 
 private void addNode(int hash, K key, V value, int bucketIndex) {
 Node<K, V> newNode = new Node<>(hash, key, value, table[bucketIndex]);
 table[bucketIndex] = newNode;
 
 if (++size > threshold) {
 resize(2 * table.length);
 }
 }
 
 @SuppressWarnings("unchecked")
 private void resize(int newCapacity) {
 Node<K, V>[] oldTable = table;
 Node<K, V>[] newTable = (Node<K, V>[]) new Node[newCapacity];
 
 for (Node<K, V> oldNode : oldTable) {
 while (oldNode != null) {
 Node<K, V> next = oldNode.next;
 int index = indexFor(oldNode.hash, newCapacity);
 oldNode.next = newTable[index];
 newTable[index] = oldNode;
 oldNode = next;
 }
 }
 
 table = newTable;
 threshold = (int) (newCapacity * LOAD_FACTOR);
 }
 
 private int hash(Object key) {
 int h = key.hashCode();
 return h ^ (h >>> 16);
 }
 
 private int indexFor(int hash, int length) {
 return hash & (length - 1);
 }
 }
 
 | 
第五章:排序算法(Sorting Algorithms)
5.1 排序算法分类与复杂度对比
| 算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 | 
| 冒泡排序 | O(n²) | O(1) | 稳定 | 小规模数据 | 
| 选择排序 | O(n²) | O(1) | 不稳定 | 小规模数据 | 
| 插入排序 | O(n²) | O(1) | 稳定 | 近乎有序数据 | 
| 希尔排序 | O(n log n) | O(1) | 不稳定 | 中等规模数据 | 
| 归并排序 | O(n log n) | O(n) | 稳定 | 大规模数据 | 
| 快速排序 | O(n log n) | O(log n) | 不稳定 | 大规模数据 | 
| 堆排序 | O(n log n) | O(1) | 不稳定 | 大规模数据 | 
| 计数排序 | O(n+k) | O(k) | 稳定 | 整数范围小 | 
| 桶排序 | O(n+k) | O(n+k) | 稳定 | 均匀分布数据 | 
| 基数排序 | O(d(n+k)) | O(n+k) | 稳定 | 整数或字符串 | 
5.2 经典排序算法实现
5.2.1 快速排序(Quick Sort)
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | public class QuickSort {
 public void quickSort(int[] arr, int low, int high) {
 if (low < high) {
 int pivotIndex = partition(arr, low, high);
 quickSort(arr, low, pivotIndex - 1);
 quickSort(arr, pivotIndex + 1, high);
 }
 }
 
 private int partition(int[] arr, int low, int high) {
 int pivot = arr[high];
 int i = low - 1;
 
 for (int j = low; j < high; j++) {
 if (arr[j] <= pivot) {
 i++;
 swap(arr, i, j);
 }
 }
 
 swap(arr, i + 1, high);
 return i + 1;
 }
 
 private void swap(int[] arr, int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 }
 
 
 private int medianOfThree(int[] arr, int low, int high) {
 int mid = low + (high - low) / 2;
 
 if (arr[low] > arr[mid]) swap(arr, low, mid);
 if (arr[low] > arr[high]) swap(arr, low, high);
 if (arr[mid] > arr[high]) swap(arr, mid, high);
 
 swap(arr, mid, high - 1);
 return arr[high - 1];
 }
 }
 
 | 
5.2.2 归并排序(Merge Sort)
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 
 | public class MergeSort {
 public void mergeSort(int[] arr, int left, int right) {
 if (left < right) {
 int mid = left + (right - left) / 2;
 
 mergeSort(arr, left, mid);
 mergeSort(arr, mid + 1, right);
 
 merge(arr, left, mid, right);
 }
 }
 
 private void merge(int[] arr, int left, int mid, int right) {
 int n1 = mid - left + 1;
 int n2 = right - mid;
 
 int[] leftArr = new int[n1];
 int[] rightArr = new int[n2];
 
 for (int i = 0; i < n1; i++) {
 leftArr[i] = arr[left + i];
 }
 for (int j = 0; j < n2; j++) {
 rightArr[j] = arr[mid + 1 + j];
 }
 
 int i = 0, j = 0, k = left;
 
 while (i < n1 && j < n2) {
 if (leftArr[i] <= rightArr[j]) {
 arr[k++] = leftArr[i++];
 } else {
 arr[k++] = rightArr[j++];
 }
 }
 
 while (i < n1) {
 arr[k++] = leftArr[i++];
 }
 
 while (j < n2) {
 arr[k++] = rightArr[j++];
 }
 }
 }
 
 | 
5.2.3 堆排序(Heap Sort)
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | public class HeapSort {
 public void heapSort(int[] arr) {
 int n = arr.length;
 
 
 for (int i = n / 2 - 1; i >= 0; i--) {
 heapify(arr, n, i);
 }
 
 
 for (int i = n - 1; i > 0; i--) {
 swap(arr, 0, i);
 heapify(arr, i, 0);
 }
 }
 
 private void heapify(int[] arr, int heapSize, int root) {
 int largest = root;
 int left = 2 * root + 1;
 int right = 2 * root + 2;
 
 
 if (left < heapSize && arr[left] > arr[largest]) {
 largest = left;
 }
 
 
 if (right < heapSize && arr[right] > arr[largest]) {
 largest = right;
 }
 
 
 if (largest != root) {
 swap(arr, root, largest);
 heapify(arr, heapSize, largest);
 }
 }
 
 private void swap(int[] arr, int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 }
 }
 
 | 
5.3 排序算法优化技巧
5.3.1 快速排序优化
- 三数取中法:选择更好的基准值
- 小数组使用插入排序:减少递归开销
- 三向切分:处理大量重复元素
- 尾递归优化:减少栈空间使用
5.3.2 归并排序优化
- 小数组使用插入排序
- 原地归并:减少空间复杂度
- TimSort:结合归并和插入排序的优点
第六章:算法设计技巧
6.1 双指针技巧
双指针技巧通过使用两个指针遍历数据结构,将O(n²)的时间复杂度优化到O(n)。
6.1.1 快慢指针
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public ListNode middleNode(ListNode head) {
 ListNode slow = head;
 ListNode fast = head;
 
 while (fast != null && fast.next != null) {
 slow = slow.next;
 fast = fast.next.next;
 }
 
 return slow;
 }
 
 | 
6.1.2 左右指针
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public int[] twoSum(int[] numbers, int target) {
 int left = 0;
 int right = numbers.length - 1;
 
 while (left < right) {
 int sum = numbers[left] + numbers[right];
 if (sum == target) {
 return new int[]{left + 1, right + 1};
 } else if (sum < target) {
 left++;
 } else {
 right--;
 }
 }
 
 return new int[]{-1, -1};
 }
 
 | 
6.2 分治算法
分治法将问题分解为子问题,分别解决后合并结果。
6.2.1 分治框架
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public Result divideAndConquer(Problem problem) {
 if (problem.isBaseCase()) {
 return problem.solveBaseCase();
 }
 
 
 Problem[] subProblems = problem.divide();
 
 
 Result[] subResults = new Result[subProblems.length];
 for (int i = 0; i < subProblems.length; i++) {
 subResults[i] = divideAndConquer(subProblems[i]);
 }
 
 
 return problem.merge(subResults);
 }
 
 | 
6.3 贪心算法
贪心算法在每一步选择当前最优解,希望最终得到全局最优解。
6.3.1 贪心算法适用条件
- 最优子结构:问题的最优解包含子问题的最优解
- 贪心选择性质:局部最优选择能导致全局最优解
6.3.2 经典贪心问题
| 12
 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 ActivitySelection {
 public int maxActivities(int[] start, int[] end) {
 int n = start.length;
 
 
 int[][] activities = new int[n][2];
 for (int i = 0; i < n; i++) {
 activities[i] = new int[]{start[i], end[i]};
 }
 
 Arrays.sort(activities, (a, b) -> a[1] - b[1]);
 
 int count = 1;
 int lastEnd = activities[0][1];
 
 for (int i = 1; i < n; i++) {
 if (activities[i][0] >= lastEnd) {
 count++;
 lastEnd = activities[i][1];
 }
 }
 
 return count;
 }
 }
 
 | 
第七章:高频面试真题解析
7.1 链表高频面试题
7.1.1 LRU缓存机制
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 
 | class LRUCache {private class Node {
 int key, value;
 Node prev, next;
 Node(int k, int v) {
 key = k;
 value = v;
 }
 }
 
 private Map<Integer, Node> map;
 private Node head, tail;
 private int capacity;
 
 public LRUCache(int capacity) {
 this.capacity = capacity;
 map = new HashMap<>();
 head = new Node(0, 0);
 tail = new Node(0, 0);
 head.next = tail;
 tail.prev = head;
 }
 
 public int get(int key) {
 if (map.containsKey(key)) {
 Node node = map.get(key);
 remove(node);
 addToHead(node);
 return node.value;
 }
 return -1;
 }
 
 public void put(int key, int value) {
 if (map.containsKey(key)) {
 remove(map.get(key));
 }
 
 Node newNode = new Node(key, value);
 addToHead(newNode);
 map.put(key, newNode);
 
 if (map.size() > capacity) {
 Node tailNode = removeTail();
 map.remove(tailNode.key);
 }
 }
 
 private void addToHead(Node node) {
 node.next = head.next;
 node.prev = head;
 head.next.prev = node;
 head.next = node;
 }
 
 private void remove(Node node) {
 node.prev.next = node.next;
 node.next.prev = node.prev;
 }
 
 private Node removeTail() {
 Node node = tail.prev;
 remove(node);
 return node;
 }
 }
 
 | 
7.2 树结构高频面试题
7.2.1 二叉树序列化与反序列化
| 12
 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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | public class Codec {private static final String NULL = "#";
 private static final String SEP = ",";
 
 
 public String serialize(TreeNode root) {
 StringBuilder sb = new StringBuilder();
 serializeHelper(root, sb);
 return sb.toString();
 }
 
 private void serializeHelper(TreeNode node, StringBuilder sb) {
 if (node == null) {
 sb.append(NULL).append(SEP);
 return;
 }
 
 sb.append(node.val).append(SEP);
 serializeHelper(node.left, sb);
 serializeHelper(node.right, sb);
 }
 
 
 public TreeNode deserialize(String data) {
 Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(SEP)));
 return deserializeHelper(queue);
 }
 
 private TreeNode deserializeHelper(Queue<String> queue) {
 String val = queue.poll();
 if (val.equals(NULL)) {
 return null;
 }
 
 TreeNode node = new TreeNode(Integer.parseInt(val));
 node.left = deserializeHelper(queue);
 node.right = deserializeHelper(queue);
 
 return node;
 }
 }
 
 | 
7.3 图论高频面试题
7.3.1 课程表问题(拓扑排序)
| 12
 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
 32
 33
 34
 35
 36
 
 | public boolean canFinish(int numCourses, int[][] prerequisites) {
 List<List<Integer>> adj = new ArrayList<>();
 for (int i = 0; i < numCourses; i++) {
 adj.add(new ArrayList<>());
 }
 
 
 int[] inDegree = new int[numCourses];
 for (int[] pre : prerequisites) {
 adj.get(pre[1]).add(pre[0]);
 inDegree[pre[0]]++;
 }
 
 
 Queue<Integer> queue = new LinkedList<>();
 for (int i = 0; i < numCourses; i++) {
 if (inDegree[i] == 0) {
 queue.offer(i);
 }
 }
 
 int count = 0;
 while (!queue.isEmpty()) {
 int course = queue.poll();
 count++;
 
 for (int next : adj.get(course)) {
 if (--inDegree[next] == 0) {
 queue.offer(next);
 }
 }
 }
 
 return count == numCourses;
 }
 
 | 
第八章:算法复杂度分析
8.1 时间复杂度分析
8.1.1 大O表示法
- O(1):常数时间复杂度
- O(log n):对数时间复杂度
- O(n):线性时间复杂度
- O(n log n):线性对数时间复杂度
- O(n²):平方时间复杂度
- O(2ⁿ):指数时间复杂度
8.1.2 递归复杂度分析
主定理(Master Theorem)
对于形如T(n) = aT(n/b) + f(n)的递归式:
- 如果f(n) = O(n^(log_b a - ε)),则T(n) = Θ(n^(log_b a))
- 如果f(n) = Θ(n^(log_b a)),则T(n) = Θ(n^(log_b a) log n)
- 如果f(n) = Ω(n^(log_b a + ε)),且af(n/b) ≤ cf(n),则T(n) = Θ(f(n))
8.2 空间复杂度分析
- O(1):原地算法
- O(n):需要额外线性空间
- O(log n):递归调用栈空间
- O(n²):二维数组存储
总结
数据结构与算法是计算机科学的核心基础,掌握这些知识点对于技术面试和实际开发都至关重要。本文系统梳理了:
- 链表:单链表、双链表、循环链表的核心操作和经典题型
- 树结构:二叉树、BST、AVL树、B树、B+树的原理和实现
- 图结构:图的表示、遍历算法和最短路径算法
- 哈希表:哈希函数设计、冲突解决和Java实现
- 排序算法:各种排序算法的原理、实现和优化技巧
- 算法技巧:双指针、分治、贪心等经典算法设计技巧
建议读者通过大量刷题来巩固这些知识点,推荐使用LeetCode、力扣等平台进行练习。同时,理解算法背后的思想比死记硬背更重要,要学会举一反三,灵活运用。
参考文献
- 《算法导论》- Thomas H. Cormen等
- 《数据结构与算法分析》- Mark Allen Weiss
- 《算法》- Robert Sedgewick
- LeetCode官方题解
- 力扣中国官方文档
- 《Java数据结构和算法》- Robert Lafore
本文档持续更新,如有错误或建议,欢迎指正。