深入理解排序算法

原創 Linux閱碼場 2022-08-31 08:02

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??


作者簡介:

程磊,某手機大廠系統開發工程師,閱碼場榮譽總編輯,最大的愛好是鉆研Linux內核基本原理。


目錄:

一、排序算法的基本邏輯

? ? ?1.1 什么是排序

? ? ?1.2 排序算法分類

? ? ?1.3 比較排序

? ? ?1.4 非比較排序

? ? ?1.5 排序算法評價維度

? ? ?1.6 比較排序的高級算法

? ? ?1.7 遞歸性與原地性

? ? ?1.8 排序算法概覽

二、排序算法實現與分析

? ? ?2.1 如何分析排序算法

? ? ?2.2 簡單選擇排序

? ? ?2.3 堆排序

? ? ?2.4 簡單插入排序

? ? ?2.5 希爾排序

? ? ?2.6 冒泡排序

? ? ?2.7 快速排序

? ? ?2.8 歸并排序

? ? ?2.9 計數排序

? ? ?2.10 桶排序

? ? ?2.11 基數排序

三、總結回顧



一、排序算法的基本邏輯?


序是數據結構與算法里面最基礎最入門的內容,雖然簡單,但是深入研究的話里面還是有很多內容的,今天我們來全面詳細的講一講各種排序算法的分類、原理、復雜度、穩定性和實現方法。


1.1 什么是排序

們先來說一說什么是排序、為什么要排序。什么是排序,這個很簡單,就是把無序的東西按照一定的規則順序排列成升序或者降序。為什么要排序,有兩個原因,一是為了方便后面的查找,如果沒有排序的話只能進行線性查找,時間復雜度是O(n),如果排序了就可以進行二分查找,時間復雜度是O(logn),復雜度一下子就大大降低了。我們來說明一下這兩種復雜度的差別有多么懸殊(雖然用詞錯誤,但是這么用確實很符合氣氛),假設n是10億的話,O(n)還是10億,而O(logn)是30多(以2為底,假設系數是1),30多和10億比都可以忽略不計了。二是為了顯示的時候按照順序顯示,人類的習慣就是喜歡看有序的東西。


1.2 排序算法分類


那么該怎么進行排序的呢,最基本的方法是什么呢,最基本的方法那當然是比較了,不比較怎么排序呢,只有比較了才能知道該誰前誰后。可是當我看到很多算法書上都說排序有比較排序和非比較排序,我第一眼看到的時候都驚呆了,不可能,絕對不可能。非比較還能排序,排序還能不比較,這怎么可能,絕對是瞎扯。當我繼續看下去的時候發現確實能。后來我仔細思考了一下發現,非比較排序本質上還是在比較,只不過它們不是在和別人比較,而是在和自己比較,在和自己的本位比較 (突然想起了上學時老師經常說的話,不要老是和別人比,要多和自己比,非比較排序做到了)。什么是和自己的本位比較呢,比如說有1到9共9個數順序是亂的,1本來就該在1的位置,2本來就該在2的位置,……,9本來就該在9的位置,它們不用去和別人比,只需要去站到自己本來應該站的位置,順序就自然就排好了。所以比較排序、非比較排序也可以叫做顯式比較排序、隱式比較排序。


1.3 比較排序


光比較還不行,誰給誰比較呢,比較了之后怎么做呢,這些做法的不同又產生了很多不同類型的比較排序方法。同樣,隱式比較也存在這些問題,怎么找到它們的本位呢,它們站到本位之后又該怎么辦呢,這些做法的不同又把非比較排序分為了很多的類別。我們先說比較排序,我們最容易想到的做法就是選擇排序,先選一個最高的站在第一位,在從剩下的選擇一個最高的站在第二位,以此類推,到最后一個的時候就已經從高到低排好序了,我們上學時排隊也會經常用到這種方法。還比較容易想到的另一個方法就是插入排序,先隨便過來一個人,再過來一個,比他高就站到他前面,比他低就站在他后面,再過來一個人,如果比前面的高就一直往前面走,直到不比前面的高就不走了,就在這個位置插入,這種排序方式上學排隊的時候也有用到過。還有一種比較常見的排序方式是交換排序,在軍訓的時候很常用,教官突然叫集合,大家匆匆忙忙的站成一排,教官說從左往右從高到低排列,大家左右互看,你看看我我看看你,比左邊的人高就和左邊的人交換,比右邊的人低就和右邊的人交換,不一會就排好序了。比較排序中,我們已經說了選擇排序、插入排序、交換排序三種方法,這三種排序都是很直觀很容易想到的,生活中也很是很常用的。比如我們打牌的時候會把手里的牌排成一定的順序,有人習慣用插入排序法,有人喜歡用選擇排序,也有人喜歡用交換排序。還有一種排序方法,是不容易想到的,生活中很少有用到,叫做歸并排序。它的大概邏輯是先兩個人一對兩個人一對的,兩個人之間先排好序,然后兩對人再合并成一隊并排好序,以此類推,直至所有人都排成一隊,也就排好序了。我們后面講到歸并排序的時候會再具體講它的邏輯。


1.4 非比較排序


我們舉例說明了比較排序的幾類操作邏輯,那么非比較排序是怎么操作的呢。我們前面說了可以讓數據直到站到它們的本位去就排好序了,但是這里面有一個條件,就是數據要是不重不漏的。很多時候數據都是有重有漏的,怎么辦呢,這時候我們有一種方法叫做計數排序。我們舉例說明一下什么是計數排序,比如1,5,9,6,5,2,6,7,5這個數列有9個數,能不能它們直接站到本位去呢,不能,因為5這個數有3個,6這個數有兩個,讓所有的5都站到第5位上是站不下的,同時3,4這些位上是沒有數的,因此要先對這些數進行計數,如果有一個1,就站到1位上,如果有兩個1就站到1,2位上,如果沒有1,1位就空著留給下一個數。假設有兩個1,再看2,如果有一個2,2就要站在3位上,如果有兩個2,2就站到3和4位上,如果沒有2,3位就空著留給下一個數。以此類推排序3,4,5…9,整個數列就排好序了。


計數排序適合那些數據分布比較集中的情況,如果數據比較分散,再用計數排序就比較繁瑣了,比如有10個數15,23,78,56,3,67,52,23,99,11,它們的范圍大小是0–99,此時用計數排序的話是需要統計1出現的次數,2出現的次數,……,98出現的次數,99出現的次數,這就很費事了。對于這種情況,我們想出了一個辦法,就是桶排序,先準備10個桶,0-9放到第一個桶里面,10-19放到第二桶里面,……,90-99放到第十個桶里面,然后每個桶里面進行排序,具體排序可以選擇插入排序、選擇排序或者其他排序方法都可以,然后再從10個桶里面依次把數收回來,這樣整體上就排序了。如果數據特別多還可以采取多級桶排序,比如要是有100個數,范圍是0-999,就可以采取二級桶排序,先用大桶,0-99一個桶,100-199一個桶,……,900-999一個桶,一共10個大桶,大桶里面再用小桶,10個數的范圍為一個小桶。先在小桶里面排序,然后把一個大桶里面的所有小桶的數收起來,再把所有大桶的數收起來,這樣就排好序了。


非比較排序中還有一種排序,叫基數排序,基數排序比較復雜,也不太好理解,我們先簡單地講講。基數排序只能用于非負整數的排序,基數排序是按位數進行多輪排序的,按照個位十位百位千位的次序進行多輪排序,先按照個位進行排序,再按照十位上的數字進行排序,……,直至到最高位,每一輪的排序方法都要選擇穩定排序方法,最后順序就排好了。大家可能有兩個疑問,為什么要從個位進行排序,不從高位往下進行排序,為啥從低位開始排序結果會是正確的呢。先說第一個疑問,為啥不從高位開始,如果從高位開始排序的話,那么這么排下去,最后的結果就是亂序的,比如說有四個數501,312,457,562,降序排序,先排百位,501,562,457,312,再排十位,562,457,312,501,再排個位,457,562,312,501,結果完全錯了。為啥錯了呢,這是因為十位會打亂百位的排序,個位會打亂十位的排序。如果想要結果正確的話,就需要我們進行局部排序,不是所有的十位在一起排序,而是百位相同的數十位一起排序,百位不同的數它們之間十位就不排序了。所以你還要添加數據記錄這些情況,這樣這個排序就變成了2級桶排序了,就不是基數排序了。而桶排序的麻煩點就在于,桶的操作是比較復雜的,因為每個桶放入多少個數據是不確定的,所以桶排序一般都要使用鏈表結構。基數排序就是要避免桶的操作。我們再來說第二個疑問,為啥從低位到高位排序是正確的,501,312,457,562,先排個位,457,312,562,501,再排十位,562,457,312,501,再排百位,562,501,457,312,最終結果是正確的,為什么是正確的呢,個位排好之后,排十位,十位不同的,把值大的排到前面,這沒有問題,不用考慮個位的問題。如果十位相同,十位相同的數會集中到一起,由于這是穩定排序,個位的相對位置還會保留著,所以個位大的還在前面,十位相同個位大的在前面,排序是正確的。再排百位,也是同理,百位大的排到前面,不用看十位個位的大小如何,百位相同的數會集中排在一起,它們的十位個位就很關鍵了,由于上一輪它們的十位個位的順序就排好了,這一輪也是穩定排序,所以十位的相對位置不變,所以最終排序就是正確的。


我們再來看一下計數排序、桶排序、基數排序,這三者之間的關系,計數排序可以看做是特殊的桶排序,相當于是桶數特別大的桶排序,桶數大到一個數值一個桶。基數排序其實和其他兩者沒有啥關系,但是如果基數排序每輪的排序方法都用計數排序的話,并且只有一輪的話,那么基數排序在這種情況下就是計數排序了。基數排序和桶排序之間就沒有啥關系了,如果非要硬扯上關系,只能說它們在一些特定的情況下都可以看成是計數排序,注意這僅僅是邏輯上能看成,并不是,因為計數排序沒有桶的概念,也沒有輪的概念,而桶排序必須要有桶,基數排序必要從從低位到高位進行多輪穩定排序。(有的書上把按位數進行多輪排序都叫做基數排序,把從高位往低位排序叫做MSD基數排序,這種排序要分桶,實際上就是桶排序,把從低位往高位排序叫做LSD排序,也就是狹義上的基數排序。這種分類方法并不好,MSD和LSD并沒有相似性,前者需要分桶,后者需要內層排序是穩定排序,這樣叫只會導致概念的混亂性,讓人更難以理解。所以我們采取更普遍的叫法,桶排序就是桶排序,基數排序就只能從低位開始排序)


1.5 排序算法評價維度


一個排序算法的好壞,我們該怎么去評價呢,有哪些評價維度呢。我們可以從算法復雜度、位置穩定性、適用性三個維度來評價。


算法復雜度可以分為時間復雜度和空間復雜度,時間復雜度又分為最好時間復雜度、最壞時間復雜度和平均時間復雜度。復雜度是算法輸入規模與執行規模之間的函數,復雜度的表示方法是大O表示法,常見的復雜度有O(1) 常量復雜度, O(logn) 對數復雜度, O(n) 線性復雜度, O(nlogn) 對數線性復雜度, O(n2) 平方復雜度, O(n3) 立方復雜度,O(2n) 指數復雜度, O(n!) 階乘復雜度。關于復雜度理論,大家可以去看一些專業的書籍來學習,比如《算法導論》,這里就不多講了。空間復雜度是指排序算法為了排序而額外分配的輔助內存有多少,大部分排序算法額外分配的內存并不多,多數為O(1),而且現在的計算機內存都非常多,所以一般情況空間復雜度不太重要。時間復雜度是評價一個算法的重要標準,最好時間復雜度是在最好的情況下算法的時間復雜度,比如數組已經排好序了,最壞時間復雜度是最壞的情況下算法的時間復雜度,比如數組完全逆序的情況下。最好最壞都是極端情況下的特殊情況,一般不太重要,重要是平均時間復雜度,它是所有情況下的平均值,也是一般情況下的復雜度,所以平均時間復雜度是評價算法的一個重要維度。


