一种终极高效排序算法——堆排序python实现(超详解)0基础强化版

本文详细介绍了堆排序的概念,包括其时间复杂度为O(nlogn)的原因,空间复杂度为O(1)的特点,以及堆排序的实现步骤,特别强调了maxHeapify和Heapsort函数的作用。同时,文章也提到了二叉树基础知识在堆排序中的应用,如节点关系和堆的构建过程。
摘要由CSDN通过智能技术生成

目录

在学习堆排序之前之前需要具备什么知识?

一、堆排序概念:

二、堆排序性能:

2.1时间复杂度:

2.2空间复杂度:

2.3先说思想后上代码:

堆排序的步骤主要包括以下四个方面:

2.4代码实现如下:

看不懂也没关系,我会详细解释这段代码。

2.5 maxHeapify 函数

2.6 Heapsort 函数

**2.7 这是专注于maxHeapify函数的讲解。如果你已经看懂了。可跳过。

**2.8 这是Heapsort函数的讲解,如果你看懂了可以跳过。

三、二叉树的知识:

1.以下是二叉树中的一些重要组成概念:

2.在二叉树中,节点之间的关系主要是父子关系和兄弟关系。具体来说:

3.对于任意节点,假设其父节点的下标(或索引)为 parent,那么:

4.在二叉树中,叶子节点和非叶子节点是构成树形结构的基本元素,它们之间存在明确的关系,并通过特定的方式计算。


在学习堆排序之前之前需要具备什么知识?

        递归,二叉树

如果你不了解,也没关系。

我会在代码里面与你讲解二叉树思想和递归思想,如何运用到堆排序中

一、堆排序概念:

        堆排序的基本思想是将待排序的序列构建成一个堆,然后依次将堆顶元素与堆尾元素交换,并将堆的大小减小1,然后再对剩余的堆进行调整,使其满足堆的性质。重复这个过程,直到堆的大小为1,此时排序完成。

二、堆排序性能:

2.1时间复杂度:

时间复杂度:O(nlogn)

堆排序的时间复杂度主要来自于两个步骤:建堆排序

  • 建堆:对于包含n个元素的序列,建堆的时间复杂度为O(n)。因为每个元素最多只会向下调整一层,所以总的时间复杂度是线性的。
  • 排序:排序过程需要重复n-1次,每次将堆顶元素与堆尾元素交换,并重新调整堆。每次调整堆的时间复杂度是O(logn),因为堆是一个完全二叉树,其高度为logn,每次调整需要遍历从堆顶到叶子节点的路径。因此,排序的总时间复杂度为O(nlogn)

        由于排序步骤是主要的耗时操作,所以堆排序的整体时间复杂度为O(nlogn)。无论是在最好情况、平均情况还是最坏情况下,堆排序的时间复杂度都是O(nlogn)

2.2空间复杂度:

空间复杂度:O(1)

        堆排序是原地排序算法,即它只需要一个额外的存储空间来交换元素,而不需要额外的数据结构来存储元素。因此,堆排序的空间复杂度是常数级别的,即O(1)

2.3先说思想后上代码:

如果你已经了解,递归和二叉树的概念,请往下读。不然可跳转二叉树知识。

堆排序的步骤主要包括以下四个方面:
  1. 构建初始堆
    • 将待排序的序列构造成一个大顶堆(或小顶堆)。此时,整个序列的最大值(或最小值)就是堆顶的根节点。
    • 构建初始堆的过程通常从最后一个非叶子节点开始,从后往前调整每个非叶子节点,使其成为以其为根的子树的大顶堆(或小顶堆)。
  2. 堆顶元素与末尾元素交换
    • 将堆顶元素(最大值或最小值)与末尾元素进行交换,此时末尾元素就为最大值(或最小值)。
    • 通过交换,我们将最大值(或最小值)放到了序列的末尾,即其最终位置。
  3. 调整堆
    • 由于交换后末尾元素已确定,因此末尾元素之后的序列不再参与后续的排序过程。
    • 将剩余的元素重新调整为大顶堆(或小顶堆)。这个过程与构建初始堆类似,但是调整的范围缩小了。
  4. 重复交换与调整
    • 重复步骤2和步骤3,每次将当前堆顶元素与剩余序列的末尾元素交换,并调整剩余元素为堆。
    • 这个过程持续进行,直到整个序列有序。
2.4代码实现如下:
def maxHeapify(heap,start,end): #大顶堆的下沉操作
    #其中heap代表顺序表,start为根节点,end为顺序表的末尾
    son =start*2 #左节点
    while son<=end: #如果左子树存在
    #取左子树根和右子树根 两者中大者的下标
        if son +1 <=end and heap[son+1] >heap[son]:
            son+=1
        #如果子节点的值大于根节点,则将根节点和子节点交换。即下沉操作
        if heap[son]>heap[start]:
            heap[start],heap[son]=heap[son],heap[start]
            #对子节点迭代执行相同的操作
            start,son=son,son*2
        else:#如果子节点的值小于等于根节点,说明堆已经构造好了,退出循环
            break

