1 分治法

一、特点与框架

1、特点

  • 将原问题归约为规模小的子问题,子问题与原问题具有相同的性质。

  • 算法可以递归也可以迭代实现。

2、框架

Divide-and-Conquer (P)

  1. if |P|\le c then S(P)

  2. divide P into P_1, P_2, …, P_k

  3. for i\leftarrow 1 to k

    1. y_i\leftarrow Divide-and-Conquer (P_i)

  4. Return Merge (y_1, y_2, … , y_k)

二、递推方程

  • f(n)=\Sigma_{i=1}^kf(n-i)+g(n)​

    • Hanoi

  • f(n)=af(\frac{n}{b})+d(n)​

    • 二分检索、归并排序

    • d(n)=c时,T(n)=\begin{cases}O(n^{log_ba}),\quad a\ne1\\O(logn), \quad a=1\end{cases}

    • d(n)= cn时,T(n)=\begin{cases}O(n), \quad a<b\\O(n^{log_ba}),\quad a>b\\O(nlogn),\quad a=b\end{cases}

三、应用

  1. 芯片测试

  2. 快速排序

  3. 幂乘算法

四、改进策略

1、减少子问题数

(1)适用场景
  • 适用于子问题个数多,划分和综合工作量不太大,时间复杂度函数T(n) = n^{log_ba}的情况。

  • 利用子问题依赖关系,用某些子问题解的代数表达式表示另一些子问题的解,减少独立计算子问题个数。综合解的工作量可能会增加,但增加的工作量不影响 T(n)的阶。

(2)举例
  1. 矩阵乘法

    1. 分块相乘:f(n)=8f(\frac{n}{2})+cn^2. T(n)=O(n^3).

    2. Strassen矩阵乘法:f(n)=7f(\frac{n}{2})+\frac{9}{2}n^2. T(n)=O(n^{log7}).

  2. 整数位乘问题:输入:X, Y是n位二进制数,n=2^k。输出:XY。

    1. 简单划分:X= A2^{n/2} +B, Y= C2^{n/2} +D

      XY= AC2^n + (AD + BC) 2^{n/2} + BD f(n) = 4f(n/2)+O(n),T(n)=O(n^2)

    2. 利用子问题之间的依赖关系:AD+BC = (A-B)(D-C) + AC + BD f(n) = 3 f(n/2) + cn,T(n)=O(n^{log3})​

    3. 利用快速傅里叶变换可以得到时间复杂度为O(nlogn)的算法

2、增加预处理

(1)适用场景
  • 递推方程为f(n)=af(\frac{n}{b})+d(n)时,通过增加预处理来减少d(n)

(2)举例
  1. 平面点对问题

    1. 原算法:在每次划分时对子问题数组重新排序

    2. 改进算法:在递归前对 X,Y 排序,作为预处理。划分时对排序的数组 X,Y 进行拆分,得到针对子问题 P_L的数组 X_L,Y_L及针对子问题 P_R的数组 X_R,Y_R

四、典型算法讲解

1、选择问题

(1)选第2大
①思路分析
  • 成为第二大数的条件:仅在与最大数的比较中被淘汰

  • 要确定第二大数,必须知道最大数

  • 在确定最大数的过程中记录下被最大数直接淘汰的元素.

  • 在上述范围(被最大数直接淘汰的数)内的最大数就是第二大数.

  • 设计思想: 用空间换时间

②锦标赛算法
  1. 两两分组比较,大者进入下一轮,直到剩下 1个元素 max 为止

  2. 在每次比较中淘汰较小元素,将被淘汰元素记录在淘汰它的元素 的链表上

  3. 检查 max 的链表,从中找到最大元,即second

③伪码

输入: n个数的数组 L,输出: second

  1. k←n // 参与淘汰的元素数

  2. 将k个元素两两1组, 分成 k/2 组

  3. 每组的2个数比较,找到较大数

  4. 将被淘汰数记入较大数的链表

  5. if k 为奇数 ,then k←k/2 +1

    1. else k左k/2

  6. if k>1 then goto 2

  7. max←最大数

  8. second←max 的链表中的最大

④时间复杂度
  • 找max:n-1次比较,因为淘汰了n-1个元素

  • 找second:O(logn)​

(2)一般选择问题:选第k小
①简单算法
  1. 调用 k 次选最小算法,时间复杂度为O(kn)

  2. 先排序,然后输出第 k 小的数时间复杂度为O(nlogn)

②分治算法设计思路
  1. 用某个元素 m作为标准将 S 划分成 S1 与 S2,其中 S1的元素小于 m,S2 的元素大于等于 m。

  2. 如果 k≤|S1|,则在 S1中找第 k 小。如果 k=|S1|+1,则m是第 k 小 如果 k > |S1|+1,则在 S2中找第k-|S1|-1小。

③伪码
④时间复杂度

|M|与归约后子问题规模之和小于 n,递归树每行的工作量构成公比小于 1的等比级数,算法复杂度才是O(n)

⑤补充

用stl库中的nth_element函数可以实现这一功能,利用了随机算法,原理类似快速排序,期望复杂度是O(n)。见C++ STL nth_element原理与应用_nth element 原理-CSDN博客

2、信号平滑处理

(1)快速傅里叶变换(FFT)
(2)卷积

3、计算几何

(1)计算平面点集的凸包

参考:北京大学《算法设计与分析》MOOC,授课教师汪小林

  • 36
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是基于分治法思想实现归并排序的C++代码: ```c++ #include <iostream> using namespace std; void merge(int arr[], int left, int mid, int right) { int i, j, k; int n1 = mid - left + 1; int n2 = right - mid; // 创建临时数组 int L[n1], R[n2]; // 将左右两部分的元素复制到临时数组中 for (i = 0; i < n1; i++) { L[i] = arr[left + i]; } for (j = 0; j < n2; j++) { R[j] = arr[mid + 1 + j]; } // 归并已经排序好的左右两部分 i = 0; j = 0; k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 将左边或右边剩余的元素复制到 arr 中 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(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() { int arr[] = { 12, 11, 13, 5, 6, 7 }; int n = sizeof(arr) / sizeof(arr[0]); mergeSort(arr, 0, n - 1); cout << "排序后的数组:\n"; for (int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << endl; return 0; } ``` 归并排序是一种基于分治法思想的排序算法。它的基本思路是将待排序的数组分成两部分,分别对左右两部分进行排序,最后将排序好的左右两部分合并成一个有序的数组。归并排序的时间复杂度为 $O(n\log n)$,空间复杂度为 $O(n)$。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值