分而治之
今天我们来学习一个基础算法策略——分而治之
分治和递归经常同时用于算法设计中。
什么是分治法?分而治之的思想就是将一个大问题分成一些小问题,再逐个击破。(这与递归不谋而合,但区别是:递归是方法,分治是思想)其中,这些子问题相互独立且与原问题的结构相同但规模比原问题小,,所以与递归十分契合。
分治的步骤:分解(分解原问题成子问题)、求解(对子问题进行求解)、合并(将子问题合并成原问题)
归并排序
归并排序便是运用了分治思想
将待排元素分成两个子序列,对每个子序列再进行排序,最后再合并两个子序列
对n个元素进行归并排序:
(1)将n个元素分成各有n/2个元素的子序列
(2)对n/2个元素进行归并排序
(3)合并两个已排序的子序列
//输入数组A[1…n],数组下标left, right。平均时间复杂度O(nlogn) void Merge(int a[],int left,int mid,int right) { //临时数组用于存放初始未合并的两个子序列的元素 int temp[MAXSIZE]; int i,j,k; for(k=left;k<=right;k++) temp[k-left]=a[k]; i=left;//第一个子序列的起始点 j=mid+1;//第二个子序列的起始点 for(k=left;k<=right;k++) { //如果第一个子序列的所有元素都已排完,将第二子序列中未排元素依次放入已排数组(即将两个序列合 并的目标数组)中 if(i>mid) { a[k]=temp[j-left]; j++; } //如果第二个子序列的所有元素都已排完,将第一子序列中未排元素依次放入目标数组尾部中 else if(j>right) { a[k]=temp[i-left]; i++; } //在两个序列都未排完的情况下,当前第一子序列元素值大于当前第二子序列元素值,将较小的元素放入 目标数组尾部 else if(temp[i-left]>temp[j-left]) { a[k]=temp[j-left]; j++; } //在两个序列都未排完的情况下,当前第二子序列元素值大于当前第一子序列元素值,将较小的元素放入 目标数组尾部 else { a[k]=temp[i-left]; i++; } } } void mergesort(int a[],int left,int right) { if(left>=right) return ; int mid=(left+right)/2; //将数组a分成两组{a[left]~a[mid]}和{a[mid+1]~a[right]},并进行归并排序 mergesort(a,left,mid); mergesort(a,mid+1,right); Merge(a,left, mid, right);//合并 }
最大子段和
最大子段和问题是在一个由n个整数组成的序列中找出最大的子段和。例如(-2,11,-4,13,-5,-2)的最大子段和为20
算法策略:
(1)分解。将数组分成左右两个长度相等的段,即A[1~n] -> A[1~n/2] + A[n/2+1 ~ n]
此时最大子段和有三种情况:
- 最大子段在左,即A[1n/2]的最大子段和A[1n]的最大子段相等
- 最大子段在右,即A[n/2+1n]的最大子段和A[1n]的最大子段相等
- 最大子段跨越左右子段,即A[i~j]的和,其中 1 <= i <= n/2、n/2+1 <= j <= n
(2)情况处理:对于第一、二种情况利用递归可求。对于第三种情况而言,由于需要求出A[i, j]的和,可以在左子段中求出s1 = MAX(A[i, n/2]),从右子段中求出s2 = MAX(A[n/2+1, j]),s1+s2即为所求
(3)合并,在三种情况中取出最大
//输入数组A[1…n],数组下标0, n-1。平均时间复杂度O(nlogn) int MaxSubSum(int *a, int left, int right) { int sum = 0; if(left == right) { sum = a[right]; return sum; } else { //分解 int mid = left + (right-left)/2; int leftsum = MaxSubSum(a, left, mid);//情况1 int rightsum = MaxSubSum(a, mid+1. right);//情况2 //情况3 int s1 = 0; int lefts = 0; for(int i = mid; mid>=left;mid--) { left +- a[i]; s1 = max(s1, lefts); } int s2 = 0; int rights = 0; for(int i = mid+1;i<=right;i++) { rights += a[i]; s2 = max(s2, rights); } sum = s1+s2; return max(max(sum, rightsum), leftsum); } }