笨辦法學(xué) Python · 續(xù) 練習(xí) 16:冒泡、快速和歸并排序

練習(xí) 16:冒泡、快速和歸并排序

原文:Exercise 16: Bubble, Quick, and Merge Sort

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

你現(xiàn)在將嘗試為你的DoubleLinkedList數(shù)據(jù)結(jié)構(gòu)實現(xiàn)排序算法。對于這些描述,我將使用“數(shù)字列表”來表示隨機的事物列表。這可能是一堆撲克牌,一張紙上的數(shù)字,名稱列表或其他任何可以排序的東西。當(dāng)你嘗試排序數(shù)字列表時,通常有三個備選方案:

冒泡排序

如果你對排序一無所知,這是你最可能嘗試的方式。它僅僅涉及遍歷列表,并交換你找到的任何亂序偶對。你不斷遍歷列表,交換偶對,直到你沒有交換任何東西。很容易理解,但是特別慢。

歸并排序

這種排序算法將列表分成兩半,然后是四個部分,直到它不能再分割為止。然后,它將這些返回的東西合并,但是在合并它時,通過檢查每個部分的順序,以正確的順序進行操作。這是一個聰明的算法,在鏈表上工作得很好,但在固定大小的數(shù)組上并不是很好,因為你需要某種Queue來跟蹤部分。

快速排序

這類似于歸并排序,因為它是一種“分治”算法,但它的原理是交換分割點周圍的元素,而不是將列表拆分合并在一起。在最簡單的形式中,你可以選擇從下界到上界的范圍和分割點。然后,交換分割點上方的大于它的元素,和下方的小于它的它元素。然后你選擇一個新的下界,上界和分割點,它們在這個新的無序列表里面,再執(zhí)行一次。它將列表分成更小的塊,但它不會像歸并排序一樣拆分它們。

挑戰(zhàn)練習(xí)

本練習(xí)的目的是,學(xué)習(xí)如何基于“偽代碼”描述或“p-code”的實現(xiàn)算法。你將使用我告訴你的參考文獻(主要是維基百科)研究算法,然后使用偽代碼實現(xiàn)它們。在這個練習(xí)的視頻中,我會在這里快速完成前兩個,更細節(jié)的東西留作練習(xí)。那么你的工作就是自己實現(xiàn)快速排序算法。首先,我們查看維基百科中冒泡排序的描述,來開始:

procedure bubbleSort( A : list of sortable items )
    n = length(A)
    repeat
        swapped = false
        for i = 1 to n-1 inclusive do
            /* 如果這個偶對是亂序的 */
            if A[i-1] > A[i] then
                /* 交換它們并且記住 */
                swap( A[i-1], A[i] )
                swapped = true
            end if
        end for
    until not swapped
end procedure

你會發(fā)現(xiàn),因為偽代碼只是對算法的松散描述,它最終在不同書籍,作者和維基百科的頁面之間截然不同。它假設(shè)你可以閱讀這種“類編程語言”,并將其翻譯成你想要的內(nèi)容。有時這種語言看起來像是一種叫做 Algol 的舊語言,其他的時候它會像格式不正確的 JavaScript 或者 Python 一樣。你只需要嘗試猜測它的意思,然后將其翻譯成你需要的。這是我對這個特定的偽代碼的最初實現(xiàn):

def bubble_sort(numbers):
    """Sorts a list of numbers using bubble sort."""
    while True:
        # 最開始假設(shè)它是有序的
        is_sorted = True
        # 一次比較兩個,跳過頭部
        node = numbers.begin.next
        while node:
            # 遍歷并將當(dāng)前節(jié)點與上一個比較
            if node.prev.value > node.value:
                # 如果上一個更大,我們需要交換
                node.prev.value, node.value = node.value, node.prev.value
                # 這表示我們需要再次掃描
                is_sorted = False
            node = node.next

        # 它在頂部重置過,但是如果我們沒有交換,那么它是有序的
        if is_sorted: break

我在這里添加了其他注釋,以便你可以學(xué)習(xí)并跟蹤它,將我在此處完成的內(nèi)容與偽代碼進行比較。你還應(yīng)該看到,維基百科頁面正在使用的數(shù)據(jù)結(jié)構(gòu),與DoubleLinkedList完全不同。維基百科的代碼假設(shè)在數(shù)組或列表結(jié)構(gòu)上實現(xiàn)函數(shù)。你必須將下面這行:

if A[i-1] > A[i] then

使用DoubleLinkedList翻譯為 Python:

if node.prev.value > node.value:

我們不能輕易地隨機訪問DoubleLinkedList,所以我們必須將這些數(shù)組索引操作轉(zhuǎn)換為.next.prev。在循環(huán)中,我們還必須注意nextprev屬性是否是None。這種轉(zhuǎn)換需要大量的翻譯,學(xué)習(xí)和猜測你正在閱讀的偽代碼的語義。

學(xué)習(xí)冒泡排序

你現(xiàn)在應(yīng)該花時間研究這個bubble_sortPython 代碼,看看我如何翻譯它。確保觀看我實時的視頻,并獲得更多的透視。你還應(yīng)該繪制在不同類型的列表(已排序,隨機,重復(fù)等)上運行的圖表。一旦你了解我是如何做到的,為此研究pytestmerge_sort算法:

import sorting
from dllist import DoubleLinkedList
from random import randint

max_numbers = 30

def random_list(count):
    numbers = DoubleLinkedList()
    for i in range(count, 0, -1):
        numbers.shift(randint(0, 10000))
    return numbers


def is_sorted(numbers):
    node = numbers.begin
    while node and node.next:
        if node.value > node.next.value:
            return False
        else:
            node = node.next

    return True


