我试图从大数组中提取k个最大元素,并比较以下2个算法
T(n) = k + nlogk
T(n) = nlogn
但由于某种原因,算法2的表现更好。有人可以解释原因吗
import math
class MinHeap:
def __init__(self, arr):
self.__items = arr
self.build_heap()
def size(self):
return len(self.__items)
def items(self):
return self.__items
def build_heap(self):
for node_number in xrange(int(len(self.__items) / 2), 0, -1):
self.heapify(node_number)
def heapify(self, node_number):
# return if leave node
if node_number > int(len(self.__items) / 2):
return
node = self.__items[node_number-1]
left_child = self.__items[(2 * node_number)-1] if (((2 * node_number)-1) < len(self.__items)) else None
right_child = self.__items[(2 * node_number + 1)-1] if (((2 * node_number + 1)-1) < len(self.__items)) else None
min_node = node
if left_child != None and right_child != None:
min_node = min(node, left_child, right_child)
elif left_child != None :
min_node = min(node, left_child)
elif right_child != None :
min_node = min(node, right_child)
if min_node == node:
return
elif left_child!=None and min_node == left_child:
self.__items[node_number - 1], self.__items[(2 * node_number)-1] = self.__items[(2 * node_number)-1], self.__items[node_number - 1]
self.heapify(2 * node_number)
elif right_child!=None and min_node == right_child:
self.__items[node_number - 1], self.__items[(2 * node_number + 1)-1] = self.__items[(2 * node_number + 1)-1], self.__items[node_number - 1]
self.heapify(2 * node_number + 1)
def extract_min(self):
length = len(self.__items)
if length == 0:
return
self.__items[0], self.__items[length-1] = self.__items[length-1], self.__items[0]
min_element = self.__items.pop()
self.heapify(1);
return min_element
def insert(self, num):
self.__items.append(num)
current_node = len(self.__items)
parent_node = int(current_node / 2)
while current_node > 1:
min_node = min(self.__items[current_node-1], self.__items[parent_node-1])
if min_node == self.__items[parent_node-1]:
break
self.__items[current_node-1], self.__items[parent_node-1] = self.__items[parent_node-1], self.__items[current_node-1]
current_node = parent_node
parent_node = int(current_node / 2)
# Comparing Algorithms ::::::::::::::::::
import time
import random
from min_heap import *
numbers = random.sample(range(100000), 50000)
k = 3
n = len(numbers)
# k Largest element using Heap T(n) = k + nlogk
start = time.time()
my_heap = MinHeap(numbers[:k+1])
for number in numbers[k+1:]:
my_heap.extract_min()
my_heap.insert(number)
data = sorted(my_heap.items(),reverse=True)
print data[:len(data)-1]
end = time.time()
print "Took {} seconds".format(end-start)
# k Largest element using sorting T(n) = nlogn
start = time.time()
sorted_arr = sorted(numbers, reverse=True)
print sorted_arr[:k]
end = time.time()
print "Took {} seconds".format(end-start)
这是我得到的输出:
算法1
[99999, 99998, 99997]
Took 0.15064406395 seconds
算法2
[99999, 99998, 99997]
Took 0.0120780467987 seconds
答案 0 :(得分:0)
答案分为两部分:
实践方面:
调用用户定义的函数通常很慢,不断增长的列表需要重新分配内存等。“sort()
+ [:k]
”方法中没有一个重要。如果作者不注意内存管理的细节,Python代码可能会很慢。因此,您要测量算法中的实施差异和差异。
理论方面:
我不知道您是如何为{min}方法提出T(n) = k + nlogk
的,但我看到您呼叫my_heap.insert()
和my_heap.extract_min()
(触发heapify()
)几乎{ {1}}次。两者都是n
中的对数,因此它看起来更像n
。
以下是探查器运行的输出:
nlog(n)
答案 1 :(得分:0)
理论界限是:
堆选择方法的一个问题是您要创建整个项目集的堆。你真的只需要创建前k个项目的堆。算法是:
h = create a min heap of first k items // O(k)
for each remaining item
if item is larger than smallest item on heap
remove smallest item from heap
add new item to heap
该算法的最坏情况是O(n log k)。这个想法是,在最坏的情况下,你最终会从大小为k的堆中进行n次插入和n次删除。在一般情况下,插入和删除的数量相当小。它取决于k与n的关系。如果k远小于n,则此方法可以非常快。
在你的代码中,你创建了前k个项目的最小堆,然后你就有了这个循环:
for number in numbers[k+1:]:
my_heap.extract_min()
my_heap.insert(number)
该代码无法可靠地生成正确的结果,因为您无条件地删除了最小元素。因此,如果堆已经包含前k个元素,但仍然有数字,则它将用较小的元素替换较大的元素。您需要修改代码:
for number in numbers[k+1:]:
if (number < my_heap.get_min()) // get_min returns the minimum element, but doesn't remove it
my_heap.extract_min()
my_heap.insert(number)
这不仅可以为您提供正确答案,还可以减少您在平均情况下必须进行的插入和删除次数,这将加快您的计划。
您的堆实现也不是最佳的。您将节省大量时间使heapify
函数迭代而不是递归,并且您有充分的机会优化该函数。
如上所述,快速选择是一种O(n)算法。通常,当k大于n的约2%时,快速选择更快。因此,如果您从1,000个列表中选择100个项目,快速选择几乎肯定会更快。如果您从1,000,000的列表中选择100,那么堆选择方法应该更快。
排序不应该比快速选择更快,并且它不应该比堆选择快,除非你选择一个非常大的百分比(超过60%,我期望)项目
几年前我对此做了一些相当详细的分析。请参阅我的博客文章When theory meets practice。