- C++
C++ 分治算法入门
- 2025-6-9 23:07:28 @
C++ 分治算法入门教程(通俗易懂,详细讲解)
🧠 什么是分治算法?
分治算法(Divide and Conquer) 是一种非常经典的算法思想。它的核心思想是:
“大事化小,小事了之。”
即将一个复杂的问题分成两个或多个相同或相似的子问题,直到最后子问题变得足够简单可以直接解决,然后将子问题的解合并起来得到原问题的解。
🔁 分治的基本步骤
- 分解(Divide):将原问题分解成若干个规模较小、结构与原问题相同的子问题。
- 解决(Conquer):递归地求解各个子问题,当子问题足够小时直接求解。
- 合并(Combine):将子问题的解合并为原问题的解。
📌 常见应用示例
- 快速排序(Quick Sort)
- 归并排序(Merge Sort)
- 二分查找(Binary Search)
- 求最大子数组和(Maximum Subarray Problem)
- 大整数乘法(Karatsuba Algorithm)
✅ 示例一:归并排序(Merge Sort)
🎯 问题描述:
给定一个无序数组,使用归并排序将其从小到大排序。
🧩 思路分析:
- 分解:将数组从中间分为左右两部分
- 解决:递归地对左右两部分排序
- 合并:将两个有序数组合并为一个有序数组
💻 C++代码实现 + 注释:
#include <iostream>
#include <vector>
using namespace std;
// 合并两个有序数组
void merge(vector<int>& arr, int left, int mid, int right) {
vector<int> temp(right - left + 1); // 创建临时数组
int i = left; // 左边数组指针
int j = mid + 1; // 右边数组指针
int k = 0; // 临时数组指针
// 合并两个数组
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
// 复制左边剩余元素
while (i <= mid) {
temp[k++] = arr[i++];
}
// 复制右边剩余元素
while (j <= right) {
temp[k++] = arr[j++];
}
// 将临时数组复制回原数组
for (int p = 0; p < temp.size(); ++p) {
arr[left + p] = temp[p];
}
}
// 归并排序主函数
void mergeSort(vector<int>& arr, int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2; // 防止溢出写法
// 分解
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 合并
merge(arr, left, mid, right);
}
// 打印数组函数
void printArray(const vector<int>& arr) {
for (int num : arr) {
cout << num << " ";
}
cout << endl;
}
int main() {
vector<int> arr = {6, 5, 3, 1, 8, 7, 2, 4};
cout << "原始数组: ";
printArray(arr);
mergeSort(arr, 0, arr.size() - 1);
cout << "排序后数组: ";
printArray(arr);
return 0;
}
📝 输出结果:
原始数组: 6 5 3 1 8 7 2 4
排序后数组: 1 2 3 4 5 6 7 8
✅ 示例二:快速排序(Quick Sort)
🎯 问题描述:
给定一个无序数组,使用快速排序将其从小到大排序。
🧩 思路分析:
- 分解:选择一个基准值(pivot),将数组划分为两部分,一部分小于等于 pivot,另一部分大于 pivot
- 解决:递归地对左右两部分排序
- 合并不需要额外操作,因为划分已经完成排序
💻 C++代码实现 + 注释:
#include <iostream>
#include <vector>
using namespace std;
// 分区函数:返回基准值最终位置
int partition(vector<int>& arr, int left, int right) {
int pivot = arr[right]; // 选最右边的元素作为基准
int i = left - 1; // i 表示小于 pivot 的区域的最后一个位置
for (int j = left; j < right; ++j) {
if (arr[j] <= pivot) {
++i;
swap(arr[i], arr[j]); // 把比 pivot 小的放到前面
}
}
swap(arr[i + 1], arr[right]); // 把 pivot 放到正确位置
return i + 1;
}
// 快速排序主函数
void quickSort(vector<int>& arr, int left, int right) {
if (left >= right) return;
int pi = partition(arr, left, right); // 分解+处理
// 分别对左右两边排序
quickSort(arr, left, pi - 1);
quickSort(arr, pi + 1, right);
}
// 打印数组函数
void printArray(const vector<int>& arr) {
for (int num : arr) {
cout << num << " ";
}
cout << endl;
}
int main() {
vector<int> arr = {6, 5, 3, 1, 8, 7, 2, 4};
cout << "原始数组: ";
printArray(arr);
quickSort(arr, 0, arr.size() - 1);
cout << "排序后数组: ";
printArray(arr);
return 0;
}
📝 输出结果:
原始数组: 6 5 3 1 8 7 2 4
排序后数组: 1 2 3 4 5 6 7 8
✅ 示例三:二分查找(Binary Search)
🎯 问题描述:
在一个有序数组中查找某个目标值是否存在。
🧩 思路分析:
- 分解:每次比较中间值,缩小查找范围
- 解决:找到目标值或者范围为空
- 合并:不需要,只需返回索引即可
💻 C++代码实现 + 注释:
#include <iostream>
#include <vector>
using namespace std;
// 二分查找函数
int binarySearch(const vector<int>& arr, int target) {
int left = 0;
int right = arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid; // 找到目标值
} else if (arr[mid] < target) {
left = mid + 1; // 查找右半部分
} else {
right = mid - 1; // 查找左半部分
}
}
return -1; // 未找到
}
int main() {
vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8};
int target = 5;
int index = binarySearch(arr, target);
if (index != -1) {
cout << "找到目标值,索引为:" << index << endl;
} else {
cout << "未找到目标值" << endl;
}
return 0;
}
📝 输出结果:
找到目标值,索引为:4
📚 总结
算法 | 时间复杂度 | 特点 |
---|---|---|
归并排序 | O(n log n) | 稳定排序,适合链表排序 |
快速排序 | 平均 O(n log n),最坏 O(n²) | 不稳定,但实际运行快 |
二分查找 | O(log n) | 仅适用于有序数组 |
📝 学习建议
- 先理解递归的概念
- 掌握分治的三大步骤:分解、解决、合并
- 多练习经典题目(如归并排序、快速排序、最大子数组等)
- 能够画出递归调用树帮助理解执行流程
如果你还想学习更多分治算法,比如:
- 求最大子数组和(Kadane's Algorithm 或 分治法)
- 求幂运算(快速幂)
- 大整数乘法(Karatsuba)
欢迎继续提问!我可以为你提供更深入的讲解和完整代码示例 😄
1 条评论
-
admin SU @ 2025-6-9 23:07:41已修改
分治算法基础概念
分治算法(Divide and Conquer)是一种重要的算法设计策略,其核心思想是将一个复杂问题分解为多个相似的子问题,递归地解决这些子问题,然后将子问题的解合并得到原问题的解。
分治算法通常遵循三个步骤:
- 分解(Divide):将原问题分解为多个规模更小、相互独立的子问题
- 解决(Conquer):递归地解决各个子问题(当子问题足够小时,直接求解)
- 合并(Combine):将子问题的解合并为原问题的解
分治算法的典型应用场景
分治算法适用于以下类型的问题:
- 问题可以分解为多个相似的子问题
- 子问题的解可以合并为原问题的解
- 子问题相互独立,不包含公共子问题(避免重复计算)
经典的分治算法应用包括:归并排序、快速排序、二分查找、大整数乘法、矩阵乘法等。
分治算法的C++实现示例
下面通过几个具体例子来学习分治算法的实现:
示例1:二分查找
二分查找是分治算法的典型应用,它通过不断将搜索区间减半来快速找到目标值。
#include <iostream> #include <vector> using namespace std; // 二分查找函数(递归实现) // arr: 有序数组 // left: 左边界索引 // right: 右边界索引 // target: 目标值 int binarySearch(const vector<int>& arr, int left, int right, int target) { // 基本情况:当搜索区间为空时,返回-1表示未找到 if (left > right) { return -1; } // 分解:计算中间索引 int mid = left + (right - left) / 2; // 防止整数溢出 // 解决:比较中间元素与目标值 if (arr[mid] == target) { return mid; // 找到目标值,返回索引 } else if (arr[mid] > target) { // 目标值在左半部分,递归搜索左半区间 return binarySearch(arr, left, mid - 1, target); } else { // 目标值在右半部分,递归搜索右半区间 return binarySearch(arr, mid + 1, right, target); } } int main() { vector<int> arr = {2, 4, 6, 8, 10, 12, 14, 16}; int target = 10; int result = binarySearch(arr, 0, arr.size() - 1, target); if (result != -1) { cout << "目标值 " << target << " 在数组中的索引是: " << result << endl; } else { cout << "目标值 " << target << " 不在数组中" << endl; } return 0; }
示例2:归并排序
归并排序是另一个经典的分治算法,它将数组分成两半,分别排序后再合并。
#include <iostream> #include <vector> using namespace std; // 合并两个已排序的子数组 void merge(vector<int>& arr, int left, int mid, int right) { int n1 = mid - left + 1; // 左子数组的大小 int n2 = right - mid; // 右子数组的大小 // 创建临时数组 vector<int> L(n1), R(n2); // 复制数据到临时数组 for (int i = 0; i < n1; i++) L[i] = arr[left + i]; for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; // 归并临时数组到原数组 int i = 0; // 左子数组的初始索引 int j = 0; // 右子数组的初始索引 int k = left; // 合并后子数组的初始索引 while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 复制左子数组的剩余元素 while (i < n1) { arr[k] = L[i]; i++; k++; } // 复制右子数组的剩余元素 while (j < n2) { arr[k] = R[j]; j++; k++; } } // 归并排序函数(递归实现) void mergeSort(vector<int>& arr, int left, int right) { // 基本情况:当子数组只有一个元素或为空时,已经有序 if (left < right) { // 分解:计算中间点 int mid = left + (right - left) / 2; // 解决:递归排序左右两部分 mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); // 合并:合并已排序的两部分 merge(arr, left, mid, right); } } int main() { vector<int> arr = {12, 11, 13, 5, 6, 7}; cout << "原数组: "; for (int num : arr) { cout << num << " "; } cout << endl; // 执行归并排序 mergeSort(arr, 0, arr.size() - 1); cout << "排序后的数组: "; for (int num : arr) { cout << num << " "; } cout << endl; return 0; }
分治算法的复杂度分析
分治算法的时间复杂度通常可以用递归方程来分析,常见的情况有:
- 二分查找:每次将问题规模减半,时间复杂度为O(log n)
- 归并排序:每次将数组分成两半,递归处理,时间复杂度为O(n log n)
- 快速排序:平均情况下时间复杂度为O(n log n),最坏情况下为O(n²)
分治算法的空间复杂度主要取决于递归调用栈的深度,通常为O(log n),但在某些情况下可能更高。
分治算法的优缺点
优点:
- 结构清晰,易于理解和实现
- 利用递归解决复杂问题,代码简洁
- 适合并行计算,子问题可独立处理
- 时间效率较高,许多分治算法的时间复杂度为O(n log n)
缺点:
- 递归调用会增加空间开销
- 递归深度过大会导致栈溢出
- 某些问题的子问题可能存在重叠(需用动态规划优化)
通过掌握分治算法的基本思想和实现方法,你可以解决许多复杂的计算问题。建议多练习相关题目,加深对分治策略的理解和应用能力。
- 1