第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

快速排序

今天我們來(lái)聊一聊無(wú)論是在筆試還是面試中常??嫉降?,也是最經(jīng)典和最高效的快速排序算法。它的平均時(shí)間復(fù)雜度為 O(NlogN)O(NlogN),空間復(fù)雜度為 O(1)O(1)

1. 快速排序算法原理

快速排序算法是基于這樣的思想:從待排序的列表選擇一個(gè)基準(zhǔn)數(shù),然后將列表中比該基準(zhǔn)數(shù)大的放到該數(shù)的右邊,比該基準(zhǔn)數(shù)小的值放到該基準(zhǔn)數(shù)的左邊;接下來(lái)執(zhí)行遞歸操作,對(duì)基準(zhǔn)數(shù)左邊的待排序列表執(zhí)行前面同樣的操作,直到左邊的列表有序,對(duì)于基準(zhǔn)數(shù)右邊的待排序列表也執(zhí)行前面同樣的操作,直到右邊的列表有序。此時(shí),整個(gè)列表有序。即對(duì)于輸入的數(shù)組 nums[left:right]

  • 分解:以 nums[p] 為基準(zhǔn)將 nums[left: right] 劃分為三部分: nums[left:p-1]nums[p]a[p+1:right],使得 nums[left:p-1] 中任何一個(gè)元素小于等于 nums[p], 而 nums[p+1:right] 中任何一個(gè)元素大于等于 nums[p];
  • 遞歸求解:通過(guò)遞歸調(diào)用快速排序算法分別對(duì) nums[left:p -1]nums[p+1:right] 進(jìn)行排序;
  • 合并:由于對(duì) nums[left:p-1]nums[p+1:right] 的排序是就地進(jìn)行的,所以在 nums[left:p-1]nums[p+1:right] 都已排好序后,不需要執(zhí)行任何計(jì)算,nums[left:right] 就已經(jīng)排好序了;

下面是算法示意圖:
圖片描述

快速排序算法示意圖

2. 快速排序空間復(fù)雜度優(yōu)化

可以看到,上述快速排序算法的一個(gè)核心步驟是:**將列表中的比基準(zhǔn)數(shù)小的全部移動(dòng)到基準(zhǔn)數(shù)的左邊,其余數(shù)移動(dòng)到基準(zhǔn)數(shù)的右邊,且整個(gè)算法的過(guò)程不能使用額外的空間。**這樣的算法才比較高效,空間復(fù)雜度為 O(1)。那么怎么做到不使用額外空間達(dá)到這一目的呢?請(qǐng)看下面的示意圖進(jìn)行理解。

圖片描述

快速排序核心步驟

可以看下快速排序動(dòng)態(tài)圖的演示:
圖片描述

快速排序原理動(dòng)態(tài)圖演示

接下來(lái),我們就要使用 Python 實(shí)現(xiàn)上面的核心步驟以及最后的快排算法。

3. 快速排序算法的 Python 實(shí)現(xiàn)

首先我們實(shí)現(xiàn)上面的核心步驟,代碼如下:

# 代碼位置:sort_algorithms.py

def get_num_position(nums, left, right):
    # 默認(rèn)基準(zhǔn)值為第一個(gè)
    base = nums[left]
    while left < right:
        # 從最右邊向左直到找到小于基準(zhǔn)值的數(shù)
        while left < right and nums[right] >= base:
            right -= 1
        # 將小于基準(zhǔn)數(shù)的值放到右邊,left原來(lái)位置放的是基準(zhǔn)值(base)
        nums[left] = nums[right]
        # 然后從左邊往右遍歷,直到找到大于基準(zhǔn)值的數(shù)
        while left < right and nums[left] <= base:
            left += 1
        # 然后將left位置上的值放到right位置上,right位置上的值原本為base值
        nums[right] = nums[left]
    # left=right的位置上就是基準(zhǔn)值
    nums[left] = base
    return left

4. 實(shí)例測(cè)試快速排序代碼

上面的函數(shù)中我們已經(jīng)做了詳細(xì)的注釋?zhuān)颓懊娴诙垐D描述的過(guò)程是一致的。接下來(lái)我們來(lái)對(duì)該方法進(jìn)行測(cè)試:

PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> python
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from sort_algorithms import get_num_position
>>> nums = [10, 2, 16, 8, 4, 17, 12, 11]
>>> get_num_position(nums, 0, len(nums) - 1)        
3
>>> nums      
[4, 2, 8, 10, 16, 17, 12, 11]

可以看到,代碼確實(shí)實(shí)現(xiàn)了我們想要的結(jié)果,將比 10 小的全部移動(dòng)到了左邊,比 10 大的全部移動(dòng)到了右邊。接下來(lái)就是實(shí)現(xiàn)快速排序算法。從第一張圖中很明顯可以看到快排算法是一個(gè)遞歸的過(guò)程,因此我們用遞歸完成快排算法,代碼如下:

# 代碼位置:sort_algorithms.py

def quick_sort(nums, start, end):
    """
    快速排序算法
    """
    if start >= end:
        return 
    pos = get_num_position(nums, start, end)
    # 左邊遞歸下去,直到排好序
    quick_sort(nums, start, pos - 1)
    # 右邊遞歸下去,直到排好序
    quick_sort(nums, pos + 1, end)

對(duì)于遞歸方法,后面會(huì)詳細(xì)介紹到。這里一定要注意,使用遞歸過(guò)程一定要先寫(xiě)終止條件,不然函數(shù)無(wú)窮遞歸下去,就會(huì)導(dǎo)致堆棧溢出錯(cuò)誤。接下來(lái)我們測(cè)試這個(gè)快排算法:

>>> from sort_algorithms import quick_sort
>>> nums = [8, 7, 9, 6, 11, 3, 12, 20, 9, 5, 1, 10]
>>> quick_sort(nums, 0, len(nums) - 1)
>>> nums
[1, 3, 5, 6, 7, 8, 9, 9, 10, 11, 12, 20] 

可以看到上面的代碼實(shí)現(xiàn)了我們想要的排序效果。接下來(lái)我們分析下快排算法,它被人們研究的最多,提出了許多改進(jìn)和優(yōu)化的方案,我們簡(jiǎn)單聊一下快排算法的優(yōu)化方案。

5. 優(yōu)化快速排序算法

對(duì)于優(yōu)化快速排序,在這里我們只考慮最簡(jiǎn)單的一種優(yōu)化思路,就是基準(zhǔn)數(shù)的選擇。前面的快排算法中,我們都是選擇列表的第一個(gè)元素作為基準(zhǔn)數(shù),這在列表隨機(jī)的情況下是沒(méi)有什么問(wèn)題的,但對(duì)本身有序的列表進(jìn)行排序時(shí),時(shí)間復(fù)雜度就會(huì)退化到 O(n2)O(n^2)。我們來(lái)進(jìn)行相關(guān)測(cè)試:

# 冒泡排序算法
import datetime
import random
from sort_algorithms import get_num_position, quick_sort

# python默認(rèn)遞歸次數(shù)有限制,如果不進(jìn)行設(shè)置,會(huì)導(dǎo)致超出遞歸最大次數(shù)
import sys   
sys.setrecursionlimit(1000000)

if __name__ == '__main__':
    # 對(duì)于設(shè)置10000,本人電腦會(huì)出現(xiàn)棧溢出錯(cuò)誤
    # nums = [random.randint(10, 10000) for i in range(6000)] 
    nums = [i for i in range(6000)] 
    start = datetime.datetime.now()
    quick_sort(nums, 0, len(nums) - 1)
    end = datetime.datetime.now()
    print('Running time: %s Seconds' % (end-start))

第一個(gè)注釋的語(yǔ)言是對(duì) nums 列表隨機(jī)生成,而第二個(gè)直接情況是直接生成有序的列表。我們分別看執(zhí)行結(jié)果:

# 隨機(jī)列表快排
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.027907 Seconds
# 有序列表快排
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:02.159677 Seconds

