數據結構(9)排序

一、常見排序算法

排序在生活中無處不在,上學這么多年班級排名啥的總有吧,不可能一次都沒見過;打游戲有的排行榜不也是有排序的思想在里面,排序倒不是什么特殊的數據結構,但是是非常重要的算法思想,所以在初階數據結構的最后,學習一下常見的幾種排序算法。

當然,有些算法已經實現過了,比如冒泡排序和堆排序,到時候可以重寫復習一下,但是不再展開具體細節了。

二、插入排序

1.直接插入排序

直接插入排序的思想最經典的對比就是打斗地主,抓牌以后習慣性的就是相同的牌放在一起,一般玩的多的老手也會順手按照升序或者降序排列(當然,按照牌規大小放不一定跟數值升降序一樣)。這就是最常見的插入排序。

當然,實際到對數組的排序肯定是有細節上的差異的,之前我們也大大小小寫過排序的代碼,排序函數的參數也就只數組arr和數組內元素個數,肯定不可能真的存在一個一個插的情況的,但是我們可以自己想想出來一個一個插。

怎么想象出來呢,比如給一個數組:

想想數組為空,現有數組的數據都是要一個一個插入進去的,所以剛開始是:

當然,只看第一個肯定看不出來什么門道,接下來繼續往里插入:

有數據的話再插入肯定就得比較了,將{}類比成手,那里面就是牌,牌肯定按大小排列好看一點。

繼續:

一比較,直接放

比較后前移:

前面幾個例子只能體會到一個一個往后遍歷的循環,但是想象中插入比較交換的循環卻沒能體會到,這個例子就得好好看了。

可見插入(想象出來的)比較,交換實際上也是一個循環。

所以代碼思路基本就有了,外層循環依次遍歷數組中的元素,內層循環使這次遍歷到的元素與前面的元素進行比較,如果不符合想要的順序,那就產生交換,而且比較和交換不止一次,有兩種情況會停止比較和交換,一個是已經符合順序了(比如上面的9,6),另一個是把已經排序(想象中一個一個插入)的數據比較完了,那就該停手將元素放到數組首元素的位置了。

思路一有具體細節代碼見:

外層循環遍歷數組都寫臭了,倒是沒什么好說的。

但是內層循環每次都得有已經排序過的數據的下標,初始值倒是好算,比如這個例子:

最開始插入2,2是待排的(或者說待插的)數據,下標就是i,那i的前一個元素下標也好算,直接i-1即可,但是這個例子很明顯不是比較了一次,而是比較了多次,那么每次下標都得計算:

這樣的話就不得不創建一個變量專門存儲這次比較的兩個數的下標了,這個就有點像冒泡排序內層循環了:

不妨以end為已排序數組的最后一個元素,可知每次進入循環end = i - 1。

那么這次比較的就是end和end + 1(為什么不用i,進循環走幾次就理解了)。

如果想要實現交換肯定得先臨時存下來2,也就是進入循環不僅要計算下標end,也需要記錄下這次插入進的元素。

不過2也別著急放,9一比較往后放是就算了,2可不一定比較一次就放了,很明顯:

一比較2又該前移,或者說6該后移:

循環比較,直到:

比的不能再比了,再比較越界了,那就直接跳出循環,所以進入循環的條件是end>=0。

當然,最后別忘了跳出循環時候把存起來的值放到end+1的位置。

直接寫代碼:

內層循環就是以end作為循環的條件,如果需要換那就把end位置的后移,如果不需要換就結束循環,最后在arr[end + 1]處放置這次插入的數據,思路里面說的非常清楚了。

測試代碼:

為了方便查看就寫了個Print函數,不多說。

經測試直接插入排序無誤。

時間復雜度

如果按照最壞的肯定是如果想排升序,給的降序數組,這樣的話如果數組大小為n,那么前移的次數就為1 + 2 + ...+ n - 1所以最壞情況下直接插入排序的時間復雜度就是O(n^2)(但是注意,這種情況實在是太少太少,所以一般直接插入排序倒不至于是O(n^2),一般比這個小)。

2.希爾排序

直接插入排序如果遇到與目標順序完全相反的數組序列的話,或者說假如你要排升序序列,結果給的數據是這樣的:

大的在前小的在后,那么這樣也基本n^2的時間復雜度。

