排序算法總結(jié)

劍指offer已經(jīng)刷完一遍了,今天開始學(xué)習(xí)整理一下排序算法。爭取每天整理兩個。
參考博客:http://www.cnblogs.com/feixuelove1009/p/6143539.html

2018.10.19

一、排序的概念和分類

1、排序的穩(wěn)定性:

經(jīng)過某種排序后,如果兩個記錄序號同等,且兩者在原無序記錄中的先后秩序依然保持不變,則稱所使用的排序方法是穩(wěn)定的,反之是不穩(wěn)定的。
舉個例子,要排序的內(nèi)容是一組原本按照價格高低排序的對象,如今需要按照銷量高低排序,使用穩(wěn)定性算法,可以使得想同銷量的對象依舊保持著價格高低的排序展現(xiàn),只有銷量不同的才會重新排序。


image.png

該圖有個錯的地方,簡單選擇排序是不穩(wěn)定的算法。

2、內(nèi)排序和外排序:

內(nèi)排序:排序過程中,待排序的所有記錄全部放在內(nèi)存中
外排序:排序過程中,使用到了外部存儲。
通常討論的都是內(nèi)排序。

3、影響內(nèi)排序算法性能的三個因素:

時間復(fù)雜度:即時間性能,高效率的排序算法應(yīng)該是具有盡可能少的關(guān)鍵字比較次數(shù)和記錄的移動次數(shù)
空間復(fù)雜度:主要是執(zhí)行算法所需要的輔助空間,越少越好。
算法復(fù)雜性:主要是指代碼的復(fù)雜性。

二、冒泡排序

冒泡排序(Bubble sort):時間復(fù)雜度O(n^2)
交換排序的一種。其核心思想是:兩兩比較相鄰記錄的關(guān)鍵字,如果反序則交換,直到?jīng)]有反序記錄為止。
先定義一個交換元素位置的函數(shù):

class SQList():

    def __init__(self, nums):
        self.nums = nums

    def swap(self, i, j):
        '''將nums數(shù)組的第i個元素和第j個元素交換位置'''
        self.nums[i] , self.nums[j] = self.nums[j], self.nums[i]
        return

冒泡排序是最簡單的一種排序方法。
可以細(xì)分為以下三種:

1、簡單排序

簡單排序的思想很簡單,將第一個元素與后面所有元素依次進(jìn)行比較,如果第一個元素更大,則交換兩元素的位置,經(jīng)過一輪比較后,第一個元素就是該列表中最小的數(shù),然后再用第二個元素依次與后面的元素比較。時間復(fù)雜度為O(n^2)。

def simple_sort(self):
    '''簡單排序'''
    for i in range(len(self.nums)):
        for j in range(i+1, len(self.nums)):
            if self.nums[i] > self.nums[j]:
                self.swap(i, j)
    return self.nums

2、冒泡排序

冒泡排序雖然也是兩兩比較,但是跟簡單排序不一樣,冒泡排序是每兩個相鄰的元素比較,如果大小相反則交換,如果從數(shù)組第一個元素開始比較的話,經(jīng)過一輪后,最大的數(shù)就排到了數(shù)組的最后一位,然后再接著排第二大的數(shù)。時間復(fù)雜度為O(n^2)。

def simple_bubble(self):
    '''簡單冒泡排序'''
    j = 0
    while j <= len(self.nums)-1:
        for i in range(len(self.nums)-j-1):
            if self.nums[i] > self.nums[i+1]:
                self.swap(i, i+1)
        j += 1
    return self.nums

3、改進(jìn)版冒泡排序

改進(jìn)的冒泡排序的思路跟冒泡排序是一樣的,不過增加了一個提前中止的條件,如果在某一輪比較中,沒有任何兩個元素交換位置,那么此時的序列已經(jīng)是有序的,可以提前中止。時間復(fù)雜度為O(n^2)。

def bubble(self):
    '''改進(jìn)版冒泡排序'''
    j = 0
    while j <= len(self.nums)-1:
        flag = False
        for i in range(len(self.nums)-j-1):
            if self.nums[i] > self.nums[i+1]:
                self.swap(i, i+1)
                flag = True
        if not flag:
            return self.nums
        j += 1
    return self.nums

