排序 9 - 堆排序

10 篇文章 0 订阅
10 篇文章 0 订阅

参考:

排序 0 - 前言

百度百科:堆排序

【Python排序搜索基本算法】之堆排序

【坐在马桶上看算法】算法11:堆——神奇的优先队列(上)

【坐在马桶上看算法】算法12:堆——神奇的优先队列(下)

python数据结构之二叉树的实现


堆排序(heap sort)

关键名词

参考:

二叉树

完全二叉树

  • 二叉树:每个节点最多有两个子树的树结构

  • 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的若干位置的二叉树。设树层数为 h,则 1-(h-1) 层的节点数均达到了最大值,而第 h 层的节点均集中在最左边

    • 将完全二叉树的节点值排成列表形式,则第 i 个节点的左右子树为 2i2i+1

堆分为 大根堆小根堆

  • 大根堆:根节点(或称为 堆顶)的值 大于等于 左右子树的完全二叉树

  • 小根堆:根节点(或称为 堆顶)的值 小于等于 左右子树的完全二叉树

工作原理

根据堆的特性,其位于堆顶的值为最大值或者最小值,所以每次取堆顶节点值,删除堆顶节点,重新构建堆,直到堆为空为止,排序完成

算法思想

难点一:如何构建堆

堆既然是完全二叉树,所以可以用序列表示,每次新增加一个节点,将其加入到序列末尾,设为 n,其根节点位置即为 n // 2,比较两者的大小,进行替换,依次类推,调整堆的结构

为方便计算,设序列为 [-1, x1, x2, x3, ...],其第一个元素 -1 无效,从下标 1 开始计算

难点二:如何删除堆顶后,重新构建堆

删除堆顶后,取序列末尾元素,放在堆顶位置,重新计算堆的结构

对于大根堆来说,每次向下调整时,如果需要交换根和左右子树的值,和左右子树中较大值进行交换

对于小根堆来说,与之相反

思路

获得一个无序序列后,先将其构建成堆结构,然后每次取堆顶元素,重新构建堆,直到堆为空为止


Python 算法实现

实现一:使用一个辅助序列

# -*- coding: utf-8 -*-

"""
堆排序实现
"""

import random
import math

__author__ = 'zj'


def create_data(leng, min, max):
    """
    创建待排序序列
    :param leng: 序列长度
    :param min: 最小值
    :param max: 最大值
    :return: 列表
    """
    li = range(min, max)
    return random.sample(li, leng)


def heap_sort_add(heap, v):
    """
    在堆上加入一个元素
    :param heap: 大根堆
    :param v: 插入数据
    :return:
    """
    heap.append(v)
    idx = len(heap) - 1
    r = idx // 2
    while r >= 1:
        if heap[r] < heap[idx]:
            heap[r], heap[idx] = heap[idx], heap[r]
            idx = r
            r = idx // 2
        else:
            break
    return heap


def heap_sort_delete(heap):
    """
    删除堆顶元素
    :param heap: 大根堆
    :return:
    """
    if len(heap) == 1:
        return heap
    elif len(heap) == 2:
        del heap[0]
        return heap
    heap[1] = heap[-1]
    del heap[-1]
    n = len(heap) - 1
    idx = 1
    n = len(heap)
    while idx < n:
        lchild = 2 * idx
        rchild = 2 * idx + 1
        if lchild >= n:
            break
        elif lchild < n and rchild >= n:
            if heap[idx] < heap[lchild]:
                heap[idx], heap[lchild] = heap[lchild], heap[idx]
                idx = lchild
            else:
                break
        elif lchild < n and rchild < n:
            if heap[lchild] > heap[rchild]:
                if heap[idx] < heap[lchild]:
                    heap[idx], heap[lchild] = heap[lchild], heap[idx]
                    idx = lchild
                else:
                    break
            else:
                if heap[idx] < heap[rchild]:
                    heap[idx], heap[rchild] = heap[rchild], heap[idx]
                    idx = rchild
                else:
                    break
    return heap


def heap_sort(li, reverse=False):
    """
    堆排序实现
    :param li: 待排序列表
    :param reverse: 是否从大到小排序,默认为False
    :return: 已排序列表
    """
    src = [0]
    for item in li:
        heap_sort_add(src, item)
    n = len(li)
    for i in xrange(n):
        if reverse:
            li[n - i - 1] = src[1]
        else:
            li[i] = src[1]
        heap_sort_delete(src)
    return li


if __name__ == '__main__':
    da = create_data(10, 30, 60)
    print da
    heap_sort(da)
    print da

实现二:使用自身序列,实现从小到大排序

# -*- coding: utf-8 -*-

"""
堆排序实现
"""

import random
import math

__author__ = 'zj'


def create_data(leng, min, max):
    """
    创建待排序序列
    :param leng: 序列长度
    :param min: 最小值
    :param max: 最大值
    :return: 列表
    """
    li = range(min, max)
    return random.sample(li, leng)


def heap_sort_add_v2(heap, n):
    """
    堆中加入新数据,下标为 n
    :param heap: 堆和待排序序列
    :param n: 前 n-1 个元素为堆,后面元素为待排序序列
    :return:
    """
    idx = n
    r = idx // 2
    while r >= 1:
        if heap[r] < heap[idx]:
            heap[r], heap[idx] = heap[idx], heap[r]
            idx = r
            r = idx // 2
        else:
            break
    return heap


def heap_sort_delete_v2(heap, n):
    """
    删除堆顶元素
    :param heap: 大根堆
    :return:
    """
    heap[1], heap[n] = heap[n], heap[1]
    idx = 1
    while idx < n:
        lchild = 2 * idx
        rchild = 2 * idx + 1
        if lchild >= n:
            break
        elif lchild < n and rchild >= n:
            if heap[idx] < heap[lchild]:
                heap[idx], heap[lchild] = heap[lchild], heap[idx]
                idx = lchild
            else:
                break
        elif lchild < n and rchild < n:
            if heap[lchild] > heap[rchild]:
                if heap[idx] < heap[lchild]:
                    heap[idx], heap[lchild] = heap[lchild], heap[idx]
                    idx = lchild
                else:
                    break
            else:
                if heap[idx] < heap[rchild]:
                    heap[idx], heap[rchild] = heap[rchild], heap[idx]
                    idx = rchild
                else:
                    break
    return heap


def heap_sort_v2(li):
    """
    从小到大排序
    :param li:
    :return:
    """
    li.insert(0, -1)
    for i in xrange(2, len(li)):
        heap_sort_add_v2(li, i)
    for i in range(2, len(li))[::-1]:
        heap_sort_delete_v2(li, i)
    del li[0]
    return li


if __name__ == '__main__':
    da = create_data(10, 30, 60)
    print da
    heap_sort_v2(da)
    print da

性能分析

稳定性

堆排序是不稳定排序算法

在删除堆顶,重新构建堆的过程中,需要将末尾节点元素放置在堆顶,重新构建,这样会破坏相对位置关系

时间复杂度

其时间复杂度为 O(N*logN)

空间复杂度

空间复杂度为 O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值