一、概念擴充套件
------有序度----
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)
幾種常見的排序演算法及它們之間的比較
1.穩定性比較 插入排序 氣泡排序 二叉樹排序 二路歸併排序及其他線形排序是穩定的 選擇排序 希爾排序 快速排序 堆排序是不穩定的 2.時間複雜性比較 插入排序 氣泡排序 選擇排序的時間複雜性為o n2 其它非線形排序的時間複雜性為o nlog2n 線形排序的時間複雜性為o n 3.輔助空間的比較 ...
常見排序演算法的時間複雜度
最好最壞 平均陣列中插入元素 o 1 每次都在陣列的末尾插入 o n 每次都在陣列的頭部插入 假設資料量為n。最好 最壞平均 基本排序 冒泡 無標誌位 o n 2 o n 2 o n 2 冒泡 有標誌位 o 1 說明 已有序,只需1次 o n 2 說明 逆序,標誌位無效。同上 o n 2 說明 以有...
時間複雜度為on的排序演算法 演算法的時間複雜度理論
yishun 可計算性理論的理解 zhuanlan.zhihu.com 現在,我們來討論演算法執行的時間複雜度。表示以下函式集合 o g n 表示以下函式集合 表示以下函式集合 f n 或 o g n 表示f n 是 或 o g n 的成員。首先要明確輸入規模的概念,一般來說,它指編碼演算法輸入所需...