研究生课程 算法分析-分治法

分治法(divide and conquer)是算法分析里比较直观和朴素的思想,应用也很广泛。

分治法的思想

分治法的思想是,把一个复杂的问题 P 划分称 k 个子问题,这些子问题相互独立且与原问题相同。递归调用子问题,直到问题规模足够小,可以很容易地求解为止;接着,把小规模的问题的解合并成一个更大规模的问题的解。

可以用下面的伪代码来描述,

divide-and-conquer(P) {
    if (|P| <= n0) adhoc(P); // 小规模问题求解
    divide P into smaller subinstances P1, P2,...,Pk;  // 分解问题成小规模问题
    for (i = 1; i <= k; ++ i) 
        yi = divide-and-conquer(Pi);     // 递归解决子问题
    return merge(y1, y2,...,yk);      // 合并为原问题的解
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

分治法和递归

从分治法的思想可以看出,在求解子问题的时候往往需要用到递归,因此分治法和递归经常一起出现。递归算法的优点是结构清晰,可读性强,一般代码也比较容易写,但是缺点是运行效率很低,无论是计算时间还是占用的存储空间都很大。

一般有下面几种解决的办法,

  1. 用自定义栈,做了编译器做的事,优化效果不明显。
  2. 用递推的方法来实现递归函数
  3. 通过变换,将一些递归转化成尾递归(尾递归是所有子递归出现在递归函数的尾部),迭代求结果

后两种方法虽然范围有限,但是如果可以在问题中这样优化的话,效果会比较好。

分治法的适用条件

看一个问题是否可以用分治法求解,可以考虑这个问题是否符合下面几种条件,

  1. 小规模时,问题容易求解,这个一般都很容易满足。
  2. 问题具有 最优子结构 的性质,即可以分解成若干个规模较小的相同问题。
  3. 子问题的解可以 合并为该问题的解
  4. 子问题相互独立,即 不包含公共的子问题

满足前三条特征,可以用分治法;如果不具备第三条特征,可以考虑贪心算法和动态规划。如果问题满足第 4 个特征的话,分治法会重复计算导致效率,用动态规划较好。

分治法的应用

二分查找算法

二分查找要解决的问题是,从有序数组 a 中找出特定元素 x 出来。因为是有序,所以可以用考虑分治法,从线性扫描的复杂度 O(n) 优化到 O(logn)

有递归和非递归两种实现方法。

大整数的乘法

大整数的乘法,分治成较小规模的子问题后,从四次乘法的优化到三次乘法,可以把复杂度从 O(n2) 优化到 O(nlog3=1.59) ,有较大的改进。

Strassen 矩阵乘法

与大数乘法的例子类似,做分治后想办法减少乘法的次数,从 O(n3) 优化到了 O(nlog7=2.81) 的复杂度。

归并排序

基本思想是,把待排序的数组分成两个子集,分别排序后再合并。递归代码如下,

void merge_sort(int a[], int start, int end) {
    if (start < end) {
        int mid = (start + end) / 2;
        merge_sort(a, start, mid);     // 对左边的序列递归排序
        merge_sort(a, mid + 1, end);   // 对右边的序列递归排序
        merge(a, start, end);          // 合并子序列
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
快速排序

快排的想法很简单,是想找一个数作为排序的基准(pivot),小的放左边,大的放右边。然后对左边和右边的两个子问题递归排序。递归实现如下,

void quick_sort(int a[], int start, int end) {
    if (start < end) {  // 
        int pivot_index = partition(a, start, end);  // 挖坑划分
        quick_sort(a, start, pivot_index - 1);       // 左边的子序列递归排序
        quick_sort(a, pivot_index + 1, end);         // 右边的子序列递归排序
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意快排的划分函数 partition,用的是挖坑填坑的方法。以第一个元素为基准(pivot),最后返回 pivot 的位置 pivot_index。此时左边的数组元素都比 pivot 小,右边的数组元素都比 pivot 大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值