分治算法
分治算法通过将问题分解为更小的子问题来解决,是算法设计的重要思想。在竞赛编程中,分治算法常用于解决排序、搜索、数值计算等问题,具有优美的递归结构和高效的时间复杂度。
归并排序 - 分治算法的经典应用
归并排序完美体现了分治思想:将数组分为两半,递归排序每一半,然后合并已排序的部分。
归并排序实现
#include <iostream>
#include <vector>
using namespace std;
class MergeSort {
public:
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);
}
private:
void merge(vector<int>& arr, int left, int mid, int right) {
vector<int> temp(right - left + 1);
int i = left, j = mid + 1, 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 i = 0; i < k; i++) {
arr[left + i] = temp[i];
}
}
};
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90, 88, 76, 50, 42};
cout << "原数组: ";
for (int x : arr) cout << x << " ";
cout << endl;
MergeSort ms;
ms.mergeSort(arr, 0, arr.size() - 1);
cout << "排序后: ";
for (int x : arr) cout << x << " ";
cout << endl;
return 0;
}归并排序的时间复杂度为O(n log n),空间复杂度O(n)。它是稳定排序算法,在链表排序中特别有用。
快速排序与快速选择
快速排序通过选择基准元素将数组分为两部分,每部分独立排序。快速选择算法可以高效找到第k小的元素。
快速排序与快速选择
#include <iostream>
#include <vector>
#include <random>
using namespace std;
class QuickSort {
public:
void quickSort(vector<int>& arr, int left, int right) {
if (left >= right) return;
// 随机化选择基准,避免最坏情况
int randomIndex = left + rand() % (right - left + 1);
swap(arr[left], arr[randomIndex]);
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
// 快速选择 - 找第k小元素
int quickSelect(vector<int>& arr, int left, int right, int k) {
if (left == right) return arr[left];
int randomIndex = left + rand() % (right - left + 1);
swap(arr[left], arr[randomIndex]);
int pivotIndex = partition(arr, left, right);
if (pivotIndex == k) return arr[pivotIndex];
else if (pivotIndex > k) return quickSelect(arr, left, pivotIndex - 1, k);
else return quickSelect(arr, pivotIndex + 1, right, k);
}
private:
int partition(vector<int>& arr, int left, int right) {
int pivot = arr[left];
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) j--;
if (i < j) arr[i++] = arr[j];
while (i < j && arr[i] <= pivot) i++;
if (i < j) arr[j--] = arr[i];
}
arr[i] = pivot;
return i;
}
};
int main() {
srand(time(nullptr));
vector<int> arr = {64, 34, 25, 12, 22, 11, 90, 88, 76, 50, 42};
cout << "原数组: ";
for (int x : arr) cout << x << " ";
cout << endl;
QuickSort qs;
vector<int> sortArr = arr;
qs.quickSort(sortArr, 0, sortArr.size() - 1);
cout << "排序后: ";
for (int x : sortArr) cout << x << " ";
cout << endl;
// 测试快速选择
int k = 5; // 第6小的元素(0-indexed)
int kthSmallest = qs.quickSelect(arr, 0, arr.size() - 1, k);
cout << "第" << (k + 1) << "小的元素: " << kthSmallest << endl;
return 0;
}快速排序平均时间复杂度O(n log n),最坏情况O(n²)。快速选择算法可以在O(n)平均时间内找到第K小元素。
最大子数组问题
最大子数组问题可以用分治算法优雅地解决,时间复杂度为O(n log n)。
最大子数组 - 分治解法
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class MaxSubarray {
public:
struct Result {
int maxSum;
int left;
int right;
Result(int sum = INT_MIN, int l = -1, int r = -1)
: maxSum(sum), left(l), right(r) {}
};
Result maxSubarrayDC(vector<int>& arr, int left, int right) {
// 基础情况:单个元素
if (left == right) {
return Result(arr[left], left, right);
}
int mid = left + (right - left) / 2;
// 递归找左右两半的最大子数组
Result leftResult = maxSubarrayDC(arr, left, mid);
Result rightResult = maxSubarrayDC(arr, mid + 1, right);
// 找跨越中点的最大子数组
Result crossResult = maxCrossingSubarray(arr, left, mid, right);
// 返回三种情况中的最大值
if (leftResult.maxSum >= rightResult.maxSum &&
leftResult.maxSum >= crossResult.maxSum) {
return leftResult;
} else if (rightResult.maxSum >= leftResult.maxSum &&
rightResult.maxSum >= crossResult.maxSum) {
return rightResult;
} else {
return crossResult;
}
}
private:
Result maxCrossingSubarray(vector<int>& arr, int left, int mid, int right) {
// 从中点向左扩展的最大和
int leftSum = INT_MIN;
int sum = 0;
int maxLeft = mid;
for (int i = mid; i >= left; i--) {
sum += arr[i];
if (sum > leftSum) {
leftSum = sum;
maxLeft = i;
}
}
// 从中点+1向右扩展的最大和
int rightSum = INT_MIN;
sum = 0;
int maxRight = mid + 1;
for (int i = mid + 1; i <= right; i++) {
sum += arr[i];
if (sum > rightSum) {
rightSum = sum;
maxRight = i;
}
}
return Result(leftSum + rightSum, maxLeft, maxRight);
}
};
// Kadane算法对比(O(n))
int kadane(vector<int>& arr) {
int maxSoFar = arr[0];
int maxEndingHere = arr[0];
for (int i = 1; i < arr.size(); i++) {
maxEndingHere = max(arr[i], maxEndingHere + arr[i]);
maxSoFar = max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
int main() {
vector<int> arr = {-2, -3, 4, -1, -2, 1, 5, -3};
cout << "数组: ";
for (int x : arr) cout << x << " ";
cout << endl;
MaxSubarray ms;
MaxSubarray::Result result = ms.maxSubarrayDC(arr, 0, arr.size() - 1);
cout << "分治算法 - 最大和: " << result.maxSum << endl;
cout << "子数组索引: [" << result.left << ", " << result.right << "]" << endl;
cout << "子数组: ";
for (int i = result.left; i <= result.right; i++) {
cout << arr[i] << " ";
}
cout << endl;
cout << "Kadane算法: " << kadane(arr) << endl;
return 0;
}分治方法考虑三种情况:最大子数组在左半部分、右半部分或跨越中点。时间复杂度为O(n log n)。
最近点对问题
使用分治算法在二维平面中找最近点对,时间复杂度O(n log n),优于暴力方法的O(n²)。
最近点对问题
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <float.h>
using namespace std;
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
double distanceTo(const Point& other) const {
return sqrt((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y));
}
};
class ClosestPair {
public:
double findClosestPair(vector<Point>& points) {
// 按x坐标排序
sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
return a.x < b.x;
});
return closestPairRec(points, 0, points.size() - 1);
}
private:
double closestPairRec(vector<Point>& points, int left, int right) {
int n = right - left + 1;
// 基础情况:小数组用暴力方法
if (n <= 3) {
return bruteForce(points, left, right);
}
int mid = left + (right - left) / 2;
Point midPoint = points[mid];
// 递归找左右两半的最近点对
double leftMin = closestPairRec(points, left, mid);
double rightMin = closestPairRec(points, mid + 1, right);
double minDist = min(leftMin, rightMin);
// 找接近分割线的点
vector<Point> strip;
for (int i = left; i <= right; i++) {
if (abs(points[i].x - midPoint.x) < minDist) {
strip.push_back(points[i]);
}
}
// 按y坐标排序条带
sort(strip.begin(), strip.end(), [](const Point& a, const Point& b) {
return a.y < b.y;
});
// 在条带中找最近点对
return min(minDist, stripClosest(strip, minDist));
}
double bruteForce(vector<Point>& points, int left, int right) {
double minDist = DBL_MAX;
for (int i = left; i <= right; i++) {
for (int j = i + 1; j <= right; j++) {
minDist = min(minDist, points[i].distanceTo(points[j]));
}
}
return minDist;
}
double stripClosest(vector<Point>& strip, double minDist) {
double min_val = minDist;
for (int i = 0; i < strip.size(); i++) {
// 最多检查7个点(几何证明)
for (int j = i + 1; j < strip.size() &&
(strip[j].y - strip[i].y) < min_val; j++) {
min_val = min(min_val, strip[i].distanceTo(strip[j]));
}
}
return min_val;
}
};
int main() {
vector<Point> points = {
Point(2, 3), Point(12, 30), Point(40, 50), Point(5, 1),
Point(12, 10), Point(3, 4), Point(1, 1), Point(8, 7)
};
cout << "点集:" << endl;
for (int i = 0; i < points.size(); i++) {
cout << "(" << points[i].x << ", " << points[i].y << ")" << endl;
}
ClosestPair cp;
double closestDistance = cp.findClosestPair(points);
cout << "最近点对距离: " << closestDistance << endl;
return 0;
}算法按x坐标分割点集,在每一半中找最近点对,然后检查分割线附近的点。关键洞察是在条带中最多只需检查7个点。
关键要点
- 分治算法将问题分解为更小的子问题,递归求解,然后合并结果
- 时间复杂度通常遵循递推关系T(n) = aT(n/b) + f(n),使用主定理分析
- 常见应用:排序算法、最近点对、最大子数组、快速乘法
- 通常提供具有优雅递归结构的最优解