為了解決這樣的問題,人們在直接插入排序的基礎上就創造出了希爾排序。

希爾排序的思想如下:
選定一個整數gap(這個gap是步距的意思,具體到例子里就明白了),依據gap將數組分成gap組,對每個gap組進行直接插入排序,所有gap組直接插入排序完以后進行某種運算(一般為gap = gap / 3 + 1,初始為gap = n / 3 + 1,只是為了縮小步距),當gap縮小為1是則為直接插入排序。

可見希爾排序在直接插入排序的思想上提前進行了多次預排序,避免出現與待排序順序完全相反的序列。

注意,這里的gap變化不是依據gap = gap / 3,只是畫圖輔助理解gap的含義。

gap是5,所以就往后數5個數,第gap個正好就是同一組的。

gap理解了以后就得一點一點想代碼怎么寫了:

以這個為例,先考慮一次排序怎么做。

錯誤想法

既然每次排序的每組都是以直接插入排序實現的,直接考慮考慮怎么套直接插入排序看看:

剛上去i = 0肯定也不用移,但是可以知道,如果要進行下一次直接插入排序,不能再i++,如果直接i++就成另外的組了,所以循環變量的變化應為i += gap,同樣由此end每次不能再--而是end -= gap,比較的內層循環也免不了改end初始化為i - gap,后移代碼改為arr[end + gap] = arr[end]。

兩層循環的條件再檢查檢查:

for循環只要不超出數組范圍即可:

while循環

這么一看還是不越界就行。

但是這才是一組的排序:

如果好幾組的話還得再嵌套循環,容易發現gap是幾有幾組,所以用for循環更好:

gap也得變啊,還得套循環計算gap去:

咱就不說時間復雜度到底多少了,就這么多層循環,也少不到哪去,肯定比直接插入排序大,而且說實話,兩層循環的時候調試或者改代碼都難讀的很,這都4層了,那還不得炸了。

正確做法

所以上面的思路肯定行不通,畢竟希爾排序可是直接插入排序的優化,索性這么干:
不套直接插入排序的代碼,依次遍歷實現直接插入排序,什么意思呢?

外層for循環還是遍歷整個數組,內層循環實現組內比較和互換即可,畫圖理解:

這樣分組i = 0和i = 1沒啥好看的都是組里第一個元素,也不比也不移,直接從i = 2開始。

明顯end初始化就為i - gap,需要存的temp還是i所指元素,進入while循環顯然的比較對象應該是end和end - gap,且end不能越界,end如果是下標為0,再讓arr[end]和temp比較,故循環條件為end >= 0,內層比較條件和循環變量注意即可。

最外層gap注意循環條件即可,最內層弄清楚下標。

時間復雜度

由于希爾排序時間復雜度實在太復雜(可不要以為三層循環就是n^3),可以說到了算無可算的地步了,只需要知道肯定比直接插入排序的n^2低即可。

三、選擇排序

1.直接選擇排序

直接選擇排序就是一次次的遍歷數組,把這次遍歷到的數組最大和數組最小放在數組的頭和尾,直至遍歷完整個數組。

還是以這個數組為例:

遍歷數組肯定免不了用下標,而下標起名也好起:

從兩邊往中間遍歷,這樣的話,最后找到min和max就能直接給begin和end位置的數據賦值。

細節直接看代碼:

一次選擇排序選最大選最小,比較需要注意的是for循環的起始條件和循環的限制。

因為min和max初始為begin所以遍歷的時候不需要自己跟自己比較了,并且越界條件是超出未排序元素的下標(已排序的已經保證比現在待排的最大或者最小了)。

只不過在遍歷的時候出了一點意外:

如果按照我們寫出來的代碼來看,arr[begin]和arr[min]互換一次,arr[max]和arr[end]互換一次,這樣不就相當于沒有換嘛,所以這種begin是max,end是min的前情況得想辦法克服。

要不然如果碰見就交換一次,但是有點小麻煩,干脆如果max剛好是begin,min剛好是end,就還是先交換,但是max換為了得讓max跟上:

最后外層while循環條件基本上就得往begin和end的關系靠一靠了:

可以很明顯看出來,相等就不用再排了,同樣的,如果begin比end還大,那也不用排了,所以循環條件是:

最后代碼:

測試代碼:

時間復雜度

基本上看來是這樣的,因為我們這個也算是改進過的直接選擇排序(一般直接選擇排序每次找最小的放到前面就行了),進入循環的次數是n - 1 + n - 3 + n - 5 +……+0(當然,最后不一定是0),再估算基本就是O(n ^ 2)。

2.堆排序

堆排序代碼直接手打了:

測試代碼:

時間復雜度

沒啥好說的nlogn。

四、交換排序

1.冒泡排序

接觸過的第一個排序算法,那時候還在學C,現在數據結構初階都快干完了。

不多bb:

測試代碼:

時間復雜度

n^2,經典的循環套循環。

2.快速排序

快速排序人如其名,可以說沒有什么突出的特點,就是一個快字,借助的思想就是二叉樹的思想,可不是跟堆排序一樣,時時刻刻都在保證數組就是個堆,只不過如果畫圖的話,看起來像一棵二叉樹。

核心思路就是選取一個基準值,依照基準值的大小,將數組序列分成左右兩個子序列,左子序列是比基準值小的值(不一定有序),右子序列是比基準值大的值(不一定有序)。重復此過程,最終,所有元素都會排列到相應位置。

基準值倒是可以隨便選,但是基準值可不是生來就是左序列比它小,右序列比它大,我們得想辦法實現出來。

當然,這個操作的實現可不止一種,所以接下來慢慢講:

①hoare版本

比如這樣一個序列:

咱也不亂弄,就讓數組第一個元素當基準值,接下來就借用兩個指針和一個哨兵:
?

key站在這次的基準值

left指針從左往右遍歷,找比基準值要大的數

right指針從右往左遍歷,找比基準值要小的數

為什么left和right要這么做呢?

其實是如果在key左邊有比key大的數,肯定得放到key后面;如果在key右邊有比key小的數,肯定要放在key前面。但是在確定key位置前,很明顯的我們不知道該把left往哪放才算往后放,right往哪放才算往前放,所以這時候就要swap:left和right。

我們可以遍歷試試:

每次判斷left和right的指向值,不符合我們找的值就left++right--。

交換過以后也要++--。

相等也要繼續遍歷:

但是這么一找,明顯越界了,所以一旦越界了,就該插入key了,因為我們已經整理好左右子序列了,這就倆指針,一個right,一個left,我也不知道為啥,直接跟right交換就行。

這樣一次找key就結束了,結果是這樣的:

key指向的元素歸位了,接下來就是循環這兩個子序列了,基本操作是一樣的,key就是子序列最開頭的元素:

確定好區間:


剛才光顧著討論跳出循環以及什么時候互換,忘了說一個玩意,我們left和right指針遍歷的應該是除了key以外的元素,畢竟key所指元素一直要被比,所以left初始弄個key + 1或者說key就是此次數組的第一個元素,left再++。

循環截止的條件也能看出來點門道,如果只有一個元素,或者說沒有元素的話,直接就可以停止循環了,當然,帶上下標更清楚一點:

如果再分,就是[4,4]和[6,5]區間,很明顯,一個是左等右,一個是區間不存在。

最左側的還得再分,但是最后肯定可以得到:

成功實現了排序,從這里也可以看出來類似于二叉樹的左右子樹一樣,所以干脆我們還用遞歸寫代碼:

快速排序的參數講究的有點多,因為我們快速排序的時候要的是區間,有的人一看就說,你傳n以后自己計算區間從哪到哪不完了,但是遞歸呢,遞歸以后還要自己計算嗎,這個left和right很好的代表了區間的左右,而不僅僅是為排序數組的首元素下標和尾元素下標。

放key的實現講究太多,單獨封裝成函數來看:

right和left的移動可不止一次,所以直接給一個while循環,while循環去找right和left目標值是沒錯,但是在這之前還得保證不能越界:

這里就是很好的例子,一越界就停手吧,雖然成功找到了比key小的數,這樣再交換豈不成無底洞了,早晚right得到key的位置,這其實是我們不想看到的。

另外,碰見相等的也不能等,有這樣的例子:

如果寫了等號,那就會瘋狂的向左遍歷right指針,一越界就停止,這樣會導致遞歸的樹穩定為n層。

即:

不寫等號可能對半分分遞歸就結束了,遞歸的次數基本上就是能分出來的二叉樹的層數,但是一旦寫上等號,不說right穩定得遍歷到數組最左端,光說產生的遞歸的樹都得n

即logn變n。

②lomuto版本

lomuto版本的主體思路大概如下:

用兩個指針,一個是pcur,一個是prev,prev指針初始指向pcur指針前一個。pcur的作用是用來遍歷整個數組,找比key要小的值。

具體細節如下:

pcur指針往后遍歷,尋找比key指向的值要小的值。

1)如果找到了,prev++,并將此時prev和pcur指向的元素互換,互換完以后prev繼續站崗,pcur++

2)如果找不到,pcur++

比如這樣的例子:
符合交換條件,prev++,再swap掉prev和pcur

但是遇到prev == pcur就沒有必要再互換,直接pcur++就行。

循環遍歷:

又是相等不用變,pcur++

這次遍歷到的很明顯就比key要大了,那就不管它繼續往后遍歷,直到碰見比key小的。但是在此之前,觀察到prev++后指向的要比key要大,所以這種遍歷prev后面的要不然就直接是pcur指向的,不用互換,要不然就是比key要大的,一跟pcur互換不就是實現了小在key前,大在key后之類的操作嗎。

展示其中一次不相等的互換:

找到了就prev++

swap

swap結束pcur++

也不廢話了,直接展示遍歷完以后:

這時如果swap key和prev所指向:

剛好實現基準值前比它小,基準值后比它大。

代碼實現:

當然,內層if嵌套可以合并一下:

遞歸的代碼不變:

測試代碼:

遞推版本快速排序的時間復雜度和空間復雜度的分析

時間復雜度基本上得類比我們做二叉樹oj題的思路,因為代碼產生了遞歸,可知,時間復雜度 =?? 遞歸次數*每次遞歸的函數的循環次數。

但是對于我們這段代碼時間復雜度卻不能死板的套公式去算:

如果按照公式順著走下去的話,倒是可以硬解,假設結點剛好能夠湊夠滿二叉樹,每層還都能對半分序列,這樣的話有:

每層都是結點個數 *? 每個子序列時間復雜度(因為相當于遍歷整個數組,所以基本等于結點個數)

1 *? n + 2 * n / 2 + 4 * n / 4 + ……+?2^{log(n + 1) - 1}? * 1

2^{log(n + 1) - 1}? * 1基本也是O(n)

有多少層呢logn層

所以時間復雜度為O(nlogn)。

當然,可以用分割的思想,如果一層一層來看的話,其實每層子序列合起來剛好就是原數組的長度,這樣想的話很容易想到每層時間復雜度都是O(n),并且有logn層,很容易得到時間復雜度就是O(nlogn)。

空間復雜度一般是不必多說的,但是對于這種類似于在棧區構建出來函數二叉樹的代碼還是來見見世面。這種遞歸代碼時間復雜度的產生就是因為函數棧幀的創建:

如果沒有回歸的話,就會從一個函數棧幀跳躍到另一個函數棧幀里去,這樣的話很容易想到,總有一條路是最長的,比如其它最多分兩三層,它能分5層,這樣得創建5次函數棧幀,其實這個最大值應該就是樹的高度,我們估算的話就是logn層嘛,所以空間復雜度就是O(logn)。

③非遞歸版本——借助數據結構棧

棧里面存的是本次需要找基準值的區間,通過入棧出棧實現對不同區間的基準值的查找以及遞歸區間的分割。

找基準值我們實現了兩種方法,所以找基準值到時候直接寫就行,問題是我們如果通過棧實現類似于函數遞歸的效果呢?
比如現在第一次找到基準值以后,就應該分開左右子序列,按照上面所說,我們分別需要在棧里存0,4和6,9并分別在子序列中尋找新的基準值,后面的不再口頭描述,直接看到最后:

帶上棧:

左邊界大于右邊界的就寫了一個,只要key在邊緣就會產生,可知,如果子區間左值等于右值,或者子區間左值大于右值,都是無需再排列的子序列,不再入棧。

代碼表達:

測試代碼:

五、歸并排序

1.歸并排序基本思路

歸并排序基本思路就是把不斷的把得到的數組二分拆開,得到有序的子序列后再不斷合并得到最終的有序序列。