冒泡排序整理完了,下面開始第二大排序算法

三、簡單選擇排序

冒泡排序是每次比較如果位置錯了就會交換一次位置,換了很多次位置后才確定了一個元素的位置。那么我們可不可以只用一次交換就確定一個元素的位置呢?當(dāng)我們要排第一個位置的元素時,我們可以找到第一個以后所有元素的最小值,再和第一個位置比較,如果位置錯誤,那么就交換位置。那樣我們雖然遍歷了整個列表,但是只用了一次交換函數(shù),雖然時間復(fù)雜度跟冒泡排序一樣,但是只調(diào)用了一次交換函數(shù),所以相對冒泡排序還是有一定程度的改進(jìn)。時間復(fù)雜度為O(n^2)

def select_sort(self):
    '''簡單選擇排序'''
    for i in range(len(self.nums)-1):
        temp = [None, -1]
        for j in range(i, len(self.nums)):
            if not temp[0]:
                temp = [self.nums[j], j]
            if self.nums[j] < temp[0]:
                temp = [self.nums[j], j]
        if temp[0] < self.nums[i]:
            self.swap(i, temp[1])
    return self.nums

2018.10.20

四、直接插入排序

直接插入排序的思想是將一個新的元素插入到前面排好序的序列中。時間復(fù)雜度為O(n^2)

def insert_sort(self):
    '''插入排序'''
    for i in range(1, len(self.nums)):
        j = i - 1
        temp = self.nums[i]
        while j >= 0 and temp < self.nums[j]:
            self.nums[j+1] = self.nums[j]
            j -= 1
        self.nums[j+1] = temp
    return self.nums

抄原文一張圖


image.png

五、希爾排序

希爾排序是對直接插入排序的改進(jìn)版,其核心思想是將原始數(shù)據(jù)集分割成若干個子序列,然后再對子序列分別進(jìn)行插入排序,使子序列基本有序,最后再對全體進(jìn)行一次插入排序。
這里最關(guān)鍵的是跳躍和分割的策略,也就是我們要怎么分割數(shù)據(jù),間隔多大的問題。通常將相距某個“增量”的記錄組成一個子序列,這樣才能保證在子序列內(nèi)分別進(jìn)行直接插入排序后得到的結(jié)果是基本有序而不是局部有序。下面的例子中通過:increment = int(increment/3)+1來確定“增量”的值。
第一遍沒看明白,后來查了其它的資料,才完全明白是怎么回事。以下內(nèi)容來自博客:https://www.cnblogs.com/chengxiao/p/6104371.html

image.png

image.png

希爾排序的時間復(fù)雜度為:O(n^(3/2))
根據(jù)這個思路自己寫的代碼:

def shell_sort(self):
    def insertSortPerGap(nums, gap):
        for i in range(1, len(nums)):
            j = i - gap
            temp = nums[i]
            while j >= 0 and temp < nums[j]:
                nums[j+gap] = nums[j]
                j -= gap
            nums[j + gap] = temp
        return
    gap = int(len(self.nums)/2)
    while gap>=1:
        insertSortPerGap(self.nums, gap)
        gap = int(gap/2)
    return self.nums

2018.10.21

六、堆排序

參考的也是寫希爾排序的博客:https://www.cnblogs.com/chengxiao/p/6129630.html
堆排序是利用堆這種數(shù)據(jù)結(jié)構(gòu)而設(shè)計的一種排序算法,堆排序是一種選擇排序,它的最好、最壞、平均時間復(fù)雜度為O(nlogn),它是不穩(wěn)定排序
堆是具有以下性質(zhì)的完全二叉樹:每個結(jié)點的值都大于或等于其左右孩子結(jié)點的值,稱為大頂堆,或者每個結(jié)點的值都小于或等于其左右孩子結(jié)點的值,稱為小頂堆。
我們用簡單的公式來描述一下堆的定義就是(索引從0開始):
大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序的基本思想是:將待排序序列構(gòu)造成一個大頂堆,此時,整個序列的最大值就是堆頂?shù)母Y(jié)點。將其與末尾元素進(jìn)行交換,此時末尾就是最大值。然后將剩余n-1個元素重新構(gòu)造成一個堆,這樣會得到n個元素的次小值,如此反復(fù)執(zhí)行,就能得到一個有序序列。
一般升序采用大頂堆,降序采用小頂堆
所以堆排序的關(guān)鍵就在于如何將一個序列構(gòu)造成一個堆??梢詮淖詈笠粋€非葉子結(jié)點開始,從左至右,從下至上進(jìn)行調(diào)整。根據(jù)完全二叉樹的性質(zhì),可以知道最后一個非葉子結(jié)點的索引就是len(array)//2 - 1(根結(jié)點索引為0),那么它之前的所有結(jié)點都是非葉子結(jié)點,只需要按照這個順序去構(gòu)造大頂堆或小頂堆就可以了。
以下自己用python實現(xiàn)的代碼:

