Java后端-基础-常见数据结构与算法-23
背景
本文是《Java 后端从小白到大神》修仙系列第二十三篇
,正式进入Java后端
世界,本篇文章主要聊Java基础
。若想详细学习请点击首篇博文,我们开始把。
文章概览
- 常见数据结构
- 常见算法
常见数据结构
1. 数组
概念:一组固定大小的同类型元素,按连续内存存储,通过索引随机访问。
示意图:
┌───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ …
└───┴───┴───┴───┘
index: 0,1,2,3,…
分类:线性、顺序存储。
Java 实现:
数组
|
|
2. 单向链表
概念:节点通过指针顺序相连,动态大小,插入删除 O(1)(已知节点)。
示意图:
[head] → [1 | *] → [2 | *] → [3 | null]
分类:线性、链式存储。
Java 实现:
单向链表
|
|
3. 双向链表
概念:每个节点有前驱和后继指针,可双向遍历,删除节点更高效。
示意图:
head tail
↓ ↓
null ← [1] <--> [2] <--> [3] <--> [4] → null
分类:线性、链式存储。
Java 实现:
双向链表
|
|
4. 栈 (Stack)
概念:后进先出 (LIFO) ,先入在底的结构。
示意图:
stack.push(1);
stack.push(2);
stack.push(3);
栈顶(top) → 3 → 2 → 1 → 栈底
Top → [3]
[2]
[1]
分类:线性,可基于数组或链表实现。
Java 实现:
基于数组实现的栈
|
|
基于链表实现的栈
|
|
5. 队列 (Queue)
概念:先进先出 (FIFO) 结构。
示意图:
入队(enqueue)方向 →→→→→→→→→→→→→→→→→→,新元素从队列的 队尾(rear) 加入
队列: [front] → 元素A → 元素B → 元素C → [rear] (新元素在此插入)
出队(dequeue)方向 ←←←←←←←←←←←←←←←←←←,元素从队列的 队首(front) 离开
分类:线性,可基于数组/环形/链表实现。
Java 实现:
基于环形数组实现队列
|
|
基于链表实现队列
|
|
6. 哈希表 (Hash Table)
概念:通过哈希函数将键映射到数组索引,实现近似 O(1) 插入、查找。
示意图:
1. 以链表法为例,哈希表的结构如下:
数组索引(Buckets) 链表节点(键值对)
[0] → null
[1] → [Key1:Value1] → [Key2:Value2] → null
[2] → [Key3:Value3] → null
[3] → null
...
[n] → [KeyN:ValueN] → [KeyM:ValueM] → ...
无冲突:索引 2 的键 Key3 直接存储。
冲突:索引 1 的 Key1 和 Key2 哈希冲突,以链表形式存储。
2. 哈希表中链表的结构:
想象链表是一列火车,每个节点是一节车厢:
每节车厢(Node):
数据(data):车厢里装载的货物(键值对)。
连接钩(next):指向下一节车厢的挂钩。
链表的头节点(head):火车头,是整个链表的起点。
头节点(head) → [车厢1] → [车厢2] → [车厢3] → null
↑ ↑ ↑
data data data
next → next → next → null
分类:哈希存储。
Java 实现:
链表实现哈希表
|
|
7. 二叉搜索树 (Binary Search Tree)
概念:二叉树,每个左子树小于父节点,右子树大于父节点,中序遍历左->中->右。
示意图:
5
/ \
3 8
/ \ \
2 4 9
分类:非线性、树。
Java 实现:
基于链表实现二叉搜索树
|
|
8. 最小/最大堆 (Binary Heap)
概念:堆是一种 完全二叉树,所有层都填满,只有最后一层可能不满,但一定是从左往右依次填充。
最小堆(Min Heap):每个父节点的值 ≤ 其子节点的值,堆顶元素是整个堆的最小值。
示意图(最小堆):
1
/ \
3 2
/
4
最大堆(Max Heap):每个父节点的值 ≥ 其子节点的值,堆顶元素是整个堆的最大值。
示意图(最大堆):
9
/ \
7 5
/ \ / \
3 4 2 1
示意图(索引公式):
i表示数组索引
左子节点索引:2 * i + 1
右子节点索引:2 * i + 2
父节点索引:(i - 1) / 2
2*i + 1 < n 才有左子节点
2*i + 2 < n 才有右子节点
n 是数组的长度
int[] heap = {1, 3, 5, 7, 9, 8};
这个数组中:
i = 0 表示的是根节点,它的值是 heap[0] = 1
i = 1 表示的是第 2 个节点,值是 heap[1] = 3
i = 2 表示的是第 3 个节点,值是 heap[2] = 5
...
分类:非线性、树。
Java 实现:
基于数组实现最小堆
|
|
基于链表实现最小堆
|
|
9. 前缀树 (Trie)
概念:前缀树(Trie) 是一种高效的树形数据结构,专门用于存储、检索和操作字符串集合。每个节点代表一个字符,从根节点到某一节点的路径形成一个完整的字符串。根节点为空,不存储字符,是整棵树的起点。例如,插入单词 “apple”,路径为:根 → a → p → p → l → e,并在末尾节点标记为单词结尾。
示意图:
root
└─ 'c'
└─ 'a'
└─ 't'
- 标记单词结束。
分类:非线性、树。
Java 实现:
基于数组实现前缀树
|
|
基于Map实现前缀树
|
|
10. 图 (Graph)
概念:图(Graph)是一种由 顶点(Vertex) 和 边(Edge) 组成的数据结构,用于表示实体之间的复杂关系。以下是图的详细说明及其应用场景:
图的基本结构
-
顶点(Vertex)
表示实体或对象(如社交网络中的用户、地图中的城市)。 -
边(Edge)
表示顶点之间的关系,可以有以下类型:- 无向边:关系是双向的(如微信好友关系)。
- 有向边:关系是单向的(如微博关注关系)。
- 权重边:边附带数值(如两地之间的距离、交通成本)。
-
图的分类
- 无向图:所有边均为无向边。
- 有向图:边有方向性。
- 加权图:边附带权重值。
图的存储方式
1. 邻接矩阵(Adjacency Matrix)
- 实现:用二维数组
matrix[i][j]
表示顶点i
和j
的关系。- 无向图:
matrix[i][j] = matrix[j][i]
。 - 有向图:
matrix[i][j]
表示从i
到j
的边。 - 加权图:
matrix[i][j]
存储权重值。
- 无向图:
- 特点:
- 优点:快速查询两顶点是否相邻(O(1) 时间复杂度)。
- 缺点:空间复杂度高(O(V²),V 为顶点数),适合稠密图。
2. 邻接表(Adjacency List)
- 实现:每个顶点维护一个链表,存储其直接相连的邻接顶点。
- 无向图:每个边在邻接表中存储两次(如
A→B
和B→A
)。 - 有向图:仅存储单向关系。
- 加权图:链表中存储邻接顶点及权重。
- 无向图:每个边在邻接表中存储两次(如
- 特点:
- 优点:空间复杂度低(O(V + E),E 为边数),适合稀疏图。
- 缺点:查询两顶点是否相邻需遍历链表(O(d),d 为顶点度数)。
图的应用场景
-
社交网络分析
- 顶点:用户;边:好友/关注关系。
- 应用:推荐共同好友、发现社区(如 Facebook 的好友推荐)。
-
路径规划与导航
- 顶点:地点;边:道路(权重为距离或时间)。
- 应用:计算最短路径(如 Google 地图的路线规划)。
-
推荐系统
- 顶点:用户和商品;边:用户购买/浏览记录。
- 应用:基于图的协同过滤(如“购买此商品的人也买了…”)。
-
网络拓扑与依赖管理
- 顶点:服务器或任务;边:依赖关系。
- 应用:检测循环依赖、任务调度(如项目管理工具)。
-
知识图谱
- 顶点:实体(人物、地点);边:语义关系(如“出生于”、“属于”)。
- 应用:智能问答、语义搜索(如 Google 知识图谱)。
示意图:邻接表方式
0: →1 →2
1: →2
2: →0 →3
3:
分类:非线性、图。
Java 实现:
图的邻接表实现
|
|
图的邻接矩阵实现
|
|
常见算法
1. 基础算法
1.1 二分查找 (Binary Search)
原理与作用:
二分查找用于在有序数组中查找目标值。其原理是将数组分为两部分,逐步缩小查找范围,时间复杂度为 O(log n)。这是面试中非常常见的题型,也是考察对算法复杂度理解的基础题目。
Java 示例代码:
二分查找
|
|
1.2 排序算法
排序是面试中最常考的内容之一。下面列出两种常用且高效的排序算法:
1.2.1 快速排序 (Quick Sort)
原理与作用:
快速排序利用“分治”思想,将数组分为左右两个部分,使得左边都小于某个基准值,右边都大于这个基准值,然后递归排序左右子数组。平均时间复杂度为 O(n log n)。
Java 示例代码:
快速排序
|
|
1.2.2 归并排序 (Merge Sort)
原理与作用:
归并排序也是一种基于分治的排序方法。它将数组递归分割为两半,分别排序后再合并,时间复杂度为 O(n log n)。其特点是稳定性好,适合处理大数据量。
Java 示例代码:
归并排序
|
|
2. 图与树相关算法
2.1 深度优先搜索 (DFS) 与 广度优先搜索 (BFS)
原理与作用:
- DFS: 利用递归或栈实现,遍历图或树的深度优先路径。适合解决连通性、路径查找等问题。
- BFS: 利用队列实现,逐层遍历图或树。常用于最短路径问题。
Java 示例代码(以图的邻接表实现):
DFS
|
|
Java 示例代码(以队列实现):
BFS
|
|
3. 动态规划 (Dynamic Programming)
原理与作用:
动态规划用于解决具有重叠子问题和最优子结构性质的问题。它通过存储中间结果避免重复计算,典型问题包括斐波那契数列、最长公共子序列、背包问题等。
3.1 斐波那契数列
Java 示例代码(使用数组保存中间结果):
斐波那契数列
|
|
3.2 0-1 背包问题(贪心思路通常不适用,动态规划是主流解法)
原理与作用:
0-1 背包问题是一个经典的组合优化问题,给定一组物品,每个物品有自己的重量 w[i] 和价值 v[i],以及一个容量为 W 的背包,要求在不超过背包容量的前提下,选择一些物品放入背包,使得背包中物品的总价值最大。每个物品只能选择放入或者不放入背包(即 0-1 选择)。
Java 示例代码:
背包问题
|
|
4. 贪心算法 (Greedy Algorithms)
原理与作用:
贪心算法每一步都选择当前看起来最优的方案,适用于某些具有贪心选择性质的问题。例如活动选择问题、区间调度、哈夫曼编码等。
4.1 活动选择问题(Interval Scheduling)
Java 示例代码:
活动选择问题的贪心算法
|
|
5. 回溯算法 (Backtracking)
原理与作用:
回溯算法通过递归探索所有可能的解,遇到不满足条件时回退,从而解决排列组合、子集、八皇后、全排列等问题。
5.1 求全排列
Java 示例代码:
回溯问题之全排列
|
|
总结
本篇列出常见数据结构及算法,也是平时必须掌握的。
文章作者 会写代码的小郎中
上次更新 2025-04-17
许可协议 CC BY-NC-ND 4.0