堆及其堆排序

堆是一种特殊的数据结构,底层实现是靠数组存储以及完全二叉树的性质


文章目录

  • 一、堆概念
  • 二、堆实现
  • 三、堆源码
  • 四、堆排序


一、堆概念

完全二叉树用数组来存储可以达到空间的有效利用且可以直观反映它们之间的逻辑关系,双亲与孩子之间的关系。一般在数组中都是从0开始,这里存储完全二叉树根也从0开始,那么它们之间父子下标关系:

  • 双亲:prent = (child - 1)/2
  • 左孩子:lchild = parent*2 + 1
  • 右孩子:rchild = parent*2 + 2

在这里插入图片描述

用数组存储的值可以将其想象为一棵二叉树,想象出来的二叉树再加以限制就可以得到堆。这棵树中每个父亲节点的值都要大于或者等于它的孩子节点;或者每个父亲节点的值都小于或者等于它的孩子节点;且这棵树是一棵完全二叉树大于或者等于孩子节点的完全二叉树称作大堆,小于或者等于孩子节点的完全二叉树称作小堆

小堆
在这里插入图片描述

大堆
在这里插入图片描述
堆在逻辑上也是一棵完全二叉树

而大堆的堆顶根是整棵树中最大的,小堆的堆顶根是整棵树中最小的,大堆和小堆是可以快速选出最大值或者最小值

而堆也是基于数组和完全二叉树来实现的数据结构,堆不仅仅可以存储数据,也可以用来对数据进行排序。
堆既然是一种数据结构那么也可以对其进行插入数据和删除数据,但是在插入数据之后还有保证其是堆,所以插入后也要对其调整。而堆是依靠于数组实现的,而数组插入数据在尾部插入最为高效,所以每次在数组尾部插入数据,但是在插入数据后空间就有可能会不够,那么当空间不够时就要考虑扩容,因此就要用动态数组实现堆,静态数组空间给小了可能空间不够,给大了可能会浪费空间

在每一次插入数据时要保证还是大堆或者小堆,那么插入的这个值要与它的父亲比较,如果待插入的值比父亲大,那么就与父亲值交换,如果比父亲值小,则保持不动
在这里插入图片描述
而在数组中的插入我们要将其想象为是对一棵完全二叉树进行调整,每次插入有可能都会对这棵树调整,但是这种调整只会影响孩子它的祖先,这种对于这棵树从下面往根调整的方法被称为向上调整算法而向上调整最多调整二叉树的高度次 logn

而小根堆也是一样,每次插入当孩子比父亲小,将其两个交换,再往上一层比较,遇见比其小或者到达树根就停不再调整

二、堆实现

堆存储结构

typedef int HDataType;
typedef struct Heap
{
	HDataType* a;
	int size;
	int capacity;
}Heap;

初始化堆

void HeapInit(Heap* php)
{
	assert(php);
	php->a = (HDataType*)malloc(sizeof(HDataType) * 4);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}

		php->size = 0;//初始时没有元素
		php->capacity = 4;//初始容量为4
}

由于后边堆的插入和删除都会交换数据在此写一个交换接口

