【数据结构】堆的实现及排序

news2024/11/21 2:34:31

目录

  • 一、树的相关概念及其特殊二叉树
    • 1、数的相关概念
    • 2、特殊二叉树
  • 二、堆
    • 1、堆的实现
      • 1.1、堆向下调整算法和向上调整算法的时间复杂度
      • 1.2、堆的构建
      • 1.3、堆的插入
      • 1.4、堆的删除
      • 1.5、取堆顶的数据、堆的个数及堆的判空
    • 2、堆的排序


一、树的相关概念及其特殊二叉树

讲堆之前,我们先讲讲树的相关概念及其特殊二叉树,因为堆是一种二叉树是一棵完全二叉树

1、数的相关概念

在这里插入图片描述

  • 节点的度:一个节点含有的子树的个数,如A的度为3
  • 叶子节点:度为0的节点,如F、L、H、M等
  • 非叶子节点:度不为0的节点,如B、C、D、G等
  • 双亲节点或父节点:若一个节点有子节点,则这个节点称为其子节点的父节点,如A是B的父节点
  • 孩子节点或子节点:一个节点含有子树的根节点称为该节点的子节点,如B是A的子节点
  • 兄弟节点:具有树同父节点的节点互称为兄弟节点,如B、C是兄弟节点
  • 树的度:一棵树中,节点最大的度是树的度,如上图树的度为3
  • 节点的层次:从根开始定义起,根为第一层,根的子节点为第二层,依次类推
  • 树的高度或深度:树中节点的最大层次为树的高度,如上图树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟,如L、M互为堂兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所以节点,如A是所以节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙,如所以节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林

2、特殊二叉树

二叉树是由一个根节点加上左子树和右子树组成
在这里插入图片描述

2.1、满二叉树

满二叉树每一个层的节点数都达到最大值
若一个满二叉树有K层,则第K层的节点数为2^(K-1)
总节点数为2^K-1
假设一个满二叉树有N个节点,则该树的高度为h=log(N+1)
在这里插入图片描述

2.2、完全二叉树

完全二叉树前N-1层是满的,最后一层可以不满,但是必须是从左到右是连续的
假设完全二叉树的高度是h
最多节点数为2^h-1
最少节点数为2^(h-1)
在这里插入图片描述

对任何一棵二叉树,如果度为0的叶子节点个数为N0,度为2的分支节点个数为N2,则有N0=N2+1,即度为0的节点总比度为2的节点多一个


二、堆

1、堆的实现

堆分为大堆和小堆,实际存储在一个数组当中。
大堆:树中所有父亲都大于等于孩子
小堆:树中所有父亲都小于等于孩子
在这里插入图片描述

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值
  • 堆总是一棵完全二叉树

堆孩子和父亲的下标关系

parent = (child - 1) / 2
leftchild = parent * 2 +1
rightchild = parent * 2 +2
在这里插入图片描述

1.1、堆向下调整算法和向上调整算法的时间复杂度

堆调整算法有一个要求:左右子树都必须是一个堆,才能调整

1、向下调整

以满二叉树为例:
在这里插入图片描述
在这里插入图片描述
由此可知,向下调整的时间复杂度为O(N)

代码实现:

void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}

		// 1、孩子大于父亲,交换,继续向下调整
		// 2、孩子小于父亲,则调整结束
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

以上代码建大堆,若要建小堆,则把a[child + 1] > a[child]if (a[child] > a[parent]) 改为 a[child + 1] < a[child]if (a[child] < a[parent])


2、向上调整

在这里插入图片描述
在这里插入图片描述
由此可知,向下调整的时间复杂度为O(N*logN)

代码实现:

void AdjustUp(HPDataType* a, int child)
{
	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;
		}
	}
}

以上代码建大堆,若要建小堆,则把if (a[child] > a[parent]) 改为 if (a[child] < a[parent])

总结:

向下调整:节点多,调整少,节点少,调整多
向上调整:节点少,调整多,节点多,调整多
所以建堆,建议用向下调整


1.2、堆的构建

