分治算法的基本思想
当我们遇到要处理的数据相当多,或求解过程相当复杂的问题的时候。我们可以把它分解成若干的个子问题,若子问题还是很复杂,我们可以继续分解,直到解出问题为止。
首先我们看去数组的最值的问题。取最值可以直接用比较法依次比较,但是如果遇到数据量比较大的情况比较下来速度会比较慢。我们可以 将数组分成两堆,分别取他们的最值,他们取到的最值进行比较,得到结果。
public static int getMaxValue(int[] array){
int length = array.length;
//数组只有一个值直接返回
if(length==1){
return array[0];
}
//数组只有两个值比较取最大值返回
if(length==2){
return array[0]>array[1]?array[0]:array[1];
}
//数组只有超过两个值拆分成两个数组array1和array2
int length1 = array.length/2;
int[] array1 = new int[length1];
for (int i = 0; i < length1; i++) {
array1[i] = array[i];
}
int length2 = length-length1;
int[] array2 = new int[length2];
for (int i = length1; i < length; i++) {
array2[i-length1] = array[i];
}
//分别取两组数据的最值
int i = getMaxValue(array1);
int j = getMaxValue(array2);
//比较返回
return i>j?i:j;
}
测试结果:
使用场景
使用分治法能解决的问题一般具有几个特征:
1) 规模缩小到一定的程度就可以容易地解决;
2) 可以分解为规模较小的相同问题;
3) 分解而成的小问题在结果可以合并;
4) 该问题所分解出的各个子问题是相互独立的,不包含公共的子问题。
解决步骤
1)分解,将要解决的问题划分成若干规模较小的同类问题;
2)求解,当子问题划分得足够小时,用较简单的方法解决;
3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。
经典问题
1、二分查找
二分查找是一种效率较高得查找方法,二分查找必须是有序表。二分查找的查询过程:首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
二分查找步骤:1、先确定中间位置;2、将待查找得值key与中间值比较。若相等,则查找成功并返回该位置,否则须确定新得查找区间,继续二分查找,具体如下:如果中间值大于key,由于数据为有序线性表,则结果一定在左边,否则在右边。然后继续上述步骤,直到中间值和key相等为止。
/**
* 返回数组data中key值得索引
* @param data查找对象
* @param left查找范围左
* @param right查找范围右
* @param key待查找内容
* @return
*/
public static int search(int[] data,int left,int right,int key){
//获取中间位置
int middle = (left+right)/2;
//若key值如相等返回当前位置,否则继续查找
if(data[middle] == key){
return middle;
}else if(data[middle] >key){
return search(data, left, middle-1, key);
}else{
return search(data, middle+1, right, key);
}
}
测试结果:
2、全排列问题
输出一个数组的的全排列。如果数组大小为1是全排列就是它自己;如果数组大小为n大于1时,可以把数组分成第一个数据和其他数据的组成的数组,这个数据作为全排列的第一个,第一数据有n种可能,分成n个分支,剩下的n-1在进行全排列,重复上述操作,得到的数据作为全排列的第二个,依次类推。
/**
* 创建一个全局变量用于接收全排列的结果
*/
public static List<Integer> list = new ArrayList<>();
public static void FullPermutation(int[] arr){
//结束条件
if(arr.length<2 && arr.length>0){
list.add(arr[0]);
System.err.println(list);
list.remove(list.size()-1);
}
for (int i =0;i<arr.length;i++){
list.add(arr[i]);
//将本次循环数据的以外的添加到新的数组中
int[] arr1 = new int[arr.length-1];
int k = 0;
for(int j =0;j<arr.length;j++){
if(i==j){
continue;
}
arr1[k] = arr[j];
k++;
}
//递归调用
FullPermutation(arr1);
//每次循环结束将这次循环添加的数据剔除
list.remove(list.size()-1);
}
}
总结
运用分治策略解决的问题一般来说具有以下特点:
1、原问题可以分解为多个子问题,这些子问题与原问题相比,只是问题的规模有所降低,其结构和求解方法与原问题相同或相似;
2、原问题在分解过程中,递归地求解子问题,由于递归都必须有一个终止条件,因此,当分解后的子问题规模足够小时,应能够直接求解;
3、在求解并得到各个子问题的解后,应能够采用某种方式、方法合并或构造出原问题的解。
在分治策略中,由于子问题与原问题在结构和解法上的相似性,用分治方法解决的问题,大都采用了递归的形式。在各种排序方法中,如归排序、堆排序、快速排序等,都存在有分治的思想。