void Swap(HDataType* e1, HDataType* e2)
{
	HDataType tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

堆插入数据
以大堆为例。在插入数据时要保证为堆,那么就要对其向上调整
向上调整

在数组尾部插入数据,插入的这个数据要与其父亲比较,比父亲大,它们要交换,且孩子下标与父亲下标都要发生改变它们都往上一层走。孩子下标child = parent父亲下标parent = (child - 1) / 2;直到孩子小于父亲或者孩子走到了树根停止

//向上调整算法
//大根堆
void AdjustUp(HDataType* a, int n)
{
	int child = n;
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])//如果要建小堆只需要将  '>'改为'<'
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}

		else
		{
			break;
		}
	}
}
//每次插入一个就对其进行向上调整使其成为堆
void HeapPush(Heap* php, HDataType x)
{
	assert(php);
	//扩容
	if (php->size == php->capacity)
	{
		HDataType* tmp = (HDataType*)realloc(php->a, sizeof(HDataType) * php->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		
			php->a = tmp;
			php->capacity *= 2;
	}
   
	php->a[php->size] = x;//插入数据

	AdjustUp(php->a, php->size);//调整使其还是堆
	php->size++;
}

堆删除数据

堆通过插入数据将其建成了一个大堆,堆顶也就是最大的了,那么我们在对对的数据进行删除时应该删除堆顶还是堆尾,如果是删除最后一个的话那么可以很轻松删除,直接将数组长度减一就可,但是这没有很大的意义。有一句话叫做擒贼先擒王,将最大的做掉然后老二才能上来,而且堆不仅仅是存储数据,既可以实现选数据也可以实现排序,选数据选出最大的前k个或者最小的前k个,因此在堆删除数据时,删除堆顶可以很轻松选出堆中前k个数据,但是在直接删除堆顶时,在逻辑上不再是一棵树了
在这里插入图片描述
因此有一种很妙的方法先将堆最后一个与堆顶交换,然后再删除堆中最后一个元素
在这里插入图片描述
但是交换并删除后不再是堆了,我们需要将其再次调整为堆,由于交换的是堆顶和堆尾,只动了这两个元素,那么堆中其它位置元素没有发生改变也就是其它位置还是堆,而这时交换过后树不一定是堆,但是除了树根之外其余的子树都还是堆,这时需要从树根对其调整,这种调整也叫做向下调整算法

调整到叶子或者遇见孩子比它小的就不会再调
向下调整
在这里插入图片描述
向下调整最坏的调整次数为高度次logn

//向下调整算法是基于根的左右子树都是大根堆或者小根堆的前提下
// 由于在插入数据是就已经将堆调整为大根堆了,因此在删除时将根与最后一个数据交换,然后再队整个树进行向下调整
// 但是除了根之外其他的子树都已经是大根堆了,因此就好调整了
//原本它的左右子树已经是大根堆了
void AdjustDown(HDataType* a,int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认左孩子值较大
	while (child < n)
	{
		if (child+1<n&& a[child] < a[child + 1])//这两个判断条件位置不可改变,先判断值可能会导致非法访问
		{
			child++;
		}

		//调整时注意极端情况,调到叶子节点,且右孩子大于左孩子但是这时的右孩子是不在数组范围内的,那么这时如果孩子大于父亲值,那么就会交换
		//会导致出现随机值,因此要保证它的左孩子节点和右孩子节点是在数组长度范围内
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}

		else
		{
			break;
		}
	}
}

判断堆空

bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

而将堆顶数据删除之后

//堆的删除
void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));//删空
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size,0);//调整范围变为堆删除数据后的大小
	
}

获取堆顶元素

//获取堆根
HDataType HeapTop(Heap* php)
{
	assert(php);
	//assert(!HeapEmpty(php));
	return php->a[0];
}

堆大小

//获取堆大小
int HeapSize(Heap* php)
{
	return php->size;
}

销毁堆

//销毁堆
void HeapDestroy(Heap* php)
{
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

三、堆源码

Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

typedef int HDataType;
typedef struct Heap
{
	HDataType* a;
	int size;
	int capacity;
}Heap;

void HeapInit(Heap* php);
void HeapPush(Heap* php, HDataType x);
void HeapDestroy(Heap* php);
bool HeapEmpty(Heap* php);
void HeapPop(Heap* php);
int HeapSize(Heap* php);
HDataType HeapTop(Heap* php);
int HeapSize(Heap* php);

heap.c

#include"Heap.h"
//初始话堆
void HeapInit(Heap* php)
{
	assert(php);
	php->a = (HDataType*)malloc(sizeof(HDataType) * 4);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	else
	{
		php->size = 0;
		php->capacity = 4;
	}
}

void Swap(HDataType* e1, HDataType* e2)
{
	HDataType tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}
//向上调整算法
//大根堆
void AdjustUp(HDataType* a, int n)
{
	int child = n;
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}

		else
		{
			break;
		}
	}
}

void HeapPush(Heap* php, HDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HDataType* tmp = (HDataType*)realloc(php->a, sizeof(HDataType) * php->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}


		php->a = tmp;
		php->capacity *= 2;


	}

	php->a[php->size] = x;
	AdjustUp(php->a, php->size);
	php->size++;
}


