數據結構與算法筆記day08:排序(冒泡排序|插入排序|選擇排序)

? ? ? ? 排序算法非常多,這里我們只學習眾多排序算法中最經典、最常用的一小部分:冒泡排序、插入排序、選擇排序、歸并排序、快速排序、計數排序、基數排序、桶排序。

? ? 1如何分析一個“排序算法”?? ? ? ?

? ??(1)排序算法的執行效率

? ? ????對于排序算法執行效率的分析,我們一般會從這幾個方面來衡量:

? ? ? ? 1.最好情況、最壞情況、平均情況時間復雜度

? ? ? ? 對于要排序的數據,有的接近有序,有的完全無序,有序度不同的數據,對于排序的執行時間肯定是有影響的,我么要知道排序算法在不同數據下的性能表現。

? ? ? ? 2.時間復雜度的系數、常數、低階

? ? ? ? 在實際的軟件開發中,我們排序的可能是規模很小的數據,所以在對同一階時間復雜度的排序算法性能對比的時候,我們就要把系數、常數、低階也考慮進來。

? ? ? ? 3.比較次數和交換(或移動)次數

? ? (2)排序算法的內存消耗

? ? ? ? 排序算法的內存消耗也可以通過空間復雜度來衡量,不過針對排序算法的空間復雜度,我們引入了一個新的概念:原地排序原地排序算法,就是特指空間復雜度是O(1)的排序算法。

? ? (3)排序算法的穩定性

????????針對排序算法,我們還有一個重要的度量指標:穩定性。這個概念是說,如果待排序的序列中存在值相等的元素,經過排序之后,相等元素之間原有的先后順序不變。? ?

? ? ? ? 比如,有一組數據:5,7,2,1,5,8,按大小排序后為:1,2,5,5,7,8。這組數據里面有兩個5,經過某種排序算法排序之后,如果兩個5的前后順序沒有改變,那我們就把這種排序算法叫做穩定的排序算法,如果前后順序發生變化,那對應的排序算法就叫做不穩定的排序算法

? ? 2冒泡排序

? ? ? ? 冒泡排序只會操作相鄰的兩個數據。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關系要求,如果不滿足就讓它倆互換。依次冒泡會讓至少一個元素移動到它應該在的位置,重復n次,就完成了n個數據的排序工作。

? ? ? ? 下面用一個例子來看看冒泡排序的整個過程。對一組數據:4,5,6,3,2,1,從小到大進行排序,第一次冒泡操作的詳細過程如下:

? ? ? ? 經過依次冒泡操作之后,6這個元素已經存儲在正確的位置上了。要想完成所有數據的排序,我們只要進行6次這樣的冒泡操作就行了。

? ? ? ? 如下圖所示:

? ? ? ? 剛剛冒泡的過程還可以優化:當某次冒泡操作已經沒有數據交換時,說明已經達到完全有序,不用再繼續執行后續的冒泡操作。

? ? ? ? 下面的例子給6個元素排序,只需要4次冒泡操作就可以了:

? ? ? ? 冒泡排序的代碼:

? ? ? ? 運行結果:

? ? ? ? 總結三點:

? ? ? ? 第一,冒泡排序是原地排序算法。因為冒泡的過程指設計相鄰數據的交換操作,只需要常量級的臨時空間,所以它的空間復雜度為O(1),是原地排序算法。

? ? ? ? 第二,冒泡排序是穩定的排序算法。在冒泡排序中,只有交換才可以改變兩個元素的前后順序,為了保證冒泡排序算法的穩定性,當有相鄰的兩個元素大小相等的時候,我們不做交換,相同大小的數據在排序前后不會改變順序,所以冒泡排序是穩定的排序算法。

? ? ? ? 第三,冒泡排序的最好情況時間復雜度是O(n),最壞情況時間復雜度是O(n^2),平均情況時間復雜度是O(n^2)。要排序的數據已經是有序的是最好的情況,我們只需要進行一次冒泡操作。要排序的數據剛好是倒序排列的是最壞的情況,我們需要進行n次冒泡操作。

? ? ? ? 這里來說一下平均時間復雜度的推導方法,這個方法其實并不嚴格,但是比較實用。我們引入有序度的概念,有序度就是有序的元素對的個數:

? ? ? ? 比如:

? ? ? ? 對于一個倒序排列的數組,有序度是0;對于一個完全有序的數組,有序度就是n*(n-1)/2,我們把這種完全有序的數組的有序度叫做滿有序度

????????逆序度的定義跟有序度正好相反:

? ? ? ? 這三個概念間還存在一個公式:逆序度=滿有序度-有序度

? ? ? ? 我們排序的過程就是一種增加有序度,減少逆序度的過程,最后達到滿有序度,就說明排序完成了。

? ? ? ? 冒泡排序包含兩個操作原子:比較交換。每交換依次,有序度就加1,而交換的次數總是確定的,即為逆序度,也就是n*(n-1)/2-初始有序度

