簡體   English   中英

為什么我的Python腳本運行速度比HeapSort實現慢?

[英]Why does my Python script run slower than it should on my HeapSort implementation?

我已經完成將堆排序算法實現為Python或Java(或任何其他語言)的任務。 由於我並不是真的能熟練使用Python或Java,因此我決定同時使用這兩種方法。

但是在這里我遇到了一個問題,程序的運行時間比“應該”要高得多。 我的意思是,堆排序應該運行到O(n * log n),並且對於當前以幾GHz時鍾頻率運行的處理器,我沒想到該算法會在數組中運行超過2000秒大小320k

因此,對於我所做的事情,我在Python和Java中從此類偽代碼中實現了算法(我還嘗試了Rosetta Code中的Julia中的代碼,以查看運行時間是否相似,為什么選擇Julia?隨機選擇)

因此,我檢查了輸出是否存在較小的輸入大小問題,例如大小為10、20和30的數組。看來該數組在兩種語言/實現中均正確排序。

然后,我使用實現相同算法的heapq庫再次檢查運行時間是否相似。 當確實如此時,這讓我感到驚訝……但是經過幾次嘗試,我嘗試了最后一件事,即更新Python,然后,使用heapq的程序比以前的程序運行得快得多。 實際上,320k陣列大約需要2k秒,而現在大約需要1.5秒。

我重試了算法,問題仍然存在。

所以這是我實現的Heapsort類:

class MaxHeap:
    heap = []

    def __init__(self, data=None):
        if data is not None:
            self.buildMaxHeap(data)

    @classmethod
    def toString(cls):
        return str(cls.heap)

    @classmethod
    def add(cls, elem):
        cls.heap.insert(len(cls.heap), elem)
        cls.buildMaxHeap(cls.heap)

    @classmethod
    def remove(cls, elem):
        try:
            cls.heap.pop(cls.heap.index(elem))
        except ValueError:
            print("The value you tried to remove is not in the heap")

    @classmethod
    def maxHeapify(cls, heap, i):
        left = 2 * i + 1
        right = 2 * i + 2
        largest = i
        n = len(heap)

        if left < n and heap[left] > heap[largest]:
            largest = left
        if right < n and heap[right] > heap[largest]:
            largest = right
        if largest != i:
            heap[i], heap[largest] = heap[largest], heap[i]
            cls.maxHeapify(heap, largest)

    @classmethod
    def buildMaxHeap(cls, heap):
        for i in range(len(heap) // 2, -1, -1):
            cls.maxHeapify(heap, i)
        cls.heap = heap

    @staticmethod
    def heapSort(table):
        heap = MaxHeap(table)

        output = []

        i = len(heap.heap) - 1
        while i >= 0:
            heap.heap[0], heap.heap[i] = heap.heap[i], heap.heap[0]
            output = [heap.heap[i]] + output
            heap.remove(heap.heap[i])
            heap.maxHeapify(heap.heap, 0)
            i -= 1
        return output

要記錄每個數組大小(10000-320000)的運行時,我在main函數中使用以下循環:

     i = 10000
     while i <= 320000:
         tab = [0] * i
         j = 0
         while j < i:
             tab[j] = randint(0, i)
             j += 1
         start = time()
         MaxHeap.heapSort(tab)
         end = time()
         pprint.pprint("Size of the array " + str(i))
         pprint.pprint("Total execution time: " + str(end - start) + "s")
         i *= 2

如果您需要其余代碼來查看錯誤可能在哪里,請不要猶豫,我會提供它。 只是不想無緣無故地共享整個文件。

如前所述,我期望的運行時間來自最壞的運行時間:具有現代體系結構和2.6GHz處理器的O(n * log n),我希望大約1秒或更短的時間(因為運行時間要求以納秒為單位)我想即使1秒仍然太長)

結果如下:

Python (own) :                 Java (Own)

  Time        Size               Time       Size 
 593ms.       10k               243ms.      10k
 2344ms.      20k               600ms.      20k
 9558ms.      40k               1647ms.     40k
 38999ms.     80k               6666ms.     80k
 233811ms.    160k              62789ms.    160k
 1724926ms.   320k              473177ms.   320k

Python (heapq)                 Julia (Rosetta Code)
  Time        Size               Time        Size
 6ms.         10k               21ms.        10k
 14ms.        20k               21ms.        20k
 15ms.        40k               23ms.        40k
 34ms.        80k               28ms.        80k
 79ms.        160k              39ms.        160k
 168ms.       320k              60ms.        320k


