複雜度分析

  同一個問題可使用不一樣的算法解決,那麼不一樣的算法孰優孰劣如何區分呢?所以咱們須要一個表示方法來表明每一個程序的效率。算法

 

  衡量一個程序好壞的標準,通常是運行時間與佔用內存兩個指標。數組

  不過咱們在寫代碼的時候確定沒法去估量程序的執行時間,由於真實的執行時間受到多方面因素的影響,好比一樣一段程序,放在高配服務器上跑和放在低配服務器上跑徹底是兩個表現效果,好比遍歷一個數組的函數,執行時間徹底取決於調用函數傳入的數組大小。服務器

 

  如何在不運行程序的狀況下,判斷出代碼的執行時間呢?顯然是不可能的。函數

 

  不過咱們雖然沒法預估代碼的絕對執行時間,可是咱們能夠預估代碼基本的執行次數。優化

  一段代碼的執行時間若是有變化,則必定是受到外部輸入的數據所影響,咱們將代碼中全部不變的因素,表示爲大O,將變化的因素做爲基數n,表示爲:O(n),大O的意思是忽略重要項之外的內容,咱們常以這種大O表示法來判斷比較各類算法的執行效率。spa

 

  接下來我會介紹幾種經常使用的複雜度表示方法。code

 

  PS:專業的解釋必然全篇都是數學證實,未免太過於複雜,讓學者更加迷茫,我這裏寫的並非教材,而是最直白的理解。blog

 

時間複雜度

  本節中例舉的各類時間複雜度以好到差依次排序。排序

常數時間 O(1)

  先看下這個函數:內存

    private static void test(int n)
    {
        int a = 2;
        int b = 3;
        int c = a+b;
        System.out.println(c);
    }

  一共4行代碼,CPU要將a的值寫入內存,b的值寫入內存,a和b進行計算,將計算結果寫入c,最後將c輸出到控制檯。

  儘管計算機內部要作這麼多事情,這段代碼的時間複雜度依然是O(1),緣由是這幾行代碼所作的操做是固定的,是不變的因素。

 

  再看下這個函數:

    private static void test(int n)
    {
        for (int i=0;i<100000;i++){
            System.out.println(i);
        }
    }

  循環10W次,可能你以爲功耗可能有點大,不過它的時間複雜度仍然是O(1)。

 

  咱們能夠這麼固定的認爲:不管接收的參數怎麼變化,只要代碼執行次數是無變化的,則用1來表示。 凡是O(1)複雜度的代碼,一般表明着它是效率最優的方案。

 

對數時間 O(log n)

  廣泛性的說法是複雜度減半,就像紙張對摺。

  示例代碼:

    private static void test(int n)
    {
        for (int j=1;j<=n;j=j*2){
            System.out.println(j);
        }
    }

  這段代碼的執行效果並不是是一次折半,它是次次折半,以2爲底,不斷的進行冪運算,實際上只要有冪指數關係的,無論你的底數是幾,只要可以對原複雜度進行求冪逆運算咱們均可以稱之爲O(log n)

  好比:

    private static void test(int n)
    {
        for (int j=1;j<=n;j=j*3){
            System.out.println(j);
        }
    }

  在忽略係數、常數、底數以後,最後均可以表示爲O(log n),只不過咱們遇到的算法幾乎不會出現一些極端例外狀況,對數時間的所在地常見以二分查找爲表明。

 

線性時間 O(n)

  咱們將test方法稍稍修改一下:

    private static void test(int n)
    {
        for (int i=0;i<n;i++){
            System.out.println(i);
        }
    }

  修改以後此次不是執行10W次,而是執行n次,n是由參數傳入的一個未知值,在沒有真實運行的時候咱們沒法判斷這個n究竟是多少?由於它能夠是任意int型數字,你能夠這麼認爲:在理想的狀況下,它的複雜度是O(1),在惡劣的狀況下,它的複雜度是無限大。徹底取決於方法調用方。

  直白的說,for循環就是循環n次,所以這段代碼的時間複雜度爲O(n),這種複雜度經常表現爲線性查找。

 

線性對數時間 O(n log n)

  線性對數時間也就是線性時間嵌套對數時間:

    private static void t(int n){
        for (int i=0;i<n;i++){
            test(n);
        }
    }
    private static void test(int n)
    {
        for (int j=1;j<=n;j=j*2){
            System.out.println(j);
        }
    }

  t這個方法的時間複雜度就是O(n log n)

 

平方時間 O(n^2)

  平方時間就是執行程序須要的步驟數是輸入參數的平方,最多見的是嵌套循環:

    private static void test(int n)
    {
        for (int i=0;i<n;i++){
            for (int j=n;j>0;j--){
                System.out.println(j);
            }
        }
    }

 

其餘時間

  比O(n^2)還要慢的天然有立方級O(n^3)

  比O(n^3)更慢慢的還有指數級O(2^n)

  慢到運行一次程序要繞地球三百圈的有O(n!)

 

  正常狀況下咱們不會接觸到這些類型的算法。

 

空間複雜度

  所謂空間,就是程序運行佔用的內存空間,空間複雜度指的就是執行算法的空間成本。

 

  這裏咱們拋一道題來作例子:在一個數組中找出有重複的值,如數組[3,8,13,7,15,8,6,6] 找出8和6

 

  解法:

    private static void test(int[] arr)
    {
        for (int i=0;i<arr.length;i++){
            for(int j=0;j<i;j++){
                if(arr[j] == arr[i]){
                    System.out.println("找到了:"+arr[i]);
                }
            }
        }
    }

  很顯然:時間複雜度爲O(n^2)。

 

  那咱們還可使用一種更優的解法:

    private static void test(int[] arr)
    {
        HashSet hashSet = new HashSet();
        for (int i=0;i<arr.length;i++){
            if(hashSet.contains(arr[i])){
                System.out.println("找到了:"+arr[i]);
            }
            hashSet.add(arr[i]);
        }
    }

  也許你會驚訝的發現,時間複雜度被優化成了O(n)。

 

  雖然時間複雜度下降成了O(n),可是付出的代價是空間複雜度變成了O(n),由於新的解法使用了一個HashSet來存儲數據,存儲數據天然要佔用內存空間,而佔用的空間大小徹底取決於傳入數組大小。

  咱們之因此說第二種解法更優,實際上是一種常規思想,由於現實中絕大部分狀況,時間複雜度顯然比空間複雜度更爲重要,咱們寧願多分配一些存儲空間做爲代價,來提高程序的執行速度。

 

  總而言之,比較兩個算法優劣的指標有兩個,時間複雜度與空間複雜度,優先比較時間複雜度,時間複雜度相同的狀況下比較空間複雜度。

 

  最後:感謝閱讀。

相關文章
相關標籤/搜索