Python---簡析八大排序算法

前言

1 、排序的概念

排序是計算機內(nèi)經(jīng)常進(jìn)行的一種操作,其目的是將一組“無序”的記錄序列調(diào)整為“有序”的記錄序列。
排序分為內(nèi)部排序外部排序
若整個排序過程不需要訪問外存便能完成,則稱此類排序問題為內(nèi)部排序
反之,若參加排序的記錄數(shù)量很大,整個序列的排序過程不可能在內(nèi)存中完成,則稱此類排序問題為外部排序

2 、排序分類

八大排序算法均屬于內(nèi)部排序。如果按照策略來分類,大致可分為:交換排序、插入排序、選擇排序、歸并排序和基數(shù)排序。如下圖所示:

3 、算法分析

下表給出各種排序的基本性能,具體分析請參看各排序的詳解:



一、插入排序

插入排序分為兩種:直接插入排序和希爾排序

直接插入排序

基本思想:
直接插入排序的核心思想就是:將數(shù)組中的所有元素依次跟前面已經(jīng)排好的元素相比較,如果選擇的元素比已排序的元素小,則交換,直到全部元素都比較過。
因此,從上面的描述中我們可以發(fā)現(xiàn),直接插入排序可以用兩個循環(huán)完成:

  • 第一層循環(huán):遍歷待比較的所有數(shù)組元素
  • 第二層循環(huán):將本輪選擇的元素與已經(jīng)排好序的元素相比較。
    如果:selected > ordered,那么將二者交換
def insert_sort(array):
    for i in range(len(array)):
        for j in range(i):
            if array[i] < array[j]:
                array.insert(j, array.pop(i))
                break
    return array

希爾排序

基本思想:
將數(shù)組列在一個表中并對列分別進(jìn)行插入排序,重復(fù)這過程,不過每次用更長的列(步長更長了,列數(shù)更少了)來進(jìn)行。最后整個表就只有一列了。將數(shù)組轉(zhuǎn)換至表是為了更好地理解這算法,算法本身還是使用數(shù)組進(jìn)行排序。

希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序算法的一種更高效的改進(jìn)版本。希爾排序是非穩(wěn)定排序算法。該方法因DL.Shell于1959年提出而得名。 希爾排序是把記錄按下標(biāo)的一定增量分組,對每組使用直接插入排序算法排序;隨著增量逐漸減少,每組包含的關(guān)鍵詞越來越多,當(dāng)增量減至1時,整個文件恰被分成一組,算法便終止。

def shell_sort(alist):
    n = len(alist)
    # 初始步長
    gap = n // 2
    while gap > 0:
        # 按步長進(jìn)行插入排序
        for i in range(gap,n):
            j = i
            # 原地插入排序,之前的插入排序另辟空間存儲的
            while j>=gap and alist[j-gap] > alist[j]:
                alist[j-gap], alist[j] = alist[j], alist[j-gap]
                j -= gap
        # 得到新的步長
        gap = gap //2
    return alist

最壞情況O(n^2) ,平均O(nlog(n))~O(n2),最好O(n1.3) ,不穩(wěn)定。


二、選擇排序

選擇排序分為簡單選擇排序和堆排序

簡單選擇排序

基本思想:
比較+交換。

  1. 從待排序序列中,找到關(guān)鍵字最小的元素;
  2. 如果最小元素不是待排序序列的第一個元素,將其和第一個元素互換;
  3. 從余下的 N - 1 個元素中,找出關(guān)鍵字最小的元素,重復(fù)(1)、(2)步,直到排序結(jié)束。
    因此我們可以發(fā)現(xiàn),簡單選擇排序也是通過兩層循環(huán)實現(xiàn)。
  • 第一層循環(huán):依次遍歷序列當(dāng)中的每一個元素
  • 第二層循環(huán):將遍歷得到的當(dāng)前元素依次與余下的元素進(jìn)行比較,符合最小元素的條件,則交換。
def select_sort(array):
    for i in range(len(array)):
        x = i  # min index
        for j in range(i, len(array)):
            if array[j] < array[x]:
                x = j
        array[i], array[x] = array[x], array[i]
    return array

堆排序

