堆排序详解(含对时间复杂度的分析)

简介: 堆排序详解(含对时间复杂度的分析)

@TOC


一、堆

1.概念

堆的物理结构(我们能看到的)是一个数组

堆的逻辑结构(我们想象出来的)是一个完全二叉树

  在这里插入图片描述  

2.特性

1.结构性:用数组表示完全二叉树

2.有序性: 任一结点的关键字是其子树所有结点的最大值(最小值)

而拥有最大值在顶叫做 大堆

   拥有最小值在顶叫做 小堆

3. 父子结点

因为都是由数组表示的完全二叉树

而数组对应下标

左孩子下标 =父亲节点下标2+1
右孩子下标 =父亲节点下标
2+2

  在这里插入图片描述

二、向下调整算法

1.概念

向下调整算法

以小堆为例,

当满足左子树与右子树都是小堆时

从根节点开始

取左右孩子小的那个,与父亲比较,如果比父亲小就交换

然后往下调,以此时的child赋值给parent,

直到调到叶节点就结束

在这里插入图片描述  

2. 实现

void justdown(int* a, int n, int root)//向下调整算法 ——建堆  小堆
{
    int parent = root;
    int child = parent * 2 + 1;//假设左右孩子小的是左孩子
    while (child<n)//child作为下标小于元素个数就成立,否则数据不存在
    {
        if (a[child + 1] < a[child]&&child+1<n)//作为完全二叉树存在,有可能只存在左子树,而不存在右子树
        {
            child++;//如果左孩子大于右孩子,设小的为右孩子
        }
        if (a[child] < a[parent])//孩子小于父亲,两者就交换
        {
            swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else//孩子比父亲大 就结束循环  因为左右子树都是小堆 
        {
            break;
        }
    }
}

三 、堆排序的实现

1.建堆

以大堆为例

若发现左子树与右子树不是大堆,则不能直接使用向下调整算法

可以倒着从最后一颗子树开始调 ,使其变成左右子树都是大堆

但是由于叶节点调并由实际作用,所以从倒数第一个非叶子树开始调 ,而正好都是用数组下标表示的,每次减一,都会达到前一个数的位置。

元素个数为n,最后一个数的下标为n-1,0作为8的左子树,8就为(n-1-1)/2

在这里插入图片描述

2. 排序

以升序为例

正常来说,我们排升序都应该想到是用小堆,

但是会存在一个问题

在这里插入图片描述  

建小堆,我们应该把最小数放在堆顶,这个数已经被选出来了,然后在剩下的数中在去选数,此时的树的结构已经乱了,必须重新建堆才能选出下一个数,建堆的时间复杂度是O(N)

这时的时间复杂度为O(N-1)  N-2 N-3 N-4….

最后建堆选序的时间复杂度为O(N^2) 对比其他排序这样都没有效率

所以我们采用大堆排升序

使用大堆可以不改变二叉树本身的结构

将 堆顶与最后一个数交换 ,这样最大的数就排到最后了

再将前n-1个数再次使用向下调整算法,找到次大的数 ,与倒数第二个数交换

直到有一个数时停止

在这里插入图片描述  

3.代码实现

void justdown(int* a, int n, int root)//向下调整算法 ——建堆  大堆
{
    int parent = root;
    int child = parent * 2 + 1;//假设左右孩子大的是左孩子
    while (child < n)//child作为下标小于元素个数就成立,否则数据不存在
    {
        if (a[child + 1] > a[child] && child + 1 < n)//作为完全二叉树存在,有可能只存在左子树,而不存在右子树
        {
            child++;//如果左孩子大于右孩子,设大的为右孩子
        }
        if (a[child] > a[parent])//孩子大于父亲,两者就交换
        {
            swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else//孩子比父亲小 就结束循环  因为左右子树都是大堆 
        {
            break;
        }
    }
}
void heapsort(int* a, int n)
{
    int i = 0;
    for (i = (n - 1 - 1) / 2; i >= 0; i--)//(n-1-1)/2是从叶节点的上一个父节点开始,
    {
        justdown(a, n, i);//因为实际是一个数组,所以每次减一都往前调一个下标位置
    }
    int end = n - 1;
    while (end > 0)//多于一个值时就进行
    {
        swap(&a[0], &a[end]);//排升序用大堆  
        justdown(a, end, 0);
        end--;
    }
}

四、堆排序的时间复杂度

1.建堆的时间复杂度  O(N)

  在这里插入图片描述  

2.排序中运用向下调整算法 ,向下调整算法需要调整高度次h

2^h -1 =N     h=log N      时间复杂度为O(logN)

不太懂高度计算的 二叉树的详细图解

堆排序的整体时间复杂度为 O(N*log N)

目录
相关文章
|
存储 芯片
第六章 半导体存储器【微机原理】2
第六章 半导体存储器【微机原理】2
1652 0
|
4月前
|
存储 数据库
RAG分块技术全景图:5大策略解剖与千万级生产环境验证
本文深入解析RAG系统中的五大文本分块策略,包括固定尺寸、语义、递归、结构和LLM分块,探讨其工程实现与优化方案,帮助提升知识检索精度与LLM生成效果。
673 1
|
5月前
|
存储 算法 安全
JAVA 八股文全网最详尽整理包含各类核心考点助你高效学习 jAVA 八股文赶紧收藏
本文整理了Java核心技术内容,涵盖Java基础、多线程、JVM、集合框架等八股文知识点,包含面向对象特性、线程创建与通信、运行时数据区、垃圾回收算法及常用集合类对比,附有代码示例与学习资料下载链接,适合Java开发者系统学习与面试准备。
1234 0
|
6月前
|
API 开发工具 计算机视觉
YOLO11 语句整理
本内容介绍基于YOLOv11模型的开发流程,涵盖模型下载、安装依赖库、训练与推理、模型转换为OpenVINO格式及部署。通过Ultralytics工具包实现模型加载、训练和预测,并使用OpenVINO优化推理性能。此外,提供数据集划分方法,按指定比例生成训练集、验证集和测试集,确保数据准备规范化,提升模型训练效果与实用性。
中断向量表的作用是什么?
【10月更文挑战第28天】中断向量表在计算机系统中扮演着至关重要的角色,它是实现中断处理、优先级管理、系统初始化以及硬件与软件交互的核心机制。通过中断向量表,计算机系统能够高效地响应各种中断事件,保证系统的稳定性、可靠性和实时性,为计算机的正常运行和各种应用程序的执行提供了有力支持。
1121 60
|
存储 缓存 算法
漫谈代码优化与效率提升
在当今快节奏的技术发展中,对于程序员来说,不仅仅是写出能运行的代码,更重要的是如何写出高效、优雅的代码,以提升工作效率和代码性能。本文从优化思路、技巧和实践三个方面探讨了代码优化与效率提升的方法,旨在为开发者提供一些实用的指导和启发。
524 31
|
弹性计算 Java 芯片
阿里云张伟分享 | 软件跨架构迁移(x86-&gt;ARM)的原理及实践
2023年8月31日,系列课程第四节《软件跨架构迁移(X86 -&gt; ARM)的原理及实践》正式上线,由阿里云弹性计算架构师主讲,内容涵盖:ARM与x86架构的差异分析;软件跨架构迁移的原理;软件迁移策略制定、环境准备、执行、测试优化及持续部署与维护等;以及软件迁移的全流程解读。
阿里云张伟分享 | 软件跨架构迁移(x86-&gt;ARM)的原理及实践
|
存储 安全 数据管理
磁盘分区全解:快速搞定硬盘分区
本文介绍了磁盘分区的重要性和好处,如数据管理、性能提升和安全增强,并为初学者提供了Windows系统下的磁盘分区指南。文章提到了三种磁盘分区工具:磁盘管理器、Diskpart命令行工具和第三方软件DiskGenius。同时,详细阐述了如何在磁盘管理器中创建新分区、使用DiskGenius一键重新分区、拆分现有分区以及通过Diskpart命令创建分区的步骤。最后,文章强调了磁盘分区在数据管理和系统优化中的价值。
|
网络协议 数据库 数据安全/隐私保护
|
机器学习/深度学习 自然语言处理 PyTorch
多模态条件机制
多模态条件机制
802 0

热门文章

最新文章