void AdjustDown(HDataType* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}

		else
		{
			break;
		}
	}
}

//堆的删除
void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	Swap(&php->a[0], &php->a[php->size - 1]);

	AdjustDown(php->a, php->size - 1, 0);
	php->size--;
}//判空
bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

//获取堆根
HDataType HeapTop(Heap* php)
{
	assert(php);
	//assert(!HeapEmpty(php));
	return php->a[php->size];
}

//获取堆大小
int HeapSize(Heap* php)
{
	return php->size;
}

//销毁堆
void HeapDestroy(Heap* php)
{
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

test.c

#include"Heap.h"

//
void test()
{
	Heap php;
	HeapInit(&php);
	HeapPush(&php, 2);
	HeapPush(&php, 3);
	HeapPush(&php, 4);
	HeapPush(&php, 10);
	///*int k = 0;
	//k = 3;
	while (!HeapEmpty(&php))
	{
		HeapPop(&php);
		printf("%d ", HeapTop(&php));
		;
	}
  // HeapPop(&php);
	/*printf("%d ", HeapTop(&php));
	
	HeapPop(&php);
	printf("%d ", HeapTop(&php));
	HeapPop(&php);
	printf("%d ", HeapTop(&php));
	HeapPop(&php);
	printf("%d ", HeapTop(&php));
	HeapPop(&php);
	printf("%d ", HeapTop(&php));*/
}

int main()
{
	test();
	return 0;
}

四、堆排序

堆如何做到排序的?

堆排序可以借助堆这个数据结构,但是如果是在学校考试的时候你要手搓一个堆结构吗,这时耗费大量时间等你写好一个堆考试都结束了。而且其空间复杂度为O(N),由于将数组内容插入到堆中去,而堆内部是需要开空间的。

因此堆排序可以脱离堆这个结构。对数组进行排序,我们可以将数组看作一个完全二叉树,但是完全二叉树不一定是堆,而可以将这棵完全二叉树建成堆,可以向上调整建堆(模拟堆插入过程)将整个数组看作是堆里面的,然后从第一个数开始向上调
在这里插入图片描述
向上调整建堆

//向上建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

在这里插入图片描述
在这里插入图片描述

向上调整大堆已经建成,也就是模拟堆插入的过程,向上调整建堆时间复杂度为nlogn

也可以向下调整建堆

向下调整建堆不能从根就开始调,因为不一定满足它的左右子树都是大堆或者小堆,向下调整要满足左右子树是大堆或者小堆。从数组首元素不行,那么有人想到了从数组末尾开始向下调整,也就是完全二叉树的叶子去调,但是叶子只有一个无意义,那么从最后一个叶子它的父亲开始调,然后调好之后依次往前调,从后往前跳可以保证它的左右子树都是堆,倒着来调整

在这里插入图片描述

for (int i = (n - 1-1) / 2; i >= 0; i--)
	{
		//那么这里就从数组下标末尾开始向上调
		AdjustDown(a, n, i);
	}

在这里插入图片描述
在这里插入图片描述

堆已经建好,那么堆排序排升序是建大堆还是小堆,建大堆,排降序建小堆。

升序:因为堆顶是最大或者最小的,大堆每一次可以得出最大的那个数,让堆顶元素和最后一个交换,然后再缩小调整范围,这时堆的最后一个元素不参与调整,然后让0~n-1调整,每调整一次就会将堆中最大的数弄到最后,然后它又不参与调整了,每一次调整范围减一,最后就将数据排为升序的了
在这里插入图片描述

降序:建小堆,堆顶为最小,堆顶与最后一个交换可以将最小值交换到最后,得到堆中最小值,这时交换后的最小值不再参与堆调整的过程,然后调整堆,每次交换后堆的调整范围都会缩小一个,直到最后将这些数据排为降序了
在这里插入图片描述

向上调整与向下调整时间复杂度比较
向上调整nlogn
向下调整logn

向上调整算法时间复杂度nlog^n 在这里插入图片描述

向下调整时间复杂度为N因此建堆时采用向下调整算法较好

在这里插入图片描述

这时堆排序已成,
堆排序源码

#include<stdio.h>

void Swap(int* e1, int* e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

//向下调整算法
void AdjustDown(int* 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 += 1;
		}
   
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2+1;
		}

		else
		{
			break;
		}
	}
}


