超详细的堆排序,进来看看吧。

1.堆的基本概念

1.1什么是堆

堆是一种叫做完全二叉树的数据结构,

1.2大堆和小堆

大堆:每个节点的值都大于或者等于他的左右孩子节点的值

小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值

1.3完全二叉树节点之间的关系

  • leftchild = parent*2 + 1

  • rightchild = parent*2 + 2

  • parent = (child - 1) / 2

1.4堆这个数据结构的实现

heap.h//需要实现的功能列表

#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef int HPDataType;
typedef struct Heap
{
    HPDataType* _a;
    int _size;
    int _capacity;
}Heap;

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

heap.c

堆的向上调整和向下调整

(只能处理一次,创建大小堆的时候需要反复调用)

void Swap(HPDataType* x1, HPDataType* x2)
{
    HPDataType tmp = *x1;
    *x1 = *x2;
    *x2 = tmp;
}

void AdjustDown(HPDataType* a, int n, int root)//孩子不断向下
{
    int parent = root;
    int child = parent * 2 + 1;//初始化成左孩子
    while (child < n)
    {
        // 选左右孩纸中大的一个
        if (child + 1 < n
            && a[child + 1] > a[child])
        {
            ++child;
        }

        //如果孩子大于父亲,进行调整交换 
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void AdjustUp(HPDataType* a, int n, int child)//孩子不断向上
{
    int parent;
    assert(a);
    parent = (child - 1) / 2;
    while (child > 0)
    {
        //如果孩子大于父亲,进行交换
        if (a[child] > a[parent])
        {
            Swap(&a[parent], &a[child]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

定义出堆的数据结构

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* arr;
    int size;
    int capacity;
}Heap;

初始化一个堆

void HeapInit(Heap* hp, HPDataType* a, int n)
{
    int i;
    assert(hp && a);
    hp->arr = (HPDataType*)malloc(sizeof(HPDataType) * n);
    hp->size = n;
    hp->capacity = n;

    for (i = 0; i < n; ++i)
    {
        hp->arr[i] = a[i];
    }

    // 建堆: 从最后一个非叶子节点开始进行调整
    // 最后一个非叶子节点,按照规则: (最后一个位置索引 - 1) / 2
    // 最后一个位置索引: n - 1
    // 故最后一个非叶子节点位置: ((n - 1) / 2)-1
    for (i = (n - 2) / 2; i >= 0; --i)
    {
        AdjustDown(hp->arr, hp->size, i);
    }  
    //for (i = 1; i < n; i++)
    //{
    //    AdjustUp(hp->arr,hp->size, i);
    //}
}

// 建堆: 从最后一个非叶子节点开始进行调整

// 最后一个非叶子节点,按照规则: (最后一个位置索引 - 1) / 2

// 最后一个位置索引: n - 1

// 故最后一个非叶子节点位置: ((n - 1) / 2)-1

  • 我们向下调整来建堆时,要保证当前要调整的节点的左右子树都已经是堆后,才可以向下调整。所以我们要从倒数第一个非叶子结点开始调整

  • 向上调整来建立堆的时候,要保证要调整的节点前面已经是一个合法的堆才行——为了达到这个目的,我们在向上建堆的时候,就需要从数组的第二个元素开始

堆的插入和删除

我们需要注意的是插入删除后我们仍需保持调整后的数组仍是一个堆

void HeapPush(Heap* hp, HPDataType x)
{
    assert(hp);
    //检查容量
    if (hp->size == hp->capacity)
    {
        hp->capacity *= 2;
        hp->arr = (HPDataType*)realloc(hp->arr, sizeof(HPDataType) * hp->capacity);
    }
    //尾插
    hp->arr[hp->size] = x;
    hp->size++;
    //向上调整
    AdjustUp(hp->arr, hp->size, hp->size - 1);//因为数据插在尾向不会打乱已有关系,可以直接向上调整
}

void HeapPop(Heap* hp)
{
    assert(hp);
    //交换
    Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
    hp->size--;
    //向下调整
    AdjustDown(hp->arr, hp->size, 0);
}

HPDataType HeapTop(Heap* hp)
{
    assert(hp);
    return hp->arr[0];
}

int HeapSize(Heap* hp)
{
    return hp->size;
}

int HeapEmpty(Heap* hp)
{
    return hp->size == 0 ? 0 : 1;
}

void HeapPrint(Heap* hp)
{
    int i;
    for (i = 0; i < hp->size; ++i)
    {
        printf("%d ", hp->arr[i]);
    }
    printf("\n");
}

堆的插入,因为数据插在尾向不会打乱已有关系,可以直接向上调整。

堆的删除,因为是删除堆顶的数据,为了防止打乱已有关系,交换数据后向下调整,构建一个新的堆。

测试test.c

void test(void)
{
    Heap hp;
    int arr[] = { 0,4,13,45,32,45,2,56,33,32 };
    HeapInit(&hp, arr, sizeof(arr) / sizeof(arr[0]));
    HeapPrint(&hp);
}

int main()
{
    test();
    //TestHeap1();
}

2.向上调整建堆和向下调整建堆的区别

2.1向上调整建堆

时间复杂度计算的是其调整的次数,根据上文的知识我们已经知晓其是从数组的第二个元素开始的,也就是可以理解为第二层的第一个节点。计算的思想非常简单:计算每层有多少个节点乘以该层的高度次,然后累计相加即可。

2.2向下调整建堆

向下调整我们前面已经知道它是从倒数第1个非叶节点开始调整的

每层的调整次数为:该层的节点个数*该层高度减1

一直从倒数第2层开始调直至第1层,并将其依次累加,累加的计算过程和向上调整差不多,都是等比*等差的求和,(但不同的是:每层的调整次数不同)

3.堆的应用

2.1堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:每个结点的值都大于等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于等于其左右孩子结点的值,称为小顶堆。该算法时间复杂度为O(n log n)。

堆排序算法的原理如下:

​ 1.将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;

​ 2.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];

​ 3.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

void HeapSort(Heap* hp, int n)
{
    // 向上调整建堆 -- N*logN
    /*for (int i = 1; i < n; ++i)
    {
    AdjustUp(a, i);
    }*/

    // 向下调整建堆 -- O(N)
    // 升序:建大堆,把最大的依次拿到下面去
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(hp->arr, n, i);
    }

    // O(N*logN)
    int end = n - 1;
    while (end > 0)
    {
        Swap(&hp->arr[end],&hp->arr[0]);
        AdjustDown(hp->arr, end, 0);
        --end;
    }
}

2.2 topK问题

从n个数据中选出排在前k个的数据。

建一个小堆,堆里假设就是前k个数据,如果有比堆顶大的数据则弹出堆顶数据,在插入新的数据,然后向下调整堆,得到新的小堆,如此往复,直到读完n个数据。

//1. 找最大的K个元素
//假设堆为小堆
void PrintTopK(int* a, int n, int k)
{
    Heap hp;
    //建立含有K个元素的堆
    HeapInit(&hp, a, k);
 
    for (size_t i = k; i < n; ++i)  // N
    {
        //每次和堆顶元素比较,大于堆顶元素,则删除堆顶元素,插入新的元素
        if (a[i] > HeapTop(&hp)) // LogK
        {
            HeapPop(&hp);
            HeapPush(&hp, a[i]);
        }
    }
    for(int i = 0; i < k; ++i){
        printf("%d ",HeapTop(&hp));
        HeapPop(&hp);
    }
}
 
//2. 找最小的K个元素
//假设堆为大堆
void PrintTopK(int* a, int n, int k)
{
    Heap hp;
    //建立含有K个元素的堆
    HeapInit(&hp, a, k);
 
    for (size_t i = k; i < n; ++i)  // N
    {
        //每次和堆顶元素比较,小于堆顶元素,则删除堆顶元素,插入新的元素
        if (a[i] < HeapTop(&hp)) // LogK
        {
            HeapPop(&hp);
            HeapPush(&hp, a[i]);
        }
    }
    for(int i = 0; i < k; ++i){
        printf("%d ",HeapTop(&hp));
        HeapPop(&hp);
    }
}

用大数据测一下

void TestTopk()
{
    int n = 1000000;
    int* a = (int*)malloc(sizeof(int) * n);
    srand(time(0));
    //随机生成10000个数存入数组,保证元素都小于1000000
    for (size_t i = 0; i < n; ++i)
    {
        a[i] = rand() % 1000000+1000000;
    }
    //确定10个最大的数
    a[5] = 1;
    a[1231] =  2;
    a[531] =  3;
    a[5121] = 4;
    a[115] = 5;
    a[2335] = 6;
    a[9999] =  7;
    a[76] = 8;
    a[423] = 9;
    a[3144] = 10;

    PrintTopK(a, n, 10);
}
int main()
{
    test();
    //TestHeap1();
    TestTopk();
}

4.附带一张排序算法的表(仅供参考)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/1465.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

string类(上)

string类&#xff08;上&#xff09;1.标准库中的string类2.string类对象的常见构造①string()②string(const char* s)③string(size_t n,char c)④string(const string&s)⑤string(const string& str,size_t pos,size_t lennpos)⑥string&#xff08;const char* s,s…

【基于协同过滤算法的推荐系统项目实战-2】了解协同过滤推荐系统

本文目录1、推荐系统的关键元素1.1 数据1.2 算法1.3 业务领域1.4 展示信息2、推荐算法的主要分类2.1 基于关联规则的推荐算法基于Apriori的算法基于FP-Growth的算法2.2 基于内容的推荐算法2.3 基于协同过滤的推荐算法3、推荐系统常见的问题1、冷启动2、数据稀疏3、不断变化的用…

java 每日一练 (9)

文章目录1. 单选2. 编程1. 单选 1. 下面程序的输出是:() A &#xff1a; FmNwxy B &#xff1a;fmnwxy C &#xff1a;wxyfmn D &#xff1a; Fmnwxy 答案 &#xff1a; D &#xff0c; 这里主要考察 toUpperCase 和 replace 方法 &#xff0c; 注意点 &#xff1a; toUpperCas…

动态规划-基础(斐波那契数、爬楼梯、使用最小花费爬楼梯、不同路径、不同路径II、整数拆分、不同的二叉搜索树)

动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。所以动态规划中每一个状态一定是由上一个状态推导出来的。动态规划问题&#xff0c;五步走&#xff1a;状态定义&am…

【数据结构】双向链表

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; 初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是…

海思SD3403/SS928V100开发(7)mcp2515-SPI转CAN驱动开发

1. 前言 需求: 需要一路can进行收发 分析: 根据目前使用较多的方案是使用主控端SPI接口 接入MCP2515芯片进行CAN协议转换 硬件: MCP2515->SPI2->SS928 2. Uboot开发 2.1 pinmux复用配置 2.1.1 修改uboot参数表 路径: osdrv/tools/pc/uboot_tools/ SS928V100…

Android 进程间通信机制(三) 系统进程与应用进程通信

一. 概述 Android中有一个重要的系统进程(system_server)&#xff0c;运行着系统中非常重要服务(AMS, PMS, WMS等)&#xff0c; 针对Activity而言&#xff0c;系统进程需要不断地调度Activity执行&#xff0c;管理Activity的状态; 每一个APK都需要运行在一个应用进程中&#xf…

【动态规划】最长上升子序列(单调队列、贪心优化)

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

jvm-题库

1、JVM内存模型 JVM内存区域总共分为两种类型 线程私有区域&#xff1a;程序计数器、本地方法栈和虚拟机栈 线程共享区域&#xff1a;堆&#xff08;heap&#xff09;和方法区 特征 线程私有区域&#xff1a;依赖用户的线程创建而创建、销毁而销毁&#xff0c;因用户每次访问都…

带头双向循环链表

在前面我们学习了单链表&#xff0c;发现单链表还是有一些不够方便&#xff0c;比如我们要尾插&#xff0c;需要遍历一遍然后找到它的尾&#xff0c;这样时间复炸度就为O(N),现在我们引入双向带头链表就很方便了&#xff0c;我们先看看它的结构。通过观察&#xff0c;我们发现一…

Vue全新一代状态管理库 Pinia【一篇通】

文章目录前言1. Pinia 是什么&#xff1f;1.1 为什么取名叫 Pinia?1.2. 为什么要使用 Pinia ?2. 安装 Pinia2.1.创建 Store2.1.1. Option 类型 Store2.1.2 Setup 函数类型 Store2.1.3 模板中使用3. State 的使用事项&#xff08;Option Store &#xff09;3.1 读取 State3.2 …

EEPROM芯片(24c02)使用详解(I2C通信时序分析、操作源码分析、原理图分析)

1、前言 (1)本文主要是通过24c02芯片来讲解I2C接口的EEPROM操作方法&#xff0c;包含底层时序和读写的代码&#xff1b; (2)大部分代码是EEPROM芯片通用的&#xff0c;但是其中关于某些时间的要求&#xff0c;是和具体芯片相关的&#xff0c;和主控芯片和外设芯片都有关系&…

一天吃透TCP面试八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

vue模板语法

src目录文件说明&#xff1a; 1&#xff0c;数据绑定{{}} 数据绑定最常见的形式就是使用{{}}&#xff08;双花括号&#xff09;语法的文本插值 在template中使用{{}}文本插值语法中&#xff0c;设置一个变量&#xff0c;再在script中引入data函数&#xff0c;在return中进行数…

接口测试和性能测试有什么区别?我敢打赌你一定不知道

目录 一、什么是接口测试 二、接口测试原理 三、接口测试步骤 四、什么是性能测试 五、性能测试步骤 六、接口测试和性能测试的区别 一、什么是接口测试 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点…

2023最全Python+Selenium环境搭建教程-你绝对想不到有这么简单!

还有视频版本结合项目实战介绍&#xff0c;轻松学习&#xff01; PythonSelenium自动化测试环境搭建Web自动化测试全套教程_哔哩哔哩_bilibiliPythonSelenium自动化测试环境搭建Web自动化测试全套教程共计180条视频&#xff0c;包括&#xff1a;1、Web自动化测试需求和挑战、2…

深度学习-Tensorflow使用Keras进行模型训练

本文以FasionMNIST/加州房价数据集为例&#xff0c;介绍KerasAPI进行分类问题/回归问题模型训练的方法Tensorflow版本Tensorflow和keara都需要2.0及以上版本import tensorflow as tf from tensorflow import keras print(tf.__version__) print(keras.__version__)分类MLP构建数…

AI_Papers周刊:第六期

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 2023.03.13—2023.03.19 文摘词云 Top Papers Subjects: cs.CL 1.UPRISE: Universal Prompt Retrieval for Improving Zero-Shot Evaluation 标题&#xff1a;UPRISE&#xff1a;改进零样本评估…

要是早看到这篇文章,你起码少走3年弯路,20年老程序员的忠告

文章目录前言一、程序员的薪资是怎么样的&#xff1f;二、我现在的情况适合做程序员吗&#xff1f;三、大学期间到底应该学些什么&#xff1f;四、工作还是考研&#xff1f;五、总结前言 我是龙叔&#xff0c;一名工作了20多年的退休老程序员。 如果你在工作之前看到这篇文章…

【AI大比拼】文心一言 VS ChatGPT-4

摘要&#xff1a;本文将对比分析两款知名的 AI 对话引擎&#xff1a;文心一言和 OpenAI 的 ChatGPT&#xff0c;通过实际案例让大家对这两款对话引擎有更深入的了解&#xff0c;以便大家选择合适的 AI 对话引擎。 亲爱的 CSDN 朋友们&#xff0c;大家好&#xff01;近年来&…