比如上圖,最開始輸入的序列基本都是無序的吧,不斷的二分,使得數組最終被分割為一個又一個的元素,如果只看每個元素的話,每個元素自己都是有序的,接下來合并,思路就是合并兩個有序數組,這個思路在OJ題里見識過,大致思路還是一樣的。最終得到的數組就是有序的。

而分割數組類似于快排每次找完基準值后的分割,所以可以嘗試用遞歸實現,即大體步驟為:
遞歸分割和合并兩個有序數組

2.代碼實現

遞歸分割

很明顯如果想要分割,看成區間的話,區間左端和右端都得十分清楚,才能算出來區間中點,所以遞歸的函數我們就傳左端點和右端點的值。

看著圖其實很容易寫出來代碼,實際上這個分割類似于快排,不是真的分割,只不過給的區間導致能訪問到的元素類似于分割的效果。

遞歸有遞推肯定還得有回歸,回歸條件很明顯,如果這次區間左等右,也就是只有一個元素的時候,就該回歸了。

拆分完全以后就可以兩兩進行合并了:

就看這兩個序列,合并兩個有序數組的代碼肯定免不了循環,因為需要往目標數組里存放的值肯定不止一個,而且如果一個數組遍歷完以后,就應該停下來,防止越界比較,這樣肯定會存在一個數組放完了,另一個數組還沒有訪問,所以得三個循環才能保證最終兩個數組的元素都被有序的放到了目標數組中。

有思路了,寫代碼前我想應該畫一下圖,看看執行思路大概是什么:

我們從單個的元素的序列合并成兩個元素的序列我們采取原地修改會將原數組順序改成:

現在有左序列區間[left,mid]右序列區間[mid + 1,right],這時候就發現了一個問題:

不管是找兩個序列的最大的放到序列末尾,還是找到最小的放到序列開頭,都可能會導致未排序的數據被覆蓋,如果這樣肯定排序就會失敗。

我們每次放之前存一下的話等于還得用一個變量index去記錄這次該放的位置的下標,以及temp保存下標對應的元素,這樣的話每次取出來的可就不只是有序數組里的兩個元素了,也就是每次放都得考慮兩個有序數組里取出來的和temp里存放的。

這么寫又出現問題了,假如現在插入小的:

你temp存的就應該是6對吧,存6以后遍歷左邊子序列的指針肯定不能再指向6了,得指向temp,但是如果指向temp的話你該怎樣遍歷原子序列6后的10呢,難不成還得再存一下嗎?
所以原地插入實在是太麻煩了,我們OJ題里可以實現原地插入純純是因為當時的題給的有空位:

所以才往空的地方先插大的。

原地實現歸并排序太麻煩為何不嘗試一下創建一個等大的臨時數組呢?

遞歸函數分割的時候確實不需要temp這個臨時數組,但是一旦開始合并就需要了,所以索性遞歸函數加上temp這個參數,而排序結束及時歸還操作系統我們申請的空間,所以歸并排序主函數邏輯就是如此了,那么細節就是看遞歸函數了:

分割其實還是不用變,但是一旦分割到頭,如:

這里就是左右子樹都分割好了,就得合并了,直接就著現有指針實在是太抽象了,所以我們不妨創建幾個變量:

而且注意,我們的思路是拿著有序序列,檢測著先后順序往temp里放,一旦組合完成就返回給arr,所以我們循環內部判斷大小引用的是arr里的值(如果剛分割完還沒放,temp為空也沒辦法訪問,都是隨機值)

但是寫著寫著發現又有問題了,如果以遍歷子序列的指針遍歷temp數組的話,就會很明顯的發現,你begin1小那放begin1,遍歷過了就++,但是如果begin2大的話,begin1現在指向的元素實際上是沒有插入temp里,但是你為了遍歷temp的指針放到正確的位置,還是++了,所以現在就應該單獨給遍歷temp數組創建一個變量:

這樣就合理多了。

我們說過了,這個循環結束一定還有元素未放:

最后把temp里的元素給arr復制過去:

選擇用memcpy,但是不知道得傳多大空間,所以畫圖看一下:

很明顯(right - left + 1) * sz(int)個字節。

但是還有細節,如果遍歷到這里:

arr是數組首元素的地址,這兩個數組合并的話應該是從temp + left開始逐個從arr + left開始復制,不然只會去修改從首元素開始的right - left + 1個數據。

