05-分治算法
💡 核心结论
分治本质
定义:分解→递归解决→合并结果
三步骤:Divide(分解)→ Conquer(解决)→ Combine(合并)
关键:子问题独立、可递归、能合并
时间:T(n) = aT(n/b) + f(n),主定理分析
应用:归并排序、快排、二分查找、大整数乘法
分治 vs 其他
策略 |
特点 |
典型算法 |
|---|---|---|
分治 |
子问题独立 |
归并、快排 |
动态规划 |
子问题重叠 |
斐波那契、背包 |
贪心 |
局部最优 |
Dijkstra、哈夫曼 |
经典分治问题
排序:归并O(n log n)、快排O(n log n)
搜索:二分O(log n)
矩阵:Strassen矩阵乘法O(n^2.81)
大整数:Karatsuba乘法O(n^1.58)
几何:最近点对O(n log 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)
🔍 快速选择(第K大)
def quick_select(nums, k):
"""平均O(n)找第k大元素"""
if not nums:
return None
pivot = nums[len(nums) // 2]
left = [x for x in nums if x > pivot]
mid = [x for x in nums if x == pivot]
right = [x for x in nums if x < pivot]
if k <= len(left):
return quick_select(left, k)
elif k <= len(left) + len(mid):
return mid[0]
else:
return quick_select(right, k - len(left) - len(mid))
📚 LeetCode练习
💻 完整代码实现
Python 实现
1"""
2分治算法实现
3"""
4
5# ========== 快速选择(第K大元素) ==========
6
7def quick_select(nums, k):
8 """找第k大元素(平均O(n))"""
9 if not nums:
10 return None
11
12 pivot = nums[len(nums) // 2]
13 left = [x for x in nums if x > pivot]
14 mid = [x for x in nums if x == pivot]
15 right = [x for x in nums if x < pivot]
16
17 if k <= len(left):
18 return quick_select(left, k)
19 elif k <= len(left) + len(mid):
20 return mid[0]
21 else:
22 return quick_select(right, k - len(left) - len(mid))
23
24
25# ========== 归并排序 ==========
26
27def merge_sort(arr):
28 """归并排序"""
29 if len(arr) <= 1:
30 return arr
31
32 mid = len(arr) // 2
33 left = merge_sort(arr[:mid])
34 right = merge_sort(arr[mid:])
35
36 return merge(left, right)
37
38
39def merge(left, right):
40 """合并两个有序数组"""
41 result = []
42 i = j = 0
43
44 while i < len(left) and j < len(right):
45 if left[i] <= right[j]:
46 result.append(left[i])
47 i += 1
48 else:
49 result.append(right[j])
50 j += 1
51
52 result.extend(left[i:])
53 result.extend(right[j:])
54 return result
55
56
57# ========== 计算逆序对 ==========
58
59def merge_count(arr):
60 """归并排序同时计算逆序对"""
61 if len(arr) <= 1:
62 return arr, 0
63
64 mid = len(arr) // 2
65 left, left_count = merge_count(arr[:mid])
66 right, right_count = merge_count(arr[mid:])
67
68 merged, merge_count_val = merge_and_count(left, right)
69
70 return merged, left_count + right_count + merge_count_val
71
72
73def merge_and_count(left, right):
74 """合并并计算跨越两部分的逆序对"""
75 result = []
76 count = 0
77 i = j = 0
78
79 while i < len(left) and j < len(right):
80 if left[i] <= right[j]:
81 result.append(left[i])
82 i += 1
83 else:
84 result.append(right[j])
85 count += len(left) - i # 逆序对数
86 j += 1
87
88 result.extend(left[i:])
89 result.extend(right[j:])
90
91 return result, count
92
93
94def count_inversions(arr):
95 """计算逆序对数量"""
96 _, count = merge_count(arr)
97 return count
98
99
100# ========== 最大子数组和(分治) ==========
101
102def max_subarray_divide_conquer(nums, left, right):
103 """分治法求最大子数组和"""
104 if left == right:
105 return nums[left]
106
107 mid = (left + right) // 2
108
109 # 左半部分最大
110 left_max = max_subarray_divide_conquer(nums, left, mid)
111 # 右半部分最大
112 right_max = max_subarray_divide_conquer(nums, mid + 1, right)
113 # 跨中点最大
114 cross_max = max_crossing_sum(nums, left, mid, right)
115
116 return max(left_max, right_max, cross_max)
117
118
119def max_crossing_sum(nums, left, mid, right):
120 """计算跨越中点的最大子数组和"""
121 # 左边最大
122 left_sum = float('-inf')
123 curr_sum = 0
124 for i in range(mid, left - 1, -1):
125 curr_sum += nums[i]
126 left_sum = max(left_sum, curr_sum)
127
128 # 右边最大
129 right_sum = float('-inf')
130 curr_sum = 0
131 for i in range(mid + 1, right + 1):
132 curr_sum += nums[i]
133 right_sum = max(right_sum, curr_sum)
134
135 return left_sum + right_sum
136
137
138def demo():
139 """演示分治算法"""
140 print("=== 分治算法演示 ===\n")
141
142 # 快速选择
143 nums = [3, 2, 1, 5, 6, 4]
144 k = 2
145 print(f"第{k}大元素 {nums}:")
146 print(f" 结果: {quick_select(nums, k)}\n")
147
148 # 归并排序
149 arr = [5, 2, 8, 1, 9, 3]
150 print(f"归并排序 {arr}:")
151 print(f" 结果: {merge_sort(arr)}\n")
152
153 # 逆序对
154 arr = [8, 4, 2, 1]
155 print(f"逆序对 {arr}:")
156 print(f" 数量: {count_inversions(arr)}\n")
157
158 # 最大子数组和(分治)
159 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
160 print(f"最大子数组和 {nums}:")
161 result = max_subarray_divide_conquer(nums, 0, len(nums) - 1)
162 print(f" 结果: {result}")
163
164
165if __name__ == '__main__':
166 demo()
167
C++ 实现
1/**
2 * 分治算法实现
3 */
4
5#include <iostream>
6#include <vector>
7#include <algorithm>
8#include <climits>
9
10using namespace std;
11
12// 归并排序
13void merge(vector<int>& arr, int left, int mid, int right) {
14 vector<int> L(arr.begin() + left, arr.begin() + mid + 1);
15 vector<int> R(arr.begin() + mid + 1, arr.begin() + right + 1);
16
17 int i = 0, j = 0, k = left;
18
19 while (i < L.size() && j < R.size()) {
20 if (L[i] <= R[j]) {
21 arr[k++] = L[i++];
22 } else {
23 arr[k++] = R[j++];
24 }
25 }
26
27 while (i < L.size()) arr[k++] = L[i++];
28 while (j < R.size()) arr[k++] = R[j++];
29}
30
31void mergeSortHelper(vector<int>& arr, int left, int right) {
32 if (left < right) {
33 int mid = left + (right - left) / 2;
34 mergeSortHelper(arr, left, mid);
35 mergeSortHelper(arr, mid + 1, right);
36 merge(arr, left, mid, right);
37 }
38}
39
40vector<int> mergeSort(vector<int> arr) {
41 mergeSortHelper(arr, 0, arr.size() - 1);
42 return arr;
43}
44
45// 快速选择(第K大元素)
46int partition(vector<int>& nums, int left, int right) {
47 int pivot = nums[right];
48 int i = left;
49
50 for (int j = left; j < right; j++) {
51 if (nums[j] >= pivot) { // 降序,找第k大
52 swap(nums[i], nums[j]);
53 i++;
54 }
55 }
56 swap(nums[i], nums[right]);
57 return i;
58}
59
60int quickSelect(vector<int>& nums, int left, int right, int k) {
61 if (left == right) return nums[left];
62
63 int pivotIndex = partition(nums, left, right);
64
65 if (pivotIndex == k) {
66 return nums[k];
67 } else if (pivotIndex < k) {
68 return quickSelect(nums, pivotIndex + 1, right, k);
69 } else {
70 return quickSelect(nums, left, pivotIndex - 1, k);
71 }
72}
73
74int findKthLargest(vector<int> nums, int k) {
75 return quickSelect(nums, 0, nums.size() - 1, k - 1);
76}
77
78// 最大子数组和(分治)
79int maxCrossingSum(const vector<int>& nums, int left, int mid, int right) {
80 int leftSum = INT_MIN;
81 int sum = 0;
82 for (int i = mid; i >= left; i--) {
83 sum += nums[i];
84 leftSum = max(leftSum, sum);
85 }
86
87 int rightSum = INT_MIN;
88 sum = 0;
89 for (int i = mid + 1; i <= right; i++) {
90 sum += nums[i];
91 rightSum = max(rightSum, sum);
92 }
93
94 return leftSum + rightSum;
95}
96
97int maxSubArrayDC(const vector<int>& nums, int left, int right) {
98 if (left == right) {
99 return nums[left];
100 }
101
102 int mid = (left + right) / 2;
103
104 int leftMax = maxSubArrayDC(nums, left, mid);
105 int rightMax = maxSubArrayDC(nums, mid + 1, right);
106 int crossMax = maxCrossingSum(nums, left, mid, right);
107
108 return max({leftMax, rightMax, crossMax});
109}
110
111int maxSubArray(const vector<int>& nums) {
112 return maxSubArrayDC(nums, 0, nums.size() - 1);
113}
114
115// 计算逆序对
116long long mergeAndCount(vector<int>& arr, int left, int mid, int right) {
117 vector<int> L(arr.begin() + left, arr.begin() + mid + 1);
118 vector<int> R(arr.begin() + mid + 1, arr.begin() + right + 1);
119
120 int i = 0, j = 0, k = left;
121 long long invCount = 0;
122
123 while (i < L.size() && j < R.size()) {
124 if (L[i] <= R[j]) {
125 arr[k++] = L[i++];
126 } else {
127 arr[k++] = R[j++];
128 invCount += L.size() - i; // 逆序对数
129 }
130 }
131
132 while (i < L.size()) arr[k++] = L[i++];
133 while (j < R.size()) arr[k++] = R[j++];
134
135 return invCount;
136}
137
138long long mergeSortCount(vector<int>& arr, int left, int right) {
139 long long invCount = 0;
140
141 if (left < right) {
142 int mid = left + (right - left) / 2;
143 invCount += mergeSortCount(arr, left, mid);
144 invCount += mergeSortCount(arr, mid + 1, right);
145 invCount += mergeAndCount(arr, left, mid, right);
146 }
147
148 return invCount;
149}
150
151long long countInversions(vector<int> arr) {
152 return mergeSortCount(arr, 0, arr.size() - 1);
153}
154
155int main() {
156 cout << "=== 分治算法演示 ===" << endl << endl;
157
158 // 快速选择
159 vector<int> nums = {3, 2, 1, 5, 6, 4};
160 int k = 2;
161 cout << "第" << k << "大元素 [3,2,1,5,6,4]:" << endl;
162 cout << " 结果: " << findKthLargest(nums, k) << endl << endl;
163
164 // 归并排序
165 vector<int> arr = {5, 2, 8, 1, 9, 3};
166 cout << "归并排序 [5,2,8,1,9,3]:" << endl;
167 vector<int> sorted = mergeSort(arr);
168 cout << " 结果: [";
169 for (size_t i = 0; i < sorted.size(); i++) {
170 cout << sorted[i];
171 if (i < sorted.size() - 1) cout << ", ";
172 }
173 cout << "]" << endl << endl;
174
175 // 逆序对
176 vector<int> arr2 = {8, 4, 2, 1};
177 cout << "逆序对 [8,4,2,1]:" << endl;
178 cout << " 数量: " << countInversions(arr2) << endl << endl;
179
180 // 最大子数组和
181 vector<int> nums2 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
182 cout << "最大子数组和 [-2,1,-3,4,-1,2,1,-5,4]:" << endl;
183 cout << " 结果: " << maxSubArray(nums2) << endl;
184
185 return 0;
186}