void AdjustUp(int* a, int n)
{
	int child = n;
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])//如果要建小堆只需要将  '>'改为'<'
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}

		else
		{
			break;
		}
	}
}

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

	//向下调整建堆
	/*for (int i = (n - 1-1) / 2; i >= 0; i--)
	{
		那么这里就从数组下标末尾开始向上调
		AdjustDown(a, n, i);
	}*/

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
		
	}
}
 

void TestHeapSort()
{
	int arr[] = { 6,7,8,9,1,2,3,4,5,6,7,8,9 };
	HeapSort(arr, sizeof(arr) / sizeof(arr[0]));

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	TestHeapSort();
	return 0;
}

堆排序时间复杂度为NlogN
在这里插入图片描述


堆可以实现得到很多数据中前k个最大值或者最小值,在选数中及其重要

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

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

相关文章

一文说透虚拟内存

为什么我们需要虚拟内存 提供一个虚拟化封装&#xff0c;让上层的程序员不用担心内存分配&#xff0c;物理地址的总大小。同时如果要手动管理内存是一件麻烦的事&#xff0c;比如一个程序读到另一个程序的物理地址&#xff0c;并且也很难保障多个处理器不会同时读取写入同一块…

GitHub Action 使用

GitHub Action 使用 GitHub Actions 是一种持续集成和持续交付 (CI/CD) 平台&#xff0c;可用于自动执行生成、测试和部署管道。 您可以创建工作流程来构建和测试存储库的每个拉取请求&#xff0c;或将合并的拉取请求部署到生产环境。GitHub 提供 Linux、Windows 和 macOS 虚拟…

训练AI数据模型所需要的高性能计算机配置

目录 配置一 配置二 配置三 云服务器和超级计算机 AI模型训练是一种机器学习的过程&#xff0c;通过训练深度学习模型来自动化处理数据和完成任务。AI训练可以帮助企业和研究人员开发出更加智能、高效的应用&#xff0c;从而提高生产力和创新能力。 以下是按训练性能从低到…

对挖矿病毒 kdevtmpfsi 的处理办法

需求背景&#xff1a; 服务器CPU资源使用一直处于100%的状态&#xff0c;通过 top 命令查看&#xff0c;发现可疑进程 kdevtmpfsi。通过 google搜索&#xff0c;发现这是挖矿病毒。 排查方法 首先&#xff1a;查看 kdevtmpfsi 进程&#xff0c;使用 ps -ef | grep kdevtmpfsi …

数据结构之线性表

文章目录1. 线性表的定义2. 线性表的抽象数据类型3. 线性表的顺序存储结构4. 线性表的链式存储结构5. 单链表结构和顺序存储结构优缺点6. 静态链表7. 循环链表8. 双向链表1. 线性表的定义 零个或多个数据元素的有限序列 线性表的定义中强调有限和序列两个方面。 有限&#xff…

华硕ROG|玩家国度 冰刃7双屏 GX650PY Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原

华硕ROG|玩家国度 冰刃7双屏 GX650PY Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原 文件地址&#xff1a;https://pan.baidu.com/s/1snKOsH3OMl3GZLqeAf-GLA?pwd8888 华硕工厂恢复系统 &#xff0c;安装结束后带隐藏分区以及机器所有驱动软件 需准备一个…

【树】你真的会二叉树了嘛? --二叉树LeetCode专题Ⅲ

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

MySQL基础-变量/流程控制/游标/触发器

文章目录MySQL基础-变量/流程控制/游标/触发器一、变量1、系统变量2、用户变量二、流程控制1、分支语句2、循环语句3、跳转语句三、游标1、概念2、使用四、触发器1、触发器概念2、触发器使用3、触发器的优缺点MySQL基础-变量/流程控制/游标/触发器 一、变量 在MySQL数据库的存…

RocketMQ水平扩展及负载均衡详解

文章目录 Broker端水平扩展Broker负载均衡commit logProducer负载均衡Consumer负载均衡集群模式广播模式RocketMQ是一个分布式具有高度可扩展性的消息中间件。本文旨在探索在broker端,生产端,以及消费端是如何做到横向扩展以及负载均衡的。 Broker端水平扩展 Broker负载均衡…

