Python---簡(jiǎn)析八大排序算法

前言

1 、排序的概念

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

2 、排序分類

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

3 挖帘、算法分析

下表給出各種排序的基本性能绢馍,具體分析請(qǐng)參看各排序的詳解:



一、插入排序

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

直接插入排序

基本思想:
直接插入排序的核心思想就是:將數(shù)組中的所有元素依次跟前面已經(jīng)排好的元素相比較肠套,如果選擇的元素比已排序的元素小舰涌,則交換,直到全部元素都比較過你稚。
因此瓷耙,從上面的描述中我們可以發(fā)現(xiàn),直接插入排序可以用兩個(gè)循環(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ù)組列在一個(gè)表中并對(duì)列分別進(jìn)行插入排序,重復(fù)這過程宇弛,不過每次用更長的列(步長更長了鸡典,列數(shù)更少了)來進(jìn)行。最后整個(gè)表就只有一列了枪芒。將數(shù)組轉(zhuǎn)換至表是為了更好地理解這算法彻况,算法本身還是使用數(shù)組進(jìn)行排序。

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

def shell_sort(alist):
    n = len(alist)
    # 初始步長
    gap = n // 2
    while gap > 0:
        # 按步長進(jìn)行插入排序
        for i in range(gap,n):
            j = i
            # 原地插入排序祷舀,之前的插入排序另辟空間存儲(chǔ)的
            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)定抛丽。


二、選擇排序

選擇排序分為簡(jiǎn)單選擇排序和堆排序

簡(jiǎn)單選擇排序

基本思想:
比較+交換饰豺。

  1. 從待排序序列中亿鲜,找到關(guān)鍵字最小的元素;
  2. 如果最小元素不是待排序序列的第一個(gè)元素冤吨,將其和第一個(gè)元素互換蒿柳;
  3. 從余下的 N - 1 個(gè)元素中,找出關(guān)鍵字最小的元素漩蟆,重復(fù)(1)垒探、(2)步,直到排序結(jié)束怠李。
    因此我們可以發(fā)現(xiàn)圾叼,簡(jiǎn)單選擇排序也是通過兩層循環(huán)實(shí)現(xiàn)。
  • 第一層循環(huán):依次遍歷序列當(dāng)中的每一個(gè)元素
  • 第二層循環(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é)點(diǎn)的元素一定是當(dāng)前序列的最大值)
  2. 取出當(dāng)前大頂堆的根節(jié)點(diǎn)惕鼓,將其與序列末尾元素進(jìn)行交換;
    (此時(shí):序列末尾的元素為已排序的最大值;由于交換了元素路鹰,當(dāng)前位于根節(jié)點(diǎn)的堆并不一定滿足大頂堆的性質(zhì))
  3. 對(duì)交換后的n-1個(gè)序列元素進(jìn)行調(diào)整凤薛,使其滿足大頂堆的性質(zhì);
  4. 重復(fù)2.3步驟叫胁,直至堆中只有1個(gè)元素為止

堆排序的時(shí)間復(fù)雜度分為兩個(gè)部分一個(gè)是建堆的時(shí)候所耗費(fèi)的時(shí)間,一個(gè)是進(jìn)行堆調(diào)整的時(shí)候所耗費(fèi)的時(shí)間汞幢。而堆排序則是調(diào)用了建堆和堆調(diào)整驼鹅。
建堆是一個(gè)線性過程,從len/2-0一直調(diào)用堆調(diào)整的過程森篷,相當(dāng)于o(h1)+o(h2)+…+o(hlen/2)這里的h表示節(jié)點(diǎn)深度输钩,len/2表示節(jié)點(diǎn)深度,對(duì)于求和過程仲智,結(jié)果為線性的O(n) 堆調(diào)整為一個(gè)遞歸的過程买乃,調(diào)整堆的過程時(shí)間復(fù)雜度與堆的深度有關(guān)系,相當(dāng)于lgn的操作钓辆。 因?yàn)榻ǘ训臅r(shí)間復(fù)雜度是O(n),調(diào)整堆的時(shí)間復(fù)雜度是lgn剪验,所以堆排序的時(shí)間復(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

三、歸并排序

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

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

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

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

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

分解:
在這里聂宾,我們采用遞歸的方法,首先將待排序列分成A,B兩組诊笤;然后重復(fù)對(duì)A系谐、B序列
分組;直到分組后組內(nèi)只有一個(gè)元素讨跟,此時(shí)我們認(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ù)排序

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

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

收集:當(dāng)序列中所有的元素都分配到對(duì)應(yīng)的桶中,再按照順序依次將桶中的元素收集形成新的一個(gè)待排序列L[ ]
對(duì)新形成的序列L[]重復(fù)執(zhí)行分配和收集元素中的十位澜共、百位...直到分配完該序列中的最高位向叉,則排序結(jié)束
根據(jù)上述“基數(shù)排序”的展示,我們可以清楚的看到整個(gè)實(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é)束后奇唤,序列最后一個(gè)元素一定是當(dāng)前序列的最大值幸斥;)
  • 對(duì)序列當(dāng)中剩下的n-1個(gè)元素再次執(zhí)行步驟1。
  • 對(duì)于長度為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)中選擇一個(gè)基準(zhǔn)數(shù)(pivot)
