各排序的時間復雜度分析
插入排序——直接插入排序
-
在最好的情況下,序列已經是有序的,每次插入元素最多只需要與有序表中最后一個元素進行比較,時間復雜度為O(n)。在最壞的情況下,每次插入元素需要與前面所有的元素進行比較,時間復雜度為O(n2),平均時間復雜度為O(n2)。
-
代碼分析
public static <T extends Comparable<T>> void insertionSort(T[] data) { //外層循環次數為n,時間復雜度為O(n) for (int index = 1; index < data.length; index++) { T key = data[index]; int position = index; //內層循環次數為n,時間復雜度為O(n) while (position > 0 && data[position-1].compareTo(key) > 0) { data[position] = data[position-1]; position--; } data[position] = key; } }
插入排序——希爾排序
-
它的基本思想是:假設序列中有n個元素,首先選擇一個間隔gap,將全部的序列分為gap個子序列,然后分別在子序列內部進行簡單插入排序,得到一個新的主序列;而后縮小gap,再得到子序列,對子序列進行簡單插入排序,又再次得到新的主序列,直到gap=1為止。在算法中,排序前期,由於gap值比較大,插入排序的元素個數少,排序快,到了排序后期,由於前面的排序導致序列已經基本有序,插入排序對於有序的序列效率很高。所以說希爾排序最好的情況:縮小增量的插入排序,待排序已經有序。時間復雜度O(n),一般情況為下平均時間復雜度o(n1.3),最差也是時間復雜度o(n1.3)。希爾排序的時間復雜度與gap的選擇有很大的關系,一般時間復雜度是低於O(n^2)。
-
需要注意的是:希爾排序的時間復雜度依賴於所取希爾序列的函數,但是到目前為止還沒有一個最好的希爾序列。有人在大量的實驗后得出結論:當n在某個特定的范圍后希爾排序的比較和移動次數減少至n^1.3 不管增量序列如何取值,都應該滿足最后一個增量值為1.
選擇排序——簡單選擇排序
-
簡單選擇排序不論是否序列已經有序每個數都需要進行n-1次最小數選擇,所以它的最好、最壞以及平均時間復雜度都是O(n^2)。
-
代碼分析
public static <T extends Comparable<T>> void selectionSort(T[] data) { int min; T temp; //外層循環次數為n-1,時間復雜度為O(n) for (int index = 0; index < data.length-1; index++) { min = index; //內層循環次數為n,時間復雜度為O(n) for (int scan = index+1; scan < data.length; scan++) { if (data[scan].compareTo(data[min])<0) { min = scan; } } swap(data, min, index); } }
選擇排序——堆排序
-
把待排序的元素按照大小在二叉樹位置上排列,排序好的元素要滿足:父節點的元素要大於等於其子節點;這個過程叫做堆化過程,如果根節點存放的是最大的數,則叫做大根堆;如果是最小的數,自然就叫做小根堆了。根據這個特性(大根堆根最大,小根堆根最小),就可以把根節點拿出來,然后再堆化下,再把根節點拿出來,,,,循環到最后一個節點,就排序好了。整個排序主要核心就是堆化過程,堆化過程一般是用父節點和他的孩子節點進行比較,取最大的孩子節點和其進行交換;但是要注意這應該是個逆序的,先排序好子樹的順序,然后再一步步往上,到排序根節點上。然后又相反(因為根節點也可能是很小的)的,從根節點往子樹上排序。最后才能把所有元素排序好。
-
時間復雜度在任何情況下都為O(nlogn)
堆排序的時間復雜度為O(nlogn),需要一個臨時空間用於交換元素,所以空間復雜度為O(1)。 -
排序包括兩個階段,初始化建堆和重建堆。所以堆排序的時間復雜度由這兩方面組成。
-
初始化堆:假設高度為k,則從倒數第二層右邊的節點開始,這一層的節點都要執行子節點比較然后交換(如果順序是對的就不用交換);倒數第三層,則會選擇其子節點進行比較和交換,如果沒交換就可以不用再執行下去了。如果交換了,那么又要選擇一支子樹進行比較和交換;高層也是這樣逐漸遞歸。
那么總的時間計算為:s = 2^( i - 1 ) * ( k - i );其中 i 表示第幾層,2^( i - 1) 表示該層上有多少個元素,( k - i) 表示子樹上要比較的次數。
S = 2^(k-2) * 1 + 2(k-3)2…..+2(k-2)+2(0)*(k-1) ===> 因為葉子層不用交換,所以i從 k-1 開始到 1;
S = 2^k -k -1;又因為k為完全二叉樹的深度,而log(n) =k,把此式帶入;
得到:S = n - log(n) -1,所以時間復雜度為:O(n) -
排序重建堆:在每次重建時,隨着堆的容量的減小,層數會下降,函數時間復雜度會變化。重建堆一共需要n-1次循環,每次循環的比較次數為log(i),相加約為nlog(n)。
-
所以總的時間復雜度為O(n+nlogn)=O(nlogn)。
-
交換排序——冒泡排序
-
在最好的情況下,序列已經是有序的,只進行了第一趟冒泡比較,此時算法的時間復雜度為O(n)。在最壞的情況下,執行了n-1次冒泡,時間復雜度為O(n^2)。
-
代碼分析:
public static <T extends Comparable<T>> void bubbleSort(T[] data) { int position, scan; T temp; //循環次數為n-1,時間復雜度為O(n) for (position = data.length - 1; position >= 0; position--) { //循環次數為n,時間復雜度為O(n) for (scan = 0; scan <= position - 1; scan++) { if (data[scan].compareTo(data[scan+1]) > 0) { swap(data, scan, scan + 1); } } } }
交換排序——快速排序
- 時間復雜度分析:快速排序每次要將列表分成兩個分區,遞歸的次數取決於元素的數目,最理想的情況下,每次划分左右兩部分的長度相等,需要遞歸次nlog2n次,平均次數也為nlog2n次,而每次分區后要進行n次比較操作,因此平均時間復雜度為O(nlogn)。快速排序比大部分排序算法都要快,但快速排序是一個非常不穩定的排序,因為若初始序列按關鍵碼有序或基本有序時,快速排序反而蛻化為冒泡排序,此時它的時間復雜度就為O(n^2)了。
歸並排序
-
歸並排序是先遞歸的把數組划分為兩個子數組,一直遞歸到數組中只有一個元素,然后再調用函數把兩個子數組排好序,因為該函數在遞歸划分數組時會被壓入棧,所以這個函數真正的作用是對兩個有序的子數組進行排序。
-
時間復雜度分析:每次歸並時要將待排序列表中的所有元素遍歷一遍,因次時間復雜度為O(n)。與快速排序類似,歸並排序也先將列表不斷分區直至每個列表只剩余一個元素,這個過程需要進行log2n次分區。因此歸並排序的平均時間復雜度為O(nlogn)。因為不管元素在什么情況下都要做這些步驟,所以花銷的時間是不變的,所以該算法的最優時間復雜度和最差時間復雜度及平均時間復雜度都是一樣的為:O( nlogn )。
-
復雜公式分析:總時間=分解時間+解決問題時間+合並時間。分解時間就是把一個待排序序列分解成兩序列,時間為一常數,時間復雜度o(1)。解決問題時間是兩個遞歸式,元素長度為n的歸並排序所消耗的時間T[n],把一個規模為n的問題分成兩個規模分別為n/2的子問題,時間為2T(n/2)。合並時間復雜度為o(n)。總時間T(n)=2T(n/2)+o(n),所以得出的結果為:T[n] = O( nlogn )。參考: http://blog.csdn.net/yuzhihui_no1/article/details/44198701#t2
基數排序
-
基本思想就是把元素從個位排好序,然后再從十位排好序,,,,一直到元素中最大數的最高位排好序,那么整個元素就排好序了。
-
時間復雜度分析:對於有n個元素的序列,對每一位數放置和收集的時間為O(n+r),則其時間復雜度為 O(d(n+r))。(r為基數,d為位數)
-
既然基數排序的時間復雜度這么低,為什么不是所有的排序都使用基數排序法呢?
首先,基數排序無法創造出一個使用於所有對象類型的泛型基數排序,因為在排序過程中要進行關鍵字取值的切分,因此關鍵字的類型必須是確定的。
其次,當基數排序中的基數大小與列表中的元素數目非常接近時,基數排序法的實際時間復雜度接近於O(n^2)。