深入浅出堆—C语言版【数据结构】

这篇具有很好参考价值的文章主要介绍了深入浅出堆—C语言版【数据结构】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

深入浅出堆—C语言版【数据结构】

二叉树概念博客:http://t.csdn.cn/XIW84

目录

1. 了解堆

1.1 堆的概念

1.2 堆的性质:

1.3 堆的结构图片

1.3.1 小堆

1.3.2 大堆

2. 堆的实现

2.1 插入数据进堆

2.2 向上调整函数

2.3 堆的删除

2.4 向下调整

3. 堆的应用

3.1 建堆(两种方式)

3.1.1 建堆方式1

3.1.2 建堆方式2

3.2 堆排序 

3.3 堆的TOP—K问题


1. 了解堆

1.1 堆的概念

深入浅出堆—C语言版【数据结构】

1.2 堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

1.3 堆的结构图片

1.3.1 小堆

 满足下面条件的是小堆

深入浅出堆—C语言版【数据结构】

深入浅出堆—C语言版【数据结构】

1.3.2 大堆

满足下面条件的是大堆

深入浅出堆—C语言版【数据结构】

深入浅出堆—C语言版【数据结构】

 注意不一定是从大到小、从小到大存储的!!!


堆有什么作用呢?

深入浅出堆—C语言版【数据结构】

下面来细讲,别走开!!!



2. 堆的实现

2.1 插入数据进堆

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)
		{
			printf("realloc fail\n");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newcapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

注意点!!!

假如一开始我们的堆是小堆,但是在插入数据以后要保持还是小堆,要将插入的数据的大小和它的父亲进行比较,比较的两种情况:

1. 如果插入的数据比父亲还要大,那就不需要调整

2. 如果插入的数据比父亲还要小,那就需要调整   

  如果需要调整,我们就要使用向上调整算法,保持插入数据后的堆还是小堆


2.2 向上调整函数

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;
		}
	}
}

2.3 堆的删除

能不能使用覆盖删除呢—不能!!!

使用覆盖删除,会打乱父子之间的下标关系,父子关系就会全部乱掉,因此我们使用下面的方法来删除数据


1. 先将下标为0位置的数据和下标最大的数据进行交换

2. 然后直接size--

3. 然后还需要使用向下调整算法,把堆再调整为小堆

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

	Swap(&(php->a[0]), &(php->a[php->size - 1]));1.交换
	php->size--;//2. 删除堆顶元素

	AdjustDwon(php->a, php->size, 0);//向下调整,保证还是小堆
}

2.4 向下调整

void AdjustDwon(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		// 选出左右孩子中小那个
        //这里的if里面的判断大小尽量写成小于是小堆,大于是大堆
		if (child+1 < size && a[child+1] < a[child])
		{
			++child;
		}

		// 孩子跟父亲比较
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}	
}


3. 堆的应用

3.1 建堆(两种方式)

3.1.1 建堆方式1

利用插入元素的方式,向上调整建堆 

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	
	while (child > 0)
	{
		//if (a[child] < a[parent])
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
/

void HeapSort(int* a, int n)//传一个数组过来,还有元素个数
{
	// 建堆方式1:O(N*logN)
	for (int i = 1; i < n; ++i)
	{
		AdjustUp(a, i);//从插入的第二个元素开始
	}
}

建堆方式1的时间复杂度 ——错位相减法

深入浅出堆—C语言版【数据结构】


3.1.2 建堆方式2

利用向下调整建堆

方法:找到最后一个元素的父亲,并从这个位置开始向下调整                                    

void HeapSort(int* a, int n)
{

	// 建堆方式2:O(N)
	for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}

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

建堆方式2的时间复杂度——错位相减法


深入浅出堆—C语言版【数据结构】

深入浅出堆—C语言版【数据结构】


3.2 堆排序 

排升序,建大堆,再向下调整

为什么建大堆呢?

建大堆,堆顶元素是最大的数,让堆顶元素和最后一个元素交换,再向下调整,注意:这里向下调整时是调整的数组大小-1个,也就是调整刚刚交换下来前面的数据


排降序,建小堆,再向下调整

void HeapSort(int* a, int n)
{

	// 建堆方式2:O(N)
	for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}

	// O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);//这里的end是9,传过去向下调整的元素个数也是9,
                             //就不会调整刚刚从堆顶传下来的数据
		AdjustDwon(a, end, 0);
		--end;
	}

3.3 堆的TOP—K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

实现思路: 

深入浅出堆—C语言版【数据结构】

这样空间复杂度非常小