在這里我們選擇序列當(dāng)中第一個(gè)數(shù)最為基準(zhǔn)數(shù)
將序列當(dāng)中的所有數(shù)依次遍歷甲葬,比基準(zhǔn)數(shù)大的位于其右側(cè),比基準(zhǔn)數(shù)小的位于其左側(cè)
重復(fù)步驟1.2冗栗,直到所有子集當(dāng)中只有一個(gè)元素為止演顾。
用偽代碼描述如下:
1.i =L; j = R; 將基準(zhǔn)數(shù)挖出形成第一個(gè)坑a[i]供搀。
2.j--由后向前找比它小的數(shù)隅居,找到后挖出此數(shù)填前一個(gè)坑a[i]中。
3.i++由前向后找比它大的數(shù)葛虐,找到后也挖出此數(shù)填到前一個(gè)坑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í),直接插入排序較好西疤;否則如果直接選擇移動(dòng)的記錄數(shù)少于直接插人烦粒,應(yīng)選直接選擇排序?yàn)橐恕?/li>
  • (2)若文件初始狀態(tài)基本有序(指正序),則應(yīng)選用直接插入代赁、冒泡或隨機(jī)的快速排序?yàn)橐耍?/li>
  • (3)若n較大扰她,則應(yīng)采用時(shí)間復(fù)雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序芭碍。
     快速排序是目前基于比較的內(nèi)部排序中被認(rèn)為是最好的方法徒役,當(dāng)待排序的關(guān)鍵字是隨機(jī)分布時(shí),快速排序的平均時(shí)間最短窖壕;
     堆排序所需的輔助空間少于快速排序忧勿,并且不會(huì)出現(xiàn)快速排序可能出現(xiàn)的最壞情況。這兩種排序都是不穩(wěn)定的瞻讽。
     若要求排序穩(wěn)定鸳吸,則可選用歸并排序。但本章介紹的從單個(gè)記錄起進(jìn)行兩兩歸并的排序算法并不值得提倡卸夕,通巢闶停可以將它和直接插入排序結(jié)合在一起使用。先利用直接插入排序求得較長的有序子文件快集,然后再兩兩歸并之贡羔。因?yàn)橹苯硬迦肱判蚴欠€(wěn)定的廉白,所以改進(jìn)后的歸并排序仍是穩(wěn)定的。

七乖寒、參考文獻(xiàn)

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逐虚,隨后出現(xiàn)的幾起案子聋溜,更是在濱河造成了極大的恐慌,老刑警劉巖叭爱,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撮躁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡买雾,警方通過查閱死者的電腦和手機(jī)把曼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漓穿,“玉大人嗤军,你說我怎么就攤上這事』挝#” “怎么了叙赚?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長山害。 經(jīng)常有香客問我纠俭,道長,這世上最難降的妖魔是什么浪慌? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任冤荆,我火速辦了婚禮,結(jié)果婚禮上权纤,老公的妹妹穿的比我還像新娘钓简。我一直安慰自己,他們只是感情好汹想,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布外邓。 她就那樣靜靜地躺著,像睡著了一般古掏。 火紅的嫁衣襯著肌膚如雪损话。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音丧枪,去河邊找鬼光涂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拧烦,可吹牛的內(nèi)容都是我干的忘闻。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼恋博,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼齐佳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起债沮,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤炼吴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后秦士,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缺厉,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡永高,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年隧土,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片命爬。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡曹傀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饲宛,到底是詐尸還是另有隱情皆愉,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布艇抠,位于F島的核電站幕庐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏家淤。R本人自食惡果不足惜异剥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望絮重。 院中可真熱鬧冤寿,春花似錦、人聲如沸青伤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狠角。三九已至号杠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丰歌,已是汗流浹背姨蟋。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工辣吃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芬探。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓神得,卻偏偏與公主長得像,于是被迫代替她去往敵國和親偷仿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哩簿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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