跳转至

9、分治算法详解

分治算法(Divide and Conquer) 是一种解决问题的策略,其核心思想是将一个大问题分解为几个小问题,然后独立解决每个小问题,最后将这些小问题的解组合起来得到大问题的解。分治法是一个递归过程,通常由三个步骤组成:

  1. 分解(Divide):将原问题分解成一系列子问题;
  2. 解决(Conquer):递归地解决每个子问题。如果子问题足够小,则直接求解;
  3. 组合(Combine):将子问题的结果合并成原问题的解。

​ 这个策略的主要优点是它可以简化问题的复杂性,通过解决更小的、更容易处理的子问题来解决大问题。

以下是分治算法的一些典型应用:

归并排序(Merge Sort)

​ 归并排序是一个典型的分治算法。首先,将数组分为两个等长的子数组;然后,递归地对每个子数组进行排序;最后,将两个排序后的子数组合并成一个有序数组。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#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, j = mid + 1, k = 0;

    // 合并两个子数组
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[k] = arr[i];
            i++;
        } else {
            temp[k] = arr[j];
            j++;
        }
        k++;
    }

    // 处理剩余的元素
    while (i <= mid) {
        temp[k] = arr[i];
        i++;
        k++;
    }
    while (j <= right) {
        temp[k] = arr[j];
        j++;
        k++;
    }

    // 将排序好的元素拷贝回原数组
    for (i = left, k = 0; i <= right; i++, k++) {
        arr[i] = temp[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 = {9, 4, 3, 7, 5, 6, 2, 1, 8};
    mergeSort(arr, 0, arr.size() - 1);  // 对数组arr进行归并排序

    cout << "归并排序结果:";
    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";  // 输出排序后的数组
    }
    return 0;
}

​ 该代码定义了merge函数进行归并操作,mergeSort函数进行递归归并排序。在main函数中调用mergeSort对数组进行归并排序,然后输出排序后的结果。

快速排序(Quick Sort)

​ 快速排序也是分治算法的一个应用。首先,选择一个元素作为"基准",将数组分为两个子数组:一个包含所有小于基准的元素,另一个包含所有大于或等于基准的元素;然后,递归地对这两个子数组进行快速排序。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <vector>
using namespace std;

// 快速排序中的分区函数
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];    // 取最后一个元素作为基准
    int i = (low - 1);  // 索引小于i的元素都小于基准

    for (int j = low; j <= high - 1; j++) {
        // 如果当前元素小于或等于基准
        if (arr[j] <= pivot) {
            i++;    // 增加小于基准的元素的索引
            swap(arr[i], arr[j]);  // 将小于基准的元素放在索引i之前
        }
    }
    swap(arr[i + 1], arr[high]);  // 将基准放在它正确的位置
    return (i + 1);
}

// 快速排序函数
void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);  // 分区,并得到基准的位置

        quickSort(arr, low, pi - 1);  // 对基准左侧的部分递归排序
        quickSort(arr, pi + 1, high);  // 对基准右侧的部分递归排序
    }
}

int main() {
    vector<int> arr = {9, 4, 3, 7, 5, 6, 2, 1, 8};
    quickSort(arr, 0, arr.size() - 1);  // 对数组arr进行快速排序

    cout << "快速排序结果:";
    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";  // 输出排序后的数组
    }
    return 0;
}

​ 该代码定义了partition函数进行分区操作,quickSort函数进行递归快速排序。在main函数中调用quickSort对数组进行快速排序,然后输出排序后的结果。

​ 二分搜索是分治算法的一个简单应用。在一个已排序的数组中查找一个特定的元素,首先比较中间元素和目标值,如果目标值小于中间元素,那么在左边的子数组中查找,否则在右边的子数组中查找,递归进行这个过程直到找到目标值或搜索范围为空。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <vector>
using namespace std;