注意:

           求前k个最大的数,是建小堆

           解释:由于建立的前k个数是小堆,后面n-k个数都可能比对顶的数值大,比堆顶的元素大,就替换堆顶的元素,然后再向下调整,保持前k个数是小堆,然后再比较····

           求前k个最小的数,是建大堆(同上


 代码实现:

void PrintTopK(int* a, int n, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	int* kMinHeap = (int*)malloc(sizeof(int)*k);
	assert(kMinHeap);
	for (int i = 0; i < k; ++i)//将a数组里面前10个数赋值给KMinHeap
	{
		kMinHeap[i] = a[i];
	}
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)//向下调整建堆,建立k个数的小堆
	{
		AdjustDwon(kMinHeap, k, i);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	for (int j = k; j < n; ++j)
	{
		if (a[j] > kMinHeap[0])
		{
			kMinHeap[0] = a[j];
			AdjustDwon(kMinHeap, k, 0);//再向下调整,保持前k个数是小堆
		}
	}

	for (int i = 0; i < k; ++i)
	{
		printf("%d ", kMinHeap[i]);
	}
	printf("\n");
}

void TestTopk()
{    
    //随机生成一万个数字,每个数字%1百万,这一万都是比一百万小的数字,
    //我们将其中的10个数改为比一百万大的值
	int n = 10000;
	int* a = (int*)malloc(sizeof(int)*n);
	srand(time(0));
	for (int i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[120] = 1000000 + 5;
	a[99] = 1000000 + 6;
	a[0] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;

	PrintTopK(a, n, 10);
}


本文讲的是二叉树的顺序存储结构(堆)的实现,下期我们来讲二叉树的链式存储结构,到时候记得来支持小余哦!!!

如果觉得文章不错,期待你的一键三连哦,你个鼓励是我创作的动力之源,让我们一起加油,顶峰相见!!!文章来源地址https://www.toymoban.com/news/detail-441177.html

到了这里,关于深入浅出堆—C语言版【数据结构】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法】深入浅出:单链表的实现和应用

      🌱博客主页:青竹雾色间. 😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注  ✨ 人生如寄,多忧何为  ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法: 尾插法: 插入(增)操作  删除(删)操作: 查找(查)操作: 修改(改

    2024年02月08日
    浏览(54)
  • 深入浅出循环语句—【C语言】

      分支语句博客: http://t.csdn.cn/U2kZF 目录 ​编辑 前言:我们先来了解一下break 、continue在循环中的作用 1. while循环  while循环中的break  while循环中的continue  2. for循环 for循环省略出错举例:  for循环中的break  for循环中的continue 3. do   while循环 利用do while循环打印1~10   d

    2024年02月04日
    浏览(32)
  • 深入浅出C语言—【函数】下

    函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。 注意: 函数可以嵌套调用,但是不能嵌套定义。 把一个函数的返回值作为另外一个函数的参数。 上面的strlen函数是求数组长度的库函数, 特别注意的是,当数组为字符数组时,数组的末尾会自动放一个

    2024年02月17日
    浏览(56)
  • 深入浅出C语言—【函数】上

       目录 1.函数的概念 2.C语言函数的分类 2.1 库函数 2.1.1 strcpy库函数举例学习方式 2.1.2 库函数扩展知识 2.2 自定义函数 2.2.1求两个整数中的较大值 3. 函数的参数 3.1 实际参数(实参) 3.2 形式参数(形参) 4. 函数的调用 4.1 传值调用 4.2 传址调用 老铁们,网址自取,记得一键

    2024年02月07日
    浏览(26)
  • 深入浅出分支语句—【C语言】

    目录 前言:为什么要学习分支和循环语句呢? 1. 语句的分类 2. 分支语句(选择语句) 2.1 if-else语句 注意点:if-else语句后面不加{},默认只能跟一条语句 2.2  switch语句  注意点: 因为C语言是一门结构化的程序设计语言,具有三种结构:顺序结构、选择结构、循环结构,这三

    2024年02月02日
    浏览(51)
  • 【蓝桥杯日记】复盘篇一:深入浅出顺序结构

      本期是一篇关于顺序结构的题目的复盘,通过复盘基础知识,进而把基础知识学习牢固!通过例题而进行复习基础知识。 前言 1.字符三角形  分析: 知识点: 代码如下 2. 字母转换 题目分析: 知识点: 代码如下  3. 再分肥宅水 题目分析: 知识点: 代码如下  4. 数字反转 题

    2024年01月22日
    浏览(45)
  • 深入浅出:大语言模型的视觉解析

    一系列工具与文章的汇编,直观易懂地解读复杂的 AI 概念 图片由作者利用 unDraw.co 的免费插图制作 在当今世界,大语言模型(LLM)成为了热门话题。几乎每天都有新的语言模型问世,让人们在 AI 领域怀有一种“不容错过”的紧迫感。尽管如此,许多人仍对大语言模型的基础

    2024年01月19日
    浏览(30)
  • 深入浅出讲解自动驾驶 - 激光雷达原理和结构简介

    💂 个人主页 : 同学来啦 🤟 版权 : 本文由【同学来啦】原创、在CSDN首发、需要转载请联系博主 💬 如果文章对你有帮助, 欢迎关注、点赞、收藏和订阅专栏哦 激光雷达最先应用于海洋深度探测领域,其实现思路是通过相同回波之间的时间差实现海洋深度测算。后来不断演

    2024年02月16日
    浏览(29)
  • 深入浅出对话系统——自然语言理解模块

    首先回顾一下自然语言理解的概念。 自然语言理解(Natural Language Understanding)包含三个子模块: 其中领域识别和意图识别都是分类问题,而语义槽填充属于序列标注问题。所以,在自然语言理解中,我们要解决两个分类任务和一个序列标注任务。既然其中两个问题都属于分类任

    2024年02月08日
    浏览(39)
  • 深入浅出对话系统——基于预训练语言模型的对话管理

    主要讲解三篇论文,主要思想是把自然语言理解、对话管理和自然语言生成三部分整合到一起。 数据集 CamRest676 MultiWOZ 都是用的自回归语言模型 causal GPT-2、Transformer Decoder 一个概念:delexicalization 通过相应的占位符替换特定的槽值 占位符作为特定的token,不关心具体的取值

    2024年02月16日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包