基本思想:
堆排序可以按照以下步驟來完成:

  1. 首先將序列構(gòu)建成為大頂堆;
    (這樣滿足了大頂堆那條性質(zhì):位于根節(jié)點的元素一定是當(dāng)前序列的最大值)
  2. 取出當(dāng)前大頂堆的根節(jié)點,將其與序列末尾元素進(jìn)行交換;
    (此時:序列末尾的元素為已排序的最大值;由于交換了元素,當(dāng)前位于根節(jié)點的堆并不一定滿足大頂堆的性質(zhì))
  3. 對交換后的n-1個序列元素進(jìn)行調(diào)整,使其滿足大頂堆的性質(zhì);
  4. 重復(fù)2.3步驟,直至堆中只有1個元素為止

堆排序的時間復(fù)雜度分為兩個部分一個是建堆的時候所耗費的時間,一個是進(jìn)行堆調(diào)整的時候所耗費的時間。而堆排序則是調(diào)用了建堆和堆調(diào)整。
建堆是一個線性過程,從len/2-0一直調(diào)用堆調(diào)整的過程,相當(dāng)于o(h1)+o(h2)+…+o(hlen/2)這里的h表示節(jié)點深度,len/2表示節(jié)點深度,對于求和過程,結(jié)果為線性的O(n) 堆調(diào)整為一個遞歸的過程,調(diào)整堆的過程時間復(fù)雜度與堆的深度有關(guān)系,相當(dāng)于lgn的操作。 因為建堆的時間復(fù)雜度是O(n),調(diào)整堆的時間復(fù)雜度是lgn,所以堆排序的時間復(fù)雜度是O(nlgn)。

def heap_sort(array):
    def heap_adjust(parent):
        child = 2 * parent + 1  # left child
        while child < len(heap):
            if child + 1 < len(heap):
                if heap[child + 1] > heap[child]:
                    child += 1  # right child
            if heap[parent] >= heap[child]:
                break
            heap[parent], heap[child] = \
                heap[child], heap[parent]
            parent, child = child, 2 * child + 1
    heap, array = array.copy(), []
    for i in range(len(heap) // 2, -1, -1):
        heap_adjust(i)
    while len(heap) != 0:
        heap[0], heap[-1] = heap[-1], heap[0]
        array.insert(0, heap.pop())
        heap_adjust(0)
    return array

三、歸并排序

基本思想:
歸并排序是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法的一個典型的應(yīng)用。它的基本操作是:將已有的子序列合并,達(dá)到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。
歸并排序其實要做兩件事:

分解----將序列每次折半拆分

合并----將劃分后的序列段兩兩排序合并
因此,歸并排序?qū)嶋H上就是兩個操作,拆分+合并

合并:
L[first...mid]為第一段,L[mid+1...last]為第二段,并且兩端已經(jīng)有序,現(xiàn)在我們要將兩端合成達(dá)到L[first...last]并且也有序。

首先依次從第一段與第二段中取出元素比較,將較小的元素賦值給temp[]
重復(fù)執(zhí)行上一步,當(dāng)某一段賦值結(jié)束,則將另一段剩下的元素賦值給temp[]
此時將temp[]中的元素復(fù)制給L[],則得到的L[first...last]有序

分解:
在這里,我們采用遞歸的方法,首先將待排序列分成A,B兩組;然后重復(fù)對A、B序列
分組;直到分組后組內(nèi)只有一個元素,此時我們認(rèn)為組內(nèi)所有元素有序,則分組結(jié)束。

def merge_sort(nums):  # 變成二次樹
    if len(nums) == 1:
        return nums
    mid = len(nums) // 2
    left = nums[:mid]
    right = nums[mid:]
    a = merge_sort(left)
    b = merge_sort(right)
    return merge(a, b)
def merge(left, right):  # 整合排序
    c = []
    j = k = 0
    while len(left) > j and len(right) > k:
        if left[j] <= right[k]:
            c.append(left[j])
            j += 1
        else:
            c.append(right[k])
            k += 1
    if j == len(left):
        for i in right[k:]:
            c.append(i)
    else:
        for i in left[j:]:
            c.append(i)
    return c

四、基數(shù)排序

基本思想:
通過序列中各個元素的值,對排序的N個元素進(jìn)行若干趟的“分配”與“收集”來實現(xiàn)排序。

分配:我們將L[i]中的元素取出,首先確定其個位上的數(shù)字,根據(jù)該數(shù)字分配到與之序號相同的桶中

收集:當(dāng)序列中所有的元素都分配到對應(yīng)的桶中,再按照順序依次將桶中的元素收集形成新的一個待排序列L[ ]
對新形成的序列L[]重復(fù)執(zhí)行分配和收集元素中的十位、百位...直到分配完該序列中的最高位,則排序結(jié)束
根據(jù)上述“基數(shù)排序”的展示,我們可以清楚的看到整個實現(xiàn)的過程

