堆排序

此堆非彼堆,先简单说一下概念和思路。

- 是一个完全二叉树
- 每个非叶子结点都大于或等于其左右子结点的值称为大顶堆
- 每个非叶子结点都小于或等于其左右子结点的值称为小顶堆
- 根结点一定是大顶堆中的最大值,一定是小顶堆中的最小值

构建完全二叉树

- 待排序数字为 30,20,80,40,50,10,60,70,90
- 构造一个列表为 [0,30,20,80,40,50,10,60,70,90]

构建大顶堆

- 度数为2的结点A,如果它的左右子结点的最大值比它大,将这个最大值和该结点交换
- 度数为1的结点A,如果它的子节点的值大于它,则交换
- 如果结点A被交换到新的位置,还需要和其子结点重复上面的过程

起点的选择

- 从完全二叉树的最后一个结点的父结点开始
- 结点数为n,则起始结点的编号为 n//2

下一个节点选择

- 从起始结点开始向左找其同层结点,到头后再从上一层的最右边结点开始向左逐个查找,直至根结点

排序

- 将大顶堆根结点这个最大值和最后一个叶子结点交换,那么最后一个叶子结点就是最大值,将这个叶子结点排除在待排序结点之外
- 从根结点开始(新的根结点),重新调整为大顶堆后,重复上一步

1、堆排序是利用堆性质的一种选择排序,在堆顶选出最大值或者最小值
2、时间复杂度为O(nlogn)
3、由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏时间复杂度均为O(nlogn)
4、不稳定的排序算法(在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的,否则称为不稳定的)

import math

# 构建完全二叉树
origin = [0, 30, 20, 80, 40, 50, 10, 60, 70, 90]

total = len(origin) - 1

# 打印树
def print_tree(origin, unit_width=2):
    length = len(origin)
    index = 1
    # 由于前面补0了,原本应该是math.ceil(math.log2(len(origin)+1)),这里减一
    depth = math.ceil(math.log2(length))
    sep = ' ' * unit_width
    for i in range(depth-1, -1, -1):    # range(3,-1,-1) [3,2,1,0]
        # 前半部分空格数 7,3,1,0
        pre = 2**i - 1
        print(sep * pre, end='')
        # 偏移量,1,2,4,8
        offset = 2 ** (depth - i - 1)
        # 切片获取数据,origin[1,2],origin[2,4],origin[4,8],origin[8,16]
        line = origin[index: index+offset]
        # 元素间距,0,7,3,1
        intervalspace = sep * (2*pre + 1)
        print(intervalspace.join(map(str, line)))
        index += offset

# 最大堆调整,单个节点调整
def heap_adjust(total, i, origin):
    """
    total: 节点总数
    i: 当前节点的索引
    """
    #               30
    #       20              80
    #   40      50      10      60
    # 70  90
    while 2*i <= total:
        # 2i为左子节点,2i+1为右子节点
        lchild_index = 2*i
        max_child_index = lchild_index

        # total>2i说明还有右子节点
        if total > lchild_index and origin[lchild_index + 1] > origin[lchild_index]:
            max_child_index = lchild_index + 1

        # 和子树的根节点比较
        if origin[max_child_index] > origin[i]:
            origin[i], origin[max_child_index] = origin[max_child_index], origin[i]
            i = max_child_index
        else:
            break

# 构建大顶堆
def max_heap(total, origin):
    # 从i=total//2开始,到堆顶节点,依次调整
    for i in range(total//2, 0, -1):    # range(4,0,-1) [4,3,2,1]
        heap_adjust(total, i, origin)
    return origin

max_heap(total, origin)
#               90
#       70              80
#   40      50      10      60
# 20  30

# 排序
def sort(total, origin):
    while total > 1:
        # 堆顶和最后一个节点交换
        origin[1], origin[total] = origin[total], origin[1]
        # 每交换一次就固定一个数
        total -= 1
        if total == 2 and origin[total] >= origin[total - 1]:
            break
        # 这时只有堆顶节点不平衡,调整堆顶节点即可
        heap_adjust(total, 1, origin)
    return origin

print_tree(sort(total, origin))
#               10
#       20              30
#   40      50      60      70
# 80  90

参考:
https://zh.wikipedia.org/wiki/堆排序

posted @ 2018-09-19 21:36  KeithTt  阅读(147)  评论(0编辑  收藏  举报