我們所說的排序算法的穩定性是指位置穩定性,不是程序的穩定性(程序有沒有BUG會不會崩潰啥的),而是兩個數值相等的元素在排序前后的相對位置會不會改變,這對于整數來說可能看不出來,也沒啥意義,但是對于結構體來說就能看的出來,而且很有意義。比如我們要對struct student 進行排序,要求按照年齡排序,年齡相同的按照身高排序,我們就可以先進行身高排序,再進行年齡排序(穩定排序),這樣就能達到目的了。如果年齡排序的方法不是穩定排序,就會把身高的相對性打亂,就沒有達到我們的要求。


算法的適用性也很重要,比如非比較排序的時間復雜度都很好,都是線性復雜度或者接近線性復雜度,但是非比較排序的適用條件比較苛刻,很多情況下不太適用。


1.6 比較排序的高級算法


講完了排序算法的評價維度,我們知道時間復雜度是一個很重要的維度,那么比較排序算法的最好的時間復雜度是多少呢,能不能小于O(nlogn)呢,我們是不是還有啥新的排序算法沒有發現呢?根據決策樹模型我們可以計算出來比較排序算法最好的時間復雜度是O(nlogn),不可能比這個再好了,具體原因大家可以去看《算法導論》8.1節。我們前面講的選擇排序、插入排序、交換排序的時間復雜度都是O(n2) ,歸并排序的時間復雜度是O(nlogn),對于前三種,其時間復雜度大于O(nlogn),我們有沒有辦法優化算法,使其達到O(nlogn)或者接近O(nlogn)呢,有,對于選擇排序,我們優化后的算法叫做堆排序,相應的把之前的算法叫做簡單選擇排序。堆排序也是每次都選擇一個最大值放到最后一位,但是它選擇最大值的方法和簡單選擇排序不同,它利用了堆這個數據結構,堆能保留之前比較的結果,所以可以減少比較次數,從而達到優化性能的目的。對于交換排序,我們優化后的算法叫做快速排序,之前的算法可以叫做簡單交換排序,業界都叫做冒泡排序。冒泡的問題在于只能相鄰的元素做比較并交換,一個數據每次移動的位置只有一位,效率很低。快速排序采取的方法是選取一個元素作為key,每個人都和它進行比較,比它小的都移動到它左邊,比它大的都移動到它右邊,這樣就大大的提高了移動的效率。對于插入排序,優化之后的算法叫做希爾排序,之前的算法叫做簡單插入排序。插入排序的特點是它對接近排序的序列效率特別高,對于比較雜亂的序列效率就要低很多。希爾排序利用了插入排序的這個特點,它先對整個數組進行分組插入排序,當分組數比較多的時候,一輪分組插入排序的效率是接近O(n)的,然后逐步降低分組的數量進行插入排序,最后當分組數為1的時候,也就是整個序列就分為一個組,就是直接插入排序了,此時整個數組比較接近排序狀態,插入排序的效率很高。這幾個算法的邏輯都非常復雜,這里只是簡單介紹一下,第二章里會進行詳細的講解。


1.7 遞歸性與原地性


排序算法的實現還有兩個特征,遞歸性和原地性。遞歸性,一個算法實現是否是遞歸實現。原地性,算法是否在原地排序,還是分配了臨時空間把原數據騰挪過去進行排序。這兩個特點為什么不放到算法評價里面去說呢,因為我們對算法進行評價時并不太在意一個算法是否是遞歸的,是否是原地排序,這兩點是算法的屬性,是算法自身的實現邏輯所決定的。算法的評價維度是我們比較在乎的點,我們比較在乎的是算法的效率,包括時間效率和空間效率,也就是算法的時間復雜度和空間復雜度,我們有時候也在乎算法的穩定性,因為我們有時候需要算法穩定,算法的適用性我們也在乎,因為如果你的算法不適用于我們的數據,我們也用不了這個算法啊。但是我們很少會說我們需要一個排序算法它必須是遞歸的或者原地的,這聽起來有點莫名其妙。所以遞歸性和原地性我們不放入算法的評價維度中,我們把它叫做算法的實現特征。


1.8 排序算法概覽


現在我們對排序算法的分類、操作邏輯、評價維度都有了基本的了解,下面我們畫個簡單的圖,先對本文要講的所有排序算法有個大概的認識。

大家此時不必想著要對這個圖進行完全的理解,有個大概的印象簡單的理解就行。下一章我們會用C語言對每一個算法進行實現,并會具體分析它的實現邏輯以及它的復雜度和穩定性等,到本文結束的時候你對這個圖可能就理解比較深刻了。


二、排序算法實現與分析


本章用C語言實現每個排序算法,一個算法一個小節,每個小節的內容依次是算法簡介、算法描述、C語言實現、代碼分析、算法總結、時間復雜度、空間復雜度、穩定性、遞歸性、原地性。


2.1 如何分析排序算法


如何計算一個算法的時間復雜度和空間復雜度呢,這里面有嚴謹的數學和嚴密的邏輯,想要學習的同學可以去研究相關的專業書籍,本文會使用比較直觀好理解的,但是不太嚴謹的方法進行分析。


我們先說時間復雜度,對于大部分算法來說,一般都是內外雙重循環結構,外循環的復雜度一般都是O(n),內循環復雜度和外循環復雜度的乘積就是整個算法的復雜度。內循環復雜度有幾種情況,如果內循環復雜度每輪都是個固定值,那就很簡單,比如內循環總是循環n次,那么內循環復雜度就是O(n),算法的復雜度就是O(n2)。如果內循環的復雜度是O(logn),那么算法的復雜度就是O(nlogn)。但是很多時候內循環的執行次數往往是變化的,有的是遞增序列從1到n,有的是遞減序列從n到1,此時我們可以算一下內循環的平均執行次數,(n+1)*n/2/n = 0.5n+0.5,忽略常量和系數,內循環的復雜度就是O(n),此時算法的復雜度就是O(n2),這種情況比較常見。還有一些特殊情況,對于有些特殊數列,內循環的條件執行一次就結束了,內循環的復雜度就是O(1),所以算法的復雜度是O(n)。還有一種情況是內循環第一輪執行了n次,外循環一次就結束了,此時算法復雜度也是O(n)。


對于遞歸算法來說,要看它的遞歸樹層數和每層的時間復雜度,我們畫個圖看一下。

我們可以看到每一層的數據規模之和都是n,而樹的高度一般是logn,所以遞歸算法的時間復雜度一般都是O(nlogn),只要樹別退化成線性結構。如果退化了,樹的高度就是n,那么算法復雜度就變成了O(n2)了。

我們再來看空間復雜度,如果我們分配的變量是簡單變量,與輸入規模n無關,那么這個變量本身的空間復雜度就是O(1),如果它分配在外層循環里,它的復雜度并不會乘以n,因為循環的每一輪它都銷毀重建了,它并不會累積。如果它出現在內循環里,它的復雜度既不會乘以n,更不會乘以n2,這點可能難以理解。我們先只看內循環,和剛才講的道理一樣,它一直在銷毀重建,所以復雜度還是O(1),再把內循環當成一個整體,它在外循環里也是不斷地銷毀重建,所以復雜度還是O(1)。所以對于雙重循環結構來說,只要定義都是簡單變量,空間復雜度就一定是O(1),推廣一下對于任意n層循環也是如此。


遞歸調用的空間復雜度最好的情況也至少是O(logn),這是因為遞歸調用是要傳遞參數的,參數會不停地壓棧一直到遞歸樹的最深處,所以空間復雜度至少是O(logn)。如果在遞歸前定義了簡單變量,效果和參數是一樣的,空間復雜度還是O(logn)。如果在遞歸調用后面定了簡單變量,則這個變量不會累積,空間復雜度是O(1),如果定義的不是簡單變量,而是和輸入長度n相關的數組變量,則其空間復雜度是O(n)。


如何判斷一個算法是不是穩定的呢?非原地算法一般都是穩定的,或者可以實現成穩定的。而原地算法要想實現排序就必須交換元素,如果算法只交換相鄰的元素,那么算法一定是穩定的,假設一個數列里面有兩個5,把前面的叫做A5后面的叫做B5吧,B5要想跑到A5前面就必須先不停地交換到緊挨著A5,然后再和A5進行交換,但是排序算法都是在數據大于或者小于的時候才可能進行交換,A5等于B5,是不會執行交換的,所以B5不可能跑到A5前面,所以只交換相鄰元素的算法一定是穩定算法。如果算法可能交換不相鄰的元素,比如B5和A5前面的3交換了,那么A5和B5的順序就交換了。注意必須在任何情況下都穩定才能叫做穩定排序,只要有一種情況下不穩定那就是不穩定排序。交換不相鄰元素不一定能導致這個數列的排序結果不穩定,但是一定存在一個數列它的排列結果是不穩定的。所以,交換不相鄰元素的排序是不穩定排序。


2.2 簡單選擇排序


簡單選擇排序是最簡單最直接的排序方法,先通過全員比較找到最小的那個值放在首位,然后排除首位,在剩余的數里面全員比較放到剩余的首位,以此類推,直到所有元素都排好序。


算法描述: 遍歷整個數組[0-n),通過比較找到最小的數,放在第0位,再遍歷[1–n),找到最小的數,放在第1位,再遍歷[2-n),找到最小的數,放到第2位,……,直到遍歷[n-2,n),找到最小的數,放到第n-2位,排序完成。


C語言實現:

void select_sort(int arr[], int nr){  for(int i = 0; i < nr-1; i++){    int min= i;    for(int j = i+1; j < nr; j++){      if(arr[min] > arr[j])        min= j;    }    int tmp = arr[i];    arr[i] = arr[min];    arr[min] = tmp;  }}

代碼分析: 通過雙重循環,外循環從0遍歷到nr-2,外循環確定內循環的起點,內循環從外循環的i+1遍歷到nr-1,外循環中定義 min = i,先假定內循環的起點就是最小值,內循環中,不斷去與之前記錄的最小值的下標所對應的值進行比較,如果發現有更小的值,則更新最小值下標為j,內循環結束后,min代表當前輪中最小值的下標,通過tmp變量交換arr[i]與arr[min],把最小值交換到當前輪的首位。外循環結束,整個序列就是升序排序了。

算法總結: 雙重循環,同向而行,外循環右缺,內循環左缺。(同向而行指的是內外循環的index都是++或者–,右缺指的是index值到nr-2,左缺指的是內循環的index是外循環的index+1,下同,不再贅述)

時間復雜度: 外循環是O(n),內循環是遞減序列是O(n),所以算法復雜度是O(n2),內外循環的執行都是必然的,不存在特例,所以最好最壞平均時間復雜度都是O(n2)。

空間復雜度: 雙重循環,只定義了一個簡單變量,所以空間復雜度是O(1)。

穩定性: 每次交換元素時都很大可能交換的是不相鄰元素,所以簡單選擇排序是不穩定的。

遞歸性: 非遞歸。


2.3 堆排序


堆排序是利用堆這種數據結構進行排序的一種算法。堆是一個近似完全二叉樹,并且對于大頂堆來說每個子節點的值都小于等于它的父節點,對于小頂堆來說每個子節點的值都大于等于它的父節點。堆排序是對簡單選擇排序的一種優化,簡單選擇排序的問題在于它的比較次數太多,因為它每次比較完一遍之后只留下了最小值下標信息,其他比較信息都丟了,導致比較次數是O(n2)。堆排序利用堆的數據結構,每次選擇出來一個最大值之后,之前的很多比較數據還保留在堆結構中,從而減少了比較次數。堆排序的總體邏輯和簡單選擇排序差不多,我們以大頂堆升序排序為例說明,先把數組建立為大頂堆,然后把堆頂也就是0號元素和最后一位元素交換,然后把[0 – nr-2]看做一個堆重新建立大頂堆,此時0號元素又是最大值,和nr-2位置交換,然后再縮小堆的范圍,再重建大頂堆,再把堆頂和nr-3交換,以此類推,直到堆頂和位置1交換,整個數組就排序完成了。堆排序的難點在于理解堆的數據結構,在于理解是如何建堆和調堆的。


