数据结构与算法简介
在计算机科学中,数据结构和算法是构建高效、可靠软件的核心基础,它们是程序员解决复杂问题的工具箱,决定了程序的运行速度和资源消耗。
想象一下,你正在整理一个杂乱无章的书架。如果只是把书随意堆放,找一本特定的书会非常困难。但如果你按照书名首字母排序,或者按照类别(如小说、历史、科学)分类,查找效率就会大大提高。数据结构就像是整理书架的方法,它定义了数据应该如何组织和存储。而算法则是查找、插入或删除一本书的具体步骤。
数据结构与算法是计算机科学的底层能力,用来解决据如何组织、问题如何高效求解这两件事。
数据结构
研究数据在内存或存储中的组织方式,以及如何高效访问和修改。核心目标是降低时间和空间成本。
常见类型:
- 线性结构:数组、链表、栈、队列
- 非线性结构:树、图、堆、哈希表
算法
研究解决问题的步骤和策略,本质是对计算过程的抽象。评价标准只有两个:时间复杂度、空间复杂度。
常见分类:
- 基础算法:遍历、递归、分治
- 排序与查找:快速排序、归并排序、二分查找
- 高级策略:贪心、动态规划、回溯
关系
数据结构决定怎么存,算法决定怎么算。同一个问题,结构不同,算法复杂度可能天差地别。
数据结构
数据结构是计算机存储、组织数据的方式。
数据结构旨在实现一种或多种特定关系的数据元素的集合,以及定义在这些数据上的一组操作。
核心概念
- 数据: 计算机可以识别、存储和处理的符号集合,如数字、字符等。
- 数据元素: 数据的基本单位,在程序中通常作为一个整体进行考虑和处理。
- 数据项: 构成数据元素的、有独立含义的、不可分割的最小单位。
- 数据结构: 相互之间存在一种或多种特定关系的数据元素的集合。
数据结构的分类
数据结构主要分为两大类:逻辑结构和物理结构。

