数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解

阅读目录

      • 最大堆(大顶堆)和 最小堆(小顶堆)
      • 堆排序
      • 堆排序实质和思路过程
        • 堆--完全二叉树
        • 思路过程
          • 构造大顶堆
          • 开始实现
      • 堆排序Python实现

最大堆(大顶堆)和 最小堆(小顶堆)

  • 堆结构:首先它必须满足完全二叉树的定义
    在这里插入图片描述
  • 最大堆
    数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第1张图片
  • 最小堆
    数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第2张图片

堆排序

  • 基本思想 :将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点;将其与末尾元素进行交换,此时末尾就为最大值,可称为有序区。
    然后将剩余 n-1个元素重新构造成一个堆,估且称为堆区(未排序),这样会得到 n个元素的次小值(将开始的第一个堆顶也算进去了,所以还是n个数)。
    重复执行,有序区从:1—>n,堆区:n–>0,便能得到一个有序序列了

  • 复杂度:对O(n)级别个非叶子节点进行堆调整操作O(logn),时间复杂度O(nlogn);之后每一次堆调整操作确定一个数的次序,时间复杂度O(nlogn);合起来时间复杂度O(nlogn)。
    额外空间开销出在调整堆过程,根节点下移交换时一个暂存空间,空间复杂度O(1);堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),是不稳定排序!

  • 一般升序 用 大顶堆,降序 用 小顶堆

堆排序实质和思路过程

堆–完全二叉树

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第3张图片

思路过程

在这里插入图片描述

构造大顶堆
  • 在构造有序堆时,开始时只需要扫描一半的元素(所有父节点)(length/2-1 --> 0)
    因为只有他们才有子节点:3–>2 -->1 -->0

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第4张图片

  • 从最后一个父节点开始,将父节点、他所有的子节点中的最大值交换到父节点。父节点:3

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第5张图片

  • 将倒数第二个父节点同理交换,父节点:2

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第6张图片

  • 父节点:1

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第7张图片

  • 根节点:0

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第8张图片

  • 注意很重要:务必注意-承接第3步。
    假设根节点值为:10, 当他和两个子节点70, 80,

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第9张图片

  • 父节点和两子节点中的大的(80)交换后位于父节点2:原来80的位置

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第10张图片

  • 可是他还有子节点,且子节点中的值比根节点大,那就还需要以他为父节点构造一次,
    与子节点6 值为20交换一次

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第11张图片

  • 同理在其他所有父节点的构造中都需要判断调整
    忽略第五步。构造好的的大顶堆如下:

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第12张图片

原文链接:https://www.cnblogs.com/shiqi17/p/9694938.html

开始实现

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第13张图片

  • 每次将堆顶(根节点)最的的元素和堆尾列表最后一个元素交换,80 和40交换
    即上面说的堆区(未排序):n–>0最大元素(根节点),和有序区从:1—>n,最后一个元素交换

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第14张图片

  • 按照上面原理继续排序,70, 30 交换。然后调整堆

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第15张图片

  • 堆顶元素60尾元素20交换后–>调整堆

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第16张图片

  • 最后结果

数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第17张图片

堆排序Python实现

详细版
数据结构与算法--排序算法:堆排序 最大堆(大顶堆)和 最小堆(小顶堆)详解_第18张图片