所以最終代碼:

測試代碼:

時間復雜度和空間復雜度分析

時間復雜度還是和快速排序類似,畢竟都是分樹,樹每層總得時間復雜度就是O(n),容易得到樹的層數就是logn,所以時間復雜度就是O(nlogn)。

空間復雜度直接看總的代碼:

一個是temp,一個是遞歸函數的函數棧幀的創建,很明顯O(n + logn),那么空間復雜度就是O(n)。

六、測試所有比較排序的性能

有這樣一段代碼:

創造十萬個數據來查看具體時間的快慢,與時間復雜度對照:

直接插入排序最壞是O(n^2),但是最壞情況是小的數據都在后面,大的數據都在前面,實際情況下其實不一定達到,所以直接插入排序時間復雜度實際達不到的多。

希爾排序很少討論它的時間復雜度,因為希爾排序時間復雜度不好計算,一般認為O(nlogn~n^2)

直接選擇排序我們實現的時候是用兩個指針,一個找最小,一個找最大,其實每次都遍歷完了、最后不管數據什么情況都是O(n^2),但是由于數據量太小,所以顯得我們直接插入排序時間復雜度其實小于冒泡排序,但是多次試驗以后基本都是冒泡的一半。

堆排序不必多說,O(nlogn)深入人心。

快排歸并都剛寫,O(nlogn)

冒泡排序典型循環套循環,O(n^2)毋庸置疑。

七、計數排序

1.原理即實現

實際上我們常見的排序是八大排序,但是我們見得比較排序只有其中啊,插入排序的直接插入排序和希爾排序,選擇排序的直接選擇排序和堆排序,比較排序的冒泡排序和快速排序,以及歸并排序。

既然計數排序不屬于比較排序,難道屬于不比較排序,嗎,但是這其實有點匪夷所思,不比較大小我怎么能知道哪個大哪個小,不妨來看一下計數排序:

計數排序的原理是運用哈希直接定址法對相同的數據進行計數,并最終將計數結果還原到原數組里。

具體操作如下:

從小到大計數:
1:2

2:2

4:3

6:1

9:1

創建一個數組,專門用來存儲計數的結果,其中下標為原數據,數組元素為計數數據:

當然,空白的地方不是沒有數據嘛,就存0,所以到時候要不然calloc,要不然malloc以后memset,反正動態開辟的數組默認值為0。

遍歷我們的計數數組,寫一個循環,外層循環的次數是數組元素大小,內層不斷往原數組里塞下標,直至遍歷完整個計數數組。

即:

老說什么哈希映射,哈希不哈希不管,確實是體會到了映射。

但是寫代碼之前再整一個例子,因為開辟的數組空間大小還有個講究:

如果仍舊按照最大元素的值創建數組,按照下標照應原數值的原理,我們realloc的大小實際上是max + 1,因為數組下標是從0開始的嘛,這樣就會造成其實基本100個空間的浪費,因為0~99根本不會存放元素,當然,下標100往后也不是每個都存,但是至少可以接受那么一點點浪費。

所以一般會做此處理:
寫一個循環遍歷整個數組,找出最大值最小值,根據最大值和最小值的差確定數組大小,比如這里的max = 109,min = 100,假設這中間每個元素都存在的話就是max - min + 1個元素,所以到時候數組的大小就得搞個max - min + 1個元素。

這時候往里存肯定就得先-min,不然下標不好照樣,到時候還原的時候+min就行,比如這個例子

處理后應為:{0,1,9,5,1,5}

如果存起來的話應該是:

0:1

1:2

5:2

9:1

到時候還原的時候下標+min就行。

思路清楚代碼表達:

說啥做啥,所以代碼不再解釋。

可以看到其實還是比較了,至少遍歷整個數組給出了最大值和最小值,只不過正兒八經改變數組的時候確實不需要比較。

測試代碼:

2.限制

分析計數排序的時間復雜度可以得到:
O(n + range)

復雜度其實不老好分析,畢竟range取決于所給序列的最大值和最小值的差值。

所以為使時間復雜度趨近于O(n),一般只適用于數據大小比較密集的序列。

八、所有排序算法時間復雜度及穩定性分析

