第一章 人工智能基础
第三部分:算法分析与设计
第二节:分治法
内容:分治策略、归并排序和快速排序的实现与分析
一、什么是分治法(Divide and Conquer)?
分治法是一种经典的算法设计思想,其基本思路是:
将一个复杂问题分成若干个规模较小的子问题,分别求解,然后将子问题的解合并得到原问题的解。
分治法通用结构
┌────────────────────────────┐
│ 原问题(Problem) │
└────────────────────────────┘
│
▼
┌───────────────────────┐
│ 分解为子问题(Divide) │
└───────────────────────┘
│
▼
┌───────────────────────────┐
│ 递归地解决子问题(Conquer) │
└───────────────────────────┘
│
▼
┌─────────────────────────┐
│ 合并子问题结果(Combine) │
└─────────────────────────┘
│
▼
┌──────────────────────────┐
│ 原问题的最终解(Solution) │
└──────────────────────────┘
二、分治法的三个步骤
-
分解(Divide):将原问题分解为若干个子问题。
-
解决(Conquer):递归地解决这些子问题。
-
合并(Combine):将子问题的结果合并,得到原问题的解。
三、典型算法示例
1. 归并排序(Merge Sort)
-
原理:递归将数组分为两半,分别排序后合并有序数组。
-
时间复杂度:始终为 O(n log n)。
-
空间复杂度:O(n),因为需要额外数组用于合并。
示例代码:
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
merged = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i])
i += 1
else:
merged.append(right[j])
j += 1
merged.extend(left[i:])
merged.extend(right[j:])
return merged
2. 快速排序(Quick Sort)
-
原理:选择一个基准值(pivot),将数组划分为小于/大于基准值的子数组,再递归排序。
-
平均时间复杂度:O(n log n)
-
最坏时间复杂度:O(n²)(当数组已排序时)
-
空间复杂度:O(log n)(递归栈空间)
示例代码:
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[0]
less = [x for x in arr[1:] if x <= pivot]
greater = [x for x in arr[1:] if x > pivot]
return quick_sort(less) + [pivot] + quick_sort(greater)
四、归并排序 vs 快速排序
特性 | 归并排序 | 快速排序 |
---|---|---|
思路 | 分→治→合 | 分→治(无需合) |
时间复杂度 | O(n log n) | 平均 O(n log n),最坏 O(n²) |
空间复杂度 | O(n)(额外数组) | O(log n)(递归栈) |
是否原地排序 | 否 | 是(原地划分) |
稳定性 | 稳定 | 不稳定 |
五、总结
-
分治法是一种非常常用的思想,适用于很多递归问题。
-
核心是把问题变成子问题来解决,再合并。
-
归并排序和快速排序都是优秀的典范:
-
归并排序适合大数据和链表;
-
快速排序适合内存中的大数组,且实现简洁。
-