def radix_sort(array):
    bucket, digit = [[]], 0
    while len(bucket[0]) != len(array):
        bucket = [[], [], [], [], [], [], [], [], [], []]
        for i in range(len(array)):
            num = (array[i] // 10 ** digit) % 10
            bucket[num].append(array[i])
        array.clear()
        for i in range(len(bucket)):
            array += bucket[i]
        digit += 1
    return array

五、交換排序

交換排序分為兩種:冒泡排序和快速排序

冒泡排序

基本思想:

  • 將序列當(dāng)中的左右元素,依次比較,保證右邊的元素始終大于左邊的元素;
    ( 第一輪結(jié)束后,序列最后一個元素一定是當(dāng)前序列的最大值;)
  • 對序列當(dāng)中剩下的n-1個元素再次執(zhí)行步驟1。
  • 對于長度為n的序列,一共需要執(zhí)行n-1輪比較
    (利用while循環(huán)可以減少執(zhí)行次數(shù))
def bubble_sort(array):
    for i in range(len(array)):
        for j in range(i, len(array)):
            if array[i] > array[j]:
                array[i], array[j] = array[j], array[i]
    return array

快速排序

基本思想:挖坑填數(shù)+分治法

從序列當(dāng)中選擇一個基準(zhǔn)數(shù)(pivot)
在這里我們選擇序列當(dāng)中第一個數(shù)最為基準(zhǔn)數(shù)
將序列當(dāng)中的所有數(shù)依次遍歷,比基準(zhǔn)數(shù)大的位于其右側(cè),比基準(zhǔn)數(shù)小的位于其左側(cè)
重復(fù)步驟1.2,直到所有子集當(dāng)中只有一個元素為止。
用偽代碼描述如下:
1.i =L; j = R; 將基準(zhǔn)數(shù)挖出形成第一個坑a[i]。
2.j--由后向前找比它小的數(shù),找到后挖出此數(shù)填前一個坑a[i]中。
3.i++由前向后找比它大的數(shù),找到后也挖出此數(shù)填到前一個坑a[j]中。
4.再重復(fù)執(zhí)行2,3二步,直到i==j,將基準(zhǔn)數(shù)填入a[i]中

def quick_sort(array):
    sys.setrecursionlimit(10 ** 8)
    def recursive(begin, end):
        if begin > end:
            return
        l, r = begin, end
        pivot = array[l]
        while l < r:
            while l < r and array[r] > pivot:
                r -= 1
            while l < r and array[l] <= pivot:
                l += 1
            array[l], array[r] = array[r], array[l]
        array[l], array[begin] = pivot, array[l]
        recursive(begin, l - 1)
        recursive(r + 1, end)

    recursive(0, len(array) - 1)
    return array

六、總結(jié)

  • (1)若n較小(如n≤50),可采用直接插入或直接選擇排序。
     當(dāng)記錄規(guī)模較小時,直接插入排序較好;否則如果直接選擇移動的記錄數(shù)少于直接插人,應(yīng)選直接選擇排序為宜。
  • (2)若文件初始狀態(tài)基本有序(指正序),則應(yīng)選用直接插入、冒泡或隨機的快速排序為宜;
  • (3)若n較大,則應(yīng)采用時間復(fù)雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序。
     快速排序是目前基于比較的內(nèi)部排序中被認(rèn)為是最好的方法,當(dāng)待排序的關(guān)鍵字是隨機分布時,快速排序的平均時間最短;
     堆排序所需的輔助空間少于快速排序,并且不會出現(xiàn)快速排序可能出現(xiàn)的最壞情況。這兩種排序都是不穩(wěn)定的。
     若要求排序穩(wěn)定,則可選用歸并排序。但本章介紹的從單個記錄起進(jìn)行兩兩歸并的排序算法并不值得提倡,通常可以將它和直接插入排序結(jié)合在一起使用。先利用直接插入排序求得較長的有序子文件,然后再兩兩歸并之。因為直接插入排序是穩(wěn)定的,所以改進(jìn)后的歸并排序仍是穩(wěn)定的。

七、參考文獻(xiàn)

[1] 李春葆. 數(shù)據(jù)結(jié)構(gòu)教程[M].北京市:清華大學(xué)出版社,2013.

[2] li563868273.各種排序的比較和使用場景分析.CSDN,2016.

[3] LeeLom. 數(shù)據(jù)結(jié)構(gòu)常見的八大排序算法.簡書,2016.

[4] woider. Python 八大排序算法速度比較.博客園,2016.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容