一、概念擴展
------有序度----
1、有序元素對:a[i] <= a[j], 如果i < j; 逆序元素對:a[i] > a[j], 如果 i < j。
2、一組數據中有/逆序元素對的個數即為有/逆序度
3、2,3,1,6這組數據的有序度為4(因為其有有序元素對為4個,分別是(2,3)、(2,6)、(3,6)和(1,6))逆序度為2(因為其有逆序元素對為2個,分別是(2,3)和(2,1))
4、1,2,3,6這樣完全有序的數組叫作滿有序度;滿有序度的計算公式為 n*(n-1)/2;
5、逆序度 = 滿有序度 - 有序度
-----原地排序算法---
空間復雜度是 O(1) 的排序算法,如:冒泡排序,插入排序
----穩定排序算法---
如果待排序的序列中存在值相等的元素,經過排序之后,相等元素之間原有的先后順序不變
二、冒泡排序
1、冒泡排序只會操作相鄰的兩個數據。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關系要求。如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重復 n 次,就完成了 n 個數據的排序工作
2、冒泡的過程只涉及相鄰數據的交換操作,只需要常量級的臨時空間,所以它的空間復雜度為 O(1),是一個原地排序算法
3、當有相鄰的兩個元素大小相等的時候,我們不做交換,此時冒泡排序是穩定的排序算法。
4、冒泡排序每交換一次,有序度就加 1,直到滿有序度;
5、冒泡排序最壞情況下,初始狀態的有序度是 0,所以要進行 n*(n-1)/2 次交換,最好情況下,初始狀態的有序度是 n*(n-1)/2,就不需要進行交換。我們可以取個中間值 n*(n-1)/4,換句話說,平均情況下,需要 n*(n-1)/4 次交換操作,所以平均時間復雜度就是 O(n^2)
三、插入排序
1、插入排序是將數據分為兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是數組的第一個元素。插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將自己插入,並通過當前插入位置后的已排序區間數據一個一個往后移,原來位置后的未排序區間數據位置不變來保證已排序區間數據一直有序。重復這個過程,直到未排序區間中元素為空,算法結束
2、插入排序算法的運行並不需要額外的存儲空間,所以空間復雜度是 O(1),也就是說,這是一個原地排序算法
3、對於值相同的元素,我們可以選擇將后面出現的元素,插入到前面出現元素的后面,此時插入排序是穩定的排序算法
4、在數組中插入一個數據的平均時間復雜度是O(n)。所以,對於插入排序來說,每次插入操作都相當於在數組中插入一個數據,循環執行 n 次插入操作,所以插入排序的平均時間復雜度為 O(n^2)
5、冒泡排序的數據交換要比插入排序的數據移動要復雜,冒泡排序需要 3 個賦值操作,而插入排序只需要 1 個(這也是插入排序要比冒泡排序更受歡迎的原因)
四、選擇排序
1、選擇排序算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。唯一不同是選擇排序每次會從未排序區間中找到最小的元素,然后與已排序區間最后一個元素的后面的元素交互位置,這樣自己就變為已排序區間的最后一個元素
2、選擇排序每次都要找剩余未排序元素中的最小值,並和前面的元素交換位置,所以選擇排序空間復雜度是O(1)即原地排序且不是穩定的排序算法
3、選擇排序的最好情況時間復雜度、最壞情況和平均情況時間復雜度都為 O(n^2)
五、歸並排序
1、歸並排序是利用分治思想將數據從中間分成前后兩部分,然后對前后兩部分分別排序,再將排好序的兩部分合並在一起,這樣整個數組就都有序了
2、歸並排序的遞推公式為:merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r)),當p >= r 不用再繼續分解;merge_sort(p…r) 表示,給下標從 p 到 r 之間的數組排序。我們將這個排序問題轉化為了兩個子問題,merge_sort(p…q) 和 merge_sort(q+1…r),其中下標 q 等於 p 和 r 的中間位置,也就是 (p+r)/2。當下標從 p 到 q 和從 q+1 到 r 這兩個子數組都排好序之后,我們再將兩個有序的子數組合並在一起,這樣下標從 p 到 r 之間的數據就也排好序了
3、實現方式:申請一個臨時數組 tmp,大小與 A[p…r] 相同。我們用兩個游標 i 和 j,分別指向 A[p…q] 和 A[q+1…r] 的第一個元素。比較這兩個元素 A[i] 和 A[j],如果 A[i]<=A[j],我們就把 A[i] 放入到臨時數組 tmp,並且 i 后移一位,否則將 A[j] 放入到數組 tmp,j 后移一位。繼續上述比較過程,直到其中一個子數組中的所有數據都放入臨時數組中,再把另一個數組中的數據依次加入到臨時數組的末尾,這個時候,臨時數組中存儲的就是兩個子數組合並之后的結果了。最后再把臨時數組 tmp 中的數據拷貝到原數組 A[p…r] 中
4、歸並排序的合並函數,在合並兩個有序數組為一個有序數組時,需要借助額外的存儲空間,所以不是原地排序算法(這也是歸並排序並沒有像快排那樣應用廣泛)
5、在合並的過程中,如果 A[p…q] 和 A[q+1…r] 之間有值相同的元素,那我們可以像偽代碼中那樣,先把 A[p…q] 中的元素放入 tmp 數組。這樣就保證了值相同的元素,在合並前后的先后順序不變。所以,歸並排序是一個穩定的排序算法
6、歸並排序的時間復雜度是 O(nlogn)
7、歸並排序盡管每次合並操作都需要申請額外的內存空間,但在合並完成之后,臨時開辟的內存空間就被釋放掉了。在任意時刻,也就只會有一個臨時的內存空間在使用。臨時內存空間最大也不會超過 n 個數據的大小,所以空間復雜度是 O(n)。
8、歸並排序的處理過程是由下到上的,先處理子問題,然后再合並。而快排正好相反,它的處理過程是由上到下的
9、歸並排序和快速排序都是時間復雜度為 O(nlogn) 的排序算法,適合大規模的數據排序
六、快速排序
1、快速排序也是利用分治思想從要排序數組中下標p 到 r 之間的任意一個數據作為 pivot(分區點,一般情況下可以選擇 p 到 r 區間的最后一個元素),將小於 pivot 的放到左邊,將大於 pivot 的放到右邊,將 pivot 放到中間。根據分治、遞歸的處理思想,我們可以用遞歸排序下標從 p 到 q-1 之間的數據和下標從 q+1 到 r 之間的數據,直到區間縮小為 1,就說明所有的數據都有序了
2、快速排序遞推公式為:quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r),當p >= r終止
3、如果我們不考慮空間消耗的話,partition() 分區函數可以寫得非常簡單。我們申請兩個臨時數組 X 和 Y,遍歷 A[p…r],將小於 pivot 的元素都拷貝到臨時數組 X,將大於 pivot 的元素都拷貝到臨時數組 Y,最后再將數組 X 和數組 Y 中數據順序拷貝到 A[p…r]
4、上面快速排序的實現方式不是原地排序,如果我們希望快排是原地排序算法,可以通過游標 i 把 A[p…r-1] 分成兩部分。A[p…i-1] 的元素都是小於 pivot 的,我們暫且叫它“已處理區間”,A[i…r-1] 是“未處理區間”。我們每次都從未處理的區間 A[i…r-1] 中取一個元素 A[j],與 pivot 對比,如果小於 pivot,則將其加入到已處理區間的尾部,也就是 A[i] 的位置(類似選擇排序)
5、快排是一種原地、不穩定的排序算法,時間復雜度也是 O(nlogn)
七、桶排序
1、桶排序核心思想是將要排序的數據分到幾個有序的桶里,每個桶里的數據再單獨進行排序。桶內排完序之后,再把每個桶里的數據按照順序依次取出,組成的序列就是有序的了
2、把 n 個數據均勻地划分到 m 個桶內,每個桶里就有 k=n/m 個元素。每個桶內部使用快速排序,時間復雜度為 O(k * logk)。m 個桶排序的時間復雜度就是 O(m * k * logk),因為 k=n/m,所以整個桶排序的時間復雜度就是 O(n*log(n/m))。當桶的個數 m 接近數據個數 n 時,log(n/m) 就是一個非常小的常量,這個時候桶排序的時間復雜度接近 O(n)
3、桶排序必須滿足的條件:
3.1、要排序的數據需要很容易就能划分成 m 個桶,並且,桶與桶之間有着天然的大小順序。這樣每個桶內的數據都排序完之后,桶與桶之間的數據不需要再進行排序
3.2、如果數據經過桶的划分之后,有些桶里的數據非常多,有些非常少,很不平均,那桶內數據排序的時間復雜度就不是常量級了。如果數據都被划分到一個桶里,那就退化為 O(nlogn) 的排序算法了
4、桶排序比較適合用在外部排序中。所謂的外部排序就是數據存儲在外部磁盤中,數據量比較大,內存有限,無法將數據全部加載到內存中
八、計數排序
1、計數排序是指需要排序數據范圍不大且數據划分到桶以后,同一個桶內的數據值都相同的桶排序;如分數是0-900分,有50W考生成績的排序,根據考生的成績將50萬考生划分到這901個桶里。桶內的數據都是分數相同的考生,依次掃描每個桶,就實現了 50 萬考生的排序
2、計數排序必須滿足的條件:
2.1、計數排序只能用在數據范圍不大的場景中,如果數據范圍 k 比要排序的數據 n 大很多,就不適合用計數排序了
2.2、計數排序只能給非負整數排序(如果要排序的數據是其他類型的,要將其在不改變相對大小的情況下,轉化為非負整數)
九、基數排序
1、基數排序算法是根據每一位來排序,且每一位中又可以用桶排序或者計數排序;如10 萬個手機號碼,希望將這 10 萬個手機號碼從小到大排序,先按照最后一位來排序手機號碼,然后,再按照倒數第二位重新排序,以此類推,最后按照第一位重新排序。經過 11 次排序之后,手機號碼就都有序了
2、要排序的數據有k位,那我們就需要k次桶排序或者計數排序,總的時間復雜度是 O(k*n)。當 k 不大的時候,比如手機號碼排序的例子,k 最大就是 11,所以基數排序的時間復雜度就近似於 O(n)
3、有時候要排序的數據並不都是等長的,如單詞的排序,可以把所有的單詞補齊到相同長度,位數不夠的可以在后面補“0”
4、基數排序必須滿足的條件:
4.1、要排序數據可以分割出獨立的“位”來比較,而且位之間有遞進的關系,如果 a 數據的高位比 b 數據大,那剩下的低位就不用比較了
4.2、每一位的數據范圍不能太大,要可以用線性排序算法來排序,否則,基數排序的時間復雜度就無法做到O(n)了
總結:時間復雜度冒泡、插入、選擇都是O(n^2);快排、歸並都是O(nlogn);桶、計數、基數都是O(n)