// 二分搜索的递归实现
int binarySearchRecursive(vector<int>& arr, int left, int right, int target) {
    if (right >= left) {
        int mid = left + (right - left) / 2;  // 中间位置,防止直接相加导致的溢出

        // 如果找到目标,返回索引
        if (arr[mid] == target) {
            return mid;
        }

        // 如果中间元素大于目标,搜索左半部分
        if (arr[mid] > target) {
            return binarySearchRecursive(arr, left, mid - 1, target);
        }

        // 如果中间元素小于目标,搜索右半部分
        return binarySearchRecursive(arr, mid + 1, right, target);
    }

    // 没有找到目标
    return -1;
}

int main() {
    vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};  // 已经排好序的数组
    int target = 5;  // 需要搜索的目标

    int result = binarySearchRecursive(arr, 0, arr.size() - 1, target);

    if (result != -1) {
        cout << "元素在数组中的索引为 " << result;
    } else {
        cout << "在数组中没有找到该元素";
    }

    return 0;
}

​ 在这段代码中,binarySearchRecursive函数执行递归的二分搜索,main函数调用binarySearchRecursive函数并输出结果。

大整数乘法(Karatsuba Algorithm)

​ Karatsuba算法是一个高效的大整数乘法算法,首先将大数分为两部分,然后递归地计算出两部分的乘积和总的乘积,最后通过一些加法和位移操作得到最终结果。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

// 将数字字符串反转,方便计算
string reverseString(const string &str) {
    string reversedStr = str;
    reverse(reversedStr.begin(), reversedStr.end());
    return reversedStr;
}

// Karatsuba算法实现大整数乘法
string karatsubaMultiply(const string &a, const string &b) {
    // 先处理特殊情况
    if (a.length() == 0 || b.length() == 0)
        return "0";

    // 将字符串反转,使得低位在前,高位在后
    string A = reverseString(a);
    string B = reverseString(b);

    int n = max(A.length(), B.length());
    if (n == 1) 
        return to_string((A[0]-'0') * (B[0]-'0')); // 递归终止条件,单个数字的乘法

    string A1 = A.substr(0, n/2);
    string A0 = A.substr(n/2);
    string B1 = B.substr(0, n/2);
    string B0 = B.substr(n/2);

    string P0 = karatsubaMultiply(A0, B0);  // P0 = A0*B0
    string P2 = karatsubaMultiply(A1, B1);  // P2 = A1*B1
    string P1 = karatsubaMultiply(A0+A1, B0+B1); // P1 = (A0+A1)*(B0+B1)

    // 计算结果,注意逐位相加的进位处理
    string result = P0;
    for (int i = 0; i < 2 * (n / 2); ++i) P2 = "0" + P2;  // P2需要左移2*(n/2)位
    for (int i = 0; i < n / 2; ++i) P1 = "0" + P1;  // P1需要左移n/2位

    result = stringAdd(stringAdd(result, P1), P2);  // 结果为P0 + P1 + P2
    result = reverseString(result);  // 结果反转回来
    return result;
}

int main() {
    string a = "12345";
    string b = "6789";
    string result = karatsubaMultiply(a, b);
    cout << "大整数乘法结果是:" << result << endl;  // 输出: 83810205
    return 0;
}

​ 这段代码中的karatsubaMultiply函数实现了Karatsuba算法。它首先处理一些特殊情况,然后将输入字符串反转,以便于后续的计算。然后,它递归地计算乘积P0,P1和P2,并将这三个乘积相加得到最终的结果。