堆是一個樹狀結構,但是卻是用數組表示的,它的節點連接是隱含在下標中的。每個節點(根節點除外)的父節點都等于自己的(index-1)/2,每個節點的左子節點(如果存在的話)等于自己的 index2 + 1,右子節點(如果存在的話)等于 index2 + 2。如下圖所示,可以幫助我們理解堆的結構,把堆的數組結構轉化拆分為樹形結構,可以很清楚地看到堆的父子節點之間的下標的關系。

算法描述: 算法的第一步是要建立大頂堆,我們接著上圖講述如何建立大頂堆。堆的建立是從最后一個非葉子節點開始調堆,一直往前調,直到最后對根節點進行調堆,然后大頂堆就建成了。最后一個非葉子節點就是最后一個葉子節點的parent,也就是 (nr-1)/2 == nr/2 - 1,對于圖中來說就是index 4,也就是說對index 4, index 3, index 2, index 1, index 0,依次調堆,這個大頂堆就建成了。為什么要從下往上調堆呢,因為只有子樹是合格堆了再對自己調堆,才能保證自己和子樹都是合格堆。如果子樹不是合格堆而堆自己進行調堆的話,是不能把自己調成合格堆的。調堆的邏輯是,先看自己的左子和右子誰大,誰大就和誰交換值,這樣自己就是三者之間的最大值了,同時又因為左子和右子都是合格堆,所以左子是左子樹的最大值,右子是右子數的最大值,所以自己現在是自己樹上的最大值,自己就是個合格堆了,被交換的左子或者右子此時就不一定是合格堆了,所以再對其進行遞歸調堆。整個調堆過程如下圖所示:
?調堆完成之后,把堆頂和最后一個元素互換,最后一個元素就是最大值了。再把[0 – nr-2] 看成一個堆,此時index 1 和 index 2 都是一個合格的大頂堆,只有index 0 不是,因此只對 index 0 進行一次調堆就可以了。如下圖所示:
至此我們已經把最后兩個元素排列好了,以此類推,不停地對index 0調堆,與當前尾元素互換,直至最后就能把整個數組排列好。

C語言實現:

static void heap_adjust(int arr[], int length, int node){  int key = arr[node];  while(1){    int child = node*2 + 1;    if(child >= length)      break;
if(child+1 < length && arr[child+1] > arr[child]) child++; if(arr[child] <= key) break;
arr[node] = arr[child]; node = child; } arr[node] = key;}
void heap_sort(int arr[], int nr){ for(int node = nr/2 - 1; node >= 0; node--) heap_adjust(arr, nr, node);
for(int length = nr-1; length > 0; length--){ int tmp = arr[0]; arr[0] = arr[length]; arr[length] = tmp; heap_adjust(arr, length, 0); } }

代碼分析: 首先有個輔助函數heap_adjust就是用來調堆的,它的操作邏輯就是上圖中所說的調堆邏輯。主函數heap_sort,第一個for循環建立大頂堆,從最后一個非葉子節點開始依次調堆,直至對index 0進行調堆,整個大頂堆就建立完成了。第二個for循環,先把堆頂也就是index 0和當前的堆尾進行交換,然后對index 0進行調堆,此時傳遞堆大小是length,也就是堆尾是length-1,也即是把剛才的堆尾排除在外了。第二次循環,先把length–,此時index 0是個合格的大頂堆,再把index 0 和堆尾交換,然后再對index 0 進行調堆。循環執行完之后,整個數組就是升序排序了。

算法總結: 兩個for循環,第一個for循環建立大頂堆,循環范圍是[nr/2-1 – 0],第二個for循環不斷地交換堆頂和尾元素,并重建大頂堆,循環范圍是[nr-1 – 1]。

時間復雜度: 先看heap_adjust的時間復雜度,它的操作次數就是樹的高度,所以復雜度是O(logn),第一個for循環執行了nr/2 個 heap_adjust,所以時間復雜度是O(nlogn)。第二個for循環是nr個heap_adjust,時間復雜度也是O(nlogn),兩個O(nlogn)加起來還是O(nlogn),所以堆排序的時間復雜度是O(nlogn),沒有什么特殊情況,所以最好最壞平均時間復雜度都是O(nlogn)。

空間復雜度: 兩個雙重循環,只定義了幾個簡單變量,所以空間復雜度是O(1)。

穩定性: 大部分操作都是非相鄰元素交換,所以堆排序是不穩定的。

遞歸性: 非遞歸。

原地性: 原地。

我們可以發現,簡單選擇排序和堆排序有幾個共同點:
1.最好最壞平均時間復雜度都是相同的,不存在特殊的排序情況
2. 空間復雜度都是O(1)
3.兩者都是不穩定排序


2.4 簡單插入排序


簡單插入排序的方法是逐步構建已排序序列,把未排序區的元素一個一個地往排序區插入,在排序區里面從后往前搜索,找到自己的位置并插入,它之后的元素各往后移動一位,當未排序區的元素清空時,排序就完成了。簡單插入排序在元素數量少時是一種非常高效的排序。


算法描述: [0 – 0] 是已排序區,[1 – n-1] 是未排序區,把1號元素插入已排序區,根據大小插在0號元素之前或者之后,形成新的排序區[0 – 1]和未排序區[2 – n-1],再把2號元素根據大小插入排序區,可能在0之前,在0和1之間,或者1之后,形成新的排序區[0 – 2]和未排序區[3 – n-1]。一直如此操作,直到未排序區變為空集,排序完成。

C語言實現:

void insert_sort(int arr[], int nr){  for(int i = 1; i < nr; i++){    int j, key = arr[i];    for(j = i; j > 0 && arr[j-1] > key; j--)      arr[j] = arr[j-1];    arr[j] = key;  }}

代碼分析: 外層循環表達的是未排序區,index從1開始,到nr-1結束,初始排序區是[0 – 0],就一個元素,肯定是已排序的。取未排序區的第一個數作為待插入數,保存在局部變量key中,未排序的首位空間轉換為已排序區的空間,根據key掃描已排序空間,比key大的都右移一位,直到遇到不比key大的數值為止。內循環結束后,j的值就是key要插入的位置,這個位置之前的值都小于等于key,之后的位置都大于key。執行arr[j] = key,完成插入。外循環結束后,未排序區為空,排序成功。

算法總結: 雙重循環,背道而行,數據不斷右移為key騰挪位置,直到找到key應該在的位置,最后插入。

時間復雜度: 外循環復雜度是O(n),內循環的執行次數是有條件的,假設條件總成立,也就是數列是逆序的情況,內循環的復雜度是O(n),所以最壞時間復雜度都是O(n2)。假設內循環的條件總是不成立,也就是數列已排序的情況,內循環的復雜度是O(1),所以最好時間復雜度都是O(n)。平均情況也就是一般情況,內循環里面的操作有一半的概率會執行,也就是最壞情況的一半,所以平均時間復雜度還是O(n2)。

空間復雜度: 雙重循環,定義了兩個簡單變量,所以空間復雜度是O(1)。

穩定性: 邏輯上可以看成是key不斷地和前面的元素進行交換,也是屬于只交換相鄰元素,所以簡單插入排序是穩定的。

遞歸性: 非遞歸。

原地性: 原地。

簡單插入排序和簡單選擇排序對比一下,最好時間復雜度,前者是O(n),后者是O(n2),平均時間復雜度雖然都是O(n2),但是前者的系數是1/4,后者的系數是1/2,穩定性,前者穩定,后者不穩定,所以簡單插入排序完勝簡單選擇排序。


2.5 希爾排序


希爾排序是對簡單插入排序的一種改進,又叫做縮小增量排序,也叫分組插入排序。它對插入排序的改進是基于插入排序的兩個特點,1是插入排序對于越接近排好序的數列效率越高,2是插入排序一般情況下是低效的,因為內循環一次只能把數據移動一位。針對插入排序的特點和缺點,我們可以這樣改進它。對數列進行分組插入排序,比如先分5組分別進行插入排序,再分3組分別進行插入排序,再分1組也就是不分組進行插入排序。也就是說希爾排序最后要進行一次插入排序,你也許會覺得之前就進行了很多次操作,最后還要進行一次插入排序,效率肯定比插入排序差。但是這是不對的,因為插入排序的效率受它的輸入數據的有序性影響很大,如果輸入數據是已經排序的,那么插入排序的效率就是O(n),輸入數據越接近已排序,插入排序的效率就越接近O(n)。前面的分組插入排序就是為了使整個數組更接近排序狀態。它是第一個突破O(n2)的算法。


算法描述: 增量d是一個遞減序列,最后遞減為1,d序列的選擇并不是一個絕對的事情,一般會選擇為初始值為nr/2,并不停地除以2。d序列應該盡量使同一組的數不再分配到同一組,也就是d序列要盡量避免16,8,4,2,1,這種序列,因此我們每次都 d |= 1,把d變為奇數。假設nr是10,d第一次是5,進行5組插入排序,0,5一組,1,6一組,2,7一組,3,8一組,4,9一組,分別進行插入排序。第二次d是3,0,3,6,9一組,1,4,7一組,2,5,8一組,分別進行插入排序。第三次d是1,進行簡單插入排序。

C語言實現:

void shell_sort(int arr[], int nr){  for(int d = nr/2; d > 0; d /= 2){    d |= 1;    for(int i = d; i < nr; i++){      int j, key = arr[i];      for(j = i; j >=d && arr[j-d] > key; j -= d)        arr[j] = arr[j-d];      arr[j] = key;    }  }}

代碼分析: 三重循環,最外層循環,是d增量循環,從nr/2開始,每次減半,到1停止。內層兩層for循環是標準的簡單插入排序算法,加入了分組考慮。

算法總結: 三重循環,外層循環是d增量每次減半,內兩層循環是簡單插入排序。

時間復雜度: 希爾排序的時間復雜度是和增量序列有著密切的關系的,最好時間復雜度可以達到O(n),最壞時間復雜度可以到O(n2),如果按照Sedgewick提出的增量序列,最壞時間復雜度和平均時間復雜度可以達到O(n1.3)。目前數學上還沒有證明希爾排序的最壞時間復雜度的下限是多少,因為不太好證明哪個增量序列是最優的,不太好計算平均情況。

空間復雜度: 三重循環,只定義了兩個簡單變量,顯然空間復雜度是O(1)。

穩定性: d不為1的時候發生了不相鄰元素交換的情況,所以希爾排序是不穩定的。

遞歸性: 非遞歸。


2.6 冒泡排序


冒泡算法的邏輯是從一端走向另一端的過程中,不斷地比較相鄰的元素,把較小的或者較大放到前面,這樣一遍下來之后,最小值或者最大值就到了數組的某一端,把這個值扣除,剩下的數組元素再按這個邏輯走一遍,次大的數又浮動到一端了,一直這樣下去,數列就排序好了。


算法描述: 我們以升序排序向左浮動為例進行講解,不斷的進行(nr-1, nr-2),(nr-2, nr-3),……,(2, 1),(1, 0),比較并把較小值往前交換,這樣一輪下來,最小值就到了位置0了。然后下一輪進行(nr-1, nr-2),(nr-2, nr-3),……,(2, 1)比較并把較小值往前交換,把剩余的數中最小值交換到了位置1,然后再進行(nr-1, nr-2),(nr-2, nr-3),……,(3, 2)比較并交換,直到最后一輪進行(nr-1, nr-2)比較并交換,這樣整個數列就排序好了。這個過程很像氣泡往上冒泡的過程,所以就叫做冒泡排序。