def Heapsort(arr):#进行堆排序操作
    heap=[None]+arr #这里是因为列表从0开始计数,而我们找的子节点父节点的关系是2倍或2倍+1
    root=1 #堆顶下标
    count=len(heap) #获取堆元素个数
    for i in range(count//2,root-1,-1):#逆序枚举列表的元素
        #自底向上地构造堆
        maxHeapify(heap,i,count-1)
    for i in range(count-1,root,-1): #将堆顶和最后一个元素交换
        heap[i],heap[root]=heap[root],heap[i]#保持除最后一个元素以外整个堆的合法性
        #保持除最后第二个元素以外整个堆的合法性
        maxHeapify(heap,root,i-1)
    return heap[1:]
arr=[2,5,1,4,9,7,8,3]
print(Heapsort(arr))
看不懂也没关系,我会详细解释这段代码。

注意:Python列表最后一个下标为len(列表)-1,所以我增加了[None],方便理解父节点和子节点的关系。

2.5 maxHeapify 函数

maxHeapify 函数是一个私有辅助函数,用于维护大顶堆的性质。当堆的某个节点违反大顶堆性质时,这个函数通过下沉操作来修复它。

参数:

  • heap: 代表整个堆的顺序表。
  • start: 需要调整的子树的根节点。
  • end: 顺序表的末尾。

逻辑流程:

  1. 计算左子节点的下标。
  2. 检查左子节点是否存在,如果存在,再检查右子节点是否存在且值是否大于左子节点。
  3. 找出左子节点和右子节点中值较大的那个。
  4. 如果选中的子节点值大于根节点,交换它们,并继续调整交换后的子树。
  5. 如果选中的子节点值不大于根节点,说明以 start 为根的子树已经是大顶堆,退出循环。

2.6 Heapsort 函数

Heapsort 函数是堆排序的主要函数,它实现了堆排序的全过程。

参数:

  • arr: 待排序的数组。

逻辑流程:

  1. 构造大顶堆:在 Heapsort 函数中,首先创建一个新的列表 heap,其中包含 None 和原数组 arr 的所有元素。这样做是为了简化索引计算,因为堆的根节点位于索引 1,而不是 0。然后,从最后一个非叶子节点开始,通过调用 maxHeapify 函数,自底向上地构造大顶堆。
  2. 排序:通过不断将堆顶元素(最大值)与当前未排序部分的最后一个元素交换,并重新调整堆,实现排序。这个过程持续进行,直到整个数组都排好序。
  3. 返回结果:最后,返回除去 None 的排序后的数组。
**2.7 这是专注于maxHeapify函数的讲解。如果你已经看懂了。可跳过。
# 定义maxHeapify函数,用于维护大顶堆的性质  
# 这个函数用于调整以start为根的子树,确保它满足大顶堆的性质  
# 参数heap是待调整的堆,start是要调整的子树的根节点下标,end是堆的末尾下标  
def maxHeapify(heap, start, end):  
  
    # 计算左子节点的下标  
    # 对于数组中的元素,如果父节点的下标是start,则左子节点的下标是start*2  
    son = start * 2  
  
    # 当左子节点下标不超过末尾时,即左子节点存在  
    # 循环条件确保我们只处理存在的子节点  
    while son <= end:  
  
        # 检查右子节点是否存在且值是否大于左子节点  
        # 如果存在右子节点,并且它的值大于左子节点,则我们可能需要交换start和右子节点  
        if son + 1 <= end and heap[son + 1] > heap[son]:  
  
            # 如果右子节点值更大,则更新son为右子节点下标  
            # 这样,我们接下来会与右子节点进行比较和可能的交换  
            son += 1  
  
        # 如果子节点(son指向的节点)的值大于根节点(start指向的节点)  
        # 这表示当前的大顶堆性质被破坏了,因为子节点的值不应该大于父节点  
        if heap[son] > heap[start]:  
  
            # 交换根节点和子节点的值  
            # 通过交换,我们尝试恢复大顶堆的性质  
            heap[start], heap[son] = heap[son], heap[start]  
  
            # 更新start和son,继续向下调整子树  
            # 由于我们交换了start和son的值,所以新的start就是原来的son  
            # 同时,我们计算新的son(新start的左子节点)  
            start, son = son, son * 2  
  
        else:  
  
            # 如果子节点的值不大于根节点,说明以start为根的子树已是大顶堆  
            # 也就是说,当前子树已经满足大顶堆的性质,不需要进一步调整  
            # 退出循环  
            break
**2.8 这是Heapsort函数的讲解,如果你看懂了可以跳过。

# 定义Heapsort函数,用于执行堆排序  
def Heapsort(arr):  
    # 创建新的列表heap,其中包含一个None和arr的所有元素  
    # 这样做的目的是为了简化索引计算,因为堆的根节点通常从下标1开始,而Python的列表下标从0开始  
    heap = [None] + arr  
      
    # 堆顶元素的下标  
    # 在这个实现中,堆顶元素的下标是1,而不是通常的0  
    root = 1  
      
    # 获取堆元素的总数  
    count = len(heap)  
      
    # 从最后一个非叶子节点开始,自底向上构造大顶堆  
    # 最后一个非叶子节点的下标是(count // 2) - 1  
    # 这是因为叶子节点开始的下标是(count // 2)  
    for i in range(count // 2, root - 1, -1):  
        # 调用maxHeapify函数,维护以当前非叶子节点为根的子树的大顶堆性质  
        maxHeapify(heap, i, count - 1)  
      
    # 堆排序的主要循环:从最后一个元素开始,与堆顶元素交换,并重新调整堆  
    # 这样做可以确保最大的元素被放到正确的位置(数组的末尾)  
    for i in range(count - 1, root, -1):  
        # 交换堆顶元素和当前最后一个元素  
        heap[i], heap[root] = heap[root], heap[i]  
          
        # 由于交换了元素,堆的性质可能已经被破坏  
        # 调用maxHeapify函数,保持除最后一个元素以外整个堆的合法性  
        # 注意,此时堆的末尾元素已经是排好序的,所以maxHeapify的end参数是i - 1  
        maxHeapify(heap, root, i - 1)  
      
    # 返回排序后的数组(去掉列表首部的None)  
    # 因为在创建heap时我们在列表开头添加了一个None,所以现在要移除它  
    return heap[1:]

至此,就结束了。

来喝一口鸡汤吧^-^:

        文学远远比政治要美好。政治教人打架,文学教人恋爱。         ——莫言

三、二叉树的知识:

1.以下是二叉树中的一些重要组成概念:
  1. 节点:二叉树中的每个元素被称为一个节点。节点不仅包含数据元素,还包含指向其子树的链接。
  2. 左子树和右子树:对于二叉树中的每个节点,它有两个可能的子节点,一个位于左侧(称为左子树),另一个位于右侧(称为右子树)。这两个子树也是二叉树。
  3. 根节点:二叉树的最顶部节点被称为根节点。它是整个二叉树的起点。
  4. 叶子节点:没有子节点的节点被称为叶子节点。它们是二叉树中的终止节点。
  5. 内部节点:非叶子节点被称为内部节点。它们至少有一个子节点。
  6. 层次:从根节点开始,根节点位于第一层,根的子节点位于第二层,以此类推。节点的层次表示其在二叉树中的深度或位置。
  7. 深度或高度:二叉树中节点的最大层次被称为二叉树的深度或高度。它表示从根节点到最远叶子节点的最长路径上的节点数。
2.在二叉树中,节点之间的关系主要是父子关系和兄弟关系。具体来说:
  1. 父子关系:在二叉树中,一个节点可以有一个父节点和两个子节点(左子节点和右子节点)。如果一个节点B是另一个节点A的子节点,那么节点A就是节点B的父节点。这种关系形成了二叉树的层次结构。
  2. 兄弟关系:在二叉树中,具有相同父节点的两个节点被称为兄弟节点。例如,如果一个节点的左子节点和右子节点都存在,那么这两个子节点就是兄弟节点。

前提是数组中arr[.....]的第一个下标为0,如果是[None]+arr[.....],则下标从一开始,给的代码是从1开始的。

3.对于任意节点,假设其父节点的下标(或索引)为 parent,那么:
  • 左子节点的下标 leftchild 可以通过公式 leftchild = parent * 2 + 1 计算得出。
  • 右子节点的下标 rightchild 可以通过公式 rightchild = parent * 2 + 2 计算得出。

反过来,如果已知一个子节点的下标,想要找到其父节点的下标,可以使用公式:

  • parent = (child - 1) / 2
4.在二叉树中,叶子节点和非叶子节点是构成树形结构的基本元素,它们之间存在明确的关系,并通过特定的方式计算。
  1. 叶子节点:是那些没有子节点的节点,也就是说它们没有左孩子和右孩子,指向NULL。在二叉树中,叶子节点通常位于树的最底层。

  2. 非叶子节点:是那些至少有一个子节点的节点,也就是说它们有左孩子或右孩子,或者两者都有。非叶子节点通常位于树的中间或上层,它们连接着其他的节点,形成了树的结构。

  • 57
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值