Strassen的矩阵乘法(Strassen's Matrix Multiplication)

​ 这是一个高效的矩阵乘法算法,通过将大矩阵分解为子矩阵,递归地计算子矩阵的乘积,然后通过一些加法和减法操作合并结果,减少了乘法操作的次数。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <vector>
using namespace std;

// 简单的矩阵相加函数,用于Strassen算法中的子矩阵相加
vector<vector<int>> add(vector<vector<int>>& a, vector<vector<int>>& b) {
    int n = a.size();
    vector<vector<int>> res(n, vector<int>(n));
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            res[i][j] = a[i][j] + b[i][j];
    return res;
}

// 简单的矩阵相减函数,用于Strassen算法中的子矩阵相减
vector<vector<int>> subtract(vector<vector<int>>& a, vector<vector<int>>& b) {
    int n = a.size();
    vector<vector<int>> res(n, vector<int>(n));
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            res[i][j] = a[i][j] - b[i][j];
    return res;
}

// 用Strassen算法实现的矩阵乘法函数
vector<vector<int>> strassen(vector<vector<int>>& A, vector<vector<int>>& B) {
    int n = A.size();

    vector<vector<int>> R(n, vector<int>(n));

    if (n <= 2) {
        R = multiply(A, B);  // 这里的multiply是传统的矩阵乘法
    } else {
        int newSize = n / 2;
        vector<vector<int>> a11(newSize, vector<int>(newSize));
        vector<vector<int>> a12(newSize, vector<int>(newSize));
        vector<vector<int>> a21(newSize, vector<int>(newSize));
        vector<vector<int>> a22(newSize, vector<int>(newSize));

        vector<vector<int>> b11(newSize, vector<int>(newSize));
        vector<vector<int>> b12(newSize, vector<int>(newSize));
        vector<vector<int>> b21(newSize, vector<int>(newSize));
        vector<vector<int>> b22(newSize, vector<int>(newSize));

        // TODO: 在这里添加代码,将矩阵A和B分为四个子矩阵
        // TODO: 在这里添加代码,使用Strassen算法计算P1到P7
        // TODO: 在这里添加代码,计算子矩阵C11到C22
        // TODO: 在这里添加代码,将子矩阵C11到C22组合为结果矩阵R
    }

    return R;
}

int main() {
    // TODO: 在这里添加代码,定义输入矩阵A和B,调用strassen函数,输出结果矩阵
    return 0;
}

​ 完整实现Strassen算法需要一些关于矩阵操作的深入理解,包括如何分割矩阵,如何进行矩阵的加减运算,以及如何组合计算得到的子矩阵。在这个简单的实现中,这些部分被留作练习。此外,完整的Strassen算法应用还需要注意一些性能优化的问题,例如通过选择合适的基础案例大小(不一定是2)可以帮助提高算法的效率。

最大子序列和问题

​ 在这个问题中,我们需要在一个整数数组中找到一个具有最大和的连续子数组。我们可以通过分治法来解决这个问题,将数组分为两半,然后分别找出左半部分、右半部分以及跨越中间的最大子序列和,最后的结果就是这三者中的最大值。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 辅助函数:在给定数组的给定子区间中寻找最大子序列和
int maxSubArrayHelper(vector<int>& nums, int left, int right) {
    // 递归结束条件:子区间长度为1
    if (right == left)
        return nums[left];

    int mid = (left + right) / 2;

    // 计算左半部分的最大子序列和
    int leftMax = maxSubArrayHelper(nums, left, mid);
    // 计算右半部分的最大子序列和
    int rightMax = maxSubArrayHelper(nums, mid + 1, right);

    // 计算横跨左右两半部分的最大子序列和
    int leftBorderMax = nums[mid];
    int rightBorderMax = nums[mid + 1];
    int temp = 0;
    for (int i = mid; i >= left; i--) {
        temp += nums[i];
        if (temp > leftBorderMax)
            leftBorderMax = temp;
    }
    temp = 0;
    for (int i = mid + 1; i <= right; i++) {
        temp += nums[i];
        if (temp > rightBorderMax)
            rightBorderMax = temp;
    }

    // 返回三者中的最大值
    return max(max(leftMax, rightMax), leftBorderMax + rightBorderMax);
}

int maxSubArray(vector<int>& nums) {
    return maxSubArrayHelper(nums, 0, nums.size() - 1);
}

int main() {
    vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int maxSum = maxSubArray(nums);
    cout << "最大子序列和是:" << maxSum << endl;
    return 0;
}

最近点对问题

​ 这是一个经典的计算几何问题,要求在平面上给定的一组点中找到最近的两点。我们可以使用分治算法来解决这个问题,将点集按照x坐标分为两半,然后递归地在每一半中找出最近点对,最后再在这两对中找出最近的一对。

具体步骤如下:

  1. 将点集合按照x坐标排序。
  2. 分割点集合,递归求解左半部分和右半部分的最近点对。
  3. 在中间区域寻找可能存在的更近的点对。
  4. 返回以上三部分中距离最小的点对。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <float.h>
using namespace std;

typedef pair<double, double> Point;

double dist(Point a, Point b) {
    // 计算两点间距离的函数
    return sqrt(pow(a.first - b.first, 2) + pow(a.second - b.second, 2));
}

bool compX(Point a, Point b) {
    // 用于按x坐标排序的比较函数
    return a.first < b.first;
}

bool compY(Point a, Point b) {
    // 用于按y坐标排序的比较函数
    return a.second < b.second;
}

double closestPair(vector<Point>& px, vector<Point>& py) {
    // 输入参数是已经按照x和y坐标排序的点的列表

    // 只有一个点,返回无穷大
    if(px.size() == 1)
        return DBL_MAX;

    // 只有两个点,返回两点间的距离
    if(px.size() == 2)
        return dist(px[0], px[1]);

    // 分割点集
    vector<Point> lx(px.begin(), px.begin() + px.size()/2);
    vector<Point> rx(px.begin() + px.size()/2, px.end());
    vector<Point> ly, ry;
    double midx = px[px.size()/2].first;
    for(int i=0; i<py.size(); i++) {
        if(py[i].first <= midx)
            ly.push_back(py[i]);
        else
            ry.push_back(py[i]);
    }

    // 递归求解左右两半部分的最近点对
    double dl = closestPair(lx, ly);
    double dr = closestPair(rx, ry);

    // 中间区域可能存在的最近点对
    double d = min(dl, dr);
    vector<Point> strip;
    for(int i=0; i<py.size(); i++)
        if(abs(py[i].first - midx) < d)
            strip.push_back(py[i]);

    // 检查中间区域的最近点对
    for(int i=0; i<strip.size(); i++)
        for(int j=i+1; j<strip.size() && strip[j].second-strip[i].second<d; j++)
            d = min(d, dist(strip[i], strip[j]));

    return d;
}

int main() {
    vector<Point> points = {make_pair(2,3), make_pair(12,30), make_pair(40,50), make_pair(5,1), 
    make_pair(12,10), make_pair(3,4)};
    vector<Point> px = points, py = points;
    sort(px.begin(), px.end(), compX);
    sort(py.begin(), py.end(), compY);
    double min_dist = closestPair(px, py);
    cout << "最近点对的距离是:" << min_dist << endl;
    return 0;
}

多项式乘法

​ 给定两个多项式,我们希望计算出它们的乘积。这个问题可以通过分治法来解决,将每个多项式分解为两部分,然后递归地计算出四个部分的乘积,最后通过一些加法和位移操作得到结果。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <vector>
#include <iostream>
#include <cmath>
using namespace std;

// 辅助函数,对齐两个多项式的次数(长度),不足的高次补零
vector<int> align(vector<int>& v1, vector<int>& v2) {
    int n = max(v1.size(), v2.size());
    v1.resize(n, 0);
    v2.resize(n, 0);
    return v1;
}

// 基于Karatsuba算法实现的多项式乘法
vector<int> multiply(vector<int>& A, vector<int>& B) {
    // 对齐两个多项式
    A = align(A, B);
    B = align(A, B);
    int n = A.size();

    // 基本情况
    if (n == 1) {
        return vector<int>{A[0] * B[0]};
    }

    // 分解为两部分
    vector<int> a(A.begin(), A.begin() + n / 2);
    vector<int> b(A.begin() + n / 2, A.end());
    vector<int> c(B.begin(), B.begin() + n / 2);
    vector<int> d(B.begin() + n / 2, B.end());

    // 递归计算
    vector<int> ac = multiply(a, c);
    vector<int> bd = multiply(b, d);
    for (int& ai : a) ai += b[i];
    for (int& ci : c) ci += d[i];
    vector<int> abcd = multiply(a, c);
    for (int& abcdi : abcd) abcdi -= ac[i] + bd[i];

    // 结果整合
    vector<int> result(2 * n);
    for (int i = 0; i < n; ++i) {
        result[i] += ac[i];
        result[i + n / 2] += abcd[i];
        result[i + n] += bd[i];
    }

    return result;
}

int main() {
    vector<int> A = {5, 0, 10, 6};  // 表示 5 + 10x^2 + 6x^3
    vector<int> B = {1, 2, 4};  // 表示 1 + 2x + 4x^2
    vector<int> C = multiply(A, B);
    for (int i = 0; i < C.size(); ++i) {
        if (C[i] != 0) {
            cout << C[i] << "x^" << i;
            if (i != C.size() - 1) {
                cout << " + ";
            }
        }
    }
    cout << endl;

    return 0;
}

​ 这个代码使用了分治策略和递归的方式来实现多项式乘法。首先把输入的两个多项式A和B分别分成两半a、b和c、d,然后进行递归计算,最后将结果整合起来。在这个过程中,我们首先解决了小规模的子问题,然后再解决大规模的问题,这就是分治策略的主要思想。

汉诺塔问题

​ 这是一个经典的递归问题,涉及到三个塔和一堆大小各异的盘子,要求将所有的盘子从一个塔移动到另一个塔。这个问题可以通过分治策略来解决,将问题分解为先将除了最大盘子以外的所有盘子移动到中间的塔,然后将最大的盘子移动到目标塔,最后再将中间塔的所有盘子移动到目标塔。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

// 汉诺塔问题的递归解决方案
// n 是盘子的数量,src是源塔座,aux是辅助塔座,dest是目标塔座
void hanoi(int n, char src, char aux, char dest) {
    if (n == 1) {  // 只有一个盘子时,直接移动即可
        cout << "将盘子 " << n << " 从 " << src << " 移动到 " << dest << endl;
    } else {
        // 先将n-1个盘子从源塔座借助目标塔座移动到辅助塔座
        hanoi(n - 1, src, dest, aux);
        // 然后将剩下的一个盘子从源塔座移动到目标塔座
        cout << "将盘子 " << n << " 从 " << src << " 移动到 " << dest << endl;
        // 最后将n-1个盘子从辅助塔座借助源塔座移动到目标塔座
        hanoi(n - 1, aux, src, dest);
    }
}

int main() {
    int n;
    cout << "请输入盘子的数量:";
    cin >> n;
    cout << "移动步骤如下:" << endl;
    hanoi(n, 'A', 'B', 'C');  // 假设有三个塔座分别被命名为A, B, C
    return 0;
}

​ 这个代码首先检查是否只有一个盘子需要移动,如果是,则直接将其移动到目标塔座。如果有多个盘子,则首先将n-1个盘子从源塔座借助目标塔座移动到辅助塔座,然后将剩下的一个盘子从源塔座移动到目标塔座,最后将n-1个盘子从辅助塔座借助源塔座移动到目标塔座。通过这种方式,我们能够逐步解决汉诺塔问题。

统计学中的中位数和第k大元素问题

​ 在这个问题中,我们需要在一个无序的数组中找到中位数或第k大的元素。这个问题可以通过分治策略解决。例如,利用类似于快速排序的分割策略,我们可以在平均线性时间内找到第k大的元素。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

// 划分函数
int partition(vector<int>& nums, int left, int right) {
    int pivot = nums[right];  // 选择最右边的元素作为主元
    int i = left;
    for (int j = left; j < right; ++j) {
        if (nums[j] < pivot) {  // 将小于主元的元素放在左侧
            swap(nums[i++], nums[j]);
        }
    }
    swap(nums[i], nums[right]);  // 将主元放到正确的位置
    return i;
}

// 快速选择
int quickSelect(vector<int>& nums, int left, int right, int k) {
    if (left == right) {  // 如果列表中只有一个元素
        return nums[left];
    }
    int pivotIndex = partition(nums, left, right);  // 划分索引
    if (k == pivotIndex) {  // 如果划分后的主元正好是我们需要找的第k个元素
        return nums[k];
    } else if (k < pivotIndex) {  // 如果第k个元素在主元的左侧
        return quickSelect(nums, left, pivotIndex - 1, k);
    } else {  // 如果第k个元素在主元的右侧
        return quickSelect(nums, pivotIndex + 1, right, k);
    }
}

// 查找中位数
double findMedian(vector<int>& nums) {
    int n = nums.size();
    // 如果元素数量为奇数
    if (n % 2 == 1) {
        return quickSelect(nums, 0, n - 1, n / 2);
    } else {  // 如果元素数量为偶数
        int left = quickSelect(nums, 0, n - 1, n / 2 - 1);
        int right = quickSelect(nums, 0, n - 1, n / 2);
        return (left + right) / 2.0;
    }
}

int main() {
    vector<int> nums = {3, 2, 1, 5, 6, 4};
    double median = findMedian(nums);
    cout << "中位数是:" << median << endl;
    return 0;
}

​ 这段代码中,partition函数是快速选择的关键部分,它将数组划分为两个部分:一个是小于主元的元素,另一个是大于主元的元素。然后,根据k和主元索引的相对大小,我们可以决定继续在哪个部分寻找。

幂运算问题

​ 如果我们要计算xn(n为正整数),可以使用分治算法来加快计算速度。如果n是偶数,xn 可以被分解为 (x(n/2))2,这样就把一个大问题分解为了一个小问题。如果n是奇数,xn = x * x(n-1),也能够利用分治策略。通过这种方式,我们可以将幂运算的计算复杂度从O(n)降低到O(logn)。

​ 对应的C++代码如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

// 计算x的n次幂
double power(double x, int n) {
    if (n == 0) {  // 任何数的0次幂都是1
        return 1;
    }
    if (n < 0) {  // 如果n是负数,我们可以将问题转化为求1/x的-n次幂
        return 1 / power(x, -n);
    }
    // 如果n是偶数,我们可以将x^n的计算转化为计算(x^(n/2))^2
    if (n % 2 == 0) {
        double half = power(x, n / 2);
        return half * half;
    } else {  // 如果n是奇数,我们可以将x^n的计算转化为x*x^(n-1)
        return x * power(x, n - 1);
    }
}

int main() {
    double x = 2.0;
    int n = 10;
    cout << x << " 的 " << n << " 次幂是: " << power(x, n) << endl;
    return 0;
}

在图像处理中的应用

​ 分治策略在图像处理中也有广泛的应用,比如在计算机图形学中的线段树、区间树等数据结构,以及在计算机视觉中的图像分割等问题。

FFT(快速傅里叶变换)

​ 这是一种在数字信号处理中常用的算法,用于将一个信号从时域转换到频域。FFT使用分治策略,将一个大的DFT(离散傅里叶变换)问题分解为多个小的DFT问题,大大提高了计算效率。

​ 分治算法是一种强大的问题解决策略,被广泛应用在许多领域。然而,它并不总是最优的解决方案。有时,问题可能不容易分解,或者子问题之间可能存在重叠,这时候,其他的策略,如动态规划或贪婪算法,可能会更有效。