分治思想
分治法(divide and conquer,D&C) :将原问题划分成若干个规模较小而结构与原问题一致的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
➢容易确定运行时间,是分治算法的优点之一。
➢分治模式在每一层递归上都有三个步骤:
- 一分解(Divide) :将原问题分解成一系列子问题;
- 一解决(Conquer):递归地解各子问题。若子问题足够小,则直接有解;
- 一合并(Combine):将子问题的结果合并成原问题的解。
分治关键点
- 原问题可以一直分解为形式相同子问题,当子问题规模较小时,可自然求解,如一个元素本身有序。
- 子问题的解通过合并可以得到原问题的解。
- 子问题的分解以及解的合并一定是比较简单的,否则分解和合并所花的时间可能超出暴力解法,得不偿失。
快排排序
快排是Java里内部封装的算法思想。他的事件复杂度是O(nlgn)级别的,相对于O(n^2)快了很多。快排将一个数组的排序分为两个子数组的排序问题,每个子数组的问题规模是O(n)级别的。所以事件复杂度为O(nlgn)。取数组的主元,令主元左边的值都小于主元,主元右边的值都大于主元。以主元为界限分为两个子数组,对两个子数组继续按此取主元,直至单元素数组。找主元位置介绍两种方法,第一种是单向法,拿图举个例子:
每次主元取第一个元素,然后分别设立头尾指针。每次检测头指针所指数值和主元比较,如果小于主元,头指针右移。如果大于主元,和尾指针所指数之交换数值,尾指针左移。图中2<6,所以右移头:
头指针的8>6,与尾指针所指7交换位置,尾指针左移。
头指针的7>6,与尾指针所指9交换位置,尾指针左移。
头指针的9>6,与尾指针所指4交换位置,尾指针左移。
头指针的4<6,头指针右移。
头指针的1<6,头指针右移。
头指针的5<6,头指针右移。
头指针的3<6,头指针右移。
头指针小于尾指针,此时,主元指针的值与尾指针的值交换数值。
这时,一轮找主元就结束了,我们需要返回主元6的索引,可以看到,6的左边全都是小于6的,6的右边都是大于6的值,分成了两块,但是两个子数组内部没有顺序,我们可以再分别对两个子数组找主元,最后会达成整体有序的结果。
public static void quickSort(int[] A,int p,int r){
if (p < r) {
//找到主元位置
int q = partition(A, p, r);
//主元左边排序
quickSort(A, p, q - 1);
//主元右边排序
quickSort(A, q + 1, r);
}
}
// 单向扫描
private static int partition(int[] a, int p, int r) {
int pivot = a[p];
int sp = p + 1;
int bigger = r;
while (sp <= bigger) {
if (a[sp] <= pivot) {
sp++;
}
else {
Util.swap(a, sp, bigger);
bigger--;
}
}
Util.swap(a, p, bigger);//自写的交换数值方法
return bigger;
}
另外一种找主元的方法是双向法,我们还是用上述例子演示一轮。
还是先定数组第一个值为主元,然后定首尾指针。头指针向右移,检测到大于主元值停下,尾指针向左移,检测到小于主元值停下,交换头尾指针对应的数组数值。
头指针左移动。
8>6头指针停,开始移动尾指针。
4<6,尾指针停止,交换两数数值。
继续向左移动头指针。
8>6,头指针停止,移动尾指针。
检测尾指针小于头指针,遍历数组结束,交换主元和尾指针数值。
双向法,需时刻检测头尾指针是否满足头>尾。满足情况,就代表这一轮已经结束。
public static void quickSort(int[] A,int p,int r){
if (p < r) {
int q = partition2(A, p, r);
quickSort(A, p, q - 1);
quickSort(A, q + 1, r);
}
}
// 双向扫描
public static int partition2(int[] A,int p,int r){
int pivot=A[p];
int sp = p + 1;
int bigger = r;
while (sp<=bigger){
while (sp<=bigger&&A[sp]<=pivot) {
sp++;
}
while (sp<=bigger&&A[bigger]>pivot) {
bigger--;
}
if (sp<bigger){
Util.swap(A,sp,bigger);
}
}
Util.swap(A,p,bigger);
return bigger;
}
快排这个方法思想对我们写算法有很大的帮助,如何去把一个O(n^2)的算法优化,我们可以使用分治思想,把一个大问题变成小问题再合并,小问题的规模只要够小,我们就可以写出不错的算法。
归并排序
归并排序就是一个非常标准的分治思想了,取数组的中间位置,分别对前后排序,然后再合前后。
private static int[] helper;
public static void Sort(int[] arr){
helper = new int[arr.length];
Sort(arr,0,arr.length-1);
}
//归并排序
public static void Sort(int[] A,int low,int high){
if (low < high) {
int middle=low+((high-low)>>1);
Sort(A, low, middle);
Sort(A, middle + 1, high);
Merage(A,low,high,middle);
}
}
//合并
private static void Merage(int[] a, int low, int high, int middle) {
System.arraycopy(a,low,helper,low,high-low+1);
int left=low;
int right = middle + 1;
int index=low;
while (left<=middle&&right<=high){
if (helper[left] >= helper[right]) {
a[index++]=helper[right++];
} else if (helper[left] < helper[right]) {
a[index++] = helper[left++];
}
}
while (left <= middle) {
a[index++]=helper[left++];
}
}