? ? ? ? 而最壞情況下的交換次數是n*(n-1)/2,最好情況下的交換次數是0,我們取個中間值,即為n*(n-1)/4。比較操作肯定比交換操作多,而復雜度的上限是O(n^2),所以平均情況下的時間復雜度就是O(n^2)

? ? 3插入排序

? ? ? ? 我們先來看看插入排序的思想。

? ? ? ? 一個有序的數組,我們往里面添加一個新的數據,為了保持有序,我們需要遍歷數組,找到數據應該插入的位置再將它插入:

? ? ? ? 我們把這個思想用到排序中,需要將數組中的數據分為兩個區間:已排序區間未排序區間。初始已排序區間只有一個元素,就是數組的第一個元素。插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將它插入,并保證已排序區間數據一直有序。重復這個過程,直到未排序區間中元素為空,算法結束。

? ? ? ? 一個例子:

? ? ? ? 插入排序包含兩個操作:元素的比較元素的移動,通過比較找到插入位置,然后將插入點后的元素順序往后移動一位,騰出位置給元素插入。

? ? ? ? 對不同的查找插入點方法(從頭到尾/從尾到頭),元素的比較次數是有區別的,但對于一個給定的初始序列,移動操作的次數是固定的:為逆序度。

? ? ? ? 下圖中例子的滿有序度為n*(n-1)/2=15,初始序列的有序度是5,逆序度是10,移動次數為3+3+4=10:

? ? ? ? 插入排序的代碼(寫的過程中捋了好幾次才捋順,以后再寫幾遍!):

? ? ? ? 運行結果:

? ? ? ? 總結一下:

? ? ? ? 第一,插入排序是原地排序算法。它并不需要額外的存儲空間,空間復雜度是O(1)。

? ? ? ? 第二,插入排序是穩定的排序算法。對于值相同的元素,我們選擇將后面出現的元素插入到前面出現元素的后面,這樣就保持了原有的前后順序不變。

? ? ? ? 第三,插入排序的最好時間復雜度為O(n),最壞時間復雜度是O(n^2),平均時間復雜度是O(n^2)。

? ??4選擇排序

? ? ? ? 選擇排序的工作原理是每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。以此類推,直到全部待排序的數據元素排完。

? ? ? ? 選擇排序原理示意圖:

? ? ? ? 直接簡單總結一下:

? ? ? ? 第一,選擇排序空間復雜度為O(1),是原地排序算法。

? ? ? ? 第二,選擇排序的最好情況時間復雜度、最壞情況時間復雜度和平均情況時間復雜度都是O(n^2)。

? ? ? ? 第三,選擇排序是不穩定的排序算法。由上面圖示的過程可以看出,選擇排序每次都要找剩余未排序元素中的最小值,并和前面的元素交換位置,這樣破壞了穩定性。比如5,8,5,2,9這樣一組數據,使用選擇排序算法來排序的話,第一次找到最小元素2,與第一個5交換位置,那第一個5和中間的5的順序就變了,所以就不穩定了。

? ? 5為什么插入排序比冒泡排序更受歡迎呢?

? ? ? ? 選擇排序是不穩定的排序算法,所以比冒泡排序和插入排序要遜色。

? ? ? ? 插入排序和冒泡排序的時間復雜度都是O(n^2),都是原地排序算法,而且都是穩定的排序算法。那么為什么插入排序比冒泡排序更受歡迎呢?

? ? ? ? 前面有講到,冒泡排序和插入排序不管怎么優化,元素交換的次數是一個固定值,是原始數據的逆序度。但是,從代碼實現上來看,冒泡排序的數據交換要比插入排序的數據移動要復雜,冒泡排序需要3個賦值操作,而插入排序只需要1個,如下圖所示:

? ? ? ? 若執行一個賦值語句的時間粗略的估計為單位時間,分別用冒泡排序和插入排序對同一個逆序度是K的數組進行排序,用冒泡排序需要K次交換操作,每次需要3個賦值語句,交換操作總耗時就是3*K個單位時間,而插入排序中數據移動操作只需要K個單位時間。

? ? ? ? 因此,雖然他們的時間復雜度是一樣的,但是如果我們希望把優化做到極致,肯定首選插入排序。插入排序也可以進行優化,感興趣的話可以學習一下希爾排序

????6小結

? ? ? ? 要想分析、評價一個排序算法,需要從執行效率、內存消耗、穩定性三個方面來看。

? ? ? ? 這三種時間復雜度為O(n^2)的排序算法中,冒泡排序、選擇排序我們更多的是了解它的理論,實際開發中應用并不多,插入排序還是挺有用的,有些編程語言中的排序函數的實現原理會用到插入排序算法(優化后的)。

? ? ? ? 這三種排序算法對于小規模數據的排序非常高效,但是大規模數據排序的時候,時間復雜度還是稍微有點高,下一節我們會講更適用于大規模數據的時間復雜度為O(nlogn)的排序算法~

? ? ? ? 戳這里查看源代碼。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容