master公式
T(N) = a*T ( N/b ) + O (N^d)
当log(b,a) > d => 复杂度为O ( N^log(b,a) ) 当log(b,a) = d => 复杂度为O ( N^d * logN ) 当log(b,a) < d => 复杂度为O ( N^d )
关于master公式详情可以参考: 算法的复杂度与 Master 定理
1. 求一个无序数据的最大值(递归)
递归排序算法的基本思想:将数组分为左右两半部分,分别在左/右半部分别求局部最大值,然后对这两部分的局部最大值求一个全局最大值。递归实际就是系统不断调用栈记录现场相信和还原现场信息的过程,所以所有的递归算法都可以使用非递归实现。时间复杂度O(N)
/** * * 递归排序 * 复杂度T(N) = 2* T(N/2) + O(1) * a=2 b=2 d=0, log(2,2) > 0 * => N */ @Slf4j public class Alg005_SearchMax { @Test public void test(){ for(int i=0;i<5000;i++){ int[] arr = generateArr(); log.info("arr:{}",arr); int max = getMax(arr, 0, arr.length-1); int matchVal = Arrays.stream(arr).max().getAsInt(); if(max != matchVal){ arr =null; log.info("test failed!"); return; } log.info("max:{}",max); arr =null; } log.info("test success!"); } public int getMax(int[] arr, int left, int right){ if(left == right){ return arr[left]; } int mid = left+ ((right-left) >> 1); int lMax = getMax(arr,left,mid); int rMax = getMax(arr, mid+1,right); return Math.max(lMax, rMax); } private int[] generateArr(){ return new Random().ints(-100, 100).distinct().limit(20).toArray(); } }
时间复杂度分析:时间复杂度 T(N) = 2T(N/2) + O(1) .O(1)表示Math.max()方法复杂度。根据master公式可知,a = b = 2,d = 0. log(2,2) = 1< 0,所以时间复杂度为O(N).
2. 将一个无序的数组排序 (归并排序)
归并排序基本思路:先左边排好序,然后右边排好序,然后利用外排的方式进行排序
时间复杂度O(N*logN),额外空间复杂度O(N)
/** * 归并排序 * 复杂度T(N) = 2* T(N/2) + O(N) * a=2 b=2 d=1, log(2,2)=1 * => NlogN */ @Slf4j public class Alg006_MergeSort { @Test public void test(){ for(int i=0;i<5000;i++){ int[] arr = generateArr(); mergeSort(arr, 0, arr.length-1); log.info("arr:{}",arr); } } private void mergeSort(int[] arr, int L, int R){ if(L == R){ return; } int mid = L + ((R-L)>>1); // left part merge sort mergeSort(arr, L, mid); // right part merge sort mergeSort(arr,mid+1,R); merge(arr, L, mid, R); } private void merge(int[] arr, int L, int M, int R){ // additional array int[] help = new int[R-L+1]; int i = 0; int p1 = L; int p2 = M+1; // while(p1<=M && p2<=R){ help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; } // if the point moved to end in the right part while(p1<=M){ help[i++] = arr[p1++]; } // if the point moved to end in the left part while(p2<=R){ help[i++] = arr[p2++]; } // copy the array to arr for(i = 0;i<help.length;i++){ arr[L+i] = help[i]; } } private int[] generateArr(){ return new Random().ints(-100, 100).distinct().limit(20).toArray(); } }
外排:首先将数组的左右两边排好,准备一个辅助数组arr。然后a指针指向左边第一个,b指针指向右边第一个。如果a比b大,那么a指针不变,移动b指针指向下一个数;如果a比b小,那么b指针不动,移动a指针指向下一个数。谁小谁往辅助数组填,直到a或者b指针移动到最后一个数,然后将剩下的拷贝到辅助数组的后面即可。比如现在a指向3,b指向0,b小,辅助数组第一个填0,然后b移动到下一个,指向1,a不动还是指向3,b比a小,将1填进辅助数组,然后b移动到下一个,指向2,a不动还是指向3,b比a小,将2填进辅助数组,b到尽头,将a剩下的拷贝到辅助数组后面,最后将辅助数组拷贝回原数组,排序完成。
外排的复杂度分析:左右两边T(N) = 2T(N/2),a,b指针移动,拷贝到辅助函数O(N),所以T(N) = 2T(N/2)+O(N),利用master公式可得归并排序复杂度为O(N*logN)。
3. 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和
基本思路是在归并排序上的拓展:先左边排好序求小和,然后右边排好序求小和,然后合并左右求小和
/** * 归并排序拓展 - 求小和 */ @Slf4j public class Alg007_SmallSort { @Test public void test(){ for(int i=0;i<5000;i++){ // int[] arr = new int[]{1,2,3,4,5}; // 4*1+3*2+2*3+4*1 int[] arr = generateArr(); int[] arr2 = Arrays.copyOf(arr, arr.length); int val = mergeSort(arr, 0, arr.length-1); int compareVal = compareVal(arr2); log.info("avl:{}, compareVal:{}", val, compareVal); } } private int mergeSort(int[] arr, int L, int R){ if(L == R){ return 0 ; } int mid = L + ((R-L)>>1); // lfet part sort and get small sum // right part sort and get small sum // merge left and right part and get small sum return mergeSort(arr, L, mid) + mergeSort(arr,mid+1,R) + merge(arr, L, mid, R); } private int merge(int[] arr, int L, int M, int R){ // additional array int[] help = new int[R-L+1]; int i = 0; int p1 = L; int p2 = M+1; int val = 0; // while(p1<=M && p2<=R){ // if the p1< p2, add the value (r-p2+1)* arr[p1], because alrady sorted both the left and right part // if p1 >= p2, add 0 val += arr[p1] < arr[p2] ? (R-p2+1)* arr[p1]: 0; help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } // if the point moved to end in the right part while(p1<=M){ help[i++] = arr[p1++]; } // if the point moved to end in the left part while(p2<=R){ help[i++] = arr[p2++]; } // copy the array to arr for(i = 0;i<help.length;i++){ arr[L+i] = help[i]; } return val; } private int[] generateArr(){ return new Random().ints(-100, 100).distinct().limit(20).toArray(); } private int compareVal(int[] arr){ int val = 0; for(int i = arr.length-1;i>0;i--){ for(int j = i-1;j>=0 && i>0;j--){ val += arr[j] < arr[i] ? arr[j] : 0; } } return val; } }