繁体   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