聲明:本筆記所涉及的資料來源於牛客網
常數時間的操做:一個操做若是和數據量沒有關係,每次都是固定時間內完成的操做,叫作常數操做。個人理解是這種操做最終的執行就是執行彙編命令,而彙編命令執行花費的時間都是有限的機器時鐘時間,能夠簡單理解爲執行一個相加指令,因此常數操做花費的時間是肯定有限的,和數量級不要緊。java
時間複雜度爲一個算法流程中,常數操做數量的指標,經常使用O(讀做 big O)來表示。具體來講,在常數操做數量的表達式中,只要有高階項,不要低階項,也不要高階項的係數,剩下的部分若是記爲f(N),那麼時間複雜度爲O(f(N))。算法
評價一個算法流程的好壞,先看時間複雜度的指標,而後再分析不一樣數據樣本下的實際運行時間,也就是常數項時間。數組
一個有序數組A,另外一個無序數組B,請打印B中的全部不在A中的數,A數組長度爲N,B數組長度爲M。
這至關於兩個for循環,因此常數操做量f(M,N) = M N ,因此算法複雜度爲O(M N)
二分查找常數操做量約爲Log2N,因此總的算法複雜度爲 O(M * logN),log下標可省,通常看作2
排序時間複雜度可爲O(M logM),外排打印時間複雜度爲O(M + N),全部總的時間複雜度爲O(M logM) + O(M + N)
因此具體算法的優劣還要看實際的數量級,算法二確定比算法一要優,算法二和算法三須要考慮M,N的取值範圍來判斷dom
若是你有個想要測的方法a,你能夠實現一個絕對正確可是複雜度很差的方法b,而後實現一個隨機樣本產生器,還要實現一個對比的方法,這樣方法a和方法b比對屢次來驗證方法a是否正確。並且若是有一個樣本使得對比出錯,能夠打印樣本分析是哪一個方法錯誤。當樣本數量不少時比對測試依然正確,能夠肯定方法a已經正確。測試
第一次冒:0 ............. n-1
第二次冒:0 ....... n-2
第三次冒:0 ... n-3
直到冒完
全部僞代碼能夠這樣:ui
public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int e = arr.length - 1; e > 0; e--) { for (int i = 0; i < e; i++) { if (arr[i] > arr[i + 1]) { swap(arr, i, i + 1); } } } }
可知當數量級爲N時,常數操做量就是N*N,時間複雜度就爲O(N^2),空間複雜度由於只是操做原數組,沒有聲請什麼額外的空間,因此爲常數,額外空間複雜度爲O(1)spa
第一次從0 .................n-1中選擇出最小(大)值放在0
第二次從 1 .................n-1中選擇出最小(大)值放在1
第三次從 2 .................n-1中選擇出最小(大)值放在2
知道選擇完
僞代碼:指針
public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int e = 0; e < arr.length-1; e ++) {// 最後一個不用比 int min = e; int i = e + 1; while( i < arr.length ){ min = arr[min] < arr[i] ? i : min; i++; } swap(arr,e,min); } }
同理:時間複雜度O(N^2),額外空間複雜度O(1)code
遞歸的實現實質是方法數據的棧的壓入和彈出,壓入或彈出的數據即爲方法的一些屬性數據,如方法指針,參數,結果等
最典型的應用就是歸併排序
套路就是:最外面一層把主問題分爲兩個小問題,加一個merge處理塊
套路公式:
master公式:T(N) = a * T (N / b) + O(N ^ d)blog
a 爲子規模執行次數,b 爲主規模分了幾個子規模,d 爲merge處理的負責度裏的N的係數
例:歸併排序
public static void mergeSort(int[] arr) { if (arr == null || arr.length < 2) { return; } mergeSort(arr, 0, arr.length - 1); } public static void mergeSort(int[] arr, int l, int r) { if (l == r) { return; } int mid = l + ((r - l) >> 1); mergeSort(arr, l, mid); mergeSort(arr, mid + 1, r); merge(arr, l, mid, r); } public static void 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; while (p1 <= m && p2 <= r) { 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]; } }
最外層將主規模分爲了2個子規模f(n/2)執行,因此a = 2,b = 2,merge處理複雜度爲O(2N) = O(N) ,因此d = 1,因此T(N) = 2(N/2) + O(N)
因此log(b,a) = d = 1 ,時間複雜度爲O(N * logN)
額外空間複雜度爲O(N),主要是每次merge都申請了新的數組空間
public static void main(String[] args){ int[] arr = Util.generateRandomArray(8,10); smallSum(arr); } public static void smallSum(int[] arr){ if (arr == null || arr.length < 2) { return; } System.out.println("原數組"); Util.printArray(arr); System.out.println(""); int smallSum = deal(arr,0,arr.length-1); System.out.println("處理後的數組:"); Util.printArray(arr); System.out.println("小和爲:"+smallSum); } public static int deal(int[] arr,int left,int right){ if (left == right){ return 0; } int middle = left + ((right-left)>>1); return deal(arr,left,middle) + deal(arr,middle+1,right)+merge(arr,left,middle,right); } public static int merge(int[] arr,int left,int middle,int right){ int[] help = new int[right-left+1]; int sum = 0; int index = 0; int p1=left; int p2=middle+1; while (p1 <= middle && p2 <= right){ if (arr[p1] < arr[p2]){ System.out.println((right-p2+1)+"個"+arr[p1]+" \n");//打印加數 } sum += arr[p1] < arr[p2]? (right-p2+1)*arr[p1]:0; help[index++] = arr[p1] < arr[p2]? arr[p1++] : arr[p2++]; } while (p1 <= middle){ help[index++] = arr[p1++]; } while (p2 <= right){ help[index++] = arr[p2++]; } for (int i = 0;i < help.length;i++){ arr[left+i] = help[i]; } return sum; }
隨機測試結果:
在一個數組中,左邊的數若是比右邊的數大,則折兩個數構成一個逆序對,請打印全部逆序對。
public static void main(String[] args){ int[] arr = Util.generateRandomArray(8,10); System.out.println("原始數組:"+Arrays.toString(arr)); nixudui(arr); System.out.print("處理後數組:"+Arrays.toString(arr)); } public static void nixudui(int[] arr){ deal(arr,0,arr.length-1); } public static void deal(int[] arr,int left,int right){ if (left == right){ return; } int middle = left + ((right-left)>>1); deal(arr,left,middle); deal(arr,middle+1,right); merge(arr,left,middle,right); } public static void merge(int[] arr, int left,int middle, int right){ int p1=left; int p2 = middle+1; int i = 0; int[] help = new int[right-left+1]; while (p1 <= middle && p2 <= right){ if (arr[p1] > arr[p2]){ for(int j = 0;j < middle-p1+1;j++){ System.out.println("逆序對:"+arr[p1+j]+" "+arr[p2]); } help[i++] = arr[p2++]; }else { help[i++] = arr[p1++]; } } while (p1 <= middle){ help[i++] = arr[p1++]; } while (p2 <= right){ help[i++] = arr[p2++]; } for ( i = 0;i < help.length;i++){ arr[left+i] = help[i]; } }
執行結果:
和小和問題是一樣的套路,時間複雜度爲O(N * log N),空間複雜度爲O(N)
二分查找的基本思想是將n個元素分紅大體相等的兩部分,取a[n/2]與x作比較,若是x=a[n/2],則找到x,算法停止;若是x<a[n/2],則只要在數組a的左半部分繼續搜索x,若是x>a[n/2],則只要在數組a的右半部搜索x.
時間複雜度無非就是while循環的次數!
總共有n個元素,
漸漸跟下去就是n,n/2,n/4,....n/2^k(接下來操做元素的剩餘個數),其中k就是循環的次數
因爲你n/2^k取整後>=1
即令n/2^k=1
可得k=log2n,(是以2爲底,n的對數)
因此時間複雜度能夠表示O(h)=O(log2n)