C語言實現:

void bubble_sort(int arr[], int nr){  for(int i = 0; i < nr-1;){    int pos = nr-1;    for(int j = nr-1; j > i; j--){      if(arr[j] < arr[j-1]){        int tmp = arr[j];        arr[j] = arr[j-1];        arr[j-1] = tmp;        pos = j;      }    }    i = pos;  }}

代碼分析: 雙重循環,外層循環控制每次冒泡的頂端,從0往nr-1方向不斷地壓縮空間,內層循環從最低端往上冒泡,冒泡的頂端被外層循環的index控制。內層循環比較后面的值和前面的值,如果后面的值較小,就把它交換到前面去。這個算法采取了一個優化,就是外層循環index的遞進不再是++了,而是內層循環最后一次交換的下標值pos。pos是每輪最后一次發生交換的下標值,代表著剩余區間的所有元素都比這個pos之前的值要大,下一次冒泡的頂端就沒有必要超過這個pos了。

算法總結: 雙重循環,相向而行,相鄰比較,順序不對就交換。可以簡單總結為 鄰換對開 四個字,鄰換,只有相鄰的元素才會進行比較并有可能交換,對開,內外循環的index的增加方向是相反的。

時間復雜度: 我們這個冒泡排序是優化版的冒泡排序。最好的情況是已排序的情況,第一輪的時候,內循環執行了nr-1次,if語句一直不成立,pos=j一直不執行,外循環第二輪就不執行了,所以最好時間復雜度是O(n)。最壞的情況是完全逆序,內循環的if語句一直成立,pos=j一直執行,外循環的執行次數是O(n),內循環的執行次數是遞減序列是O(n),所以最壞時間復雜度是O(n2)。平均情況下內循環執行的概率是一半,所以平均時間復雜度是O(n2)。

空間復雜度: 雙重循環,只定義了一個簡單變量,所以空間復雜度是O(1)。

穩定性: 只有相鄰的元素才有可能交換,所以冒泡排序是穩定的。

遞歸性: 非遞歸。


2.7 快速排序


快速排序是對冒泡排序的一種改進,是屬于交換排序的一種,它的基本操作也是比較和交換,但是它比較的對象和交換的方式不同,冒泡排序是相鄰的元素比較并交換,快速排序是選擇一個key,所有的數都和這個key比較,比它小的移動到它左邊,比它大的移動到它右邊。然后再對左邊的區間和右邊的區間重復進行這種操作,一直遞歸下去,直到區間只有一個元素。所以遞歸完成并返回后,數組就排序好了。

算法描述: 選擇一個元素作為key,把所有小于這個key的都移動到左邊,大于這個key的都移動到右邊,這個key放在左區和右區的中間,這個操作叫做分割(partition),然后分別對左區和右區遞歸這個操作。怎么選擇這個key有很多種方法,本文中是直接取中間index的值作為key。

C語言實現:

static void swap(int arr[], int i, int j){  int tmp = arr[i];  arr[i] = arr[j];  arr[j] = tmp;}
static void do_quick_sort(int arr[], int left, int right){ if(left >= right) return;
swap(arr, left, (left+right)/2); int index = left; for(int i = left+1; i <= right; i++) if(arr[i] < arr[left]) swap(arr, ++index, i); swap(arr, left, index);
do_quick_sort(arr, left, index-1); do_quick_sort(arr, index+1, right);}
void quick_sort(int arr[], int nr){ do_quick_sort(arr, 0, nr-1);}

代碼分析: 先寫個輔助函數swap用來交換元素。do_quick_sort函數,輸入參數是數組首地址、區間左index、區間右index,區間是左閉右閉區間。為什么函數的簽名是這樣的,和之前的排序算法的簽名不太一樣,之前參數都是arr和hr。這是因為快排是遞歸算法,所以它的參數要接收當前要處理的區間范圍。do_quick_sort函數里面,首先要做的是遞歸結束檢查,遞歸是什么時候結束呢,當區間只剩一個元素或者是空區間的時候直接返回。繼續走下去的話說明區間至少有兩個元素,可以做一輪分割。分割,我們選一個元素作為key,這里有很多種選法,我們選擇區間最中間的元素作為key,也就是 (left+right)/2處的值,然后把這個key交換到區間的最左側。然后我們以這個left處的值為key,對區間[left+1 – right]進行分割,使得最終這個區間以某個點為界,左部分的值都小于這個key,右部分都大于等于這個key。這是怎么做到的呢,這就是函數里面 for循環加 if 加 swap三個語句的神奇之處了。首先讓index = left,[left+1 – index]代表的是左區,是小于key的值,[index+1 – i-1]代表的是右區,是大于等于key的值,[i – right]代表的是還未處理的區域,開始的時候,左區是空集,右區也是空集,未處理區是全集。每次循環的時候,i++,未處理區減少一個元素,處理區增加一個元素,增加的這個元素是給左區還是右區呢,要判斷它是否小于key,小于key的話,++index,左區增加一個元素,右區由于i++了,所以右區的數量不變,swap交換的是待處理的位置i和之前右區的最左端,相當于是i增加了,右區先增加了一個元素,如果它不比key小的,就什么也不操作,i就留作右區了,右區增加一個元素,左區不變。如果它比key小的話,左區的最右端和最左端交換,index增加了1,把右區的一個位置劃給了左區,右區相當于往右平移了一位。當循環完成之后,未處理區為空,整個區域分成了三個部分,left,[left+1 – index], [index+1, right], 左區都是小于key的,右區都是大于等于key的,然后再做一個swap(arr, left, index)操作,這樣區域就變成了[left – index-1], index, [index+1, right], 很容易看出,左區都是小于key的,右區都是大于key,key自己在中間index的位置處,完美的完成了分割。下面就是遞歸調用了,分別對左區和右區進行遞歸調用。現在我們再來看一個問題,我們進來的時候整個區域的大小是大于等于2的,現在分割之后,左區或者右區有可能是空集的,所以left有可能大于index-1,index+1有可能大于right,所以函數開頭處的>=檢測是有必要的。為了讓快排算法和其他算法的接口兼容,我們把具體做算法的函數叫做do_quick_sort,再向外提供一個接口quick_sort。

網上大部分的快排算法實現都是一個while循環內嵌兩個并列的while循環,代碼比較冗長。本文的快排實現是C語言之父Dennis Ritchie 在《The C Programming Language》書中所寫的,代碼非常簡潔精巧,但是理解起來非常費勁。因此我們畫個圖來輔助理解:
算法總結: 遞歸算法都有分治合的特點,快排是先分后治沒有和。分是把整個區間分成兩個區間,一個區間都小于key,一個區間都大于等于key,這個是快排的重點和難點。治就是遞歸調用,遞歸調用在函數的尾部,所以是后遞歸算法。分的特點是先把key交換到最左邊,然后進行分,最后再把key交換到臨界點。

時間復雜度: 快排是遞歸算法,時間復雜度主要看遞歸樹的深度,那么遞歸樹深度是多少呢,如果不巧的話,每次分割的區間都是小于某個常量長度的區間和另一個區間的話,那么遞歸樹的深度就是O(n)的,所以最壞時間復雜度是O(n2),最好的情況是每次區間都是平分的,這樣遞歸樹的高度就是O(logn)的,所以最好時間復雜度是O(nlogn)。如果每次分割都是成比例的,就算是比例再小,達到1:9,甚至1:99,遞歸樹的高度也是O(logn)的,所以平均時間復雜度是O(nlogn)。

空間復雜度: 在遞歸前定義了一個簡單變量,遞歸后無變量定義,所以其空間復雜度就是遞歸樹的高度,遞歸樹高度最壞的情況是O(n),最好的情況和平均情況是O(logn),所以快排的空間復雜度最壞情況是O(n),最好和平均是O(logn),這是快排和其它排序算法一個顯著的不同,其他排序算法的空間復雜度在所有的情況下都是一樣的。

穩定性: 由于存在非相鄰元素交換的情況,所以快速排序是不穩定的。

遞歸性: 后遞歸,遞歸調用在函數的尾部叫后遞歸。


2.8 歸并排序


并排序是先把最小的子序列給排好序,然后不斷的合并子序列,最終達到排序的目的,歸并排序是一種遞歸排序,采用的是分治合的思想,分是簡單直接的分,直接平均分成兩份,治是遞歸調用自己,合是把已經排序好的兩個子序列合并成一個有序的序列。由于是遞歸調用在前,合在后,所以歸并排序會先遞歸到最小的子序列,也就是一個元素的序列,然后一個一個合成兩個元素的序列,兩個雙元素的序列合并成一個四元素序列,或者一個雙元素序列和一個單元素序列合并成一個三元素序列,就這樣一直合并下去,直至底層函數返回,整個序列就排序好了。


算法描述: 先取序列的中點把序列分成兩個區間,分別對左右兩個區間進行遞歸調用,調用返回之后得到的是兩個已排序的序列,然后把這兩個序列合并成一個序列,合并采取的是非原地操作,把兩個區間復制到一個臨時數組中,然后左右兩個區間依次選擇最小的值復制回原區間中。由于是分成兩個區間進行遞歸,所以這個算法實現是兩路遞歸排序。

C語言實現:

static void do_merge_sort(int arr[], int left, int right){  if(left >= right)    return;  int mid = (left+right)/2;  do_merge_sort(arr, left, mid);  do_merge_sort(arr, mid+1, right);
int count = right - left + 1; int tmp[count]; for(int i = 0; i < count; i++) tmp[i] = arr[left+i];
int j = 0, jmax = mid-left+1; int k = jmax, kmax = count; for(int i = left; i <= right; i++) arr[i] = j >= jmax || (k < kmax && tmp[k] < tmp[j]) ? tmp[k++] : tmp[j++];}
void merge_sort(int arr[], int nr){ do_merge_sort(arr, 0, nr-1);}

代碼分析: 入口先進行遞歸結束檢測,當區間的長度小于等于1時結束遞歸直接返回,如果區間長度大于2,繼續往下走。以中間index為分界把區間平分成兩份,分別遞歸調用,調用返回后,得到的是兩個已經排好序的序列,定義一個和區間總長度相同的臨時數組,把整個區間都復制過去,然后同時遍歷左區和右區,依次把更小的元素復制回原區間,如果某個區間先復制完了,就把另一個區間的值直接復制完。代碼使用了一定的編程技巧,使得代碼非常精巧,但是不太好理解,但是邏輯是非常簡單的,就是直接比較左區和右區的當前元素哪個小,把小的復制回原數組,并把當前元素index++。

算法總結: 先遞歸調用,再進行整合。

時間復雜度: 和快速排序的原理是一樣的,時間復雜度的關鍵點在于遞歸樹的深度,由于我們是按index分區的,所以總是能平分一個區,不存在分配不均勻的情況,所以遞歸樹的深度總是O(logn),所以最好最壞平均時間復雜度都是O(nlogn)。

空間復雜度: 遞歸前定義了一個簡單變量,其空間復雜度是O(logn),遞歸后定義了和n相關的數組變量,其空間復雜度是O(n),所以空間復雜度是O(n)。

穩定性: 沒有交換操作,合并時并不會改變元素的相對位置,所以歸并排序是穩定的。

遞歸性: 前遞歸,先進行遞歸,遞歸返回后再進行合并操作。

原地性: 非原地,定義了臨時數組來存放被排序的值。


2.9 計數排序


計數排序是非比較排序中最簡單的算法,它適用于范圍比較集中的整數進行排序,我們第一章講了非比較排序的基本原理,這里就不再贅述了。


算法描述: 先把原數組clone一份叫做arr2,再計算出數列中的最大值和最小值,創建一個長度為max-min+1的counts數組,先統計數列中每個整數出現的次數,然后累加計數數組,此時counts[i]代表在原數組中小于等于i的元素的個數,然后逆序遍歷arr2,把arr2按照正確的順序放到原數列中。