我们给一个数组,但还不是一个堆,我们可以通过建堆算法,把它构成一个堆。但是根节点的子树都不是堆,那我们怎么调整呢?我们可以从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就形成堆了。

在这里插入图片描述

此时就构建出一个大堆:
在这里插入图片描述

代码实现:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

extern void AdjustDown(HPDataType* a, int n, int parent);

// 堆的构建
void HeapCreate(HP* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;
	//建堆算法
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}
}

// 初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

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

// 交换
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

// 打印堆
void HeapPrint(HP* php)
{
	assert(php);
	for (int i = 0; i < php->size; ++i)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

// 向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}

		// 1、孩子大于父亲,交换,继续向下调整
		// 2、孩子小于父亲,则调整结束
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

int main()
{
	HP hp;
	int a[] = { 1,5,3,8,7,6 };
	HeapInit(&hp);
	int n=sizeof(a) / sizeof(int);
	HeapCreate(&hp, a, n);
	HeapPrint(&hp);
	HeapDestroy(&hp);
	return 0;
}

结果:
在这里插入图片描述


1.3、堆的插入

先插入一个10到堆尾,再进行向上调整算法,直到形成堆

在这里插入图片描述
代码实现:

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	// 扩容
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	php->size++;
	// 向上调整
	AdjustUp(php->a, php->size - 1);
}

1.4、堆的删除

删除堆是删除堆顶元素,将堆顶的数据和最后一个数据交换,然后删除最后一个数据,在进行向下调整算法。

在这里插入图片描述
代码实现:

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}

1.5、取堆顶的数据、堆的个数及堆的判空

代码如下:

//取堆顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}
//堆的个数
int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

2、堆的排序

堆的排序分两个步骤:

1、建堆

  • 升序:建大堆
  • 降序:建小堆

2、利用堆删除思想来进行排序

  • 把堆顶数据和最后一个数据进行交换,把最后一个数不看做堆里面的,相当于n-1个数,向下调整,选出次大的数。

代码如下:

#include <stdio.h>
typedef int HPDataType;

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
// 向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}

		// 1、孩子大于父亲,交换,继续向下调整
		// 2、孩子小于父亲,则调整结束
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
int main()
{
	int a[] = { 1,5,3,8,7,6 };
	int n = sizeof(a) / sizeof(int);
	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;
	}
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

运行结果:
在这里插入图片描述


TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素
如:专业前10、世界500强等

举例:N个数中找最大的前K个数
第一种方法:建立一个N个数的大堆,删除K次,依次取堆顶
但是这个方法数据如果太大,就会放不进内存,直接存放在磁盘文件中,但是磁盘文件不能创建堆,所以次方法不适合。其时间复杂度为:O(N+logN*K),空间复杂度为:O(1)

第二种方法:建立K个数的小堆,然后依次遍历数据,不堆顶大的数据,就替换堆顶,在向下调整,最后最大的K个数就在这个小堆里面。 时间复杂度:O(N*logK),空间复杂度:O(K)

假设有一个data.txt文件中有以下数据:
在这里插入图片描述
要找出前5个最大的数,代码如下:

#include <stdio.h>
typedef int HPDataType;

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
// 向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}

		// 1、孩子大于父亲,交换,继续向下调整
		// 2、孩子小于父亲,则调整结束
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
int main()
{	
	int minHeap[5];
	int k = 5;
	FILE* fout = fopen("data.txt", "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}
	for (int i = 0; i < 5; ++i)
	{
		fscanf(fout, "%d", &minHeap[i]);
	}
	// 建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(minHeap, k, i);
	}
	int val = 0;
	while (fscanf(fout, "%d", &val) != EOF)
	{
		if (val > minHeap[0])
		{
			minHeap[0] = val;
			AdjustDown(minHeap, k, 0);
		}
	}
	for (int i = 0; i < k; ++i)
	{
		printf("%d ", minHeap[i]);
	}
	printf("\n");
	fclose(fout);
	return 0;
}

运行结果:
在这里插入图片描述

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

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

相关文章

透视虎牙斗鱼三季报:游戏直播在各自“求变”中见分晓