排序?法平均情況最好情況最壞情況空間復雜度穩定性
直接插入排序O(n^{2})?O(n)?O(n^{2})?O(1)穩定
希爾排序O(nlogn) ~O(n^{2})?O(n^{1.3})O(n^{2})?O(1)不穩定
直接選擇排序O(n^{2})?O(n^{2})?O(n^{2})?O(1)不穩定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不穩定
冒泡排序O(n^{2})?O(n)O(n^{2})?O(1)穩定
快速排序O(nlogn)O(nlogn)O(n^{2}O(logn)不穩定
歸并排序O(nlogn)O(nlogn)O(nlogn)O(n)穩定

其中,關于穩定性的定義:

若待排序序列中存在多個??值相同的元素(如:r[i] = r[j]),且排序前r[i]位于r[]j之前,而排序后r[i]仍然位于r[j]之前,則稱該排序算法是??穩定的??;反之,若排序后相同元素的相對次序可能改變,則算法是??不穩定的?。

目前見的排序算法的場景太少,其實還理解不了穩定不穩定到底有什么用。

除此之外,給出例子證明排序的算法不穩定:
直接選擇排序:5 8 5 2 9

希爾排序:5 8 2 5 9

堆排序:2 2 2 2

快速排序:5 3 3 4 3 8 9 1 0 1 1

數據結構初階完,后續還會對數據結構初階文章進行補充修改。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/84447.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84447.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/84447.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

量子計算導論課程設計 之 PennyLane環境搭建

文章目錄 具體配置conda 虛擬環境配置Pennylane 正所謂,磨刀不誤砍柴工,想要進行量子計算導論的課程設計,首先就是搭建好平臺,推薦大家就是本地搭建,那么下面有三種選擇 QiskitTensorFlow QuantumPennylane 具體配置…

nginx ./nginx -s reload 不生效

問題 nginx ./nginx -s reload 不生效 解決 不是改opt/nginx下的配置文件是改/usr/local/nginx下的配置文件改之前做好備份

建造者模式深度解析與實戰應用

作者簡介 我是摘星,一名全棧開發者,專注 Java后端開發、AI工程化 與 云計算架構 領域,擅長Python技術棧。熱衷于探索前沿技術,包括大模型應用、云原生解決方案及自動化工具開發。日常深耕技術實踐,樂于分享實戰經驗與…

VScode - 我的常用插件01 - 主題插件Noctis

導言 Noctis 是一款為 Visual Studio Code 提供的主題插件,主打高對比度、護眼、美觀。它有多種配色風格,適合不同的開發者審美和工作場景。 一、安裝Noctis 二、設置顏色主題 三、測試主題 如上所示,有11種主題背景可以選擇。這里&#xff…

【IQA技術專題】圖像質量評價IQA技術和應用綜述(萬字長文!!)

專題介紹 圖像質量評價(Image Quality Assessment, IQA)是圖像處理、計算機視覺和多媒體通信等領域的關鍵技術之一。IQA不僅被用于學術研究,更在影像相關行業內實現了完整的商業化應用,涉及影視、智能手機、專業相機、安防監控、…

突然虛擬機磁盤只剩下幾十K

第一步:查找哪些文件大于 100M find / -size 100M 第二步:刪除掉無用的 log 發現,磁盤剩余空間并沒有變大 假如一個文件正在被使用,你刪除之后也是不會釋放存儲空間的。需要關閉相應的服務才能釋放。

黑馬教程強化day2-1

目錄 一、Set集合1.Set集合特點2.Set集合分類3.hashSet底層原理:(基于哈希表存儲數據的)代碼演示 5.hashSet集合元素的去重操作(有些情況搞不動)代碼演示 6.LinkedHashSet的底層原理(不常用,所以沒有代碼演…

【實習總結】C++ 通過pugi::xml庫對xml文件進行操作

目錄 相關背景 pugi::xml簡概 將配置信息寫入xml文件 讀取xml文件中的配置信息 相關背景 當我們需要將某些配置信息寫入項目目錄下的xml文件,或者再程序啟動時,加載項目下已有的的配置信息(.xml),此時,我…

Linux文件回收機制:安全刪除文件不怕誤刪

Linux文件回收機制:安全刪除文件不怕誤刪 文章目錄 Linux文件回收機制:安全刪除文件不怕誤刪一、Linux默認沒有“回收站”?二、打造你自己的Linux回收站1. 建立回收站目錄2. 創建軟刪除命令remove3. 定時清理回收站4. 替換rm命令5. 完整腳本 …

數據結構排序

目錄 1、插入排序 2、希爾排序 3、堆排序 4、直接選擇排序 5、快排 6、歸并排序 補&#xff1a;計數排序 1、插入排序 void InsertSort(int* arr, int n) {int i 0;for (int i 0; i 1 < n; i){int end i;int tmp arr[end 1];while (end > 0){if (arr[end] &…

Spring聲明式事務生效是有條件滴!

在日常工作中&#xff0c;經常使用Transactional 注解進行事務的聲明&#xff0c;但如果發現事務未生效&#xff0c;可以從下面幾個方面進行排查。 常見失效場景總結 場景原因解決方案內部方法調用繞過了Spring代理注入自身或使用AopContextprivate方法AOP無法增強改為public方…

Code Composer Studio快捷鍵

文本編輯 編輯、查找、替換功能快捷鍵 功能快捷鍵撤銷CutZ重做CutY剪切CtrlX復制CtrlC粘貼CtrlV刪除Delete全選CtrlA代碼塊選中AltShiftA查找、替換Ctrl F查找下一個匹配的字符串CtrlK查找上一個匹配的字符串CtrlShiftK查看接口注釋&#xff08;文檔&#xff09;F2查看函數幫…

從認識AI開始-----生成對抗網絡(GAN):通過博弈機制,引導生成

前言 生成對抗網絡&#xff08;GAN&#xff09;是lan J. Goodfellow團隊在2014年提出的生成架構&#xff0c; 該架構自誕生起&#xff0c;就產生了很多的話題&#xff0c;更是被稱為生成對抗網絡是“新世紀以來機器學習領域內最有趣的想法”。如今&#xff0c;基于生成對抗網絡…

限流算法java實現

參考教程&#xff1a;2小時吃透4種分布式限流算法 1.計數器限流 public class CounterLimiter {// 開始時間private static long startTime System.currentTimeMillis();// 時間間隔&#xff0c;單位為msprivate long interval 1000L;// 限制訪問次數private int limitCount…

Maven 構建性能優化深度剖析:原理、策略與實踐

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家&#xff0c;歷代文學網&#xff08;PC端可以訪問&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移動端可微信小程序搜索“歷代文學”&#xff09;總架構師&#xff0c;15年工作經驗&#xff0c;精通Java編…

JS手寫代碼篇---手寫深拷貝

17、深拷貝 深拷貝與淺拷貝最大的不同就是對象的屬性是嵌套對象&#xff0c;會新建一個對象 步驟&#xff1a; 判斷是否為對象判斷是否為i數組或者對象&#xff0c;給新的有個容器遍歷循環&#xff0c;如果是對象要遍歷循環&#xff0c;采用遞歸 function deepCopy(obj){// …

【react實戰】如何實現監聽窗口大小變化

在日常開發場景中&#xff0c;監聽窗口變化是一個比較常見又很重要的業務功能&#xff0c;其實實現起來也很簡單&#xff0c;今天就來記錄一下具體的實現以及注意事項。 實現思路 在 React 中&#xff0c;可以通過監聽 window 的 resize 事件來檢測可視區域&#xff08;viewp…

AVCap視頻處理成幀和音頻腳本

###############處理原視頻&#xff0c;使其格式和原數據一樣 import os import cv2 import subprocess import json from PIL import Image from pydub import AudioSegmentimport sys import shutil # &#x1f539; 第一步&#xff1a;強制檢測并設置FFmpeg路徑 &#x1f5…

數據冗余對企業運營的隱性成本

從客戶管理到供應鏈優化&#xff0c;再到市場分析&#xff0c;數據無處不在&#xff0c;數據已成為企業運營的核心驅動力。然而&#xff0c;隨著企業IT系統的多樣化和數據量的激增&#xff0c;數據冗余&#xff08;Data Redundancy&#xff09;問題逐漸浮出水面&#xff0c;成為…

HTML原生日期插件增加周次顯示

<div id="app" class="box box-info" style="border-top-color:white;"><!-- // 日期部分 --><div class="date-picker-container" style="position: relative; max-width: 200px;"><!-- 日期輸入框 -…