C語言實現:

void count_sort(int arr[], int nr){  int arr2[nr];  int max = arr[0];  int min = arr[0];  for(int i = 0; i < nr; i++){    arr2[i] = arr[i];    if(max < arr[i])      max = arr[i];    if(min > arr[i])      min = arr[i];  }
int length = max - min + 1; int counts[length]; for(int i = 0; i < length; i++) counts[i] = 0;
for(int i = 0; i < nr; i++) counts[arr2[i] - min]++;
for(int i = 1; i < length; i++) counts[i] += counts[i-1];
for(int i = nr-1; i >= 0; i--) arr[--counts[arr2[i]-min]] = arr2[i];}

代碼分析: 先遍歷原數組,把原數組clone一份arr2,找到原數組的最大值和最小值,然后建立一個計數數組counts用來計數,長度是max-min+1,再遍歷原數組,用元素的值減去min作為下標在counts數組中尋址,對應的counts元素++,代表這個數值的元素的個數又增加了一個,遍歷完成后,counts[i]代表在原數組中等于i的元素的個數。再累加counts,累加完成后,counts[i]代表在原數組中小于等于i的元素的個數。然后逆序遍歷arr2,把arr2按照正確的順序放到原數列中。為什么要逆序呢,這是因為counts[i]代表的是小于等于i的元素的個數,逆序的話能讓最后一個i值放到最后一個位置中去,這樣就不會顛倒值相等的元素的順序,能保存排序的穩定性。

算法總結: 先遍歷原數組,找到最大值最小值,再遍歷原數組,統計各個數值出現的次數,再累加計數數組,再逆序遍歷arr2數組回寫到原數組。

時間復雜度: 遍歷3次原數組,遍歷2次計數數組,原數組的長度是n,計數數組的長度是k,k = max-min+1,是一個與n無關的數,所以時間復雜度是O(n+k),不存在什么特殊情況,所以最好最壞平均時間復雜度都O(n+k)。

空間復雜度: 定義了一個計數數組,長度是k,k = max-min+1,是一個與n無關的數,clone了原數組,長度是n,所以空間復雜度是O(n+k)。

穩定性: 計數排序是穩定的,代碼分析中講了保持排序穩定的原因。

遞歸性: 非遞歸。


2.10 桶排序

桶排序是也是一種非比較排序,它比較適合那些數據分布比較均勻的數據,其基本思想是根據數據的范圍,把其分為N個桶,然后把所有數據放入相應的桶中,每個桶內再進行排序,然后把所有的桶按順序收回數據,整個數據就排序好了。

算法描述: 把數據分到N個桶中,每個桶中再進行排序。

C語言實現:
暫無

代碼分析: 暫無。

算法總結: 暫無

時間復雜度: 最好最壞平均時間復雜度都是O(n+k)。

空間復雜度: 空間復雜度是O(n+k)。

穩定性: 桶排序是穩定的。

遞歸性: 非遞歸。


2.11 基數排序


基數排序也是一種非比較排序,它只適用于對非負整數進行排序,它的基本原理是先對個位進行排序,再對十位進行排序,再對百位進行排序,……,直至最高位,對每位進行排序的方法一定要選擇穩定排序,比如計數排序。基數排序不一定要把整數看成是10進制的,可以把它當成任意進制的數來處理都行。

算法描述: 從最低位開始進行穩定排序,……,直至最高位。

C語言實現:

void radix_sort(int arr[], int nr){  int max = arr[0];  for(int i = 1; i < nr; i++)    if(max < arr[i])      max = arr[i];
int d = 1; while((max = max >> 4) != 0 ) d++;
for(int k = 0; k < d; k++){ int arr2[nr]; for(int i = 0; i < nr; i++) arr2[i] = arr[i];
int length = 1 << 4; int counts[length]; for(int i = 0; i < length; i++) counts[i] = 0;
for(int i = 0; i < nr; i++) counts[(arr2[i] >> 4*k) & 0x0F]++;
for(int i = 1; i < length; i++) counts[i] += counts[i-1];
for(int i = nr-1; i >= 0; i--) arr[--counts[(arr2[i] >> 4*k) & 0x0F]] = arr2[i]; }}

代碼分析: 先計算數列中的最大值,再計算出它的位數,本代碼是按照16進制來看待的,這樣方便運算。然后從低位到高位依次遍歷,每次遍歷都采取計數排序,這個計數排序它的n還是外部的n,但是它的k是常量16,因為它是按16進制來處理的,一位16進制數最大的值是15,最小是0。計數排序的邏輯我們就不再贅述了。

算法總結: 外層循環按照從低位到高位的順序進行循環,內層是計數排序。

時間復雜度: 外層循環的次數是位數k,內層循環是計數排序,由于此時計數排序的k是常量,所以內層循環的復雜度是O(n),由于不存在特殊情況,所以最好最壞平均時間復雜度都是O(nk)。

空間復雜度: 內循環的空間復雜度是O(n),外循環不會累積內循環的存儲空間,所以空間復雜度是O(n)。

穩定性: 每輪排序都是用的穩定排序,所以最終排序也是穩定的,所以基數排序是穩定的。

遞歸性: 非遞歸。


三、總結回顧?


至此,我們已經全面詳細講解了所有常見的排序算法,包括算法的原理、實現方法、以及各種性質的分析(桶排序除外)。下面我們先畫個圖回顧一下。

我們從上到下、從左到右再把這個圖看一遍,仔細回憶一下各個算法的基本原理、實現方法、和對它各種性質的分析。從左到右,排序算法先根據是否使用比較分為比較排序和非比較排序,比較排序根據其基本原理的不同分為選擇排序、插入排序、交換排序、歸并排序。歸并排序是遞歸排序,其時間復雜度是O(nlogn),已經非常優秀了,沒有改進空間了,而選擇排序、插入排序、交換排序的時間復雜度都是O(n2),還有改進空間,于是分別改進出了堆排序、希爾排序、快速排序。非比較排序中最簡單最直接的是計數排序,它為了處理數列中有重有漏的問題而采取計數方法。桶排序是對分布均勻的數據分桶進行排序。基數排序是一種比較巧妙的排序,一般人都想不到還能這樣排序。

圖中沒有寫它們的適用性,我們在這里說一下。比較排序的適用性非常強,可適用于任何數據。非比較排序的適用性比較窄,計數排序適用于對范圍比較集中的整數進行排序,桶排序適用于分布比較均勻的數據,基數排序適用于正整數。
我們再來看一看遞歸性、原地性與穩定性和空間復雜度之間的關系。可以看出非原地排序都是穩定排序,原地排序由于需要交換,所以相鄰交換的都是穩定排序,不相鄰交換的都是不穩定排序。非遞歸原地排序的空間復雜度都是O(1),非遞歸非原地排序的空間復雜度都是線性的。遞歸排序的空間復雜度至少是O(logn),因為原地排序不需要額外分配空間,所以遞歸原地排序的空間復雜度是O(logn),而非原地排序的空間復雜度是O(n),所以非遞歸非原地排序的空間復雜度是O(n)。

再來看時間復雜度,非比較排序中的計數排序和桶排序的平均時間復雜度都是O(n+k),是線性的,計數排序中,如果把位數k看成是不大于15的常量,計數排序的平均時間復雜度也可以看成是線性的。比較排序中,所有的簡單排序的平均時間復雜度都是O(n2),而復雜排序基本都是O(nlogn),除了希爾是O(n1.3)。選擇排序和歸并的時間復雜度不存在優化和惡化的情況。插入排序和冒泡排序存在優化的情況,當數列已經排好序時,其時間復雜度優化為O(n)。快排的時間復雜度存在惡化的情況,當區間分割總是極度不均勻時,其時間復雜度惡化為O(n2)。

所有算法中,邏輯上最難理解的是堆排序和基數排序,代碼上最難理解的是快速排序。一般情況下運行效率最高的是快排,所以快排才叫快排,很多庫的排序算法的默認實現就是快排。

參考文獻:
《Introduction to Algorithms》
《Algorithms》
《Algorithms in a Nutshell》
《An Introduction to the Analysis of Algorithms》

? ? ? ? ? ?

如需進程老師群溝通請添加

小馬微信:Linuxer2022