class HeapSort(object):
    def heap_sort(self, array):
        print("堆排序!!!")
        # 将一个数组(二叉树)调整成一个大顶堆
        for i in range((len(array) // 2 - 1), -1, -1):  # 参数:最后一个非叶子结点的求法;索引能取到0;递减
            self.adjust_heap(array, i, len(array))
        for j in range(len(array) - 1, 0, -1):  # 参数:需要替换的次数,不能取到0,递减
            array[j], array[0] = array[0], array[j]  # 交换,大顶堆,第一个值最大和最后一个数交换
            self.adjust_heap(array, 0, j)

    def adjust_heap(self, array, i, length):
        '''
        完成:将以 i 对应的非叶子结点部分的子树调整成大顶堆
        :param array: 待调整的数组
        :param i: 表示非叶子结点在数组中的索引
        :param length: 表示对多少个元素继续调整,length是在逐渐减少
        :return:
        '''
        temp = array[i]  # 取出当前元素的值,保存在临时变量
        k = i * 2 + 1  # 第一次拿到的 最后一个非叶结点
        while k < length:
            if k + 1 < length and array[k] < array[k + 1]:  # 说明左子结点的值小于右子结点的值
                k += 1  # k指向右子结点
            if temp < array[k]:  # 走到这一步,表明左子结点更大,那么和非叶子结点比较
                array[i] = array[k]  # 把较大的值赋给当前结点
                i = k  # 让i指向k,继续循环比较
            else:  # 说明当前结点(最后一个非叶结点)的值比左右子结点要大,则不需要调整
                break
            k = k * 2 + 1
        # for 循环结束后,已经将以i 为父结点的树最大值,放在了最顶(局部完成)
        array[i] = temp  # 将temp的值放到调整后的位置


if __name__ == '__main__':
    li = [4, 6, 8, 5, 9]
    print(li)
    h = HeapSort()
    h.heap_sort(li)
    print(li)
    # h.adjust_heap(li, 1, len(li))
    # print(li)
    # h.adjust_heap(li, 0, len(li))
    # print(li)
'''
[4,5,6,8,9]
'''

法二

  • 现在排序这么一个序列:list_ = [4, 7, 0, 9, 1, 5, 3, 3, 2, 6]
# 堆排序  heap_sort

                     4
                   /   \
                 7      0
               /  \    / \
             9    1   5   3
           / \   /
         3   2  6

list_ = [4, 7, 0, 9, 1, 5, 3, 3, 2, 6]

Python代码实现

def swap(data, root, last):
    data[root], data[last] = data[last], data[root]


# 调整父节点 与孩子大小,制作大顶堆
def adjust_heap(data, par_node, high):
    new_par_node = par_node
    j = 2 * par_node + 1  # 取根节点的左孩子,如果只有一个孩子high就是左孩子,
    						#如果有两个孩子high 就是右孩子

    while j <= high:  # 如果 j = high 说明没有右孩子,high就是左孩子
        if j < high and data[j] < data[j + 1]:  # 如果这儿不判断 j < high 可能超出索引
            # 一个根节点下,如果有两个孩子,将 j  指向值大的那个孩子
            j += 1
        if data[j] > data[new_par_node]:  # 如果子节点值大于父节点,就互相交换
            data[new_par_node], data[j] = data[j], data[new_par_node]
            new_par_node = j  # 将当前节点,作为父节点,查找他的子树
            j = j * 2 + 1

        else:
            # 因为调整是从上到下,所以下面的所有子树肯定是排序好了的,
            # 如果调整的父节点依然比下面最大的子节点大,就直接打断循环,堆已经调整好了的
            break


# 索引计算: 0 -->1 --->....
#    父节点 i   左子节点:偶数:2i +1  右子节点:基数:2i +2  
# 注意:当用长度表示最后一个叶子节点时 记得 -1

# 从第一个非叶子节点(即最后一个父节点)开始,即 list_.length//2 -1(len(list_)//2 - 1)
# 开始循环到 root 索引为:0 的第一个根节点, 将所有的根-叶子 调整好,成为一个 大顶堆
def heap_sort(lst):
    """
    根据列表长度,找到最后一个非叶子节点,开始循化到 root 根节点,制作 大顶堆
    :param lst: 将列表传入
    :return:
    """
    length = len(lst)
    last = length - 1  # 最后一个元素的 索引
    last_par_node = length // 2 - 1

    while last_par_node >= 0:
        adjust_heap(lst, last_par_node, length - 1)
        last_par_node -= 1  # 每调整好一个节点,从后往前移动一个节点

    # return lst
    while last > 0:
        #
        # swap(lst, 0, last)
        lst[0], lst[last] = lst[last], lst[0]
        # 调整堆少让 adjust 处理最后已经排好序的数,就不处理了
        adjust_heap(lst, 0, last - 1)
        last -= 1

    return lst  # 将列表返回


list_ = [4, 7, 0, 9, 1, 5, 3, 3, 2, 6]

heap_sort(list_)
print(list_)

你可能感兴趣的:(数据结构与算法,Python,数据结构与算法,堆排序,Python堆排序,完全二叉树)