And according to the formula the O(n * log n) give me :
40000       10k
86021       20k
184082      40k
392247      80k
832659      160k
1761648     320k

我認為這些結果可用於確定需要多少時間,具體取決於機器(理論上)

如您所見,運行時間長的結果來自我的算法,但是我無法確定代碼在哪里,這就是為什么我在這里尋求幫助。 (在Java和Python中運行速度都很慢)(沒有嘗試在java lib中使用堆排序是因為有一個可以看到與我的實現有區別的地方,我的錯)

非常感謝。

編輯:我忘了補充一點,我在MacBook Pro(最新版本的MacOS,i7 2,6GHz)上運行了該程序。以防萬一問題也可能源於代碼以外的其他問題。

編輯2:這是我收到的答案之后,對算法所做的修改。 該程序的運行速度比以前快了大約200倍,因此,對於大小為320k的數組,它現在僅需2秒即可運行

class MaxHeap:

    def __init__(self, data=None):
        self.heap = []
        self.size = 0

        if data is not None:
            self.size = len(data)
            self.buildMaxHeap(data)

    def toString(self):
        return str(self.heap)

    def add(self, elem):
        self.heap.insert(self.size, elem)
        self.size += 1
        self.buildMaxHeap(self.heap)

    def remove(self, elem):
        try:
            self.heap.pop(self.heap.index(elem))
        except ValueError:
            print("The value you tried to remove is not in the heap")

    def maxHeapify(self, heap, i):
        left = 2 * i + 1
        right = 2 * i + 2
        largest = i

        if left < self.size and heap[left] > heap[largest]:
            largest = left
        if right < self.size and heap[right] > heap[largest]:
            largest = right
        if largest != i:
            heap[i], heap[largest] = heap[largest], heap[i]
            self.maxHeapify(heap, largest)

    def buildMaxHeap(self, heap):
        for i in range(self.size // 2, -1, -1):
            self.maxHeapify(heap, i)
        self.heap = heap

    @staticmethod
    def heapSort(table):
        heap = MaxHeap(table)

        i = len(heap.heap) - 1
        while i >= 0:
            heap.heap[0], heap.heap[i] = heap.heap[i], heap.heap[0]
            heap.size -= 1
            heap.maxHeapify(heap.heap, 0)
            i -= 1
        return heap.heap

它使用與前面給出的相同的主函數運行

有趣的是,您發布了計算機的時鍾速度-您可以計算算法所需的實際步數...,但是您需要了解很多有關實現的知識。 例如,在python中,每次創建對象或超出范圍時,解釋器都會更新基礎對象上的計數器,並在這些引用計數達到0時釋放內存。相反,您應該查看相對速度。

您發布的第三方示例顯示,當輸入數組長度加倍時,速度減慢然后加倍。 這似乎不對,是嗎? 事實證明,對於這些示例,構建數組的初始工作可能會占據對數組進行排序所花費的時間!

在您的代碼中,已經有一條注釋可以呼出我要說的話...

heap.remove(heap.heap[i])此操作將遍歷您的列表(從索引0開始)以查找匹配的值,然后將其刪除。 這已經很糟糕了(如果它按預期工作,那么如果您的代碼按預期工作,那么您將在該行上進行320k比較!)。 但是,情況變得更糟-從數組中刪除對象不是就地修改-刪除的對象必須在列表中向前移動后的每個對象。 最后,不能保證您實際上正在刪除那里的最后一個對象……可能存在重復的值!

這是一個有用的網站,列出了python中各種操作的復雜性-https: //wiki.python.org/moin/TimeComplexity 為了盡可能高效地實現算法,您需要將盡可能多的數據結構操作設為O(1)。 這是一個示例...這是一些原始代碼,大概是用heap.heap作為列表...

        output = [heap.heap[i]] + output
        heap.remove(heap.heap[i])

        output.append(heap.heap.pop())

將避免分配新列表,並使用恆定時間操作來對舊列表進行變異。 (比起使用O(n)時間insert(0)方法,只向后使用輸出要好得多!如果您確實需要此命令,則可以使用出隊對象進行輸出以獲得appendleft方法)

如果您發布了整個代碼,則可能還有很多其他小事情我們可以幫助您。 希望這有所幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM