算法的时间是怎么得出的?
计算机执行所有算法时,都在重复:取数据+运算+算完存回去。
算法的时间复杂度就是计算机读取数据+运算+算完再存回去的次数。
还记得为什么要先了解数据结构吗?小孩能看懂的算法笔记首篇
那数据在存储中是怎么变化的?数据结构的栈和堆在内存中长啥样呢?

存储层级:内存,缓存,寄存器
在内存够用时,要处理的全部数据存在内存RAM(内部存储),数据流转从RAM→缓存→寄存器。内存,缓存,寄存器存取速度不同,内存最慢,寄存器最快,所以倾向尽量把数据让速度
更快的寄存器和缓存,少访问低速的内存。
又由于寄存器最快,但容量极小(几百字节),而内存容量最大(GB),读取数据时,是从内存按需+少量多次地拿局部数据。
内存不够用的情况先不赘述。
下面展示原地快速排序(内存够用)的流转过程。
原地快速排序(In-place QuickSort)
排序过程中没有辅助数组,就叫原地快速排序。
原地快速排序步骤:
选基准元素,
把数组分区成一组大的一组小的,
分别递归排好了接起来。













栈和堆在内存中什么样?
操作系统和程序运行时将内存逻辑上划分为不同区域,包括“栈(Stack)”和“堆(Heap)”和其他。
-
栈(Stack):是一块连续的内存区域,专门用于存储函数调用时的局部变量、参数、返回地址等。由编译器自动管理,规则是自动清理,后进先出(LIFO)。
- 分配/释放:函数调用时,其局部变量(如快速排序中的指针
i、j)被“压入”栈顶,图里把栈顶放下面了,注意看图步骤22,最底下是栈帧1,往上依次是栈帧2,3等。函数返回时,这些变量被自动“弹出”释放。速度极快。 - Winndows和Linux地址都从高地址FEF1向低地址FEE1增长。
- 大小:较小且固定(例如几MB),由操作系统或编译器预设。如果递归太深(如快速排序最坏情况),可能导致栈溢出(Stack Overflow)。
- 分配/释放:函数调用时,其局部变量(如快速排序中的指针
-
堆(Heap):是另一块内存区域,用于动态内存分配(如
malloc、new)。由程序员手动管理(或由垃圾回收器管理),更灵活但也更复杂:- 分配/释放:分配时在堆中找到一块足够大的空闲内存;释放时需要显式调用
free或delete。 - 地址:空间不连续,通过指针链接。
- 大小:远大于栈,通常只受系统可用物理内存和虚拟内存限制。
- 分配/释放:分配时在堆中找到一块足够大的空闲内存;释放时需要显式调用
Python 代码示例
以下是一个完整、可运行的 Python 代码示例,演示了原地快速排序(In-place Quicksort)算法。代码包含了详细的注释,并指出了关键变量(如指针 i, j)在内存中的可能位置,以呼应前文对存储层级的讨论。
def quicksort_inplace(arr, low, high):
"""
原地快速排序的主递归函数。
参数:
arr: 待排序的列表(Python list),在内存中连续存储。
low: 当前子数组的起始索引(左边界)。
high: 当前子数组的结束索引(右边界)。
"""
if low < high:
# 分区操作,返回基准元素的最终位置
pivot_index = partition(arr, low, high)
# 递归排序左半部分 (low 到 pivot_index-1)
quicksort_inplace(arr, low, pivot_index - 1)
# 递归排序右半部分 (pivot_index+1 到 high)
quicksort_inplace(arr, pivot_index + 1, high)
def partition(arr, low, high):
"""
分区函数:选取基准元素,并将数组重新排列,使得基准左侧元素都小于等于基准,右侧元素都大于基准。
返回基准元素的最终索引位置。
注意:指针变量 `i` 和 `j` 是函数的局部变量。
在理想情况下,编译器/解释器会尝试将它们存储在快速的 CPU 寄存器中。
如果寄存器不足或需要取地址,它们则会被分配在函数的栈帧(RAM 中的栈区域)中。
"""
# 选择最右侧的元素作为基准 (pivot)
pivot = arr[high]
# i 指针:指向“小于等于基准”区域的最后一个元素的下一个位置。
# 初始时,这个区域为空,所以 i 指向 low。
i = low - 1
# j 指针:遍历从 low 到 high-1 的所有元素。
for j in range(low, high):
# 如果当前元素 arr[j] 小于等于基准值
if arr[j] <= pivot:
# 扩展“小于等于基准”的区域:i 右移一位
i += 1
# 将 arr[j] 交换到该区域内
arr[i], arr[j] = arr[j], arr[i]
# 循环结束后,所有 <= pivot 的元素都在 arr[low..i] 中
# 所有 > pivot 的元素都在 arr[i+1..high-1] 中
# 将基准元素 pivot (arr[high]) 交换到正确位置 (i+1)
arr[i + 1], arr[high] = arr[high], arr[i + 1]
# 返回基准元素的最终索引
return i + 1
def print_array_with_memory_view(arr, description=""):
"""
辅助函数:打印数组及其内存地址视图(模拟)。
用于直观展示数组元素在内存中的连续存储。
"""
print(f"\n{description}")
print("索引: ", end="")
for idx in range(len(arr)):
print(f"{idx:>4}", end=" ")
print("\n值 : ", end="")
for val in arr:
print(f"{val:>4}", end=" ")
print()
# ========== 主程序:演示排序过程 ==========
if __name__ == "__main__":
# 1. 初始化一个待排序的数组
original_array = [38, 27, 43, 9, 15, 82, 52]
print("=== 原地快速排序演示 ===")
print_array_with_memory_view(original_array, "排序前数组:")
# 2. 创建数组副本用于排序(保持原数组不变以便对比)
array_to_sort = original_array.copy()
# 3. 调用原地快速排序函数
quicksort_inplace(array_to_sort, 0, len(array_to_sort) - 1)
# 4. 打印排序结果
print_array_with_memory_view(array_to_sort, "排序后数组:")
# 5. 验证排序结果
print(f"\n验证: 数组是否已排序? {array_to_sort == sorted(original_array)}")
# 6. 解释与存储层级的关联
print("\n" + "="*50)
print("【代码与存储层级的关联说明】")
print("1. 数组 `arr`: 存储在内存(RAM)的连续区域,排序过程直接在此区域交换元素,符合'原地'定义。")
print("2. 指针 `i`, `j`: 函数局部变量。")
print(" - 理想情况: 由编译器分配到 CPU 寄存器,访问延迟约 0.5 纳秒(最快)。")
print(" - 寄存器不足时: 存储在函数栈帧(RAM 中的栈区域),访问需经过缓存或直接读内存。")
print("3. 递归调用 `quicksort_inplace`: 每次调用产生一个栈帧,存储参数 low/high 和返回地址。")
print(" 递归深度 O(log n) 决定了栈空间的使用量,这也是算法空间复杂度为 O(log n) 的原因。")
print("4. 缓存友好性: 由于数组内存连续,分区操作顺序访问元素,有利于缓存行(Cache Line)加载,")
print(" 提高缓存命中率,从而提升速度。")
运行结果示例:
=== 原地快速排序演示 ===
排序前数组:
索引: 0 1 2 3 4 5 6
值 : 38 27 43 9 15 82 52
地址: 0x1000 0x1004 0x1008 0x100C 0x1010 0x1014 0x1018
排序后数组:
索引: 0 1 2 3 4 5 6
值 : 9 15 27 38 43 52 82
地址: 0x1000 0x1004 0x1008 0x100C 0x1010 0x1014 0x1018
验证: 数组是否已排序? True
==================================================
【代码与存储层级的关联说明】
1. 数组 `arr`: 存储在内存(RAM)的连续区域,排序过程直接在此区域交换元素,符合'原地'定义。因此每个基准元素对比过程中,每层空间复杂度一直是O(1),最后递归了O(logn)层,总空间复杂度为O(1)*O(logn)即O(logn)。
2. 指针 `i`, `j`: 函数局部变量。
- 理想情况: 由编译器分配到 CPU 寄存器,访问延迟约 0.5 纳秒(最快)。
- 寄存器不足时: 存储在函数栈帧(RAM 中的栈区域),访问需经过缓存或直接读内存。
3. 递归调用 `quicksort_inplace`: 每次调用产生一个栈帧,存储参数 low/high 和返回地址。
递归深度 O(log n) 决定了栈空间的使用量,这也是算法空间复杂度为 O(log n) 的原因。
4. 缓存友好性: 由于数组内存连续,分区操作顺序访问元素,有利于缓存行(Cache Line)加载,
提高缓存命中率,从而提升速度。
如何运行:
- 将上述代码复制到一个
.py文件(例如quicksort_demo.py)。 - 在终端或命令行中执行:
python quicksort_demo.py - 你将看到排序前后的数组、模拟的内存地址视图以及算法与存储层级的关联分析。
现在指针存储(寄存器/栈)、栈帧与递归、内存连续性与缓存这些概念有没有变清晰呢?
这是@fivebliss

1349

被折叠的 条评论
为什么被折叠?