逻辑结构描述数据元素之间的抽象关系,与计算机如何实现无关。它主要分为:
- 线性结构: 数据元素之间存在"一对一"的线性关系。如数组、链表、栈、队列。
- 非线性结构: 数据元素之间存在"一对多"或"多对多"的复杂关系。如树、图、集合。
物理结构(又称存储结构)描述数据在计算机内存中的实际存储方式。它主要分为:
- 顺序存储: 把逻辑上相邻的元素存储在物理位置也相邻的存储单元中。如数组。
- 链式存储: 不要求逻辑上相邻的元素在物理位置上也相邻,元素间的逻辑关系通过附加的指针来表示。如链表。
算法
算法是解决特定问题的一系列清晰、有限的指令步骤。
一个有效的算法应该具备以下五个特性:
- 有穷性: 算法必须在执行有限步之后结束。
- 确定性: 算法的每一步都必须有确切的定义,不会产生二义性。
- 可行性: 算法中的每一步都可以通过已经实现的基本运算执行有限次来实现。
- 输入: 一个算法有零个或多个输入。
- 输出: 一个算法有一个或多个输出,输出是与输入有特定关系的量。
算法的评价标准:时间复杂度与空间复杂度
我们如何判断一个算法的好坏?通常从时间和空间两个维度来衡量。
- 时间复杂度: 定性描述该算法的运行时间。它表示随着问题规模 n 的增大,算法执行时间的增长趋势。常用大 O 符号表示。
- 空间复杂度: 定性描述该算法在运行过程中临时占用存储空间的大小。同样用大 O 符号表示。
常见时间复杂度对比
下表展示了不同时间复杂度随数据规模增长的趋势:
| 复杂度表示 | 名称 | 举例(n=10 vs n=1000) | 说明 |
|---|---|---|---|
| O(1) | 常数阶 | 1 步 vs 1 步 | 效率最高,与问题规模无关 |
| O(log n) | 对数阶 | ~3 步 vs ~10 步 | 效率非常高,如二分查找 |
| O(n) | 线性阶 | 10 步 vs 1000 步 | 效率良好,如遍历数组 |
| O(n log n) | 线性对数阶 | ~30 步 vs ~10000 步 | 效率较好,如快速排序 |
| O(n²) | 平方阶 | 100 步 vs 1,000,000 步 | 效率较低,如简单选择排序 |
| O(2ⁿ) | 指数阶 | 1024 步 vs 天文数字 | 效率极低,应尽量避免 |
简单理解: 我们追求的是随着数据量 n 变大,耗时增长越慢的算法。O(1) 和 O(log n) 是优秀的,O(n) 是可接受的,而 O(n²) 及以上在大数据量时就需要优化了。
经典数据结构与算法示例
让我们通过一个具体问题来感受数据结构和算法的应用。
问题: 在一个无序的数字列表中,快速找到是否存在某个目标数字。
方案一:线性查找(使用数组)
这是一种直观但低效的方法。
- 数据结构: 数组(顺序存储的线性表)。
- 算法: 从第一个元素开始,逐个与目标值比较,直到找到或遍历完所有元素。
- 时间复杂度: O(n)。最坏情况下需要检查所有 n 个元素。
实例
def linear_search(arr, target):
"""
在数组 arr 中线性查找目标值 target。
参数:
arr: 待查找的列表
target: 要查找的目标值
返回:
如果找到,返回其索引;否则返回 -1。
"""
for i in range(len(arr)): # 遍历数组的每个元素
if arr[i] == target: # 如果当前元素等于目标值
return i # 返回当前索引
return -1 # 遍历完毕未找到,返回 -1
# 测试数据
test_data = [12, 45, 67, 89, 34, 23, 90, 11]
target_number = 34
# 执行查找
result = linear_search(test_data, target_number)
# 输出结果
if result != -1:
print(f"目标数字 {target_number} 在数组中的索引是:{result}")
else:
print(f"在数组中未找到目标数字 {target_number}")
输出结果:
目标数字 34 在数组中的索引是:4
方案二:二分查找(使用有序数组)
这是一种高效的方法,但前提是数据必须有序。
- 数据结构: 有序数组。
- 算法:
- 比较目标值与数组中间的元素。
- 如果相等,则找到。
- 如果目标值更小,则在数组的左半部分重复步骤1。
- 如果目标值更大,则在数组的右半部分重复步骤1。
- 如果搜索区间为空,则未找到。
- 时间复杂度: O(log n)。每次比较都将搜索范围缩小一半。
实例
def binary_search(sorted_arr, target):
"""
在已排序的数组 sorted_arr 中二分查找目标值 target。
参数:
sorted_arr: 已排序的列表(升序)
target: 要查找的目标值
返回:
如果找到,返回其索引;否则返回 -1。
"""
left, right = 0, len(sorted_arr) - 1 # 初始化搜索区间为整个数组
while left <= right: # 当区间有效时继续搜索
mid = (left + right) // 2 # 计算中间索引
if sorted_arr[mid] == target: # 找到目标值
return mid
elif sorted_arr[mid] < target: # 目标值在右半部分
left = mid + 1
else: # 目标值在左半部分
right = mid - 1
return -1 # 区间无效,未找到
# 测试数据 (必须是已排序的)
test_data = [12, 45, 67, 89, 34, 23, 90, 11]
sorted_test_data = sorted(test_data) # 对测试数据排序
print(f"有序数组:{sorted_test_data}")
target_number = 34
# 执行查找
result = binary_search(sorted_test_data, target_number)
# 输出结果
if result != -1:
print(f"目标数字 {target_number} 在有序数组中的索引是:{result}")
else:
print(f"在有序数组中未找到目标数字 {target_number}")
输出结果:
有序数组:[11, 12, 23, 34, 45, 67, 89, 90] 目标数字 34 在有序数组中的索引是:3
对比与思考:
- 当
n=1000时,线性查找最多需要 1000 次比较,而二分查找最多只需要约 10 次比较(因为 2^10 ≈ 1024)。 - 二分查找的效率远高于线性查找,但它要求数据预先排序。排序本身需要成本,因此选择哪种算法取决于具体场景(例如,数据是静态的频繁查找,还是动态的频繁插入)。
如何开始学习?
学习数据结构与算法是一个循序渐进的过程,建议按以下路径进行:
- 掌握一门编程语言: 首先你需要一个工具来实现想法,Python、Java 或 C++ 都是不错的选择。Python 语法简洁,更适合初学者聚焦算法逻辑本身。
- 从基础数据结构开始:
- 数组 / 列表: 最基础、最常用的顺序存储结构。
- 链表: 理解指针/引用和动态内存分配的关键。
- 栈与队列: 理解"先进后出"和"先进先出"的操作受限的线性表。
- 学习基础算法思想:
- 排序算法: 冒泡排序、选择排序、插入排序(理解 O(n²)),然后学习归并排序、快速排序(理解 O(n log n) 和分治思想)。
- 查找算法: 顺序查找、二分查找。
- 挑战非线性结构:
- 树: 重点学习二叉树,特别是二叉搜索树。理解树的遍历(前序、中序、后序)。
- 图: 理解图的表示方法(邻接矩阵、邻接表)和基础遍历算法(深度优先搜索 DFS、广度优先搜索 BFS)。
- 实践与刷题: 在 LeetCode、牛客网等平台从简单题目开始练习,将理论应用于实践,这是巩固知识、锻炼思维的最佳方式。
点我分享笔记