分治算法总结

来源:互联网 发布:大数据人才的薪资待遇 编辑:程序博客网 时间:2024/06/04 20:09

一.分治算法的基本思想:

将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同,递归的解这些子问题.然后将各个子问题的解合并得到原问题的解.


T(n)表示一个规模为n的问题的运行时间,

(1)如果规模足够小(可以直接求解),在此处n<=c(c为一个常量)时,则得到它的直接解的时间为常量.写成O(1).

(2)假设我们把原问题分解成a个子问题,每一个的大小是原问题的1/b(对于归并排序a和b都是2,但在许多分治法中a!=b,设分解该问题的时间为D(n),合并解的时间为C(n)),

最后得到的递归式为上图所示.


二.分治法解题的一般步骤:


(1)分解,将要解决的问题划分成若干规模较小的同类问题;

(2)求解,当子问题划分得足够小时,用较简单的方法解决;

(3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。


三.实现方法:

利用递归技术实现


四,能用分治算法解决问题的几个特征

(1)该问题的规模缩小到一定的程度就可以容易的解决(绝大多数问题都可以应用)

(2)该问题可以分解为若干较小的相同问题,即该问题具有最优子结构性质(应用分治算法的前提,反应了递归的思想,也是大多数问题满足的)

(3)该问题分解出的子问题的解可以合并为该问题的解(最重要的一点,能否利用分治算法解决问题完全取决于这一特征)

(4)该问题分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题.(这条特征涉及到分治算法的效率,如果各个子问题不是独立的,则分治法会计算很多不必要的计算,此时虽然可以用分治法,但一般用动态规划算法更好)



五.几个经典分治策略的应用.

(1)归并排序

(2)逆序对问题

(3)快速排序

(4)最接近点对

(5)二分查找

(6)线性时间选择(在一组无序整数列中选择第K小/大的数)

(7)矩阵乘法

(8)大数乘法

(9)棋盘覆盖


六.几个经典分治算法.

(1)逆序对问题(算法导论习题2-4)

问题描述:例如(2,3,8,6,1)中的逆序对为{(2,1), (3,1),(8,1),(6,1),(8,6)}.

联想归并排序的思想,归并排序是对问题划分为2个规模相同的子问题,当子问题规模为2时可以直接求解,然后合并子问题,依次递归实现.而归并排序的算法核心是合并,即对两个子问题进行合并求解.设Left[], Right[]分别为在合并过程中的两个子数组(算法导论MERGE过程的第3行),很容易想到在合并过程中,如果Left[]中的数大于Right[]中的数时,这个数对就是逆序的,修改MERGE部分的合并伪代码

count=0;/*count可以设为全局变量*/MERGE(A, p, q, r)....i=1, j=1,;for(k=p; k<=r; k++)if(Left[i] <= Right[j] )A[k] = Left[i++];elseA[k] = Right[j++];count++;/*计数器count为逆序对的个数*/


最后的递归树和归并排序相同.(要会画出递归树)

归并排序的最坏情况运行时间T(n)的递归表示:


对上面的递归式进行重写

 

利用递归树进行求解.

理解递归树树根代价(在没有进入递归子问题时,顶层的问题规模为n,求解和合并问题的代价和设为cn,其中c为常数.)


 

一共lgn+1层(n=4  4c 2c 2c c c c c = c(lg4+ 1)=3层),每一层的代价为cn

因此总代价为cn(lgn + 1)=cnlgn + cn, 因此复杂度为Θ(nlgn)


(2)线性时间选择(无序数列中选择第K小的数)

<i>:问题描述:给定一个线性无序的数集中给出n个元素和一个整数k, 其中1<=k<=n,要求找出这n个无序数列中第k小的数.

<ii>:解决方案:基数排序可以在O(n)时间内完成,但是需要O(n)的空间;堆排序也可以在O(n)的时间内完成,,在这里不做讨论.

更容易想到的方法是对n个元素排序,比如用快速排序可以在O(nlgn)时间内完成,而我们只是要找到第k小的数,联想快速排序的思想:

快速排序也是一个分治思想,也需要按照分治算法的三个步骤(设对数组A[p,r]排序)

分解:数组A[p,r]被划分为两个子数组,A[p,q-1],A[q+1,r],使得A[p,q-1]中的每个元素都小于等于A[q],而小于等于A[q+1,r]中的元素.而小标q是我们进行划分的基准点,也是在数组A中第q小的数.

求解:通过递归调用快速排序,对子数字A[p,q-1],和A[q+1][r]排序.

合并:因为两个子数组数原地排序的,将他们合并不需要操作,整个数组A[p,r]已排序.

上面是快速排序的基本步骤,接着我们开始考虑线性选择问题如何利用快速排序在线性时间内求解,我们观察在快速排序的分解步骤中的q,就是在数组A中的第q小的数,而在线性选择问题中就是找到这样的一个数,因此想到在线性选择中不需要像快速排序中对q的左侧和右侧全部递归实现,下面给出快速排序的过程

QuickSort(A, p, r) if(p < r)q = partition(A, p, r); /*此处的q为数组A中第q小的数*/QuickSort(A, p, q-1);QuickSort(A, q+1, r);


下面考虑线性选择问题.我们要选择的k如果刚好是q,那么可以得到找到的数就是A[q].如果k<q,那么只需要递归实现QuickSort(A, p, q-1);因为A[q+1, r]子数组中的数全部大于A[q],相反如果k>q,只需要递归实现QuickSort(A, q+1, r);

这样就得到了线性时间选择的过程:

Select(A, p, r, k) /*p=<k<=r*/if(p==r) return A[p];q = partition(A, p, r);//partition部分和快速排序partition相同x = q - p + 1;if(x == k) return A[q];        else if(k < x)Select(A, p, q-1, k);elseSelect(A, q+1, r, k);


画出递归树(非主流,通常无法画,可以用概率方法算出期望时间,再次略过)


(3)棋盘覆盖(省略,描述较麻烦)