def test_bubble_sort():
    numbers = random_list(max_numbers)

    sorting.bubble_sort(numbers)

    assert is_sorted(numbers)


def test_merge_sort():
    numbers = random_list(max_numbers)

    sorting.merge_sort(numbers)

    assert is_sorted(numbers)

這個測試代碼的一個重要部分是,我正在使用random.randint函數(shù)生成隨機數(shù)據(jù)進行測試。這個測試不會測試許多邊界情況,但這是一個開始,我們將在以后進行改進。記住,你沒有實現(xiàn)sort.merge_sort,所以你可以不寫這個測試函數(shù),或者現(xiàn)在注釋它。

一旦你進行了測試,并且寫完了這個代碼,再次研究維基百科頁面,然后在嘗試merge_sort之前,嘗試一些其他的bubble_sort版本。

歸并排序

我還沒準備好讓你自己實現(xiàn)它。我將再次對merge_sort函數(shù)重復(fù)此過程,但是這次我想讓你嘗試,從歸并排序的維基百科頁面 上的偽代碼中實現(xiàn)該算法,然后再查看我怎么做。有幾個建議的實現(xiàn),但我使用“自頂向下”的版本:

function merge_sort(list m)
    if length of m ≤ 1 then
        return m

    var left := empty list
    var right := empty list
    for each x with index i in m do
        if i < (length of m)/2 then
            add x to left
        else
            add x to right

    left := merge_sort(left)
    right := merge_sort(right)

    return merge(left, right)

function merge(left, right)
    var result := empty list

    while left is not empty and right is not empty do
        if first(left) ≤ first(right) then
            append first(left) to result
            left := rest(left)
        else
            append first(right) to result
            right := rest(right)

    while left is not empty do
        append first(left) to result
        left := rest(left)
    while right is not empty do
        append first(right) to result
        right := rest(right)
    return result

test_merge_sort編寫剩余測試用例函數(shù),然后在這個實現(xiàn)上進行嘗試。我會給你一個線索,當(dāng)僅僅使用第一個DoubleLinkedListNode時,該算法效果最好。你也可能需要一種方法,從給定的節(jié)點計算節(jié)點數(shù)。這是DoubleLinkedList不能做的事情。

歸并排序作弊模式

如果你嘗試了一段時間并且需要作弊,這是我所做的:

def count(node):
    count = 0

    while node:
        node = node.next
        count += 1

    return count


def merge_sort(numbers):
    numbers.begin = merge_node(numbers.begin)

    # horrible way to get the end
    node = numbers.begin
    while node.next:
        node = node.next
    numbers.end = node


def merge_node(start):
    """Sorts a list of numbers using merge sort."""
    if start.next == None:
        return start

    mid = count(start) // 2

    # scan to the middle
    scanner = start
    for i in range(0, mid-1):
        scanner = scanner.next

    # set mid node right after the scan point
    mid_node = scanner.next
    # break at the mid point
    scanner.next = None
    mid_node.prev = None

    merged_left = merge_node(start)
    merged_right = merge_node(mid_node)

    return merge(merged_left, merged_right)



def merge(left, right):
    """Performs the merge of two lists."""
    result = None

    if left == None: return right
    if right == None: return left

    if left.value > right.value:
        result = right
        result.next = merge(left, right.next)
    else:
        result = left
        result.next = merge(left.next, right)

    result.next.prev = result
    return result

在嘗試實現(xiàn)它時,我將使用此代碼作為“備忘單”來快速獲取線索。你還會看到,我在視頻中嘗試從頭開始重新實現(xiàn)此代碼,因此你可以看到我努力解決你可能遇到過的相同問題。

快速排序

最后,輪到你嘗試實現(xiàn)quick_sort并創(chuàng)建test_quicksort測試用例。我建議你首先使用 Python 的普通列表類型實現(xiàn)簡單的快速排序。這將有助于你更好地理解它。然后,使用簡單的 Python 代碼,并使其處理DoubleLinkedList(的頭節(jié)點)。記住要把你的時間花費在這里,顯然還要在你的test_quicksort里進行大量的調(diào)試和測試。

深入學(xué)習(xí)

  • 這些實現(xiàn)在性能上絕對不是最好的。嘗試寫一些喪心病狂的測試來證明這一點。你可能需要將一個很大的列表傳給算法。使用你的研究來找出病態(tài)(絕對最差)的情況。例如,當(dāng)你把一個有序的列表給quick_sort時會發(fā)生什么?
  • 不要實現(xiàn)任何改進,但研究你可以對這些算法執(zhí)行的,各種改進方法。
  • 查找其他排序算法并嘗試實現(xiàn)它們。
  • 它們還可以在SingleLinkedList上工作嗎?QueueStack呢?它們很實用嗎?
  • 了解這些算法的理論速度。你會看到O(n^2)O(nlogn)的引用,這是一種說法,在最壞的情況下,這些算法的性能很差。為算法確定“大O”超出了本書的范圍,但我們將在練習(xí) 18 中簡要討論這些度量。
  • 我將這些實現(xiàn)為一個單獨的模塊,但是將它們作為函數(shù),添加到DoubleLinkedList更簡單嗎?如果你這樣做,那么你需要將該代碼復(fù)制到可以處理的其他數(shù)據(jù)結(jié)構(gòu)上嗎?我們沒有這樣的設(shè)計方案,如何使這些排序算法處理任何“類似鏈表的數(shù)據(jù)結(jié)構(gòu)”。
  • 再也不要使用氣泡排序。我把它包含在這里,因為你經(jīng)常遇到壞的代碼,并且我們會在練習(xí) 19 中提高其性能。
最后編輯于
?著作權(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)容

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