Divide-and-Conquer算法的设计
设计过程分为三个阶段:
Divide:整个问题划分为多个子问题
Conquer:求解各子问题(递归调用正设计的算法)
Merge:合并子问题的解,形成原始问题的解
下面是几道小题使用分治算法求解的思路。
1黑白点对
题目:
给定平面上有n个白点和n个黑点,任意三点不共线,试实际一个分治算法将每个白点与一个黑点相连,是所有连线互不相交。
思路:
将所有2n个点点按照x有小到大排序,以一条垂直于x轴的线将这些点分为大小均为n的左右两部分,左右两部分递归的进行黑白点对的匹配,由于每部分分到的黑点数与白点数不一定相等,最终返回的每部分都是互不相交的黑白点对连线以及一些单独的黑点,或者是互不相交的黑白点对连线以及一些单独的白点。
在合并时,若两部分剩余的点都是黑色或都是白色,则合并完成。
若一部分剩余的是黑点,另一部分剩余的是白点,则将这些点匹配连线。每次新连接一条线时,若并不与其他线相交,则连接下一对;若与其他k对相交,可以通过k-1次交换使其不再交叉(由于不存在任意三点共线,所以必然可以通过有限次交换使其互不相交),连接下一对。直到单独的黑点或者白点全部连接完毕。
算法设计:
Divide:按照横坐标,将所有点分为等量的左右两部分。
Conquer:递归的将左右两部分进行黑白点对的匹配。当某部分中只有一个点或两个同色的点时,直接返回;有两个不同色的点时,将他们匹配。
Merge:左右两部分中若有某部分黑白点完全匹配了,直接合并,返回;左右两部分没有匹配的单独点同色,直接合并,返回;将左右两部分没有匹配的点异色,依次匹配,若存在交叉,则可以通过与交叉线段有限次交换使其互不相交,当所有某侧单独的点都完成匹配,返回。
时间复杂度分析:
设该算法的时间复杂度为T(2n),合并时,左右两侧单独的点最多进行n次连线,每次连线时最多与与其相交的n-1条线段进行交换,时间复杂度为O(n^2)。
T(n)=2T(n/2)+O(n^2).
逐步递推得到时间复杂度T(n)=O(n^2).
2求最大连续子数组
题目:
给定一个数组A[1:n],数组元素由实数构成,求A的连续子数组,使得此子数组的和最大。如:A={-2,-5,6,-2,-3,1,5,-6},结果为{6,-2,-3,1,5},和为7。设计一个分治算法,求A[1:n]的和最大连续子数组
思路:
采用二分的方法将数组从中间分为左右两个子数组,则最大子数组必然出现在以下三种情况之一:
1)完全位于左边的数组中。
2)完全位于右边的数组中。
3)跨越中点,包含左右数组中靠近中点的部分。
递归将左右子数组再分别分成两个数组,直到子数组中只含有一个元素,退出每层递归前,返回上面三种情况中的最大值。
算法设计:
Divide:将数组A划分为左右两个子数组AL和AR。
Conquer:递归的求解AL和AR的最大连续子数组。若数组中只有一个数字,最大连续子数组为这个数字本身。
Merge:AL和AR的最大连续子数组的和分别为MaxLeftSum,MaxRightSum。设从AL最右端开始的连续子序列的最大和为MaxLeftBorderSum,从AR最左端开始的连续子序列的最大和为MaxRightBorderSum,那么跨越中点的最大连续子数组的和为MaxLeftBorderSum+MaxRightBorderSum。返回MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum这三者的最大值。
时间复杂度分析:
设该算法的时间复杂度为T(n),则:
T(n)=2T(n/2)+O(n),且T(1)=1.
逐步递推得到时间复杂度T(n)=O(nlogn).
3英文字母编码
题目:
将26个英文字母进行编码,‘A’编码为‘1’,‘B’编码为‘2’,……,‘Z’编码为‘26’。那么给定一个数字序列可以对其进行解码,但解码不唯一。比如给定数字序列“234”,可以解码为“2-3-4”,对应“BCD”;也可以解码为“23-4”,对应“WD”。设计一个分治算法,对于给定的数字序列LIST,求出给数字序列有几种解码方式。
思路:
编码为1~26,所以数字有以下几种情况:
数字0必须与它前面的1或2一起编码,只有一种编码情况;数字1可单独编码,也可与其后面的数字一起编码,通常有两种情况,但当后面跟着10或20时,只能编码为A;数字2可单独编码,也可与其后面的数字0~6一起编码,但当后面跟着7、8、9、10、20时,只能编码为B;数字3~9前没有1或2时只有一种编码情况。
可以递归的将序列LIST二分,直到每个子序列中只有一个数字,此时子序列的解码方式为1,合并时考虑合并处两个数字是否有更多的解码方式。
算法设计:
Divide:将序列LIST划分为左右两个子序列LISTL和LISTR。
Conquer:递归的求解LISTL和LISTR的解码方式。若序列中只有一个数字,返回解码方式为1。
Merge:将LISTL和LISTR的解码方式数相乘,得到res。再考虑合并处的两个数字,即LISTL的最右数字n1与LISTR的最左数字n2,若n1等于1,n2不为0,则将res乘以2;若n1等于2,n2为1~6,则将res乘以2;其余情况res不变。将得到的res返回。
时间复杂度分析:
设该算法的时间复杂度为T(n),则:
T(n)=2T(n/2)+O(n),且T(1)=1.
逐步递推得到时间复杂度T(n)=O(nlogn).
4求逆序数
题目:
设A[1:n]是由不同实数组成的数组,如果iA[j],则称实数对(A[i],A[j])是该数组的一个反序。如,若A=[3,5,2,4],则该数组存在3个反序(3,2)、(5,2)和(5,4)。设计一个分治算法,求逆序数。
思路:
类似于归并排序算法。先将数组从中间分成两个部分,然后分别递归左半部分和右半部分,再合并排好序的左右两个部分,从而统计逆序对数。
对于两个排好序的数组AL和AR,初始时分别设置指针p1、p2在数组最左端,比较AL[p1]与AR[p2]的大小:如果AL[p1]>AR[p2],那么显然AL中AL[p1]及其后面的所有元素都能与AR[p2]构成逆序对,记录这个数并将p2右移;否则将p1右移,当完成两个数组的遍历后就得到了这两个数组间的逆序数。
算法设计:
Divide:将数组A划分为左右两个子数组AL和AR。
Conquer:递归的求解AL和AR的逆序数。若数组中只有一个数字,则返回逆序数为0。
Merge:AL和AR的逆序数分别为sum1、sum2。AL和AR在之前的合并中已经按从小到大的顺序排好序了,所以我们可以在一次对这两个数组的遍历中,将它们以归并排序的方法合并为A时,同时得到这两个数组间的逆序数sum3。A的逆序数为sum1+sum2+sum3。
时间复杂度分析:
每次都要将序列的的n个元素合并,时间复杂度为O(n)。
设该算法的时间复杂度为T(n),则:
T(n)=2T(n/2)+O(n),且T(1)=1.
逐步递推得到时间复杂度T(n)=O(nlogn).
5友谊点对
题目:
给定平面上n个点构成的集合S={p1,p2,……,pn}。如果存在便平行于坐标轴的矩形仅包含S中的两个点pi和pj(1<=i<j<=n),则称pi和pj为友谊点对。试设计一个分治算法统计S中友谊点对的个数。
算法设计:
预处理:将点集S中的所有点按照x有小到大排序。
Divide:将点集S用一条垂直于x轴的直线l:x=m划分为两个大小相等的子集SL和SR。
Conquer:递归的求解SL和SR中友谊点对数。若点集中点的数量为1,返回友谊点对数0;若点集中点的数量为2,这两个点一定为友谊点对,返回友谊点对数1。
Merge:S的友谊点对数=SL的友谊点对数+SR的友谊点对数+两点分别位于SL和SR的友谊点对数。两点分别位于SL和SR的友谊点对数的求法如下:
1)p0(x0,y0)为SL中最右点,以y=y0为界将SR分为上下两部分讨论。对于上半部分找出x最小的点A和y最小的点B,能与p0构成友谊点对的必然出现在以A、B为顶点的矩形区域中。
2)依次遍历横坐标在区间(xA,xB)中的点。能与p0构成友谊点对的,必然是横坐标依次增大同时纵坐标依次减小的(若横坐标增大的同时纵坐标也增大,后一个点与p0构成的矩形中会包含前一个点)。下半部分同理。
3)p1为SL中次右点,若y1>y0,则只需考虑y>y0的区域;反之,只需考虑y<y0的区域。对于pk,只需考虑SL中纵坐标与其相邻的两个点pi、pj的纵坐标所夹区域(yi,yj)。对于SL中的每个点重复上述两步,直到完全遍历。
时间复杂度分析:
设该算法的时间复杂度为T(n),则:
T(n)=2T(n/2)+f(n),且f(n)<=O(n^2).
所以T(n)=O(n^2).