1. 程式人生 > >排序演算法的時間複雜度以及空間複雜度 計數排序

排序演算法的時間複雜度以及空間複雜度 計數排序

時間複雜度為o(n)的排序演算法:不是基於比較的排序演算法;思想來自於桶排序!比如:計數排序和基數排序。其中基數排序中是分別基於個位、十位以及等等更高的位將資料放入桶中,然後再將資料倒出來!

八個經典排序演算法的時間複雜度:

O(n2)插入排序 選擇排序 氣泡排序

O(n*logn)堆排序 希爾排序  快速排序 歸併排序

O(n)不基於比較的排序演算法:計數排序  基數排序

八個經典排序演算法的空間複雜度:

O(1)插入排序 選擇排序 氣泡排序  堆排序 希爾排序

O(logN)~O(n) 快速排序(取決於劃分情況)

O(N) 歸併排序(通過手搖演算法優化之後空間複雜度可以達到O(1),但同時時間複雜度會上升)

O(M)計數排序  基數排序 (M是桶的數量)

排序演算法的穩定性:

穩定性:假定待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,稱這種排序演算法是穩定的,否則稱為不穩定的。

穩定的排序演算法:

氣泡排序 插入排序 歸併排序 計數排序 基數排序 桶排序

不穩定的排序演算法:

選擇排序 快速排序 希爾排序 堆排序

工程上的排序:

工程上的排序是綜合排序;

