首页 > 其他分享 >数据结构/排序/堆排序 --- 逻辑讲解、代码实现、图形展示

数据结构/排序/堆排序 --- 逻辑讲解、代码实现、图形展示

时间:2024-06-23 21:31:42浏览次数:19  
标签:arr 下标 int 堆排序 len --- largest 数据结构 节点

一、总体逻辑:

        1. 写一个 交换的函数swap 备用

        2. 写一个 维护堆的性质的函数heapify 备用

        3. 数组 ->  堆(不明白的别急,后面会详细解释)

        4. 维护整个堆(看不懂别急别急别急)

        5. 堆顶 和 堆底的最后一个元素 互换(不明白的也别急奥,后面会解释的详详细细,完完整整,明明白白)

        6. 维护堆顶

    !!! 还要明白父节点与孩子节点的关系!!!:

                1. 数组长度假设为 len:

                                最后一个的最小子树的父节点 = ( len - 1) / 2

                2. 父节点假设下标为n:

                                左孩子节点的下标 = 2n + 1

                                右孩子节点的下标 = 2n + 2

二、解释:(是不是迫不及待想知道了呢!来啦来啦!)

        1. 写一个交换函数:就是为了简化相同逻辑的代码,将重要的函数编写时尽可能的偏向于思维,而非繁琐的过程,因为c语言没有任何库可以调用,所以只能自己手写咯。

                注意:要使用地址,才能正常的交换,否则只是给形参赋值了,并没有交换实际的

                           变量,所以后续能看到一个“&”,就是取其地址的意思

        2. 写一个维护堆的性质的函数:

                堆是什么呢?

                                堆就是由一个数组转为的一个完全二叉树,只不过对其有了一定的要求

                为什么要维护堆呢?

                                因为一个重要的概念就是大顶堆。

                什么是大顶堆呢?

                                大顶堆就是最大的元素在根节点,其孩子节点一定是比它小的

                怎么维护堆呢?

                                就时刻依照大顶堆的性质,从最小的大顶堆开始维护,向左向上维护。且都要看一下它的下方是否满足

                                听不懂? 上图!

                                

                                其中每一个框里都是一个小堆,他们都要满足大顶堆的概念,就是将每个小

                                堆中元素最大的放在堆顶,且要向左向上维护

                                啥?不明白什么是向左向上维护?

                                                根据这个图来说就是:橙 --> 蓝 --> 紫 --> 红

                                啥?不知道什么是堆顶?上图上图!

                                

                                

                                可看出,堆顶是相比较而言的!

        3.数组 ->  堆:数组变成堆,依照树的层次遍历,什么是层次遍历呢?就是一层一层的遍历(好像说了些废话),上图!

                      

        4. 维护整个堆:就是使用调用前面编写的备用的函数咯,可以理解为从下往上进行1v1,赢的(数大的赢)就往上走,就当爹(变为父节点),所以最顶上的当然是最大的咯。

        5. 堆顶 和 堆底的最后一个元素 元素互换:这个已经讲过咯,在最大的堆中,堆顶一定是最大的,将整个堆的堆顶与最后一个元素互换(就是将最大的元素放在最后,才能排序成 小->大)

        

        6.  维护堆顶:为什么又要维护堆顶呢?因为堆底可一定不是第二大的呀,他还有个爹呢,所以它不遵循大顶堆的规则呀,所以要维护!

三、代码讲解:

       1. 写一个 交换的函数swap 备用:

void swap(int *a, int *b)
{
  int temp = *b;
  *b = *a;
  *a = temp;
}

        2. 写一个 维护堆的性质的函数heapify 备用:

                形参:数组的地址 arr,数组长度 len,待维护节点的下标 i

                思路:1.定义一个变量 largest 记录最大值,默认为最小子树的父节点的下标。      

// 默认记录父节点的下标
  int largest = i;

                            2. 定义左孩子、右孩子,并计算,根据 一、总体逻辑 中的公式:

                                                父节点假设下标为n:

                                                                左孩子节点的下标 = 2n + 1

                                                                右孩子节点的下标 = 2n + 2

  // 父节点为代维护节点i,计算的左孩子
  int lson = i * 2 + 1;
  // 父节点为代维护节点i,计算的右孩子
  int rson = i * 2 + 2;

                             3.判断左右孩子和父节点哪个大,并用思路1定义的变量记录,若父节点大于左右孩子,则无需记录,因为 largest 默认了记录父节点的下标