游戏直播行业&#xff0c;依然硝烟弥漫。 经历千播大战、熊猫出局的洗礼后&#xff0c;虎牙和斗鱼双方缠斗升级&#xff0c;另有B站和抖音、快手等视频平台来势汹汹&#xff0c;抢夺仅有的市场蛋糕。 而在游戏行业遇冷、政策趋严等因素多重考验下&#xff0c;这场争夺战无疑将…

fsync

由于目前操作系统都具有延迟写(delayed write)功能&#xff0c; fwrite/write并不会直接把数据写到磁盘上&#xff0c; 而是在buffer满时才开始写入到磁盘。 要想把数据写到磁盘上&#xff0c;需要调用fsync函数 open(fd) write(fd) fsync(fd) close(fd) 或者 fopen(fp…

金仓数据库KingbaseES查询计划剖析

目录 1、KingbaseES数据库中的查询生命周期 2、数据设置 3、KingbaseES解释一个查询 4、什么是数据库中的缓冲区和缓存&#xff1f; 5、VERBOSE 命令参数 6、KingbaseES中的 FORMAT 解释 7、总结EXPLAIN使用方式 8、执行计划查看 了解KingbaseES查询计划对于开发人员和…

HarmonyOS应用API手势方法-绑定手势方法

述&#xff1a;为组件绑定不同类型的手势事件&#xff0c;并设置事件的响应方法。 Api&#xff1a;从API Version 7开始支持 一、绑定手势识别&#xff1a; 通过如下属性给组件绑定手势识别&#xff0c;手势识别成功后可以通过事件回调通知组件。 名称参数类型默认值描述ge…

银河麒麟V10+达梦数据库8保姆级安装教程

银河麒麟V10达梦数据库8保姆级安装教程 一、系统和数据库的下载 银河麒麟V10版本&#xff1a; 首页 → 桌面操作系统 → 银河麒麟桌面操作系统V10 → 试用&#xff08;填写信息&#xff09; → 点击地址进行下载&#xff08;X86centos7&#xff09; #如果不想进行上面的操作,…

Casbin——Java版本(笔记)

文章目录一、Casbin 是什么&#xff1f;二、快速开始2.1 载入配置2.2 如何判断权限2.3 model.conf2.3.1 基本格式2.3.2 SpringBoot下的使用2.3.3 匹配的函数内置函数自定义函数2.3.4 基于角色的访问控制角色的层次区分用户和角色隐式角色权限域内RBAC角色与函数2.3.5 优先级模型…

react事件系统(老版本)

带着问题阅读探索 React 为什么有自己的事件系统&#xff1f;什么是事件合成 &#xff1f;如何实现的批量更新&#xff1f;事件系统如何模拟冒泡和捕获阶段&#xff1f;如何通过 dom 元素找到与之匹配的fiber&#xff1f;为什么不能用 return false 来阻止事件的默认行为&…

python【PyQt5】的环境搭建和使用(全网最全)其一

什么是pyQT pyqt是一个用于创建GUI应用程序的跨平台工具包&#xff0c;它将python与qt库融为一体。也就是说&#xff0c;pyqt允许使用python语言调用qt库中的API。这样做的最大好处就是在保存了qt高运行效率的同时&#xff0c;大大提高开发效率。因为&#xff0c;使用python语言…

城市路边停车收费系统/停车收费管理系统

摘 要 近年来&#xff0c;随着社会的进步和发展&#xff0c;车辆也在迅速增加&#xff0c;城市交通的瓶颈不仅体现在道路交通的拥挤上&#xff0c;也体现在传统停车场管理效率和安全性大大滞后于社会的需要&#xff0c;给人们的生活带来了极大的不便。尤其&#xff0c;随着汽车…

二、MongoDB简介及基本操作

mongodb是一个基于文档的强大、灵活、易于扩展的通用型数据库。是基于分布式文件存储的数据库。其由 C 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 mongodb也是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&…

运动健身买什么耳机好用、最优秀的健身耳机推荐分享

