不为成仙,只为在这红尘中等你回来。

您现在的位置是:网站首页>>算法与数据结构

堆排序

2020年11月5日 23:19 | 分类:算法与数据结构 | 标签: Python Tree

堆排序前传 - 树与二叉树

  • 树是一种数据结构(目录结构)
  • 树是一种可以递归定义的数据结构
  • 树是由 n 个节点组成的集合
    • 如果 n = 0,那这是一颗空树
    • 如果 n > 0,那存在1个节点作为树的根节点,其它节点可以分为 m 个集合,每个集合本身又是一棵树

  • 堆是一种特殊的完全二叉树
    • 大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
    • 小根堆,一颗完全二叉树,满足任一节点都比其孩子节点小

堆排序过程

  • 1.建立堆
  • 2.得到堆顶元素,为最大元素
  • 3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次向下调整重新使堆有序
  • 4.堆顶元素为第二大元素
  • 5.重复步骤3,直到堆变空
# 时间复杂度:O(nlogn)

def sift(lst, low, high):
    """
    堆的一次向下调整
    :param lst:列表
    :param low:堆的根节点位置
    :param high:堆的最后一个元素的位置
    :return:
    """
    i = low  # i 最初指向根节点
    j = 2 * i + 1  # j 最初指向 i 的左孩子
    tmp = lst[low]  # 把堆顶存起来
    while j <= high:  # 只要 j 位置有数
        if j + 1 <= high and lst[j + 1] > lst[j]:  # 如果右孩子存在并且比左孩子大
            j = j + 1  # j 指向右孩子
        if lst[j] > tmp:
            lst[i] = lst[j]
            i = j  # 往下看一层
            j = 2 * i + 1
        else:  # tmp 更大,把 tmp 放到 i 的位置上
            lst[i] = tmp
            break
    else:
        lst[i] = tmp  # 把 tmp 放到叶子结点上


def heap_sort(lst):
    n = len(lst)
    # 建堆
    for i in range((n - 1 - 1) // 2, -1, -1): # 从最后一个元素的父节点开始循环到0
        # i 表示建堆的时候调整的部分的根的下标
        sift(lst, i, n - 1)
    # 挨个出数
    for i in range(n - 1, -1, -1): # 循环从最后一个元素到0
        lst[0], lst[i] = lst[i], lst[0]
        sift(lst, 0, i - 1)  # i-1 是新的 high

堆排序应用 - topk 问题

  • 现有 n 个数,设计算法得到前 k 大的数。(k < n)
  • 解决思路:
    • 排序后切片
      • O(nlogn)
    • 排序 LowB 三人组
      • O(kn)
    • 堆排序思路
      • O(nlogk)
      • 1.取列表前 k 个元素建立一个小根堆。堆顶就是目前第 k 大的数。
      • 2.依次向后遍历愿列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整。
      • 3.遍历列表所有元素后,倒序弹出堆顶。
def siftUp(lst, low, high):
    """
    小根堆的向下调整
    :param lst:
    :param low:
    :param high:
    :return:
    """
    i = low  # i 最初指向根节点
    j = 2 * i + 1  # j 最初指向 i 的左孩子
    tmp = lst[low]  # 把堆顶存起来
    while j <= high:  # 只要 j 位置有数
        if j + 1 <= high and lst[j + 1] < lst[j]:  # 如果右孩子存在并且比左孩子大
            j = j + 1  # j 指向右孩子
        if lst[j] < tmp:
            lst[i] = lst[j]
            i = j  # 往下看一层
            j = 2 * i + 1
        else:  # tmp 更大,把 tmp 放到 i 的位置上
            lst[i] = tmp
            break
    else:
        lst[i] = tmp  # 把 tmp 放到叶子结点上


def topk(lst, k):
    # 建堆
    heap = lst[0:k]
    for i in range((k - 1 - 1) // 2, -1, -1):
        siftUp(heap, i, k - 1)
    # 遍历
    for i in range(k, len(lst) - 1):
        if lst[i] > heap[0]:
            heap[0] = lst[i]
            siftUp(heap, 0, k - 1)
    # 出数
    for i in range(k - 1, -1, -1):
        heap[0], heap[i] = heap[i], heap[0]
        siftUp(heap, 0, i - 1)
    return heap

import random
lst = list(range(1000))
random.shuffle(lst)
print(topk(lst, 10))