前端项目-05-轮播图banner和Floor组件开发-全局轮播图组件抽取

目录 1-轮播图模块数据开发 2-floor组件开发 3-抽取全局轮播图组件 1-轮播图模块数据开发 轮播图需要用到swiper插件&#xff0c;先安装5.4.5版本的swiper&#xff1a;npm install --save swiper^5.4.5 --force 模拟从服务器获取数据&#xff1b; 1-编写mockRequests的js…

【ACWing算法课】二分查找

前言&#x1f349; 二分查找一个简单的算法&#xff0c;但是因为边界问题往往写不好。特此记录模板&#xff0c;以便快捷使用。 [二分查找]从列表q找到第一个>k的数&#xff0c;返回位置&#x1f451; [二分查找]从列表q找到第一个>k的数&#xff0c;返回位置def bsear…

three.js实现3d球体树状结构布局——树状结构的实现

目录系列文章安装依赖基本分析实体类场景相机渲染器辅助线环境光点光源球形几何体球形几何体的材质线几何体线几何体的材质物体文本轨道控制实现效果实现源码参考文档系列文章 three.js实现3d球体树状结构布局——添加入场、出场、点击放大等动画 安装依赖 npm i three three…

Adaptive AUTOSAR——Time Synchronization(VRTE 3.0 R21-11)

15 Time Synchronization 15.1 What is Time Synchronization? 时间同步是自适应平台基础中的一个功能集群。时间同步通过库向应用程序提供C API&#xff0c;该库作为RTA-VRTE入门套件的一部分提供&#xff0c;并与应用程序链接以访问该功能。 本版本包含非常少量的时间同步…

ASIC-WORLD Verilog(1)一日Verilog

写在前面 在自己准备写一些简单的verilog教程之前&#xff0c;参考了许多资料----asic-world网站的这套verilog教程即是其一。这套教程写得极好&#xff0c;奈何没有中文&#xff0c;在下只好斗胆翻译过来&#xff08;加了自己的理解&#xff09;分享给大家。 这是网站原文&…

Helm学习笔记

文章目录概念定义helm组件helm的工作流程helm安装helm仓库helm部署应用helm应用的更新或回退或卸载概念 定义 学习helm首先得了解helm是什么&#xff0c;我们先来看一下helm的定义&#xff1a;helm是将kubernetes的各种资源对象打包&#xff0c;类似于Linux中的yum工具&#…

【HTML系列】第六章 · 框架标签、HTML实体、HTML全局属性和meta元信息

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

【前端面试题——微信小程序】

目录1.请谈谈wxml与标准的html的异同&#xff1f;2.请谈谈WXSS和CSS的异同&#xff1f;3.请谈谈微信小程序主要目录和文件的作用&#xff1f;4.请谈谈小程序的双向绑定和vue的异同&#xff1f;5.简单描述下微信小程序的相关文件类型&#xff1f;6.微信小程序有哪些传值(传递数据…

jsp+javaEE+mysql校园物品租赁系统dzkf5294程序

1&#xff0e;物品信息管理&#xff1a;管理员发布物品信息后&#xff0c;普通用户便可以查询到该物品信息&#xff0c;用户选择某个物品信息&#xff0c;查询物品信息&#xff0c;管理员审核添加&#xff0c;或删除物品信息。 2&#xff0e;租赁管理&#xff1a;管理员发布租赁…

ChatGPT大解密:带您探讨机器学习背后的秘密、利用与发展

一、什么是机器学习&#xff1f;二、ChatGPT 的运作原理三、ChatGPT 生活利用1、自然语言处理2、翻译3、自动回复四、ChatGPT vs 其他相关技术五、ChatGPT 的未来1、未来发展2、职业取代3、客服人员4、翻译人员5、语言学家6、机遇与挑战六、结语这篇文章&#xff0c;将带着各位…

ThreeJS-投影、投影模糊(十七)

无投影&#xff1a; 完整的代码&#xff1a; <template> <div id"three_div"></div> </template> <script> import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/Or…