重点算法排序之堆排序(下篇)

news2024/11/28 0:44:06

 

文章目录

一、堆排序的概念

1、1 堆的基本概念

1、2 堆的特性

二、堆排序的思路及代码实现

2、1 建堆

2、2 向下调整算法详解

2、3 建完堆后进行堆排序

2、3、1 排升序建大堆

2、3、2 建大堆后进行堆排序 

三、堆排序的例题

2、1 例题1:堆排序   

2、2 例题2:模拟堆

 四、总结 


标题:重点算法排序之堆排序(下篇)

作者:@Ggggggtm

寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景

  我们已经讲述了快速排序和归并排序,快速排序和归并排序详解文章链接:重点算法排序之快速排序、归并排序(上篇),我们本篇文章来详细讲述以下堆排序。堆排序的主要内容有:最大堆(大顶堆)、最小堆(小顶堆)、通过孩子找父亲、通过父亲找孩子、向下调整算法建堆。下面我会给大家一一介绍。

一、堆排序的概念

1、1 堆的基本概念

  堆一般指的是二叉堆,顾名思义,二叉堆是完全二叉树或者近似完全二叉树

  堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 我们是用一个数组来表示一个堆。如下图:

1、2 堆的特性

  堆的特性有如下几点:

  • 堆是一棵完全二叉树
  • 每个父亲节点的值都大于或等于其子节点的值,为最大堆;反之为最小堆。
  • 下标为 i 的结点的父结点下标为(i-1)/2
  • 其下标为i的左右子结点分别为 (2i + 1)、(2i + 2)。

  我们上述的后两点讲述了通过孩子找父亲、通过父亲找孩子的方法。那么给出一个乱序的数组,我们怎么建出来一个堆呢?我们接着往下看。

二、堆排序的思路及代码实现

2、1 建堆

  我们想用堆对数组进行排序,我们得首先有一个堆。那么问题来了,当给出我们一个乱序的数组时,我们怎么建出一个堆呢?这里就用到了我们的向下调整算法

