【算法】排序算法小結(上)

        排序算法是算法中的基礎,通常是解決其他算法的第一步。排序是將一組對象按照某種邏輯順序重新排列的過程,本文將經典的排序算法根據《算法第四版》進行整理。以備複習使用。最後,結合leetcode經典題目,進行應用。另外值得注意的是,排序算法有內排序和外排序之分,在排序過程中,全部記錄存放在內存,則稱爲內排序,如果排序過程中需要使用外存,則稱爲外排序。外排序一般來說外排序分爲兩個步驟:預處理和合並排序。我們常提到的以及下面提到的都是內排序。

        首先貼上彙總圖,選擇題必備。

        想記下來也很簡單,時間複雜度除了堆排序、歸併排序和基數,剩餘時間複雜度均爲O(n^2),從省時間角度考慮以上三種;從空間角度,除了快速排序和基數排序,剩餘均爲O(1)。剩下的問題,比如最好情況和最壞情況是相同的有直接選擇、堆排序和歸併排序最好情況下時間複雜度最大的是直接選擇排序等等。

                            

        接下來,對於每一種進行詳細的說明與實現。

一、選擇排序

        算法描述:首先,找到數組中最小的元素,其次將它和數組的第一個元素交換位置(如果第一個元素就是最小元素那麼就和自己交換);再次,在剩下的元素中找到一個最小的元素,將它和數組的第二個元素交換位置。如此往復,直至將整個數組排序。稱之爲選擇排序,是因爲它在不斷地選擇剩餘元素之中的最小者。

        複雜度分析:長度爲N的數組,0-N-1之間的任意 i 都會進行一次交換和 N-1-i 次比較,因此總的交換次數爲 N, 總的比較次數爲 (N-1) + (N-2) + (N-3) + ...... +2 + 1=N*(N-1)/2 ~ N^2/2。因而時間複雜度爲O(N^2),最好、最壞或平均均相等。無需額外空間。對於穩定性,舉個例子,序列5 8 5 2 9, 我們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對前後順序就被破壞了,所以選擇排序不是一個穩定的排序算法。

        示例

                                          

def selection(nums):
    n = len(nums)
    for i in range(n):
        mins = i
        for j in range(i+1,n):
            if nums[mins] > nums[j]:
                mins = j
        tmp = nums[i]
        nums[i] = nums[mins]
        nums[mins] = tmp
    return nums

二、冒泡排序(基於比較)

        算法描述:重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來。走訪元素的工作是重複地進行直到沒有相鄰元素需要交換,也就是說該元素已經排序完成。這個算法的名字由來是因爲越大的元素會經由交換慢慢“浮”到數列的頂端(升序或降序排列)。

        複雜度分析:若初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C和記錄移動次數M均達到最小值,Cmin=N-1, Mmin= 0, 冒泡排序最好的時間複雜度爲O(1)。若初始狀態是反序的,需要進行 N-1 趟排序。每趟排序要進行 N-i 次關鍵字的比較(1≤i≤n-1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:

                                          ,            

        因而,時間複雜度最差和平均均爲 O(N^2) 。同樣,無需額外空間。

       對於穩定性來說,冒泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,是不會再交換的;如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變,所以冒泡排序是一種穩定排序算法。

        示例

                                                                    

def blob(nums):
    "sort in ascending order"
    n = len(nums)
    for i in range(n,-1,-1):
        for j in range(i-1):
            if nums[j]>nums[j+1]:
                tmp = nums[j]
                nums[j] = nums[j+1]
                nums[j+1] = tmp
    return nums
        

三、插入排序

        算法描述:將新元素插入其他已經有序的序列中的適當位置。爲了給要插入的元素騰出空間,需要將其餘的元素在插入之前都向右移動一位。與選擇排序相同,當前索引左邊的所有元素均爲有序的,但不代表最終位置,爲了給更小的元素騰出空間,它們可能會被移動。但是當索引到達數組的最右端時,數組排序就完成了。

        複雜度分析:插入排序的時間複雜度取決於輸入中元素的初始順序。插入排序存在最好情況和最壞情況。最好情況就是,序列已經是升序排列了,在這種情況下,需要進行的比較操作需 n-1 次即可。最壞情況就是,序列是降序排列,那麼此時需要進行的比較共有 n(n-1)/2 次。插入排序的賦值操作是比較操作的次數加上 (n-1)次。平均來說插入排序算法的時間複雜度爲O(n^2)。            無需額外的空間。

        插入排序是在一個已經有序的小序列的基礎上,一次插入一個元素。當然,剛開始這個有序的小序列只有1個元素,就是第一個元素。比較是從有序序列的末尾開始,也就是想要插入的元素和已經有序的最大者開始比起,如果比它大則直接插入在其後面,否則一直往前找直到找到它該插入的位置。如果碰見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。所以,相等元素的前後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,所以插入排序是穩定的。

        適用性:對於一些部分有序的數組,例如:數組中每個元素距離它的最終位置都不遠;一個有序的大數組接一個小數組;數組中只有幾個元素的位置不正確等大多數有序的情況,插入排序十分有效。

        示例:

                                                           

def Insertion(nums):
    n = len(nums)
    for i in range(1,n):
        for j in range(i,0,-1):
            if nums[j]<nums[j-1]:
                tmp = nums[j]
                nums[j] = nums[j-1]
                nums[j-1] = tmp
    return nums

四、希爾排序

        算法描述:對於大規模亂序數組,插入排序很慢,因爲它只會交換相鄰的元素,效率十分低。爲了加快速度,進行了改進,主要思想是使數組中任意間隔爲h的元素都是有序的。這樣的數組稱爲 h 有序數組。如果h很大,就可以將元素移動到很遠的地方。具體來說,先取一個小於長度的整數 h1 作爲第一個增量,把數組的全部記錄分組。所有距離爲h1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2<d1重複上述的分組和排序,直至所取的增量ht=1( ht< ht-1< …<h2<h1),即所有記錄放在同一組中進行直接插入排序爲止。該方法實質上是一種分組插入方法.。比較相隔較遠距離(稱爲增量)的數,使得數移動時能跨過多個元素,則進行一次比較就可能消除多個元素交換。

        希爾排序的高效是因爲它權衡了子數組的規模和有序性。排序之初,各個子數組都很短,排序之後是部分有序的,這兩種情況都很適合插入排序。值得注意的是,子數組部分有序的程度取決於遞增序列的選擇,但具體的原理仍未研究透徹。在遞增序列的選擇上,可以選取初次取序列的一半爲增量,以後每次減半,直到增量爲1。也可以使用每次爲之前1/3,例如序列 h = h*3+1 (1,4,13,40,121,264,1093....), h<N/3 等。

        複雜度分析:時間複雜度依賴於增量序列的選擇,不同的序列,時間複雜度不同。可以在O(N^(1.2—2))之間。平均約爲O(N^1.5)。

        穩定性上,由於多次插入排序,我們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂,所以shell排序是不穩定的。

        示例:

def shell(nums):
    n = len(nums)
    h = 1
    while h<n/3:
        h = h*3+1

    while h>=1:
        for i in range(h,n):
            j = i
            while j>=h and nums[j]<nums[j-h]:
                nums[j-h],nums[j] = nums[j],nums[j-h]
                j -= h
        h //= 3
    return nums

        未完待續。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章