陣列極小時,插入排序(常量係數比較小

陣列較大時,快速排序或其他o(n*logn)的排序

計數排序是一個非基於比較的排序演算法,該演算法於1954年由 Harold H. Seward 提出。它的優勢在於在對一定範圍內的整數排序時,它的複雜度為Ο(n+k)(其中k是整數的範圍),快於任何比較排序演算法。

[1-2] 當然這是一種犧牲空間換取時間的做法,而且當O(k)>O(n*log(n))的時候其效率反而不如基於比較的排序(基於比較的排序的時間複雜度在理論上的下限是O(n*log(n)), 如歸併排序,堆排序)。計數排序演算法之所以能取得線性計算時間的上界是因為對元素的取值範圍作了一定的限制。必須已知要排序的資料的取值範圍。

計數排序的演算法如下:

class CountingSort {
public:
    int* countingSort(int* A, int n) {
        // write code here
         int max=A[0];
        for(int i=0;i<n;i++){
           if(A[i]>max)
               max=A[i];
        }
        int* B=new int[max+1];
        //int B[max+1];
        for(int k=0;k<max+1;k++)
        {
            B[k]=-1;
        }//代表max個桶
        int index;//代表第index個桶,第index個桶裡面存放的是index+1
        for(int j=0;j<n;j++){
            index=A[j];
            B[index]=B[index]+1;
        }
        int count=n-1;//倒著輸出才能保證演算法是穩定的,也就是說相同值的資料能夠按照之間儲存的順序進行輸出
        for(int t=max;t>=0;t--){//但是這段程式中有兩個迴圈,所以會導致演算法的時間複雜度不是O(n)。
            while(B[t]!=-1){
            A[count]=t;
            count--;
            B[t]=B[t]-1;
            }
        }
        delete B;
        return A;
    }
};

對於上面的演算法來說,儘管結果執行是正確的,但是時間複雜度並不是o(n),因為最後的一個迴圈裡面嵌套了一個迴圈,所以導致演算法的時間複雜度為o(n2)。之所以會這樣是因為B中存放的並不是t所處陣列中的位置,而是陣列中t值的數量!!!!也就是說上面程式的邏輯有問題!!!正確的程式應該如下:

class CountingSort {
public:
    int* countingSort(int* A, int n) {
        // write code here
         int max=A[0];
        for(int i=0;i<n;i++){
           if(A[i]>max)
               max=A[i];
        }
        int* B=new int[max+1];//實際上有max+1個數據,因為還有一個0,因為陣列大小中有變數max,所以需要用動態分配,不能用int B[max+1]來宣告
        int* C=new int[n];//C中存放的是排好序的資料
        //int B[max+1];
        for(int k=0;k<max+1;k++)
        {
            B[k]=0;
        }//B代表max+1個桶
        int index;//代表第index個桶,第index個桶裡面存放的是值為index的資料的個數
        for(int j=0;j<n;j++){
            index=A[j];
            B[index]=B[index]+1;
        }
        for(int m=1;m<max+1;m++){
            B[m]=B[m]+B[m-1];//這樣B中的值代表m所處排好序的序列中的位置
            
        }
        //藉助於另一個數組可以降低時間複雜度
        int pos,value;
        for(int p=n-1;p>=0;p--){
            value=A[p];
            pos=B[value]-1;
            C[pos]=value;
            B[value]--;
        }
        for(int r=0;r<n;r++){
            A[r]=C[r];
        }//這裡可以用 memcpy(A,C,n*sizeof(int));,但是絕對不可以用 memcpy(A,C,sizeof(A));或者 memcpy(A,C,sizeof(*A));
        delete C;
        delete B;
        return A;
    }
};

上面的程式才是正確的計數排序的程式,符合邏輯!其中陣列B中存放的是相應下標的資料所處的排好序的陣列中的位置;最後直接掃描陣列A,將相應的資料按照陣列B中的位置進行排列,放到陣列C中就可以了!最後將陣列C中的資料賦值給陣列A!對於上面的程式,在倒數第二個迴圈中也可以從頭開始向後將資料進行排序放到陣列C中,但是這樣就破壞了排序演算法的穩定性,這樣會導致值相同的資料中最後放到桶B中的資料先放到前面的位置!!所以必須採用倒序的方式輸出!

對於上面的程式來說,在最後一個迴圈中,可以用memcpy(A,C,n*sizeof(int));但是絕對不可以用另外兩個!!!!!!

原因如下:

sizeof是一個關鍵字,一個操作符,不是一個函式,它的計算結果在編譯時就已經確定了的,不是在執行時,如果你要在執行時分配一個空間,顯然大小在執行時應該也是可知的。
你不能要求sizeof在編譯時知道執行時的東西,這是不可能的。而A只是一個指標變數,它的大小用sizeof計算即sizeof(A)最後結果只是4.只是一個地址所佔的空間的大小。A所指向的空間的大小n只有在執行時通過動態分配才能知道;
sizeof(*p);如果指標指向的是單個變數還可以這樣用但是如果指向的是一個數組就不行了,只能得到陣列第一個元素的長度。

但是如果A是一個正常的陣列也就是A[100]之類的,這樣是可以通過sizeof(A)來計算整個陣列的長度的!!!!

c++中變數做陣列長度:

在c++中時不支援變數作為陣列長度引數的,如 int n=10;byte bs[n];   這樣寫會提示編譯錯誤”表示式必須含有常量值“。

雖然用變數宣告陣列大小會報編譯錯誤,但是可以通過指標來動態申請空間實現動陣列長度的變數賦值,寫法如下:

1 int length = 10;
2 int * varArray;
3 varArray = new int[length];

這樣varArray就可以當做陣列來用了,這個陣列的長度可以在程式執行時由計算得來。如果是普通的陣列如int is[10] 編譯時必須能確定陣列長度,不然會報編譯錯誤,這樣靈活性受限比較大。我想這個就是new的存在原因之一吧,在棧中分配的記憶體,大小都是編譯時就確定好的,如果想在執行時來動態計算使用記憶體的大小的話,就要用new這樣的動態分配函式,來達到更高的靈活性。

可以自己宣告一個結構體,來代表這個指標實現的陣列,這樣可讀性會高點,用起來也方便點。

注意:c++ 用new分配空間以後,不用的時候要記得delete釋放記憶體,不然會有記憶體洩露問題。