代码训练总结(1)算法和数据结构的框架思维
Author: Once Day Date: 2026年1月26日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦...
漫漫长路,有人对你微笑过嘛...
全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客
参考文章:
文章目录
- 代码训练总结(1)算法和数据结构的框架思维
-
-
-
- [1. 宏观认知](#1. 宏观认知)
- [2. 数组与链表](#2. 数组与链表)
- [3. 数据结构与算法](#3. 数据结构与算法)
- [4. 穷举思维](#4. 穷举思维)
-
-
1. 宏观认知
从宏观视角看,数据结构并不是零散孤立的知识点,而是一套可以被高度抽象和统一理解的组织模型。在高层次上,几乎所有常见数据结构都可以归结为数组与链表两种基础形态。
- 数组以连续内存为核心特征,强调随机访问效率和空间局部性;
- 链表则通过指针将离散内存串联,更关注结构的灵活性和节点关系的表达。
二者在内存布局、访问方式以及性能权衡上的差异,构成了后续复杂数据结构设计的原型基础。
在此之上,理解数据结构的关键不在于"结构名词",而在于对遍历与访问模型的把握。所谓增删查改,本质都是在既定遍历路径上的节点定位与状态调整。数组的遍历天然依赖下标,强调顺序和偏移;链表的遍历则依赖指针跳转,强调连接关系。因此,任何数据结构的时间复杂度分析,最终都可以还原为"定位目标元素需要走多少步",以及"修改结构时影响了多少节点"。
算法层面的核心思想可以抽象为对问题空间的穷举与剪枝。穷举并非盲目枚举所有可能,而是对信息熵的系统性覆盖,确保所有有效状态都被感知,同时通过约束条件消除冗余路径。深度优先、广度优先、动态规划、本质上都是在不同约束下寻找覆盖解空间的最短或最优路径,其优劣取决于是否做到无遗漏、无重复。
在这一框架下,各类看似复杂的数据结构都能自然落位。栈和队列是对线性遍历顺序的约束;树是对层级关系的显式建模;图是节点关系的泛化表达;哈希表则通过数组加映射函数重构访问路径。无论其外在形式如何变化,底层仍然依托数组或链表完成数据存储与连接,只是在访问策略和遍历规则上施加了不同的算法约束。
2. 数组与链表
在数据结构的宏观认知中,数组与链表构成了最基础、也是最具代表性的两种线性存储模型,它们在内存组织方式上的根本差异,直接决定了各自的性能特征和适用场景。、
数组以连续内存作为前提,所有元素在物理地址上紧密排列,使得下标到地址的映射可以通过简单的偏移计算完成。这种结构天然支持随机访问,在查找和读取操作上具备稳定的常数时间复杂度,但代价是对容量的强依赖,一旦空间不足,往往需要重新分配并整体拷贝数据,扩容成本不可忽视。
c++
// 使用数组实现顺序表,并支持随机访问
int arr[5] = {10, 20, 30, 40, 50};
int x = arr[3]; // O(1) 随机访问
链表则采取完全不同的策略,它通过指针将分散在内存中的节点串联起来,不再要求物理连续性,从而规避了数组在容量规划上的限制。节点的插入与删除只涉及局部指针的重连,在已知位置的前提下可以高效完成,因此更适合元素频繁变动的场景。但这种灵活性是以访问效率为代价的,链表无法通过下标直接定位元素,任何访问都必须沿着指针逐个遍历,时间复杂度与节点位置强相关。
c++
// 单链表节点定义与头插操作
struct Node {
int val;
Node* next;
};
Node* head = nullptr;
Node* node = new Node{10, head};
head = node; // O(1) 插入
从访问方式的角度看,数组与链表在遍历模型上具有共性。无论是基于循环的迭代,还是基于调用栈的递归,本质上都是对线性结构进行顺序扫描。不同之处在于,数组的遍历依赖索引递增,而链表的遍历依赖指针推进,这种差异会在缓存命中率、函数调用开销等底层细节上产生影响,但在算法表达层面却保持了高度一致。这种一致性也解释了为何许多算法思想可以在数组和链表之间自然迁移。
下面给出一个从工程实践角度出发的对比表,用于概括数组与链表在核心特性上的差异:
| 对比维度 | 数组(Array) | 链表(Linked List) |
|---|---|---|
| 内存布局 | 元素在内存中连续存放,地址可通过下标偏移直接计算 | 节点在内存中分散存放,通过指针建立逻辑关系 |
| 访问方式 | 支持 O(1) 随机访问,适合按下标直接定位元素 | 不支持随机访问,只能从头节点逐个遍历 |
| 插入删除 | 中间位置插入或删除需要整体搬移元素,代价较高 | 已知位置下插入和删除只需修改指针,代价较低 |
| 扩容成本 | 容量固定或扩容代价高,通常伴随整体拷贝 | 无需整体扩容,节点按需动态分配 |
| 缓存友好性 | 连续内存利于 CPU Cache,遍历性能稳定 | 内存分散,缓存命中率较低 |
| 额外空间 | 仅存储数据本身,空间利用率高 | 每个节点需额外指针空间 |
| 典型应用 | 顺序表、二分查找、排序、静态数据集 | 队列、栈、LRU、频繁插删的数据结构 |
3. 数据结构与算法
在不同技术语境下,"算法"一词所指的内涵存在显著差异,这也是许多初学者在学习数据结构与算法时产生认知混淆的根源。在密码学、机器学习等领域,算法往往是数学模型或统计理论的直接工程化表达,其核心价值在于公式本身是否严谨、假设是否成立。代码更多承担的是执行载体的角色,算法工程师的主要精力集中在数据建模、参数选择以及数值稳定性上,而非对离散结构的遍历策略。
| 对比维度 | 数学算法 | 数据结构算法 | 算法工程师(工程算法) |
|---|---|---|---|
| 问题来源 | 抽象数学问题或理论模型 | 离散数据与结构化问题 | 真实业务数据与工程需求 |
| 核心目标 | 推导闭式解或严格证明 | 高效遍历与组织解空间 | 提升指标效果与系统性能 |
| 思维方式 | 公式推导、定理归纳、逻辑证明 | 穷举、剪枝、状态转移 | 建模、特征工程、调参 |
| 对穷举的态度 | 尽量避免,视为思路不成熟 | 必然存在,是核心手段 | 受算力和成本约束而取舍 |
| 复杂度关注 | 理论可解性与精确性 | 时间与空间复杂度 | 训练/推理成本与稳定性 |
| 实现侧重点 | 正确表达数学关系 | 数据结构选择与遍历策略 | 工程实现、可扩展性 |
| 典型例子 | 欧几里得算法、数值求解 | 二分搜索、DP、图遍历 | 推荐系统、模型训练流程 |
在数据结构与算法的语境中,算法的本质则更偏向于对有限状态空间的系统性穷举。无论是数组、链表,还是树与图,所谓"设计算法",本质上都是在回答如何高效地遍历所有可能元素或状态,并在遍历过程中避免遗漏与冗余。深度优先、广度优先、双指针、滑动窗口等方法,并不是数学推导的替代品,而是对穷举路径的组织与约束,使其在时间和空间复杂度上可控。
这种思维方式与传统数学解题形成了鲜明对比。数学题更强调洞察结构,通过观察、构造几何关系或列方程来直接命中答案。如果问题需要大规模枚举才能解出,往往意味着思路尚未抽象到位,缺乏关键定理或不变量的支撑。数学追求的是"少算甚至不算",通过逻辑压缩解空间。
计算机解决问题的哲学恰恰相反。程序并不擅长顿悟,却擅长不知疲倦地执行规则。只要问题规模受限、复杂度可控,穷举就成为一种可靠而务实的手段。对于程序员而言,是否存在优雅的数学公式固然重要,但并非必要条件;更关键的是如何在既定资源约束下,将问题转化为可以被遍历、被验证、被裁剪的计算过程。
4. 穷举思维
从"穷举"这一视角出发,可以更清晰地理解不同算法范式的难点所在。真正具有挑战性的算法,往往并不在于是否穷举,而在于如何组织穷举过程。以递归类问题为例,回溯算法和动态规划都属于对解空间的系统性枚举,只是侧重点不同。回溯强调路径探索,它将问题抽象为一棵隐式搜索树,通过递归不断尝试选择、撤销选择,核心难点在于剪枝条件的设计,避免在无效分支上浪费计算资源。
动态规划在本质上同样是穷举,但思维模型更偏向问题分解。它要求先识别出重叠子问题与最优子结构,再通过状态定义与转移方程,将指数级的搜索空间压缩为多项式规模。相比回溯,动态规划更难的地方不在代码实现,而在于状态设计是否完备、维度是否合理。一旦状态遗漏关键信息,穷举就会失真;状态冗余过多,又会导致复杂度失控。
另一类算法的难点,则体现在"如何更聪明地穷举"。这类算法通常不依赖递归,而是通过对数据结构特性或问题约束的深入利用,主动缩小搜索范围。二分搜索就是典型代表:它并未放弃穷举,而是利用有序性,每一步都排除一半不可能的区间,将线性扫描转化为对数级别的搜索过程。
递归,而是通过对数据结构特性或问题约束的深入利用,主动缩小搜索范围。二分搜索就是典型代表:它并未放弃穷举,而是利用有序性,每一步都排除一半不可能的区间,将线性扫描转化为对数级别的搜索过程。
类似的思想还体现在双指针、滑动窗口、单调栈等技巧中。这些方法并不是引入新的计算能力,而是通过更精确的遍历策略,让每一次访问都"物尽其用"。从这个角度看,算法能力的提升,并非来自逃避穷举,而是来自对穷举路径的精细控制与结构化表达。

Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注!
(。◕‿◕。)感谢您的阅读与支持~~~