冬天绝对是个减肥的好季节&#xff0c;因为这个季节天气比较冷&#xff0c;我们在运动过程中消耗的热量也就会更多&#xff0c;因此选择一款不错的运动耳机来用坚持就显得尤为重要了。这款运动耳机要能稳定在耳朵上&#xff0c;还要具备防水功能&#xff0c;同时音质上也要有保…

闲人闲谈PS之三十四——项目成本费用控制阈值

**惯例闲话&#xff1a;**最近有小伙伴问闲人有没有PS顾问资源&#xff0c;闲人问了一圈&#xff0c;结果发现都没有档期&#xff0c;不免让闲人有些失落&#xff0c;好心答应帮忙&#xff0c;结果帮不上…但是隐隐约约觉得在几年前说的话被应验了&#xff0c;PS模块一定是个热…

Ubuntu G++ 编译C++源文件

工程项目代码简短的时候使用 G 进行功能模块测试 过程分为&#xff1a; 预处理&#xff1a;展开头文件&#xff0c;去掉主食&#xff0c;条件编译和文件包含编译&#xff1a;检查语法&#xff0c;生成汇编代码汇编&#xff1a;汇编代码转换成机器码链接&#xff1a;Link 主要是…

王学岗音视频开发(一)—————配置NDK开发环境

Android studio准备 Android studio需要下载Android6.0版本(Android SDK Platform 23)&#xff0c;最小支持Android6.0 NDK 下载 cmake下载安装 Android studio 代理配置 dl.google.com可能会被屏蔽&#xff0c;首先查下其IP地址。查到IP地址后修改etc/hosts文件。 Andr…

pytorch深度学习实战lesson25

第二十五课 network in network(NIN) NIN 叫做network in network或者叫做网络中的网络。这个网络现在用的不多&#xff0c;几乎很少被用到。但是它里面提出了比较重要的概念&#xff0c;在之后很多网络都会被持续的用到。所以今天认识一下这一个网络。 目录 理论部分 实践部…

高并发下丢失更新的解决方案

作者&#xff1a;谢益培 1 背景 关键词&#xff1a;并发、丢失更新 预收款账户表上有个累计抵扣金额的字段&#xff0c;该字段的含义是统计商家预收款账户上累计用于抵扣结算成功的金额数。更新时机是&#xff0c;账单结算完成时&#xff0c;更新累计抵扣金额累计抵扣金额账…

ImmunoChemistry艾美捷Annexin V-FITC细胞凋亡检测试剂盒方案

膜联蛋白V是具有血管抗凝血活性的钙和磷脂结合蛋白家族的成员。体外实验结果表明&#xff0c;它可能通过与凝血酶原竞争磷脂酰丝氨酸&#xff08;PS&#xff09;结合位点而在抑制凝血中发挥作用。在健康细胞中&#xff0c;PS通常保存在细胞膜的内小叶&#xff08;胞质侧&#x…

网络是怎样连接的--TCP大致控制流程

文章目录5.1 协议栈内部结构5.2 套接字作用5.3 创建套接字5.4 两类控制信息5.5 连接操作的实际过程5.6 收发数据5.6.1将http请求消息发给协议栈5.6.2 注意对较大数据进行拆分5.6.3 序号和ACK5.6.4 等待超时时间5.6.5 窗口机制提升效率5.6.6 ACK与窗口合并5.7 断开连接5.8 删除套…

MCE | 新冠 德尔塔病毒

冠状病毒&#xff0c;其表面有突出的棒状尖峰&#xff0c;在电镜下可以观察到像王冠一样的放射状凸起而得名。冠状病毒的基本结构如图 1 所示&#xff0c;包括刺突糖蛋白 (S)、包膜 (E)、膜 (M) 和核衣壳 (N)。 图 1. 冠状病毒结构2020 年石正丽教授在 Nature 发表的论文 A …

浅识JVM

⭐️前言⭐️ 本篇文章&#xff0c;博主分享的是在面试中JVM常考的考点&#xff0c;希望这篇文章能够对你有用。 &#x1f349;博客主页&#xff1a; &#x1f341;【如风暖阳】&#x1f341; &#x1f349;精品Java专栏【JavaSE】、【备战蓝桥】、【JavaEE初阶】、【MySQL】、…