Linux閱碼場 專業的Linux技術社區和Linux操作系統學習平臺,內容涉及Linux內核,Linux內存管理,Linux進程管理,Linux文件系統和IO,Linux性能調優,Linux設備驅動以及Linux虛擬化和云計算等各方各面.
評論
  • 做藍牙音箱的工程師大概都碰過這個局面。產品要連手機放歌,要本地播開機提示音和按鍵音,還要接 App 做配網和狀態上報。三件事拆開,早先的常規做法是音頻藍牙一顆料、數傳藍牙一顆料、本地播放再掛一顆小 MCU 加一片 Flash。板子越畫越大,BOM 越算越貴,待機電流和固件工作量也跟著漲。WT2605C 出來以后,這套需求可以集成到一顆芯片,就是 WT2605C-32N,該芯片采用QFN32 封裝,4×4 毫米,把音頻藍牙、BLE 數傳和 MP3 本地播放三樣全包了。對藍牙音箱來說,最實在的好處就
    唯創知音語音芯片 2026-08-06 15:14 382瀏覽
  • 在物聯網從“萬物互聯”邁向“萬物智聯”的進程中,LTE Cat.1憑借其在速率、成本與網絡覆蓋之間的完美平衡,正成為中低速物聯網市場的絕對主力。隨著2G/3G網絡的逐步退網以及新國標對終端設備智能化、定位功能的強制要求,Cat.1模組迎來了爆發式的增長窗口。在這一關鍵節點,基于華為海思Hi2131芯片設計的LTE Cat.1模組,憑借其在能效、射頻、架構及物理規格上的底層創新,成功打破了“高性能必高功耗”的傳統魔咒,為行業提供了極具競爭力的連接最優解。核心技術優勢:重塑Cat.1能效與連接標桿海
    用戶1778981989717 2026-08-05 21:04 206瀏覽
  • 讓微弱脈沖信號更清晰——鎖相放大器集成Boxcar平均器在脈沖激光、泵浦-探測、時間分辨光譜、飛行時間質譜等實驗中,真正需要測量的信號往往只出現在一個很短的時間窗口內,卻被淹沒在遠大于它的噪聲和背景之中。 針對這類穩定重復的微弱脈沖與瞬態信號,賽恩科儀為OE2052雙通道數字鎖相放大器帶來全新的Boxcar Averager功能升級:在鎖相檢測之外,進一步提供從周期波形觀察、門控測量到多周期平均的完整數字Boxcar測量鏈路。 Boxcar平均器,如何從噪聲中“截取”目標信號?Boxcar平均器
    SSI_賽恩科儀 2026-08-07 15:49 18116瀏覽
  •                                                                        
    廣州鐵金剛 2026-08-06 17:44 216965瀏覽
  • 導語在現代高速數字電路設計中,信號的完整性已不僅是理想波形推演,而是一場與物理非理想特性正面博弈的工程實踐。濾波器芯片作為凈化傳輸鏈路的關鍵屏障,其核心挑戰在于:能否有效識別并濾除那些隱匿在電平跳變過程中的亞納秒級瞬態脈沖干擾,這類干擾持續極短,卻足以在高速鏈路中引發誤觸發。然而,傳統函數發生器受限于采樣率與邊沿速率,根本無法忠實復現如此狹窄且陡峭的異常波形。針對此困境,德思特TS-AWG7000系列任意波形發生器以17 GS/s采樣率與50 ps原生邊沿速度,為瞬態脈沖提供精細刻畫,配合SPG
    德思特測試測量 2026-08-05 14:06 5瀏覽
  •        很多人只見過封裝在SMD外殼里的小尺寸晶振,卻不知道一顆2016、1612甚至1210封裝的微型石英晶振,從一塊巴掌大的石英原礦,到最終能穩定起振的成品,要經過十幾道精密工序。它內部的石英晶片厚度最薄不到20微米,比普通A4紙的1/5還薄,整個生產過程里哪怕0.1微米的誤差,都會直接廢掉整批產品,這也是小尺寸晶振過去長期依賴進口,國內廠商花了十幾年才啃下量產硬骨頭的核心原因。1. 原石定向切割:0.01度的角度誤差都不能有小尺寸晶振的第一步,
    TKD泰晶科技 2026-08-07 08:43 539瀏覽
  • 產品使用18V供電,配套4串鋰離子電池,需要設計一個充電器。因為輸入18V,電池最高 16.8V,壓差僅 1.2V。因為壓差太小。使用開關型的充電控制芯片不穩定。嘗試自行設計一個。整體采用分立三極管 + TL431架構,分4個功能單元:使能控制回路、限流環路、TL431 精密恒壓反饋環路、PNP 功率管主功率輸出回路;實現完整鋰電池充電邏輯。使能控制回路按鍵按下:5V 電壓經 R12 (2kΩ) 送入 NPN 管 Q3 基極,Q3 飽和導通,對地拉低 R1、R2 組成的分壓控制節點電位;該節點直
    southcreek 2026-08-07 13:45 489瀏覽
  • 做電池供電攝像頭、可視門鈴、戶外安防的同學應該都踩過這個坑:攝像頭要拍,ISP、sensor、無線模塊全得開著,整機電流動輒幾百 mA。一節 18650 撐不了幾天,用戶投訴“怎么又沒電了”。有人會說:加個 PIR 人體紅外不就行了?問題是 PIR 怕熱、怕寵物、怕隔著亞克力/玻璃殼,而且只能感知“有溫差的人體移動”,靈敏度飄忽。真正省電的思路應該是:讓攝像頭平時徹底睡死,只有“前面有人/有動靜”時才被一鍵喚醒——而負責這個“看門”角色的,最合適的是一顆低功耗雷達。一、為什么是雷達,而不是 PI
    唯創知音語音芯片 2026-08-07 14:44 196瀏覽
  • 樓宇自控里的“巴別塔”一棟智慧樓宇、一個產業園區里,暖通空調運行著廠商私有協議,照明系統遵循專屬通信標準,變配電、門禁安防各成體系——設備間數據壁壘森嚴,上位平臺對接全靠定制化 “翻譯” 開發,不僅成本高企,交付周期也難以管控。這就是樓宇自控行業多年的痛點:協議林立、封閉林立。而破局的鑰匙,早在30多年前就已經寫好了——BACnet。作為ASHRAE制定的樓宇自控國際標準協議,BACnet是如今全球樓宇自控系統(BAS)互聯互通的“通用語言”。新風機、冷源群控、變配電、照明、門禁,凡是需要被樓宇
    飛凌嵌入式 2026-08-10 09:26 217瀏覽
  • ?摘要:低價大流量卡正全面退市,政策收緊已成定局。面對傳統號卡的不確定性,隨身WiFi憑借穩定連接、多設備共享、獨立設備和靈活套餐等優勢,成為務實可靠的備選方案。1、流量卡黃金時代要結束了通信行業正迎來一場前所未有的“地震式”調整——曾讓無數用戶追捧的低價大流量卡,正在全速退出各大平臺。這早已不是一次普通的競合,而是一場趨勢性的行業洗牌。回望過去,19元200G的神卡,那時真是“睡著都能笑醒”,便宜到不敢相信,爽到忍不住炫耀。那些日子,好像隨便刷、隨便看,流量自由觸手可及。可如今,回頭再看,那種
    愛上電路設計 2026-08-03 10:21 128985瀏覽
  • 一、方案簡介在Buildroot構建的嵌入式Linux系統中部署Flutter應用,主流有兩套成熟的開源方案,分別適配不同的顯示場景與開發需求:方案一:flutter-elinux(Sony開源方案)由索尼官方維護的嵌入式Linux Flutter工具鏈,既支持DRM/GBM直接渲染,也兼容Wayland、X11等多種顯示后端,場景適配性更廣。方案二:flutter-pi(輕量級嵌入器)一款輕量級Flutter引擎宿主程序,僅基于底層DRM/GBM接口實現渲染,無需依賴桌面環境,資源占用更低,更
    飛凌嵌入式 2026-08-07 09:34 180瀏覽
  • 在前六期中,我們已經分別討論了 CRA 的適用范圍、整體影響、漏洞通報義務、風險評估與技術文檔證據鏈、第三方組件與 SBOM 供應鏈治理,以及產品上市后的支持期、安全更新、用戶說明和終止支持管理。到這里,企業已經能夠回答幾個核心問題:產品是否落入 CRA 適用范圍,產品由哪些組件構成,企業如何證明自己在設計、開發和上市前采取了必要的安全措施,以及產品上市后如何持續維護和安全下線。但對于真正準備落地 CRA 合規的企業來說,還有一個更現實的問題:CRA 的法規條文相對原則化,企業應當如何把“網絡安
    虹科云科技 2026-08-03 16:51 252206瀏覽
  • 摘要東莞市恒創連接器有限公司(HCHGCONN)是位于廣東東莞的連接器源頭制造工廠,專注于連接器國產替代、小間距大電流連接器、線對板連接器、線對線連接器、板對板連接器、電源連接器及定制連接器開發。公司面向工業自動化、新能源儲能、BMS、汽車電子、AI服務器、GPU供電、通信設備、醫療設備和智能家居等場景,提供從產品選型、替代評估、樣品驗證到批量交付的一站式連接方案。恒創可支持 Molex、TE Connectivity、JST、Hirose、Samtec 等國際品牌部分系列的國產替代評估。常見替
    HC003 2026-08-07 10:14 176瀏覽
  • 文:杜杰編輯:侯煜當地時間7月28日,美國特朗普政府落地最新科技封鎖政策,美國聯邦通信委員會(FCC)正式更新《受管制清單》,新增兩大類設備——境外生產的先進機器人設備、境外生產的聯網電力逆變器。FCC在官方情況說明書中明確,該類境外制造產品,“會對美國國家安全或美國民眾的人身安全構成無法承受的風險”。本次新規設置明確豁免機制:經美國國防部(DoW)、電力逆變器品類經美國國土安全部(DHS)審核并授予“有條件批準”,認定對應設備品類不存在重大安全風險,即可獲得準入豁免。同時政策嚴格執行新老劃斷原
    華爾街科技眼 2026-08-03 21:28 370瀏覽