// 判断父节点和孩子节点哪个大,并使用largest记录
  //  左孩子在范围之内 且 左孩子的值更大时
  if (lson < len && arr[largest] < arr[lson])
  {
    // 记录最大值的下标
    largest = lson;
  }
  // 右孩子在范围之内 且 右孩子的值更大时
  if (rson < len && arr[largest] < arr[rson])
  {
    // 记录右孩子的下标
    largest = rson;
  }

                             4. 判断父节点是否为最大

                                        若父节点为最大,则 largest 不会改变,为默认的父节点的下标,无

                                        需做任何操作就符合大顶堆的性质

                                        若父节点不为最大,即孩子中有一个大的,所以就要将大的元素放到

                                         父节点,所以就需要递归维护更小的子树,此时的 largest 记录的是

                                          刚从父节点下来的元素的下标,即待维护的下标(因为不知道他和

                                          更小的子树之间的大小关系)

// 当父节点的值不为最大时,即使用largest记录了最大值的下标
  if (largest != i)
  {
    // 将最大值放在父节点
    swap(&arr[largest], &arr[i]);
    // 递归维护更小的子树,此时largest记录了待维护节点的下标
    heapify(arr, len, largest);
  }

以下逻辑将写在同一个函数中。为什么?因为上面的函数要多次调用,同样是备用的,真正的逻辑是在下面的这些代码:

        3. 数组 ->  堆:

                ( len - 1 ) / 2 是 一、总体逻辑 底下总结的规律:

                                数组长度假设为 len:

                                           最后一个的最小子树的父节点 = ( len - 1) / 2‘

                代码在下面的 4.

        4. 维护整个堆:

// 从最后一个最小子树的堆顶开始
  for (i = (len - 1) / 2; i >= 0; i--)
  {
    // 维护从最后一个最小子树的堆顶,向左向上
    heapify(arr, len, i);
  }

        5. 堆顶 和 堆底的最后一个元素 互换:

                即将整个堆的堆顶与最后一个元素互换(就是将最大的元素放在最后,才能排序成 小->大)

                代码在下面的 6.

        6. 维护堆顶:

// 排序 -- 即将整个堆的堆顶与最后一个元素互换(就是将最大的元素放在最后,才能排序成 小->大 )
  //         然后删掉相连的线
  // 从最后一个元素开始
  for (i = len - 1; i >= 0; i--)
  {
    // 将堆的最后一个元素和堆顶元素交换,确定了堆顶元素的位置,但是此时堆顶不合法
    swap(&arr[i], &arr[0]);
    // 每次交换完已经确定了一个元素的位置,所以元素长度也是动态的,即i
    // 维护堆顶的位置
    heapify(arr, i, 0);
  }

四、代码附上:

#include <stdio.h>

void printArr(int *arr, int len)
{
  for (int i = 0; i < len; i++)
    printf("%d ", arr[i]);
  printf("\n");
}

void swap(int *a, int *b)
{
  int temp = *b;
  *b = *a;
  *a = temp;
}

/**
 * 维护堆的性质
 * @param arr 存储堆的数组
 * @param len 数组长度
 * @param i 待维护节点的下标
 */
void heapify(int *arr, int len, int i)
{
  // 默认记录父节点的下标
  int largest = i;
  // 父节点为代维护节点i,计算的左孩子
  int lson = i * 2 + 1;
  // 父节点为代维护节点i,计算的右孩子
  int rson = i * 2 + 2;

  // 判断父节点和孩子节点哪个大,并使用largest记录
  //  左孩子在范围之内 且 左孩子的值更大时
  if (lson < len && arr[largest] < arr[lson])
  {
    // 记录最大值的下标
    largest = lson;
  }
  // 右孩子在范围之内 且 右孩子的值更大时
  if (rson < len && arr[largest] < arr[rson])
  {
    // 记录右孩子的下标
    largest = rson;
  }

  // 当父节点的值不为最大时,即使用largest记录了最大值的下标
  if (largest != i)
  {
    // 将最大值放在父节点
    swap(&arr[largest], &arr[i]);
    // 递归维护更小的子树,此时largest记录了待维护节点的下标
    heapify(arr, len, largest);
  }

  // 当父节点的值为最大时,即堆无需维护,结束函数
}

/**
 * 堆排序入口
 * @param arr 存储堆的数组
 * @param len 数组长度
 */
void heapSort_circulation(int *arr, int len)
{
  int i;
  // 建堆
  // 从最后一个最小子树的堆顶开始
  for (i = (len - 1) / 2; i >= 0; i--)
  {
    // 维护从最后一个最小子树的堆顶,向左向上
    heapify(arr, len, i);
  }

  // 排序 -- 即将整个堆的堆顶与最后一个元素互换(就是将最大的元素放在最后,才能排序成 小->大 )
  //         然后删掉相连的线
  // 从最后一个元素开始
  for (i = len - 1; i >= 0; i--)
  {
    // 将堆的最后一个元素和堆顶元素交换,确定了堆顶元素的位置,但是此时堆顶不合法
    swap(&arr[i], &arr[0]);
    // 每次交换完已经确定了一个元素的位置,所以元素长度也是动态的,即i
    // 维护堆顶的位置
    heapify(arr, i, 0);
  }
}

