排序算法之归并排序(master公式、归并排序、小和问题)
1、递归算法的master公式
Master公式是用来解决递归问题时间复杂度的公式。
记录主方法的表现形式:
T [n] = aT[n/b] + f (n)(直接记为T [n] = aT[n/b] + O(N^d))
其中 a >= 1 and b > 1 是常量,其表示的意义是n表示问题的规模,a表示递归的次数也就是生成的子问题数,b表示每次递归是原来的1/b之一个规模,f(n)表示分解和合并所要花费的时间之和。
①当d<logb a时,时间复杂度为O(n^(logb a))
②当d=logb a时,时间复杂度为O((n^d)*logn)
③当d>logb a时,时间复杂度为O(n^d)
注意:适用范围为子过程规模相等的情况,否则不适用。
2、归并排序
分析:
①将一个无规律的数组分成两部分,并将这两部分分别排成有序
②定义一个辅助数组(数组和原数组长度相同)和两个指针(分别指向两部分的头)
③指针移动,比较,将较小的一个放入辅助数组,以此进行
④当其中一个数组已经放入完毕,将剩下的一个没有遍历完成的数组放入辅助数组
⑤将辅助数组都给原数组
⑥根据master公式计算时间复杂度:为O(N*logN)
public class Main {
/**
* 归并排序:先取一个中间值mid,在定义两个指针,一个指向头L,一个指向尾R
* 从中间分开,进行左右侧的排序,然后进行归并排序,分别从左右两个排好序列的序列中取
* 再定义指针p1,p2分别指向两个排好顺序的头和尾,分别是L和mid+1
* 进行两个数的判断,比较,小的放入事先准备好的数组中,指针下移
* @param arr
* @param L
* @param R
*/
public static void process(int[] arr,int L,int R){
if(L == R){
return;
}
int mid = L + ((R-L)>> 1);
process(arr,L,mid);
process(arr,mid+1,R);
merge(arr,L,mid,R);
}
private static void merge(int[] arr, int l, int mid, int r) {
//准备一个辅助空间
int[] help = new int[r-l+1];
//定义两个指针,分别指向两个有序序列的头尾
int i = 0;
int p1 = l;
int p2 = mid + 1;
//判断p1和p2都越界,利用三元运算符将大的赋值给辅助数组中
while(p1 <=mid && p2<=r){
help[i++] = arr[p1] <= arr[p2] ? (help[i++] = arr[p1++]) : (help[i++] = arr[p2++]);
}
//当其中一个数组角标越界时候,另一个直接赋值到辅助数组
//当右边数组全部输入完,将左边数组全部放入辅助数组
while(p1 <= mid){
help[i++] = arr[p1++];
}
//当左边数组全部输入完,将右边数组全部放入辅助数组
while(p2 <= r){
help[i++] = arr[p2++];
}
//将辅助数组里面的数,放回原数组
for(int j=0;j<help.length;j++){
arr[l+j] = help[j];
}
}
}
3、归并排序的小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子
[1,3,4,2,5]
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1,3
2左边比2小的数:1
5左边比5小的数:1,3,4,2
所以小和为1+1+3+1+1+3+4+2=16
public class SmallSum {
public static int smallSum(int[] arr){
return process(arr,0,arr.length-1);
}
public static int process(int[] arr,int l,int r){
if(l == r){
return 0;
}
int mid = l +((r-l)>>1);
return process(arr,l,mid)+process(arr,mid+1,r)+merge(arr,l,mid,r);
}
private static int merge(int[] arr, int l, int m, int r) {
int[] help = new int[r-l+1];
int i = 0;
int p1 = l;
int p2 = m+1;
int res = 0;
while (p1 <= m && p2 <= r){
//小和问题的核心
/**
* 代码解释:
* 利用归并排序求小和问题,就是利用每次比较的时候,都知道右边比左边数大的有几个
* 比如说此时左边的数 1 2 4 右边的数 2 4 6 8(归并的特点,每次归并都是左右排好序的)
* 此时p1 = 0;p2 = mid+1;arr[p1] = 1 arr[p2] = 2
* 所以arr[p1] < arr[p2]
* 对于arr[p1] ,因为arr[p2] 此时的数大于arr[p1]
* 又因为arr[p2]此时右边的数都比arr[p2]大,
* 所以大于arr[p1]的数的个数为(r-p2+1)个
*/
res += arr[p1] < arr[p2] ? (r-p2+1)*arr[p1] : 0;
help[i++] = arr[p1]<arr[p2] ? arr[p1++] : arr[p2++];//此时是小于号,表示相等的时候将右边的加入,与归并排序的区别
}
while(p1<=m){
help[i++] = arr[p1++];
}
while(p2<=r){
help[i++] = arr[p2++];
}
for(i = 0;i< help.length;i++){
arr[l+i] = help[i];
}
return res;
}
public static void main(String[] args){
int[] arr = new int[]{1,1,3,2};
System.out.println("小和是:"+SmallSum.smallSum(arr));
System.out.print("排序后:");
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+",");
}
}
}