Python裡sort()的排序演算法--Timsort簡介
學習計算機的肯定對各種排序演算法都很瞭解,這裡說一下,常用的排序演算法有氣泡排序,插入排序,快速排序等,
而Python裡的sort排序是一種名為Timsort的排序方法,其時間複雜度為O(n log n),而且這是一種快速的穩定的排序方法。它的發明者是Tim Peters在2001年為Python創造的一種排序演算法。下圖是Timsort的時間複雜度的介紹,可以看到Timsort排序在各方面都是最優的。而且Timsort是在C語言中實現的,因此Timsort排序的效能是毋庸置疑的。
一個演算法的穩定
Timsort在排序長度低於64的時候採取:插入排序 。高於64的時候採取Timsort是一種改良的歸併排序。下圖是插入排序的時候的演算法:
正常的歸併排序如下:
如 設有數列{6,202,100,301,38,8,1}
初始狀態:6,202,100,301,38,8,1
第一次歸併後:{6,202},{100,301},{8,38},{1},比較次數:3;
第二次歸併後:{6,100,202,301},{1,8,38},比較次數:4;
第三次歸併後:{1,6,8,38,100,202,301},比較次數:4;
總的比較次數為:3+4+4=11;
逆序數為14;
而Timsort的演算法首先遍歷列表,查詢升序和降序的部分(Run),由於現實中的很多資料都是排好序的,Timsort利用了這一特點。Timsort排序的輸入的單位不是一個個單獨的數字,而是一個個的分割槽。其中每一個分割槽叫一個“run“(圖1)。針對這個 run 序列,每次拿一個 run 出來進行歸併。每次歸併會將兩個 run 合併成一個 run。每個run最少要有2個元素。Timesor按照升序和降序劃分出各個run:run如果是是升序的
如果降序,則翻轉序列:
劃分run和優化run長度以後,然後就是對各個run進行合併。合併run的原則是 run合併的技術要保證有最高的效率。當Timsort演算法找到一個run時,會將該run在陣列中的起始位置和run的長度放入棧中,然後根據先前放入棧中的run決定是否該合併run。Timsort不會合並在棧中不連續的run。
Timsort會合並在棧中2個連續的run。X、Y、Z代表棧最上方的3個run的長度(圖2),當同時不滿足下面2個條件是,X、Y這兩個run會被合併,直到同時滿足下面2個條件,則合併結束:
(1) X>Y+Z
(2) Y>Z
例如:如果X<Y+Z,那麼X+Y合併為一個新的run,然後入棧。重複上述步驟,直到同時滿足上述2個條件。當合並結束後,Timsort會繼續找下一run,然後找到以後入棧,重複上述步驟,及每次run入棧都會檢查是否需要合併2個run。
合併run步驟
合併2個相鄰的run需要臨時儲存空閒,臨時儲存空間的大小是2個run中較小的run的大小。Timsort演算法先將較小的run複製到這個臨時儲存空間,然後用原先儲存這2個run的空間來儲存合併後的run(圖3)。
臨時儲存空間,讓Timsort排序的空間複雜度為o(n)
簡單的合併演算法是用簡單插入演算法,依次從左到右或從右到左比較,然後合併2個run。為了提高效率,Timsort用二分插入演算法(binary merge sort)。先用二分查詢演算法/折半查詢演算法(binary search)找到插入的位置,然後在插入。
例如,我們要將A和B這2個run 合併,且A是較小的run。因為A和B已經分別是排好序的,二分查詢會找到B的第一個元素在A中何處插入(圖4)。同樣,A的最後一個元素找到在B的何處插入,找到以後,B在這個元素之後的元素就不需要比較了(圖5)。這種查詢可能在隨機數中效率不會很高,但是在其他情況下有很高的效率。
run合併過程1
圖5 run合併過程2