void main()
{
  int arr1[] = {5, 6, 2, 9, 8, 7, 3, 1, 4};
  int len = sizeof(arr1) / sizeof(int);

  printf("排序前:\n");
  printArr(arr1, len);
  heapSort_circulation(arr1, len);
  printf("排序后:\n");
  printArr(arr1, len);
}

标签:arr,下标,int,堆排序,len,---,largest,数据结构,节点
From: https://blog.csdn.net/2301_80045119/article/details/139810510

相关文章

  • 深入理解栈:计算机科学中的基础数据结构
    1.栈的概念及结构栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(LastInFirstOut)的原则。压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。出栈:栈的删除操作叫做......
  • Psim仿真教程04-仿真软件功能介绍/电源工程师初级到高级进阶之路
    目录点击下面文字下载需要的版本:Psim2022中文版下载链接:Psim2022中文版软件下载地址Psim9.1经典版本下载链接:Psim9.1软件下载地址1.Psim软件的主要界面1.1文件菜单栏:1.2编辑菜单栏:1.3视图菜单栏1.4视图选项中的元件清单1.5视图选项中的元件数目菜单可以统计仿......
  • python基础 - socket编程基础
    一对一---服务端importsocketip_port=('127.0.0.1',9999)1-创建socket对象---socket也叫套接字sk=socket.socket()2-绑定ip端口sk.bind(ip_port)3-开启监听sk.listen()print('---socket服务器已经启动完成---')4-阻塞等待客户端来链接可以返回连接对象......
  • 【java-POI】如何将一个WorkBook转为一个InputStream?
    /***利用workBook创建一个输入流用于后续操作**@return*/privateInputStreamcreateInputSream(){if(inputStream!=null){try{inputStream.reset();returninputStream;......
  • 20-OWASP top10--XXS跨站脚本攻击
    目录什么是xxs?XSS漏洞出现的原因XSS分类反射型XSS储存型XSSDOM型XSSXSS漏洞复现XSS的危害或能做什么?劫持用户cookie钓鱼登录XSS获取键盘记录 同源策略(1)什么是跨域(2)同源策略(3)同源策略修改(允许所有人跨域访问)XSS绕过简单的绕过方法 使用HTML进行编码绕......
  • 移动应用开发-第8章广播机制
    广播是一种运用在组件之间传递消息的机制。如果要接收并过滤广播中的信息,则需要使用BroadcastRecciver(广播接收者)。8.1广播机制的概述Android中的广播机制更加灵活,因为Android中每个应用程序都可以根据自己的需要对广播进行注册,所以该程序只会接收自己关注的广播内容,这些广播......
  • SQL-Python
    师从黑马程序员数据库介绍数据库就是存储数据的库数据组织:库->表->数据数据库和SQL的关系MySQL的基础命令 SQL基础SQL语言的分类SQL的语法特征DDL-库管理showDATABASES;usesys;SELECTdatabase();CREATEDATABASEtestCHARSETutf-8;SHOWDATAB......
  • 58-DOS与DDOS分析(正常TCP会话与SYN Flood攻击、ICMP Flood 攻击、SNMP放大攻击等)
    目录正常TCP会话与SYNFlood攻击1、正常的三次握手过程:2、SYNFlood攻击一、攻击windows系统:二、攻击web网站:拒绝服务攻击工具-Hping3-SynFlood攻击拒绝服务攻击工具--Hping3--ICMPFlood攻击 sockstress攻击Sockstress防范 DNS放大攻击产生大流量的攻......
  • MyBatis 源码分析--获取SqlSession
    前言:前文我们从源码层面梳理了SqlSessionFactory的创建过程,本篇我们继续分析一下SqlSession的获取过程。初识MyBatis【MyBatis核心概念】MyBatis源码分析–SqlSessionFactory案例代码:publicclassMyBatisTest{@Testpublicvoidtest()throwsIOEx......
  • [题解]P2042 [NOI2005] 维护数列 - Splay解法
    P2042[NOI2005]维护数列一道思路不难,但实现细节很多的平衡树题,调了一天半终于做出来了w。对于初始序列,我们可以直接构建一条链(毕竟一个一个调用插入函数也可能形成一条链)。题解有递归直接构建成一棵严格平衡的二叉树的,这样也可以,常数可能会小一点。其中区间反转就是裸的文艺......