def heap_sort(self):

    def heap_adjust(nums, length):
        index_root = int(length // 2 - 1)
        while index_root >= 0:
            index_kid1 = index_root * 2 + 1
            index_kid2 = index_root * 2 + 2
            if nums[index_root] < nums[index_kid1]:
                self.swap(index_root, index_kid1)
            if index_kid2 <= length - 1:
                if nums[index_root] < nums[index_kid2]:
                    self.swap(index_root, index_kid2)
            index_root -= 1

    length = len(self.nums)
    while length >= 2:
        heap_adjust(self.nums, length)
        self.swap(0, length - 1)
        length -= 1
    return self.nums

七、歸并排序

思想比較簡單,參考https://www.cnblogs.com/chengxiao/p/6194356.html
歸并排序(MERGE-SORT)是利用歸并的思想實現(xiàn)的排序方法,該算法采用經(jīng)典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然后遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。

image.png

這張圖大概能看出整個流程了。
歸并排序是穩(wěn)定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般性能都不會太差。從上文的圖中可看出,每次合并操作的平均時間復(fù)雜度為O(n),而完全二叉樹的深度為|log2n|??偟钠骄鶗r間復(fù)雜度為O(nlogn)。而且,歸并排序的最好,最壞,平均時間復(fù)雜度均為O(nlogn)。
很遺憾的是,歸并算法我沒寫出來??。合并的操作寫出來了,但是前面那個遞歸調(diào)用的時候有點亂,沒寫出來,但其實就是個跟二叉樹差不多的操作過程。參考別人的代碼再重新碼了一遍。

def merge_sort(self):

    def msort(nums):
        if len(nums) <= 1:
            return nums
        mid = len(nums)//2
        left = msort(nums[:mid])
        right = msort(nums[mid:])
        return merge(left+right)

    def merge(numbers):
        temp = [0] * len(numbers)
        i = 0
        j = len(numbers) // 2
        index = 0
        while i < len(numbers) // 2 and j < len(numbers):
            if numbers[i] < numbers[j]:
                temp[index] = numbers[i]
                index += 1
                i += 1
            else:
                temp[index] = numbers[j]
                index += 1
                j += 1
        if i < len(numbers) // 2:
            temp[index:] = numbers[i:len(numbers) // 2]
        else:
            temp[index:] = numbers[j:]
        numbers = temp
        return numbers
    return msort(self.nums)

2018.10.22
今天就只剩最后一個快速排序了。

八、快速排序

快速排序(Quick Sort)由圖靈獎獲得者Tony Hoare發(fā)明,被列為20世紀(jì)十大算法之一。冒泡排序的升級版,交換排序的一種。快速排序的時間復(fù)雜度為O(nlog(n))。

快速排序算法的核心思想:通過一趟排序?qū)⒋庞涗浄指畛瑟毩⒌膬刹糠?,其中一部分記錄的關(guān)鍵字均比另一部分記錄的關(guān)鍵字小,然后分別對這兩部分繼續(xù)進(jìn)行排序,以達(dá)到整個記錄集合的排序目的。

核心的交換代碼我自己寫的比較復(fù)雜,以下是我自己寫的:

def splitByK(numbers):
    times = 0
    leftindex = 0
    rightindex = len(numbers) - 1
    k = numbers[0]
    while rightindex > leftindex:
        if times % 2 == 0:
            if numbers[rightindex] < k:
                numbers[leftindex] = numbers[rightindex]
                times += 1
                leftindex += 1
            else:
                rightindex -= 1
        if times % 2 == 1:
            if numbers[leftindex] >= k:
                numbers[rightindex] = numbers[leftindex]
                times += 1
                rightindex -= 1
            else:
                leftindex += 1
    numbers[rightindex] = k

然后參考別人的代碼思路,重新碼了一遍:

def partition(nums):
    leftindex = 0
    rightindex = len(nums) - 1
    k = nums[0]
    while leftindex < rightindex:

        while leftindex < rightindex and nums[rightindex] > k:
            rightindex -= 1
        swap(nums, leftindex, rightindex)

        while leftindex < rightindex and nums[leftindex] < k:
            leftindex += 1
        swap(nums, leftindex, rightindex)

最后是完整代碼:

def quick_sort(self):

    def partition(nums, leftindex, rightindex):
        leftindex = leftindex
        rightindex = rightindex
        k = nums[leftindex]
        while leftindex < rightindex:

            while leftindex < rightindex and nums[rightindex] > k:
                rightindex -= 1
            self.swap(leftindex, rightindex)

            while leftindex < rightindex and nums[leftindex] < k:
                leftindex += 1
            self.swap(leftindex, rightindex)
        return leftindex

    def qsort(nums, low, high):
        if low < high:
            pivot = partition(nums, low, high)
            qsort(nums, 0, pivot)
            qsort(nums, pivot+1, high)

    qsort(self.nums, 0, len(self.nums)-1)
    return self.nums

快速排序的時間性能取決于遞歸的深度。
當(dāng)pivot_key恰好處于記錄關(guān)鍵碼的中間值時,大小兩區(qū)的劃分比較均衡,接近一個平衡二叉樹,此時的時間復(fù)雜度為O(nlog(n))。
當(dāng)原記錄集合是一個正序或逆序的情況下,分區(qū)的結(jié)果就是一棵斜樹,其深度為n-1,每一次執(zhí)行大小分區(qū),都要使用n-i次比較,其最終時間復(fù)雜度為O(n^2)。
在一般情況下,通過數(shù)學(xué)歸納法可證明,快速排序的時間復(fù)雜度為O(nlog(n))。
但是由于關(guān)鍵字的比較和交換是跳躍式的,因此,快速排序是一種不穩(wěn)定排序。
同時由于采用的遞歸技術(shù),該算法需要一定的輔助空間,其空間復(fù)雜度為O(logn)。

最后再補充一點各個排序的應(yīng)用場景,參考:https://blog.csdn.net/mbuger/article/details/67643185

(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)行兩兩歸并的排序算法并不值得提倡,通??梢詫⑺椭苯硬迦肱判蚪Y(jié)合在一起使用。先利用直接插入排序求得較長的有序子序列,然后再兩兩歸并之。因為直接插入排序是穩(wěn)定 的,所以改進(jìn)后的歸并排序仍是穩(wěn)定的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 對酒當(dāng)歌,人生幾何。 古人喝不到扎啤,唱不到K,他們跋山涉水,聚會靠節(jié)日,通訊除了吼就剩下書信,連...
    MrBrant閱讀 353評論 0 0
  • 最近,老公的奶奶仙逝了,近九十歲的高齡。我們?nèi)胰藨阎林匦那榛丶亦l(xiāng)奔喪。在家鄉(xiāng),喪禮的大小事項是非常繁復(fù)的。大人...
    兔子洪洪閱讀 566評論 0 0
  • 我們都知道反饋的重要性,你知道如何收集反饋嗎?你的反饋效果又如何評價呢? 銀行 我們?nèi)ャy行辦理業(yè)務(wù),最后被要求給一...
    大偉傳說閱讀 1,335評論 0 4
  • 第九周周檢視-1組7號庾思慧 主題:委以重任,忙但充實的一周 成: 1.開始早起,和睡覺用“來也”打卡,一周有4天...
    做自己的女王Vivian閱讀 221評論 0 0
  • 不論李笑來老師、許岑老師還是烏龍明月老師(《人人都有機會,用一年時間,改變命運》作者),無一不在強調(diào)一個被大多數(shù)人...
    內(nèi)證髓脈實驗室閱讀 1,197評論 10 16

友情鏈接更多精彩內(nèi)容