1/快速排序(quick_sort)
关键词: 基准pivot(首,中间,尾都可以), left, middle,right, 递归
分治思想, 通过递归把问题分解成更小的子问题, 然后合并子问题的解即可.
最后返回,return quick_sort(left)+middle+quick_sort(right)
以下是使用Python实现的快速排序代码:
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2] # 选择中间元素作为基准值
left = [x for x in arr if x < pivot] # 小于基准值的元素
middle = [x for x in arr if x == pivot] # 等于基准值的元素
right = [x for x in arr if x > pivot] # 大于基准值的元素
return quick_sort(left) + middle + quick_sort(right) # 递归合并结果
# 测试示例
if __name__ == "__main__":
test_array = [3, 6, 8, 10, 1, 2, 1]
print("原始数组:", test_array)
sorted_array = quick_sort(test_array)
print("排序结果:", sorted_array)
代码说明:
- 基准选择:选择数组中间元素作为基准值(也可选择首/尾元素)
- 分区过程:
left:所有小于基准值的元素middle:所有等于基准值的元素right:所有大于基准值的元素
- 递归排序:对左右子数组递归调用快速排序
- 合并结果:返回
左子数组 + 基准值 + 右子数组
时间复杂度:
- 平均情况:O(n log n)
- 最坏情况:O(n²)(当数组已排序且选择首/尾元素作为基准时)
- 空间复杂度:O(n)(因创建新列表,需要额外存储空间)
平均时间复杂度的推导过程
- 递归深度:在平均情况下,每次划分能够将数组大致均分为两部分。因此,递归树的深度约为log₂n层。
- 每层操作次数:每层递归需要进行n次比较和交换操作。因此,每层的操作次数为O(n)。
- 总时间复杂度:总时间复杂度为n乘以递归层数,即n × log n = O(n log n)。
测试输出:
原始数组: [3, 6, 8, 10, 1, 2, 1]
排序结果: [1, 1, 2, 3, 6, 8, 10]
2/插入排序(insert_sort)
关键词: 整理扑克牌, 从小到大排序
从第二张牌开始, 检查其大小, 是否可以插在前面的牌中.这就是插入排序.
原理说明
插入排序是一种简单直观的排序算法,其工作原理类似于整理扑克牌:
- 将数组分为已排序和未排序两部分:初始时已排序部分只包含第一个元素
- 逐个处理未排序元素:从未排序部分取出第一个元素
- 在已排序部分找到合适位置插入:将取出的元素与已排序部分的元素从后向前比较,找到合适的位置插入
- 重复直到完成:重复上述过程直到所有元素都被处理
算法特点
- 时间复杂度:
- 最佳情况(已排序):O(n)
- 平均情况:O(n²)
- 最坏情况(逆序):O(n²)
- 空间复杂度:O(1)(原地排序)
- 稳定性:稳定排序(相同元素相对位置不变)
- 适用场景:小规模数据或基本有序的数据集
Python 实现代码
def insertion_sort(arr):
"""
插入排序实现
:param arr: 待排序的列表
:return: 原地排序(不返回新列表)
"""
# 就像整理扑克牌一样, 从第二张牌开始整理, 即index=1
# 从第二个元素开始(索引1),直到最后一个元素
for i in range(1, len(arr)):
key = arr[i] # 当前要插入的元素
j = i - 1 # 已排序部分的最后一个元素的索引
# 在已排序部分中寻找插入位置(从后向前扫描)
# 把key放在已排序部分中最合适的位置上
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j] # 元素后移
j -= 1
# 把要插入的元素放在合适的位置
arr[j + 1] = key
# 测试示例
if __name__ == "__main__":
# 测试数据
test_cases = [
[5, 2, 4, 6, 1, 3],
[3, 1, 4, 1, 5, 9, 2, 6],
[10, 9, 8, 7, 6, 5],
[1],
[] # 空数组测试
]
for i, arr in enumerate(test_cases):
print(f"测试用例 {i+1}:")
print("排序前:", arr)
insertion_sort(arr)
print("排序后:", arr)
print("-" * 30)
代码执行过程示例(以 [5, 2, 4, 6, 1, 3] 为例)
初始: [5, 2, 4, 6, 1, 3]
步骤1: [2, 5, 4, 6, 1, 3] (插入2)
步骤2: [2, 4, 5, 6, 1, 3] (插入4)
步骤3: [2, 4, 5, 6, 1, 3] (插入6,位置不变)
步骤4: [1, 2, 4, 5, 6, 3] (插入1)
步骤5: [1, 2, 3, 4, 5, 6] (插入3)
输出结果
测试用例 1:
排序前: [5, 2, 4, 6, 1, 3]
排序后: [1, 2, 3, 4, 5, 6]
------------------------------
测试用例 2:
排序前: [3, 1, 4, 1, 5, 9, 2, 6]
排序后: [1, 1, 2, 3, 4, 5, 6, 9]
------------------------------
测试用例 3:
排序前: [10, 9, 8, 7, 6, 5]
排序后: [5, 6, 7, 8, 9, 10]
------------------------------
测试用例 4:
排序前: [1]
排序后: [1]
------------------------------
测试用例 5:
排序前: []
排序后: []
------------------------------
插入排序在小规模数据或基本有序的数据集上表现良好,是许多高级排序算法(如Timsort)的组成部分。
3/冒泡排序(Bubble Sort)
关键词: 2个for循环
原理:
冒泡排序是一种简单的排序算法,通过重复遍历待排序列表,比较相邻元素并交换顺序错误的元素(升序排列时将较大元素后移)。每轮遍历会将当前未排序部分的最大元素“冒泡”到正确位置。遍历持续进行,直到所有元素有序。
核心步骤:
- 从第一个元素开始,比较相邻的两个元素
- 如果顺序错误(前 > 后),交换它们
- 对每一对相邻元素重复上述操作,直到列表末尾
- 每轮遍历后,最大的元素会沉到末尾(因此下一轮无需比较已排序部分)
- 重复直到没有元素需要交换(列表已排序)
时间复杂度:
- 最优:O(n)(当列表已有序时)
- 平均:O(n²)
- 最差:O(n²)
空间复杂度:O(1)(原地排序)
Python 实现
def bubble_sort(arr):
n = len(arr)
# 遍历所有元素
for i in range(n):
swapped = False # 优化:标记本轮是否有交换
# 最后i个元素已有序,无需比较
for j in range(0, n - i - 1):
# 如果前一个元素大于后一个,则交换
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j] # 交换
swapped = True
# 如果本轮无交换,说明已完全有序,提前终止
if not swapped:
break
return arr
# 测试示例
if __name__ == "__main__":
data = [64, 34, 25, 12, 22, 11, 90]
print("排序前:", data)
bubble_sort(data)
print("排序后:", data)
输出:
排序前: [64, 34, 25, 12, 22, 11, 90]
排序后: [11, 12, 22, 25, 34, 64, 90]
关键优化:
swapped标志位:若某轮遍历未发生交换,说明列表已有序,提前终止排序,减少不必要的比较。
可视化过程(以 [5, 1, 4, 2] 为例):
初始: [5, 1, 4, 2]
第一轮:
比较 5>1 → 交换:[1, 5, 4, 2]
比较 5>4 → 交换:[1, 4, 5, 2]
比较 5>2 → 交换:[1, 4, 2, 5] # 5沉底
第二轮:
比较 1<4 → 不交换
比较 4>2 → 交换:[1, 2, 4, 5] # 4沉底
第三轮:
无交换 → 排序完成
4/归并排序(Merge Sort)
什么是归并排序?
归并排序是一种高效的分治算法(Divide and Conquer),它将一个大问题分解为多个小问题, 然后解决小问题,再将结果合并。
核心操作是合并两个有序序列,时间复杂度稳定为 O(n log n),是稳定排序算法。
核心原理(三步走)
-
分(Divide)
将数组递归地对半拆分,直到每个子数组只剩1个元素(天然有序) -
治(Conquer)
递归排序子数组(当数组长度为1时自动完成排序) -
合(Merge)
将两个已排序的子数组合并成一个有序数组:- 比较两个子数组的头部元素
- 取较小值放入结果数组
- 重复直到所有元素合并完成
直观理解
想象整理一副扑克牌:
- 把整副牌分成两半 → 再分半 → 直到每堆只剩1张牌(分)
- 两两比较相邻的牌堆,按顺序合并(合)
- 重复合并过程直到所有牌合并成有序的一副牌
Python 代码实现
def merge_sort(arr):
# 递归终止条件:数组长度≤1时已有序
if len(arr) <= 1:
return arr
# 分:将数组对半拆分
mid = len(arr) // 2
left_arr = merge_sort(arr[:mid]) # 递归排序左半部
right_arr = merge_sort(arr[mid:]) # 递归排序右半部
# 合:合并两个有序数组
return merge(left_arr, right_arr)
def merge(left, right):
result = [] # 存储合并结果
i = j = 0 # 双指针遍历两个数组
# 比较两个数组的头部元素
while i < len(left) and j < len(right):
if left[i] <= right[j]: # 稳定排序的关键:相等时取左元素
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# 将剩余元素直接追加(因已有序)
result.extend(left[i:])
result.extend(right[j:])
return result
# 测试示例
if __name__ == "__main__":
arr = [3, 7, 6, 1, 9, 5, 4, 2]
print("原始数组:", arr)
sorted_arr = merge_sort(arr)
print("排序结果:", sorted_arr)
🚀 输出示例:
原始数组: [3, 7, 6, 1, 9, 5, 4, 2]
排序结果: [1, 2, 3, 4, 5, 6, 7, 9]
关键特性
| 特性 | 说明 |
|---|---|
| 时间复杂度 | 始终 O(n log n) - 高效稳定 |
| 空间复杂度 | O(n) - 需要额外存储空间 |
| 稳定性 | ✅ 是(相等元素顺序不变) |
| 适用场景 | 大数据量、链表排序、外部排序 |
🌟 学习要点
- 分治思想:大问题 → 小问题 → 合并解
- 双指针技巧:合并时的核心操作
- 递归实现:简洁但需注意栈深度限制(大数据量可改迭代实现)
归并排序是理解分治算法的经典案例,也是许多高效排序算法(如TimSort)的基础组件。