分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。下面我们通过几个经典的例子来深入了解分治的思想。利用分治策略求解时,所需时间取决于分解后子问题的个数、子问题的规模大小等因素,而二分法,由于其划分的简单和均匀的特点,是经常采用的一种有效的方法,例如二分法检索。
二分查找
问题描述
给定一个按升序排列的数组,找出其中某个特定的值,返回其下标的值。
eg: 1,2,3,4,5,6,7,8,9 ,请找出其中9的位置,返回其下标。
问题分析
当我们拿到这个问题时,第一种方法便是暴力破解,但当数据过于庞大时,暴力破解就会显得效率非常低下。那有没有一种更好的解决方法呢?显然是有的,这便是基于分治思想下的二分查找。对于一个有序的数组,我们可以将其一分为二,要找的的值要么在分界点,要么在分界点的左边,要么在分界点的右边,当我们找到分界点的区间后又可以按照同样的方法继续确定其下一个区间,直到每个区域不可再划分,我们便找到了答案。
代码
public class Main{
public static void main(String[] args){
int[] date = new int[]{1,2,3,4,5,6,7,8,9};
int index = binarySeach(0,8,date,9);
System.out.println(index);
}
//二分查找
public static int binarySeach(int low,int high,int[] date,int num){
while(low <= high){
int middle = (low+high)/2;
if(date[middle] == num){
return middle;
} else if (date[middle]>num) {
high = middle-1;
} else {
low = middle + 1;
}
}
return -1; //表示没有找到
}
}
快速排序
在利用快速排序算法解决排序问题时?实际上也使用了分治的思想。快速排序的基本思路是,首先任意选取一个记录作为枢纽(通常选第一个)然后我们将所有关键字较它小的记录安置在他的位置之前,将所有关键字较它大的记录安置在他的位置之后,由此我们已该枢纽为分界点,将序列分为两个子序列,重复上诉过程,直到区间缩短为1.
/*
写法一
使用中间变量temph和templ,此方法不需要枢纽归位
*/
import java.util.Arrays;
public class Main{
public static void main(String[] args){
int[] date = new int[]{49,38,65,97,76,13,27};
System.out.println(Arrays.toString(quickSort(0,date.length-1,date)));
}
//快速排序(升序排列)
public static int[] quickSort(int low,int high,int[] date){
int keyPoint = date[low]; //将最低位设置为枢纽
int l = low,h = high; //保存low,high的值方便递归调用
while(l<h){
//从high位置依次往前检索,碰到date[high]<keyPoint,就交换位置,否者则继续向前检索
while(l<h && date[h]>=keyPoint){
h--;
}
int temph = date[h];
date[h] = date[l];
date[l] = temph;
//从low位置依次向前检索碰到date[low]>keyPoint就交换位置,否者则继续向前检索
while(l<h && date[l]<=keyPoint){
l++;
}
int templ = date[l];
date[l] = date[h];
date[h] = templ;
}
//递归调用
if(low<high){
quickSort(low,l-1,date);
quickSort(l+1,high,date);
}
return date;
}
}
分析
附设两个指针low,high,他们的值分别是date[low]和date[high],枢纽的值为keyPoint。一趟快速排序的具体过程是,从high所指位置起向前搜索,当碰到date[high]的值小于keyPoint时交换位置,由于此时keyPoint的位置是date[low],所以代码为date[low] = date[high]。然后在从low的位置依次向前搜索,当碰到一个值大于keyPoint的值时,date[low]与keyPoint再次交换位置,而此时keyPoint的值为date[high],所以代码为date[high] = date[low],由此不停重复上两个过程,直到不满足low<high为止。
/*
写法二
不使用中间变量,需要枢纽归位
*/
public class Main{
public static void main(String[] args){
int[] date = new int[]{49,38,65,97,76,13,27};
}
//快速排序按升序排列
public static int[] quickSort(int low,int high,int[] date){
int keyPoint = date[low]; //设置枢纽
int l = low,h = high; //保存low和high的值,便于后续递归
while(l<h){
while(l<h && date[h]>=keyPoint){
h--;
}
/*
直接将date[low]变成date[high],
由于第一次交换时必定交换枢纽值,而枢纽值已经保存,我们就可以不必担心
而当执行此操作时对应的date[high]的值便变成了两个,
再执行覆盖时,也不用担心数据的丢失问题
*/
date[l] = date[h];
while(l<h && date[l]<=keyPoint) {
l++;
}
date[h] = date[l];
}
date[l] = keyPoint; //枢纽归位
//递归
if(low<high){
quickSort(low,l-1,date);
quickSort(l+1,high,date);
}
return date;
}
}
以上即是对分治思想的简单引入,其关键是要理解将问题规模化。分治思想下,二分法是非常常用的方法,往往会配合递归的使用。