2、2 向下调整算法详解

  我们在这里用小堆来讲述向下调整算法的实现。那到底什么是向下调整算法呢?我们先看一下向下调整算法的概念。

  向下调整算法:就是调整结点的位置,使调整的路径上满足大堆或小堆的条件,以调整小堆为例,对某一结点为根结点的堆进行向下调整,首先找到两个子结点中最小的那一个结点,然后与根结点进行比较,如果比根结点小,则交换,交换后使新的根结点为被交换结点的位置,对此位置所在结点继续进行相同的比较与交换,直到调整的结点不在堆的合法范围内或子结点没有比根结点小为止,此时这样的一个过程就叫做向下调整,因为它调整的方向是向下的。我们具体看一个例子。如下图:

 

  注意:建小堆用向下调整算法用的前提为左子树和右子树均为小堆。建大堆同理。

  我们看一下向下调整算法的代码实现,如下:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(int arr[], int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认左孩子
	while (child < n)
	{
		if (child+1<n && arr[child + 1] > arr[child])//要考虑到有孩子存在不
		{
			child += 1;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

   那要是左右子树不是小堆呢?如下图:

  我们看到上述的完全二叉树并不是一个堆,我们想要对第一个元素3进行向下调整算法,但是3的左右子树并不是小堆,那对3就不能用向下调整算法了。怎么办呢?我们不妨从最后的一颗子树开始使用向下调整算法。 也就是从8开始,依次往前使用向下调整算法即可

  我们再看建堆的整个代码:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(int arr[], int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认左孩子
	while (child < n)
	{
		if (child+1<n && arr[child + 1] > arr[child])//要考虑到有孩子存在不
		{
			child += 1;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int arr[], int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
}

2、3 建完堆后进行堆排序

2、3、1 排升序建大堆

  我们知道,大堆的根节点是最大的。同时,根节点总比子节点大。那为什么排升序要建大堆呢?假如是建的小堆,最小数在堆顶,最小的数相当于被选出来了。那么在剩下的始终再去 选数。但是剩下的树结构就乱了,如下图:   此时我们需要重新建堆才能选出下一个最小的数。建堆的时间复杂度为O(n),这样的效率就很低了。

    我们建大堆,大堆第一个元素是最大的。我们把第一个元素和最后一个元素交换,那么最大的数就到了最后面。然后把最大的数不看做堆里面的数据,此时,剩下的数据的结构并没有乱,如下图:

  再对第一个数进行数向下调整,又成为了一个大堆,反复此操作即可排序完成。

2、3、2 建大堆后进行堆排序 

  由上述我们知道,当我们要排升序时,我们要建的是大堆。当建完大堆后,我们就要进行堆排序。建完堆,交换第一个数据合作后一个数据,再对第一个数据进行向下调整。向下调整完后,有成为一个大堆,再次交换、向下调整。当每个数据都向下调整后,我们的数据就称为了升序。我们看代码的实现:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustDown(int arr[], int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认左孩子
	while (child < n)
	{
		if (child+1<n && arr[child + 1] > arr[child])//要考虑到有孩子存在不
		{
			child += 1;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int arr[], int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}


	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}
void Print(int arr[], int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}
void TestSort()
{
	int arr[10] = { 0,9,8,7,6,5,4,3,2,1 };
	int n = 10;
	HeapSort(arr, n);
	Print(arr, n);
}
int main()
{
	TestSort();
	return 0;
}

   以上即为整个堆排序的过程。我们不妨来看几道堆排序的例题。

三、堆排序的例题

2、1 例题1:堆排序   

堆排序:

输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。

输入格式:

第一行包含整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

输出格式:

共一行,包含 m 个整数,表示整数数列中前 m 小的数。

数据范围:

1≤m≤n≤10e5  1≤m≤n≤10e5,
1≤数列中元素≤10e9   1≤数列中元素≤10e9

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

这道题我们用堆排序来做一下,我们看答案:

#include<iostream>
#include <algorithm>
using namespace std;

const int N=100010;

int cnt,q[N];
int n,m;

void down(int x)
{
    int t=x;
    if(x*2+1<cnt&&q[x*2+1]<q[t])
        t=x*2+1;
    if(x*2+2<cnt&&q[x*2+2]<q[t])
        t=x*2+2;
    if(t!=x)
    {
        swap(q[t],q[x]);
        down(t);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&q[i]);
    }
    cnt=n;
    for(int i=n-1-1>>1;i>=0;i--)
    {
        down(i);
    }
    while(m--)
    {
        printf("%d ",q[0]);
        q[0]=q[cnt-1];
        cnt--;
        down(0);
    }
    return 0;
}

2、2 例题2:模拟堆

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 xx;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 kk 个插入的数;
  5. C k x,修改第 kk 个插入的数,将其变为 xx;

现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。

输入格式:

第一行包含整数 N。

接下来 NN 行,每行包含一个操作指令,操作指令为 I xPMDMD k 或 C k x 中的一种。

输出格式:

对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。

每个结果占一行。

数据范围:

1≤N≤10e5  1≤N≤10e5
−10e9≤x≤10e9  −10e9≤x≤10e9
数据保证合法。

输入样例:

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

-10
6

  我们这道题的所有操作都可以用向下调整,或向上调整都可以玩成。我们看一下答案:

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N=100010;
int h[N],qh[N],hq[N],cnt;
void heap_swap(int a,int b)
{
    swap(qh[hq[a]],qh[hq[b]]);
    swap(hq[a],hq[b]);
    swap(h[a],h[b]);
}
void down(int u)
{
    int t=u;
    if(u*2<=cnt&&h[u*2]<h[t])
        t=2*u;
    if(u*2+1<=cnt&&h[u*2+1]<h[t])
        t=2*u+1;
    if(t!=u)
    {
        heap_swap(t,u);
        down(t);
    }
    
}
void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}
int main()
{
    int n,m=0;
    scanf("%d",&n);
    while(n--)
    {
        char op[5];
        int k,x;
        scanf("%s",op);
        if(!strcmp(op,"I"))
        {
            scanf("%d",&x);
            m++;
            cnt++;
            qh[m]=cnt;
            hq[cnt]=m;
            h[cnt]=x;
            up(cnt);
        }
        else if(!strcmp(op,"PM"))
        {
            printf("%d\n",h[1]);
        }
        else if(!strcmp(op,"DM"))
        {
            heap_swap(1,cnt);
            cnt--;
            down(1);
        }
        else if(!strcmp(op,"D"))
        {
            scanf("%d",&k);
            k=qh[k];
            heap_swap(k,cnt);
            cnt--;
            down(k);
            up(k);
        }
        else
        {
            scanf("%d%d",&k,&x);
            k=qh[k];
            h[k]=x;
            down(k);
            up(k);
        }
    }
    return 0;
}

 四、总结

  对排序中,重点是要熟知向下调整算法,并且知道排升序建大堆还是小堆。这里给大家总结出各个排序的时间复杂度、空间复杂度、是否稳定性。需要掌握的。

  堆排序的讲解就到这里,希望以上内容对你有所帮助。

  感谢阅读,ovo~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTP.sys远程代码执行漏洞修复

1.漏洞描述 Http.sys是Microsoft Windows处理HTTP请求的内核驱动程序。HTTP.sys会错误解析某些特殊构造的HTTP请求&#xff0c;导致远程代码执行漏洞。成功利用此漏洞后&#xff0c;攻击者可在System帐户上下文中执行任意代码。由于此漏洞存在于内核驱动程序中&#xff0c;攻击…

VMware下的虚拟机网络设置(NAT、桥接、仅主机)

在入门使用VMware搭建Linux的环境时&#xff0c;对于网络的设置时不可避免的&#xff0c;因为linux搭建完成后&#xff0c;或多或少的回去访问外部资源或者被外部资源访问。这时候设置的虚拟机网络连接方式就显得尤为重要&#xff0c;所以在这里整理了一下虚拟机的三种连接方式…

说说压缩文件“打开密码”的两种模式

我们知道&#xff0c;如果对压缩文件有保密需求&#xff0c;可以给压缩文件设置“打开密码”&#xff0c;通过密码才能查看压缩文件里的内容。那通过WinRAR设置的“打开密码”有两种模式&#xff0c;你知道吗&#xff1f;下面来具体说说。 模式一&#xff1a;可以看到压缩包的…

springboot集成mybatis

springboot集成mybatis 文章目录springboot集成mybatis前言一、初始化项目1.创建项目2.引入依赖3.创建实体类4.修改配置文件二、使用Mybatis1.纯注解方式2.使用xml文件方式三、使用pagehelper分页前言 MyBatis 是一个开源、轻量级的数据持久化框架&#xff0c;是 JDBC 和 Hibe…

赤池信息量准则(AIC)和贝叶斯信息准则(BIC)

一 AIC 赤池信息量准则&#xff08;Akaike information criterion&#xff0c;AIC&#xff09;是评估统计模型的复杂度和衡量统计模型“拟合”资料之优良性(Goodness of fit)的一种标准&#xff0c;是由日本统计学家赤池弘次创立和发展的。赤池信息量准则建立在信息熵的概念基…

LeetCode题目笔记——面试题 02.07. 链表相交

文章目录题目描述题目难度——简单方法一&#xff1a;数数&#xff0c;然后遍历代码/C方法二&#xff1a;双指针代码/C代码/Python总结题目描述 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c…

假期无聊,不如一起刷《剑指offer》(第六天)

剑指 Offer 41. 数据流中的中位数 剑指 Offer 41. 数据流中的中位数 这道题是求数据流的中位数&#xff0c;一般情况我们可以采用排序的方式很轻松的找出中位数。如果我们采用插入排序的话&#xff0c;每次插入数字的时间复杂度大概是O(N)&#xff0c;怎么能让这个时间更短呢&a…

shell原理及Linux权限

shell及Linux权限 目录shell及Linux权限一、指令1.tar指令&#xff08;重要&#xff09;2.热键3.bc命令4.uname –r指令&#xff1a;5.关机6.以下命令作为扩展:二.shell命令以及运行原理三.权限1.权限的概念&#xff1a;2.Linux下有两种用户&#xff1a;超级用户&#xff08;ro…

一图读懂mybatis 查询接口的源码流程

图比较大&#xff1a;如果看着比较糊的话&#xff0c;可以下载高清图&#xff1a;https://download.csdn.net/download/langwuzhe/87376216 第一步&#xff1a;创建 StatementHandler、ParameterHandler、ResultSetHandler-----------(三剑客的新生) 创建 StatementHandler 对…

WPS怎么转换PDF?保证你一学就会

相信大家在处理文件的时候肯定会使用到WPS文件&#xff0c;WPS文件包括Word、Excel、PPT文件&#xff0c;是我们经常使用的几种文件&#xff0c;有这几种文件我们可以更好的完成工作&#xff0c;但是在有些情况下&#xff0c;我们需要将WPS转换成PDF文件&#xff0c;这样就会更…

AS弹性伸缩简单介绍

AS 介绍 弹性伸缩(AutoScaling)是一种服务&#xff0c;可以自动调整弹性计算资源&#xff08;ECS)&#xff0c;以满足业务需求的变化。 弹性伸缩仅支持ECS实例或ECI实例数量的增加和减少&#xff0c;但不支持单个ECS实例或ECI实例的配置变更。 应用场景&#xff1a;弹性扩张、…

Windows安装使用Docker,方便你的开发和部署(DockerDesktop篇)

前言 首先声明&#xff0c;此篇不是完全的Docker技术文章&#xff0c;而是单纯的教你使用Docker&#xff0c;不包含Docker的一些命令、如何打包Docker镜像等等。 为什么要用Docker&#xff1f; 大家好&#xff0c;我是小简&#xff0c;今天带来一篇Windosw环境下使用Docker的…

女生学软件测试有什么优势么

在IT技术行业&#xff0c;女生学习软件测试还是有很大优势的。女生相较于男生更有耐心&#xff0c;包容性强&#xff0c;心思细腻&#xff0c;对细节把控更好&#xff0c;同时还能帮助团队男女平衡&#xff0c;活跃气氛。 软件测试是一个只要你肯学习就会有回报的职业&#xf…

判断用户输入的数字是奇数还是偶数

判断用户输入的数字是奇数还是偶数代码关键知识点 条件运算符&#xff0c; 相等运算符&#xff0c;为了让两个不同的数据类型&#xff08;如number和string&#xff09;的值可以作比较&#xff0c;必须要把一种类型转换为另一种类型&#xff08;转换成相同的类型&#xff09;&…

Ae 效果详解:CC Ball Action

Ae菜单&#xff1a;效果/模拟/CC Ball ActionEffect/Simulation/CC Ball ActionCC Ball Action &#xff08;滚珠操作效果&#xff09;可以将所有的像素变成小球模样&#xff0c;并且能够打破图层成球形网格。可通过摄像机观察其所具有的 3D 效果。◆ ◆ ◆效果控件属性说明S…

【数据结构与算法——C语言版】6. 排序算法(4)——快速排序

前言 本文介绍排序算法中的快速排序&#xff0c;快速排序是比较常用的一种排序算法&#xff0c;也是面试中经常会问到的一种排序算法&#xff0c;简称快排&#xff0c;是我们要介绍的第一种时间复杂度为O(nlogn)的排序算法。 核心思想 快速排序(Quick Sort)使用分治法策略&a…

Vue--》详解状态管理工具——Vuex

目录 vuex 搭建vuex环境 vuex的使用 vuex开发者工具使用 getters mapState和mapGetters mapMutations和mapActions 多组件共享数据 vuex实现模块化 vuex 专门在Vue中实现集中式状态(数据)管理的一个Vue插件&#xff0c;对vue应用中多个组件的共享状态进行集中式的管…

c语言进阶(4)——字符函数的详细解析

文章目录1.strlen函数2.strcpy函数3.strcat函数4.strcmp函数5.strncpy函数6.strncat函数7.strncmp函数8.strstr函数9.strtok函数10. strerror函数11. 相关字符转换函数12.字符转换函数1.strlen函数 size_t strlen( const char *string ); 用途&#xff1a;用来计算字符串长度的…

【云原生进阶之容器】第二章Controller Manager原理2.8节--Resync机制

8 Resync机制 8.1 DeltaFIFO队列为什么需要Resync 为什么需要 Resync 机制呢?因为在处理 SharedInformer 事件回调时,可能存在处理失败的情况,定时的 Resync 让这些处理失败的事件有了重新 onUpdate 处理的机会。 主要的目的是为了不丢数据,处理 resync 机制还有边缘触发与…

公务员考试催生一家上市公司,公务员真的是一条好的出路吗

公务员考试能催生一家公司吗&#xff1f;还真的可以&#xff0c;而且在2023.01.09日也就是今天上市。公务员真的是一条好的出路吗&#xff0c;现在考公务员还行不行&#xff1f;这需要结合我们当下的环境来综合分析。我们都经历了疫情&#xff0c;期间各个大厂频频将裁员大棒挥…