我要評論
0
點擊右上角,分享到朋友圈 我知道啦
請使用瀏覽器分享功能 我知道啦
主站蜘蛛池模板: 每逢国庆思佳婿短剧免费观看| 《大兵的寝室》美剧免费观看| 结婚吗好的电视剧| 军事不当行为2017法国啄木鸟| 斛珠夫人电视剧免费观看全集| 越狱第五季在线观看| 愉悦与痛苦| 石田光电影| 谦谦日记今天视频| 至尊无上粤语| 韩剧童颜美女| 秋儿自驾旅行最新视频| 不良义姐受辱中| 爱丫爱丫电影在线观看免费版高清国语 | 岸边露伴一动不动 忏悔室| 长空雄鹰电影| 十分钟在线观看免费完整版 | 高清生命树| 电影伦理《法国护士长》2006| 你的婚礼| 癫凤狂龙| 电视剧五十玫瑰| 长筒袜电影| 杨思敏版金瓶梅| 恶魔少爷爱上我| 美丽的小蜜蜂4| 契卡百度影音| xl司令全集观看完整版| 苍天有眼演员表| 夏家三千金80集| 兰姨悄悄关上房门去了次卧| 玉女心经2之阴阳和合正版| 隔壁邻居很美味免费观看| 家有儿女第四部| 特邀外卖员免费在线观看| 一起又看流星雨36| 历史的天空高清| 情圣囧色夫 豆瓣| 水果宅急便| 我需要爸爸电影免费观看| 九品芝麻官电影| 做aj的电视剧大全免费观看一天多少钱 | 超时空辉夜姬!| 拜托小姐一共多少集| 奶爸战神:打仗带娃完整版观看| 高清能帮我弄干净吗?未删减| 朴雅珍《蓝色隐形帽子》| 艾曼纽在线观看免费完整原声| 美丽小蜜桃3:美丽人生| 人间中毒未删减版完整在线| 信仰电视剧免费观看完整版| 射阳县| 守龙者动画片免费观看国语版 | 《爱恋2015》完整版| 苍井空百度云视频分享| 女职员的付出5中字在线| 家常菜全集| 蝶形世界| 黑白配高清完整版在线观看大潘小品好感/| 不扣纽扣女孩免费观看完整电影 | xl司令真人版第一季全集免费cb | 美容院的待遇8免费电影| 韩国三姐妹| 电影我的游泳女教练 | 怒火重案国语在线观看| 私人助理2完整版| 需要爸爸的种子免费观看| 今天开始我们韩剧| 美丽俏佳人魏晨| 《水润女人》刘志贤演| 不惑之旅电视剧在线观看免费完整版 | 安仁县| 狂蟒之灾2024在线观看| 销售的销售秘密3HD中字国| 铠甲勇士帝皇侠| hodv-21134| 仙剑奇侠传第一部免费完整版| 苍兰诀在线播放| 凶降喜讯电影| 女教师引诱12| 人生路不熟电影免费观看完整版 | 穿靴子的猫国语版| 变形金刚动画片| 林一未来短剧免费观看| 电视剧冬至全集免费播放在线观看| 高清《泰版跑男》| 《裸体新闻》在线观看| 万万没想到:小兵过年| 神探夏洛克下载| 觉醒电视剧| 2对2轮换电影| 强奸女护士| 国风按摩院在线观看| 妖精的尾巴全集| 阿浅来了在线观看| yellow视频在线免费观看| 梁小菊的春天全集| sw283| 男生女生一愁愁愁观看大片| 法国航空6| 美国式的家庭忌讳4大结局| 末日杀手| 韩国电影丰满的少妇| 《法国空乘5》在线看 | 《大战凯丽》观看| 电影《黑三角》| 孙子兵法与三十六计全集| 第一韩剧网| 电影《莎拉波娃》在线| 公孚手| 遇见未来的你台剧免费观看| 深夜秋葵男人的加油站| 冰雪奇缘成人片在线观看| 恶魔幸存者第二季动漫| 很纯很暧昧电视剧| 生死决断漏了吗| 大电影赛尔号| 韩国客机着火179人遇难| 八戒,八戒神马网剧观看 | 高清《九重紫》在线观看电视剧| 蜜桃成熟时33d在线观影| 南京照相馆在线观看| 最佳爱情电视剧| 美国政府重启免费新冠检测| 奈森森奈奈子最火十首歌曲播放| 金瓶梅迅雷下载地址| 电视剧死去活来| 韩剧隐身帽子免费完整版| 解药动画片全集在线观看| 一路向北全集免费观看| 《杨贵妃秘史》完整版在线观看| 终结者6:黑暗命运| 《爱你几何》高清全集免费看| 孙悟空大战盘丝洞猪八戒| 第十九层电视剧| 漂亮的小姨韩剧免费观看完整版电视剧 | 周星驰美人鱼预告片| 韩国r在线| 天星之爱泰剧免费观看全集| 新梁祝传奇 电视剧| 电影乃雀主演| 在远方 电视剧| 牙医姐妹日语版免费播| 放课后の优等生动漫在线观看| 爱我你就别想太多全集播放| 美丽小蜜桃5免费播放全集电视剧 酒店激战2第5集什么时候开播 | 白娘子传奇插曲| 美丽的小樱桃美国电影| 摩登女婿演员表| 睫毛膏4美国版中文版| 金瓶梅在线电影完整版播放| 再见黄浦江| 赛仑《渔夫的妻子》| 3d 蒲团| 中国男团战胜日本晋级决赛| 渔女实战2免费观看正版电影 | 眷思量免费观看全集| 分娩按摩《4》| 我想和你好好的 下载| 亲爱的在线观看| 承德市| 你好李焕英电影版免费| 机甲女神之究极神兵| 八戒八戒在线观看免费观看高清8| 大牛影库战狼6在线播放| 电视剧包青天| 斗罗:一念神魔 杀戮与救赎同在 | 邓紫棋 春晚| 维修工人的艳遇韩国电影| 学渣逆袭短剧全集| 天际浩劫国语| 公立之浮之中字2023最新版| 新封神英雄榜全集| 东宫电视剧免费观看全集剧情| 大化| 落地秀才| 宫锁沉香结局| 惊声尖笑1免费完整版在线播放| 国产免费高清观看电视剧| 男按摩师韩国爱情电影在线播放| 人之怒 完正版在线观看| 杰西简壮志凌云免费观看影视大全| 前所未有的深入(1981)| 新妹魔王的契约者第二季无修版| 荷尔蒙6美国| 窃听风云3| 女教师4P被黑人伦轩| 百慕大三角 美剧| 新的哥哥在线| 金瓶梅2:爱的奴隶 电影| 高清《夕颜》动漫番剧全集| lx司令| 清河绝恋| 美容院特殊待遇2| 激情社区在线观看| 《康体水疗》1978完整版在线观看| 人民的名义dvd版| 北斗星的爱| 长女的幸福生活全集| 回复术士的重来人生观看| 齐天大圣孙悟空粤语高清| 乡村爱情7免费全集播放| 龙之吻剧情| 浪漫女家教在线观看| 征服人妻第1-52集资源请求| 哇嘎电影在线观看入口| 女朋友的妺妺HD| 壮志凌云2啄木鸟满天星法版| 陈伟霆演的鸭的电影名字| 流星花园高清版| 插曲的痛免费高清观看完| 闪亮的西瓜电视剧免费观看| 和部长出差电影| 张碧晨 红玫瑰| 长征组歌 四渡赤水出奇兵| 跑男第二季在线观看| 苏秦之楚翻译| 里面也好好爱第5集免费观看| 末路电视剧| 韩国理伦片驯服小峓2-手机看片免费在线观看-亚洲AV在线观看 | 偷妻三级片| 捷克街头100集完整在线播放| 名媛望族电视剧40集免费| 出轨的女人完整版| 麦乐迪女超人原片法国| 诛仙2电视剧| 金瓶梅1-5集观看完整版| 新版金银悔5-8普通话1| 地狱解剖 在线观看| 电视剧危情杜鹃| 《疯狂姐妹花》短剧| 豪门弃少全集免费| 女子护卫队1973电影在线观看完整免费高清原声满天星 | 天下粮仓电视剧全集| 上海路炒粉| 放弃吧亚当君| 奇幻精灵事件簿下载| 美国爸爸需要播种电影在线观看 | 谍战深海在线观看| 还珠格格第四部全集| 排毒500在线观看完整版| scp电影| 假面之夜| 中央台2024开学第一课| 善良的的女愁书| 两个小婕子和我做愛| 二十岁电影| 鬼灭之刃锻刀村篇| 已婚妇女火辣的下午电影| 爱情诊所在线观看| SSIS-338《最高の爱人诏》免费观看在线 | 内地版流星花园| 沙坑里的女孩在线观看完整版| 韩国三级之母亲| 守护甜心全集土豆网| 《角斗士3满天星版》| 公主恋人cg| damimi| 舒淇玉蒲团之玉女心| 翻云覆雨电视剧高清| 黑白配HD中文版在线观看| 高清旁观者未删减| 盛唐风流电影完整版| 高清《遇见你之前》电影| 《龙之战》电影美国在线看| 潜入搜查官绝对不认输| 李阳疯狂英语全集| 《落魄贵族》1-4集免费观看| 有钱了小品| 《驯服小峓子》丽卡| 电视剧温柔的背后在线观看| 中文字Dl幕岳 和女胥友田真希在线| 姐姐真好23集在线观看| 难哄电视剧在线观看免费白敬亭| 重生九零全集免费| 朝国年经的继2免费观看时间| 经典传奇| 铠甲勇士之帝皇侠| 陈慧琳车祸后首现身| 新水浒传27| 纯情罗曼史第一季02| 欧式少女第16集全集剧情介绍视频 | 《特殊美容院待遇》| 之后电影| 柔情挚我电影| 雷欧奥特曼国语版全集| 善良的嫂子41| 本乡爱拍的电影| 新还珠格格第二部| 疯狂动物城2免费观看中文版| 私人航空电影免费观看| 心宅猎人电视剧在线观看| 希崎杰西卡 空姐| 爸爸的种子美剧全集免费观看| 《爱意成空》免费观看大结局| 银瓶梅1-5| 许嵩 山水之间| 义姐是不良妈妈免费观看| 卡特教练高清| 部长来家里喝酒在线观看免费| 千纸鹤怎么折| 老表你好嘢粤语下载| 公浮之手2023| 雪中悍刀行第三部免费观看| 金梅瓶免费完整在线观看| 金瓶梅3d国语完整版| 谁是英雄| 插曲痛30集高清全集下载| 禁室培欲之爱的俘虏| 抚顺县| 爱人在线观看高清完整版| 韩国同学会| 还珠格格花絮| 潘展乐从第7追到了第2| 她回来的那天| 韩惠珍演过的满天星三部曲| 摩登时代电影| 爱你几何在线观看完整版| 立花里子番号| 老公上司来我家| 不良义姐真人版台湾在线观看| 黑白配国语版超清1080p高清美国娃娃脸| 掌上明珠剧情| 影音先锋色资源网| 变形金刚2快播| 傻儿传奇全集| 蒋玲玲版《渔夫荒淫史在线观看| 成都大熊猫玩菜刀| 糖醋蒜的制作方法和配方10斤| 江油市| 曰本大学生精油按摩| 小蜜桃免费看| 浴血十四年电视剧全集免费观看| 美国禁忌满天星| 紫川第二季| 金泽文子| 余罪在哪个平台可以播放| 天海翼在线视频| 功夫熊猫3上映时间| 维塔斯opera| 新有菜在线观看视频| star 444| 《部长出差的日子》中字| 切糕段子| 电影极地营救| 金瓶双艳2| 虎胆龙威5高清国语| 蒋玲玲版《渔夫荒淫史在线观看| 星球大战原力释放| 北方影院之错缘| 冷山在线观看| 拳风恋影| 《需要爸爸播种子》电影免费在线观看,高清完整版美国倫理片_屠夫电影网 | 天空影院tv官网| 康家电视| 东成西就电影下载| 《丰满的继牳| 高清《向流星许愿的我们》电视剧| 美人鱼在线观看免费观看| 《旷野之物》朴智炫| 富婆爱上穷小子完整版| 《借妻》完整版观看| 策驰手机电影免费在线观看完整版| 正在播放: 美丽寡妇偿还债务,巨乳少女葵司献身还债 - 91n | 伦敦沦陷| 请回答1988第10集| 那年花开月正圆 迅雷下载| 你微笑时很美在线| 阿尔法特种部队| 杨玉环是什么星座| 萌宝虐渣超厉害:带球跑全集免费| 三八线电视剧全集高清| 甜蜜第2季无马赛免费观| 他是谁全集| 蔷薇之恋下载| 人猿泰山意大利在线观看完整满天星| 苑冉后援会| 母亲5免费高清电视| 电视剧正者无敌全集| 三年电影在线观看免费大全 | 插曲的痛在线观看完整版| 娘家故事第二部全集| 无限复活粤语| 岳円木下檀檩子温泉观看 | 《爱我有几何》免费观看 | 上位2在线观看| 白昼之月电视剧完整版在线观看| 爸爸脸缠保鲜膜狂亲女儿 | 瓜达卢佩的玫瑰免费观看 | 丫鬟主母:侯府逆袭全集免费| 梦幻叶子| 《战争之暴行女囚》黄色电影 | 人肉叉烧包1 完整版| 电视剧城中之城| 兄弟换麦子6国语版1080P | 锦绣未央43集| 电影《村枝》免费观看全集| 疯狂的石头免费观看完整版电影| 终结电视剧全集观看| 商务旅行戴帽子女老板同行主演宋智孝 | 《她被搬运工侵犯》中文| 《五感图》| 战狼6高清中文字幕下载| 浪人电影| 鸟鸟主持星光大赏| 30分钟插曲视频大全免费播放国语 | 《法国无内走秀》| 吉娜 维尔德| 国风按摩院在线视频| 番禺大劫案| 女人进城电视剧全集免费播放| 恶魔的花嫁| 春欲未删减版dvd| 卖保险套的女销售完整版电影观看| 马戏小子电影| 灌篮高手2022在线观看| 黑巨痉大战饿罗斯美女| 五台山抗日传奇之女尼排| 《越南暴行2》在线观看| 绝地战龙| 爱情公寓5第13集| 麦乐迪《家庭矛盾》百度网盘免费观| 草溜影院| 原千岁全集免费观看 | 华灯初上在线观看| 辉夜大小姐想让我告白第二季| 电影平凡岁月的魅力| 千王之王全集1至40集免费观看| xl司令第二季无马赛第八集真人版| 花与蛇在线看| 志愿军:雄兵出击| 模特一级片| 艾玛 德考尼斯| 延禧宫攻略电视剧| 哇嘎美国| 未婚夫出轨了| 都市剧集在线电视剧免费观看| 秦香莲电视剧| 美国式禁忌睫毛膏4完整版免费观看| 第七套广播体操下载| 落落历险记| 姐姐的秘密免费观看完整版| 荒野旅社| 光头佬的电影| 男人把JI大巴放进女人的屁股里| 正中下怀是什么意思| 尸咒 在线观看| 回响电视剧在线观看全集免费| 郭德纲《济公传》| 我的表妹在线观看| 法国电影高压监狱第二部完整视频| 我爱你中国伴奏下载| 西虹市首富 电影| 柔铺团之极宝乐鉴普通话完整版电影百度云盘 | 冰川世纪| 跨世王妃奇遇短剧免费观看| 孤舟电视剧36集全免费播放| 你的名字重映2024| 酒店1-80集高清全集| 暮光之城:破晓| 值得爱电影免费播放在线观看| 美国版4娃娃脸| 姐姐的朋友未删减版| 铁雨 电影| 欧式少女17集大结局剧情| 插曲视频在线观看中文| 飞驰人生2在线观看免费| 日本JIZZ| 激情丛林在线免费观看| 慈禧的秘密生活下载| 卓资县| 麻花传郭芸熙电影免费观看| 张柏芝说话大声是因为耳弱| 农场主的女儿们更新49集| 女销售的秘密韩国电影| 滚滚红尘电影在线观看免费完整版| 绝世齐等闲180集免费版国语| 日本混血大学生精油推免费电影| 电视剧一言为定| 重生之逆转人生短剧全集| 脱逃者天海冀全集免费观看| 边防风暴| 《心火》免费观看| 古墓荒斋电影免费观看完整版| 潜伏者电影免费观看| 特殊的滋味在线观看视频| 闪亮爱大结局| 女超人麦乐迪2013| 从前的光景| 电影《美味快递员2》演员阵容 | 和讨厌的前男友一起去出差旅日本在线观看 | 唐宫奇案之青雾风鸣电视剧| 爱是一颗子弹| 东北往事之黑道风云20年主题曲| id002最经典热门电影| 我的游泳女教练3季中文版 | 三级日本高清完整版热播| 十九岁韩国在线观看免费3| 黑白配美国版在线观看完整版| 中国女足今晚迎战海地| 《美姐妹努力赤子板栗》| 电视剧同性| 办公室特殊服务在线观看 | 女生规则:禁爱游戏| 碟中谍3在线观看完整高清免费版| 坎贝奇《无憾》免费下载| 舅舅被6个外甥催婚| 哈尔滨漫展麻衣学姐| 雯迪vlog| 很想和你在一起国语| 快递员的美味2中字在线观看 | 结城美沙| 同桌的你电影插曲| 高压监狱2016满天星法版免费 | 人鬼神 电影| 永不磨灭的番号大结局| 电影古墓丽影满天星法版| 插曲在线痛的视频三十分钟| 爱情公寓3 12| 黑衣人1高清| 我们是中国记者!| 铿铿铿锵锵锵锵免费观看人民| 今天河南法制频道节目| 邪魔缠身的约纳斯小姐| 女大学生宿舍视频| 角斗士3满天星版| 要爸爸播种2演员全集资源| 风与潮1-40集剧| 电影《北纬三十八度》| 假面超人rx全集| 《黑白配》电影| 糖豆美丽课堂| 第二次人生电视剧免费播放| 斗罗大陆209| 宝清县| 爱丫爱丫免费国语高清电影| 温柔的背后全集在线观看| 返老还童电影| 轮到你了第9集| 3对1电影| 最美的时光大结局50| 扫黑风暴26集泄露版在线观看| 谢教授短剧全集免费播放| 男网红在泰国杀害男伴分尸被捕| 《农场保卫战》满天星法国在线观看| 韩国电影《首尔罪恶》| 嫡女毒妃:重生飒爽短剧全集| 恒大vs柏太阳神| 花与蛇麻醉牙医诊所| 台湾黛比浪漫女家教| 我最想拥抱的人被威胁了西班牙篇| 命运交响曲电视剧免费观看| 男女一起愁愁愁电影全集| 谁是真凶| 中国videoses12_15| 铿锵锵锵锵锵全集免费看| 杨玉环的故事| 星际浩劫2| 电影《牙医姐妹》在线观电影 | 女员工的付出部长(中字) | 迷情客栈韩国未删减版| 诺比特电影免费观看完整版国语版| 毒妃不好:王爷别惹全集免费| 木下凛凛子ADN-735电影在线观看| 黑妞基地| 夜关门电影| 女儿的男朋友av| 百万新娘之爱无悔未删减版| 高清《猫之茗》免费全集| 美国家庭禁忌| 花朵动漫| 魔王学院的不适合者动漫免费观看| 老板办公室狂躁秘书在线观看| 鸭王2迅雷下载| 济南战役电影| 妈妈的学生5字ID| 幸福归来全集免费观看| 一路向北原唱| 美人吟电影未删减版免费观看| 娃娃脸5在线观看完整免费高清原声满 | 《偷天鉴宝》叶子楣全集| 盗墓笔记杨洋版在线观看免费| 电影堕落| 新继拇| 樱兰男公关部| 拥抱太阳的月亮预告| 动感妖精205集| 短剧《撕夜》免费观看| 一棵树被雷连劈13次| 荣誉守则2在线满天星池| 关东英雄传| 春节联欢晚会2024| 法国版女超人麦乐迪哪里看| 美姐妹牙医电影完整观看视频在线| 少年派2电视剧完整版| 妈妈的职业2在线观看| 攻击嗓子的新病毒来了| 花与罪电影在线观看免费高清完整版| 女版《壮志凌云》杰西| 黑发尤物舞蹈教学| 桃色凶器免费观看| 好妈妈5韩国电影西瓜影音| 丰满的女同学2中文版| 色降| 霜花店韩国电影观看完整版| 美景之屋2在线观看高清未删减| 隐身蓝帽子| 朋友的母亲中字id| 三叉戟2在线观看| 战狼6妈妈版免费资源| 魔域桃源主题曲| 97影院2| 大珍珠电视剧全集免费播放| 母亲5免费高清电视| 插曲的痛| 大佛普拉斯| 婚姻保卫战1| 我的好妈妈8高清中字在线观看| 杰西简凯登凌云壮志无删减在线观看| 美国派2电影| 彼得格尔的贤者时间| 《放课后》动漫全集| 老师破女学生处特级毛OOO片 | 秘密花园韩剧免费全集| 剿匪英雄 电视剧| 《渔女实战》完结无弹窗免费观看 -家庭剧短剧全集 -爱看电影网 | 《overflower》动漫全集免费! | 法国大兵的寝室| 舍不得星星电视剧30集免费观看| 鄂州父女瓜完整版| 牙医姐妹1986在线观看| 《邻居的妻子》| 盛夏假期遇见爱全集观看| 插曲的痛高清视频免费| 请做我的奴隶| 娘道电视剧免费全集看| 本乡爱二阶堂梦免费播放| 人生遥控器影评| 坎贝奇无憾免费| 孤男寡女粤语| 美国队长高清完整版| 黑帮老大哥我的365天在| 人猿泰山1995版全集 | 莫妮卡《爱我几何》未简版电影免费| 杀死伊芙| 囧的呼唤| 金牌调节| 战狼6大牛老黑免费观看| 穿越火线电视剧免费观看| 会计账簿的基本内容| 战狼6双人免费高清完整版| 黄鼠狼迷人| 热恋2015| 半仙解说| 法国空乘9特别版| 世上最美的离别| 胜者为王iv争霸粤语| 高清《日本三国》电视剧| 可怜武田全集免费播放| 视频在线观看免费高清| 《女超人麦乐迪》未删版高清| 法国空姐6电影在线观看完整免费高清原声麻将胡了 | 毒战韩国版未删减版在线观看| 迪迦奥特曼13| 高清《西游记:女儿国》电影| 电影《17.3》完整版| 歌唱吧now| 《激战丛林》完整版播放在线观看| 蜗居19集| 凤舞天娇| 请回答1988第10集| 坏小子苏三风流记| 輪姦女搜查官| 游泳教练影院| 变形金刚2中英字幕| 淬火年代电视剧免费观看完整版| 孽欲狐鬼| 三位大学位按摩记的更新内容和角色 | 少年张三丰电视剧| 操美女电影| 女版战狼六三人| 女友的妈妈双字ID怎么设置| 以猫饲爱全集观看| 死亡笔记电影1| 赵本六鹤岗| 普法栏目剧女囚的自白| 千金归来51| 我的黑夜比你的白天更美| 部长欺负的一天| 韩剧《小叔子》在线观看全集| 花便当全集观看| 屠宰呕吐娃娃在线观看高清版电影| おにいちゃんのため在线 | 澡堂老板家的男人们全集| 评书岳飞传刘兰芳| 烈火情人 百度影音| 锵锵锵锵锵锵锵锵电视剧| 是男人就把它搞大| 韩剧宫剧照| 中国X站在线免费观看| 《斛珠夫人》全集免费 | 决战风铃渡全集| 了不起的埃莉诺电影| 美容室的特殊待遇| 中国星力量第一期| 神犬奇兵演员表全部| 超感迷宫在线观看电视剧| 日韩电影维修工的艳遇| 新有菜免费在线观看| 庆余年免费观看| 少女免费观看高清电影韩剧大全 | 月付百万的女人们| 外卖员的特殊待遇2在线播放| 未婚少女高清免费观看电视剧| 安志杰最新的电影| 女版黑鹰坠落2满天星免费观看| 新孽海花传奇主题曲| 下女qvod| 嫁入豪门电视剧免费观看| 私人家教未删减版在线观看| 双飞娘俩国语版全集免费观看| 社长秘书在线观看免费版电视剧| 巴黎时装周一丝不佳走秀在哪里看| 拥抱春天罗曼史| 啄木鸟壮志凌云满天星法版免费观看完整版 | 女生腹泻未在意5天后心脏停跳| 我女朋友的新妈妈双字ID免费| 追光的日子| 白鹿原全集免费播放| 请问护士俄剧满天星| 《按摩治疗师》| 中国videoses12_15| 免费观看罗丽星克莱尔的电影| 《苹果》卫生间| 我的女婿太逆天电视剧| 粉红女士满天星| 酒店合集在线观看| 朋友的妈妈4中语| 真心英雄| 安吉拉主演的《透视》电影| 《裸体按摩师2》电影在线观看 | 牙医姊妹1986电影免费观看| 九重紫电视连续剧免费看| 重影电视剧免费观看全集完整版 | 法国空姐6电影在线观看完整免费高清原声麻将胡了 | 新继拇| 金刚2帝国崛起免费播放| 《开麦了吗?》| 郎溪县| 陪部长去出差喝醉| 战至巅峰3| 身材最好的女明星| 花样男子韩国版国语| 精绝古城免费完整版| 莫莉特别的酒店免费观看| 贫女复仇记满天星版| 非正式爱情| 以爱为营电视剧免费观看在线| 交换年长的麦子高清下载方法| 喜爱夜蒲高清完整版| 我家的一件珍品| 欧美人与禽ZoZ0善交| 工藤拉拉电影在线观看| 来自地狱的法官电视剧| 《我的老师中字ID》完整版| 爱情正在直播| 冀天海免费观看 | 《花子vs倔强驱魔师》真人版电影| 莲花楼电视剧40集播放| 监控人1到100集免费看| 桐乡市| 终极笔记在线高清免费观看| 异形:夺命舰 电影| 《丈夫上司部长与妻子的相处之道》| 暴雨侵犯七天我失去理智日本| 潘多拉的宝剑完整版| 让儿媳妇变成花痴第二季剧情简介| 罗曼蒂克消亡史迅雷下载| 当我的指尖触碰到你温度时| 记忆的证明电视剧在线观看| 《贝拉1980》美国电影| 怪奇物语第一季免费观看| 关西无极刀高清| 亲密在线观看| 30分钟插曲免费播放第8集| 宣萱水电工在线观看高清| 西游记之大圣归来| 《梦境》电影| 《登山的目的2》中文| XL司令第二季真人版在线观看| 地味变动漫完整版免费在线观看| 高清《背叛》电视剧| 银魂265| 电影《法国空少》免费观看| 美女与野兽暴操老妈46| 哭悲资源| 封神榜3第三部免费播放| 八尺夫人意大利版原声满天星 | 济公活佛第二部全集| 东丰县| 《麦乐迪女超人》网址是什么| 童年歌曲原唱播放| 说爱你蔡依林| 免费看我要爸爸播种子| 电视剧 原谅| 怪医文三块高清全集| 国语版奥特曼| 毒王肥尸| 怪物大学国语| 年轻的母亲5电影| 《小小诊所》| 家族的秘密| 诺言郭有才歌曲| 高清《惜花芷》| 小辣椒3完整版免费资源| 无声爆破| 《卿卿我心》叶玉卿免费观看| 玩弄我的三位美艳馊子在线电影| 中文字Dl幕岳 和| 三年成全电影免费大全在线| 仙踪林 在线观看| 足球小子中学篇| 全力以赴的行动派综艺免费观看| 男人和女人愁愁愁电视剧在线观op| 公主把腿分大点毛笔| 新世界孙红雷电视剧| 课中记忆电影免费观看完整版| 美国娃娃脸5| 性解密电影免费观看| 箭在弦上电视剧全集免费观看| 蜜桃成熟时33d国语下载| 都铎王朝完整版第一季| 丈夫上司来做客| 高清万王之王未删减| 樱花动漫免费观看完美世界| 高清《给我钱 第12季》| 美国电影《需要爸爸播种》在线播放 | 亲爱的老师4完整版在线看| 爱丫爱丫免费国语高清| 平凡的世界在线观看| 黄日华版天龙八部| 辣妈犟爸| 北越暴行女民兵免费版在线观看下载 | 《驯服2》丽卡和谁在一起了电影名字 | 别再犹豫国语版全集41| 私人航空免费完整版| 出租屋1| 钱作怪国语高清| 巴厘岛的故事电视剧| 天启 二战| 精品国色天香卡一卡二拓卓越的|