算法初級筆記(一)認識時間複雜度

聲明:本筆記所涉及的資料來源於牛客網

認識時間複雜度

常數時間的操做:一個操做若是和數據量沒有關係,每次都是固定時間內完成的操做,叫作常數操做。個人理解是這種操做最終的執行就是執行彙編命令,而彙編命令執行花費的時間都是有限的機器時鐘時間,能夠簡單理解爲執行一個相加指令,因此常數操做花費的時間是肯定有限的,和數量級不要緊。java

時間複雜度爲一個算法流程中,常數操做數量的指標,經常使用O(讀做 big O)來表示。具體來講,在常數操做數量的表達式中,只要有高階項,不要低階項,也不要高階項的係數,剩下的部分若是記爲f(N),那麼時間複雜度爲O(f(N))。算法

評價一個算法流程的好壞,先看時間複雜度的指標,而後再分析不一樣數據樣本下的實際運行時間,也就是常數項時間。數組

一個簡單理解時間複雜度的例子:

一個有序數組A,另外一個無序數組B,請打印B中的全部不在A中的數,A數組長度爲N,B數組長度爲M。
  • 算法一:對於B數組的每個數,都在A中經過遍歷的方式找一下
這至關於兩個for循環,因此常數操做量f(M,N) = M N ,因此算法複雜度爲O(M N)
  • 算法二:對於B數組的每個數,都在A中經過二分查找的的方式找一下
二分查找常數操做量約爲Log2N,因此總的算法複雜度爲 O(M * logN),log下標可省,通常看作2
  • 算法三:先把數組B排序,而後用相似外排的方式打印全部在A中出現的數
排序時間複雜度可爲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

  • log(b,a) > d -> 複雜度爲O(N^log(b,a))
  • log(b,a) = d -> 複雜度爲O(N^d * logN)
  • log(b,a) < d -> 複雜度爲O(N^d)

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)

外排

相關文章
相關標籤/搜索