可以看到明顯的差別,差不多差了 80 倍,這確實(shí)是純快排算法存在的一個(gè)問(wèn)題。如果我們往這個(gè)有序列表中插入少量隨機(jī)數(shù),打破有序的情況,會(huì)看到性能會(huì)有較大提升。這個(gè)問(wèn)題的根源在于基準(zhǔn)數(shù)目的選擇,對(duì)于有序的列表排序時(shí)每次都是選擇的最小或者最大的值,這就是最壞的情況。一個(gè)顯而易見(jiàn)的改進(jìn)策略就是隨機(jī)選擇列表中的值作為基準(zhǔn)數(shù),另一種是從頭 (left)、中 ([left + right] // 2) 和尾 (right) 這三個(gè)元素中取中間值作為基準(zhǔn)數(shù)。我們分別進(jìn)行測(cè)試:

import random
def get_base(nums, left, right):
    index = random.randint(left, right)
    if index != left:
        nums[left], nums[index] = nums[index], nums[left]
    return nums[left]


def get_base_middle(nums, left, right):
    if left == right:
        return nums[left]
    mid = (left + right) >> 1

    if nums[mid] > nums[right]:
	    nums[mid], nums[right] = nums[right], nums[mid]

    if nums[left] > nums[right]:
        # nums[left]最大,nums[right]中間,所以交換
	    nums[left], nums[right] = nums[left], nums[mid]

    if nums[mid] > nums[left]:
        # 說(shuō)明nums[left]最小, nums[mid]為中間值
	    nums[left], nums[mid] = nums[mid], nums[left]

    return nums[left]


def get_num_position(nums, left, right):
    # base = nums[left]
    # 隨機(jī)基準(zhǔn)數(shù)
    base = get_base(nums, left, right)
    # base = get_base_middle(nums, left, right)
    while left < right:
        # 和前面相同,以下兩個(gè)while語(yǔ)句用于將基準(zhǔn)數(shù)放到對(duì)應(yīng)位置,使得基準(zhǔn)數(shù)左邊的元素都小于它,右邊的數(shù)都大于它
        while left < right and nums[right] >= base:
            right -= 1
        nums[left] = nums[right]
        while left < right and nums[left] <= base:
            left += 1
        nums[right] = nums[left]
    # 基準(zhǔn)數(shù)的位置,并返回該位置值
    nums[left] = base
    return left

改進(jìn)后的測(cè)試結(jié)果如下:

# 有序列表-隨機(jī)基準(zhǔn)值
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.021890 Seconds

# 隨機(jī)列表-隨機(jī)基準(zhǔn)值
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.026948 Seconds
# 有序列表-中位數(shù)基準(zhǔn)值
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.012944 Seconds

# 隨機(jī)列表-中位數(shù)基準(zhǔn)值         
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.020933 Seconds

可以看到使用中位數(shù)基準(zhǔn)值在有序列表情況下排序速度更快。最后還有一個(gè)簡(jiǎn)單的常用優(yōu)化方案,就是當(dāng)序列長(zhǎng)度達(dá)到一定大小時(shí),使用插入排序。

# 改造前面的插入排序算法
def insert_sort(nums, start, end):
    """
    插入排序
    """
    if not nums:
        return False
    for i in range(start + 1, end + 1):
        t = nums[i]
        for j in range(i - 1, start - 1, -1):
            k = j
            if nums[j] <= t:
                k = k + 1
                break
            nums[j + 1] = nums[j]
        nums[k] = t
    return True 

# ...

def quick_sort(nums, start, end):
    """
    快速排序算法
    """
    if (end - start + 1 < 10):
        # 在排序的數(shù)組小到一定程度時(shí),使用插入排序
        insert_sort(nums, start, end)
        return
    # 找到基準(zhǔn)數(shù)位置,同時(shí)調(diào)整好數(shù)組,使得基準(zhǔn)數(shù)的左邊小于它,基準(zhǔn)數(shù)的右邊都是大于它
    pos = get_num_position(nums, start, end)
    # 遞歸使用快排,對(duì)左邊使用快排算法
    quick_sort(nums, start, pos - 1)
    # 對(duì)右邊元素使用快排算法
    quick_sort(nums, pos + 1, end)

下面是使用【隨機(jī)基準(zhǔn)+插入排序優(yōu)化】的測(cè)試結(jié)果如下:

# 有序列表-[隨機(jī)基準(zhǔn)+使用插入排序優(yōu)化]
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.010962 Seconds

# 無(wú)序列表-[隨機(jī)基準(zhǔn)+使用插入排序優(yōu)化]
PS C:\Users\spyinx\Desktop\學(xué)習(xí)教程\慕課網(wǎng)教程\算法慕課教程\code> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/學(xué)習(xí)教程/慕課網(wǎng)教程/算法慕課教程/code/test_algorithms.py
Running time: 0:00:00.018935 Seconds

可以看到這些小技巧在真正大規(guī)模數(shù)據(jù)排序時(shí)會(huì)有非常明顯的效果。更多的優(yōu)化方法以及測(cè)試,讀者可以自行去實(shí)驗(yàn)和測(cè)試,這里就不再繼續(xù)講解了。

6. 小結(jié)

本小節(jié)我們介紹了快速排序算法以及相關(guān)的優(yōu)化策略,并完成了簡(jiǎn)單的實(shí)驗(yàn)測(cè)試,方便大家理解改進(jìn)的效果。至此,排序算法介紹到此就要結(jié)束了。當(dāng)然高效的排序算法并不止快排算法,海域堆排序、歸并排序、桶排序以及基數(shù)排序等等,這些讀者可以自行學(xué)習(xí)并實(shí)現(xiàn)相關(guān)代碼,打好算法基礎(chǔ)。

Tips:文中動(dòng)圖制作參考:https://visualgo.net/zh/sorting。