堆排序的python实现
思路分析:
- 使用列表存储
- 从后向前调用堆调整算法建立最大堆,使用
O(nlogn)
- 交换首尾元素
- 调整前n-1个元素成最大堆,使用
O(logn)
- 交换前n-1个元素的首尾元素
- 调整前n-2个元素成最大堆
- 交换前n-2个元素的首尾元素
- 直到调整前2个元素成最大堆
- 交换前2个元素的首尾元素
def exchange(alist, i, j):
alist[i], alist[j] = alist[j], alist[i]
def build_max_heap(alist):
for i in range(len(alist)-1, -1, -1):
tune_heap(alist, i, len(alist))
def tune_heap(alist, start, max_index_one):
left = 2 * start + 1
right = left + 1
if right<max_index_one:
tmp = right if alist[right]>alist[left] else left
elif left<max_index_one:
tmp = left
else:
return
if alist[start]<alist[tmp]:
exchange(alist, start, tmp)
tune_heap(alist, tmp, sub_length)
def sort(alist):
build_max_heap(alist)
for i in range(1, len(alist)):
exchange(alist, 0, -1*i) # 最后一次交换是交换前2个元素
tune_heap(alist, 0, len(alist)-i))
if __name__=="__main__":
alist = [22, 3, 5, 8, 0]
alist = []
alist = [1]
alist = [2,1]
sort(alist)
print(alist)
实现中的坑:
- exchange的实现中必须使用列表作为参数,不可以使用普通变量,函数不会改变不可变的实参,但是可以改变可变实参,列表是可变实参。
- tune_heap的实现中不可以使用列表的子切片作为实参,必须使用原始的整个列表,因为列表的切片是浅拷贝,改变浅拷贝不影响原始列表。
- 节点
i
的左节点index是2*i+1
不是2*i
,因为列表编号从0开始。
要点:
- 建最大堆,仅仅从后向前与父节点交换是不行的,因为不能保证任意节点的值一定不大于父节点的值,比如
alist=[22, 3, 44, 0, 66]
,第二大的元素44
不会跑到第二层去(它会跑到最后去)。需要从后向前不断调用调整堆算法。 - 调整堆中,在左右子节点存在一个时,使用
tmp
变量保存值最大的那个子节点索引是比较好的方法 。