分治策略的基本思想是,将一个问题分解成若干个子问题,这样不断的递归分解,直到分解的足够小时直接分解。经典的分治策略实现有归并排序,快速排序
步骤
- 分解:将问题分解为一些子问题,子问题的形式与原问题的方式一致,只是规模更小。
- 解决:递归的分解出子问题,如果子问题的规模足够小,则停止递归直接求解。
- 合并:将子问题的解组合成原问题的解
举例说明
- 排序中比较暴力的方法就是选择排序和冒泡排序,拿选择排序来说,每一次遍历选择数组中的最大值,需要遍历n次,也就是将排序分为n层来处理,每一层的时间复杂度为o(n),总时间复杂度为O(n^2)
- 然而对比归并排序和快排,都是将一个问题分为两个子问题来解决,以此类推总共分为logn层(可以脑部二叉树的结构),每一层的时间复杂度为o(n),总时间复杂度为O(nlogn),对比可以得知分治策略优化的是层数,可以想成将一个nn矩阵的结构优化成了二叉树的结构,明显缩短时间
例题1:求解最大子数组
- 题目给出一个数组,求给出数组的子数组和最大值
- 分析:定义一个分界点mid,最大子数组可能存在三种情况:1.子数组在mid左边。2.子数组在mid右边。3.子数组包含mid。
- 根据分治策略的解决步骤:1.分解,将这个问题中经过mid点的情况处理掉,然后将数组由mid为 分界点分为left和right两个子数组。2.解决,当数组分解足够小,即子数组中只存在一个元素时,返回该元素.3.合并,取left数组最大子数组,right数组最大子数组,当前最大子数组中的最大值返回。
- 代码
public static int MaxSubArray(int[] array,int High,int Mid,int Low){
//当问题分解足够小时,解决问题
if(High==Low){
return array[High];
}
//初始化
int right_sum=array[Mid];
int left_sum=array[Mid-1];
int sum=0;
//当子数组包含mid时,取最大子数组
for(int i=Mid-1;i>=Low;i--){
sum=sum+array[i];
if(sum>left_sum){
left_sum=sum;
}
}
sum=0;
for(int i=Mid;i<=High;i++){
sum=sum+array[i];
if(sum>right_sum){
right_sum=sum;
}
}
//分解
int Max_left=MaxSubArray(array,Mid-1,(Mid-Low)/2+Low,Low);
int Max_right=MaxSubArray(array,High,(High-Mid+1)/2+Mid,Mid);
int Max_center= right_sum+left_sum;
if(Max_center>Max_left && Max_center>Max_right){
return Max_center;
}else if(Max_left>Max_right){
return Max_left;
}else {
return Max_right;
}
}
例题2:归并排序
- 分解:将数组分解成只有一个元素的数组(元素只有一个为有序数组)
- 回溯归并:合并两个有序子数组
- 代码
public static void MergeSort(int[] array,int High,int Mid,int Low){
//分解成最小单位时返回
if(High==Low){
return;
}
//分解成两个子元素
MergeSort(array,High,(High+Mid+1)/2,Mid+1);
MergeSort(array,Mid,(Mid+Low)/2,Low);
//创建两个数组存储当前数组的left和right部分
int m=Mid-Low+1;
int n=High-Mid;
int[] Left=new int[m];
int[] Right=new int[n];
int j=0,k=0;
for(int i=0;i<=Mid-Low;i++){
Left[i]=array[Low+i];
}
for(int i=0;i<=High-Mid-1;i++){
Right[i]=array[Mid+1+i];
}
//合并两个有序数组
for(int i=Low;i<=High;i++){
if(k>=n || (j<m && Left[j]<=Right[k])){
array[i]=Left[j];
j++;
}else{
array[i]=Right[k];
k++;
}
}
}