数据结构-堆的实现及应用(堆排序和TOP-K问题)

news2025/1/16 17:51:36

数据结构-堆的实现及应用[堆排序和TOP-K问题]

  • 一.堆的基本知识点
    • 1.知识点
  • 二.堆的实现
    • 1.堆的结构
    • 2.向上调整算法与堆的插入
    • 2.向下调整算法与堆的删除
  • 三.整体代码
  • 四.利用回调函数避免对向上和向下调整算法的修改
    • 1.向上调整算法的修改
    • 2.向下调整算法的修改
    • 3.插入元素和删除元素函数的修改
  • 五.建堆
    • 1.自顶向下的建堆方式(利用向上调整算法)
    • 2.自底向上的建堆方式(利用向下调整算法)
    • 3.两种方法建堆的时间复杂度
      • 1.向下调整算法建堆的时间复杂度
      • 2.向上调整算法建堆的时间复杂度
  • 六.堆排序
    • 1.算法思想
    • 2.代码实现
    • 3.时间复杂度
    • 4.稳定性
  • 七.TOP-K问题
  • 八.一道与TOP-K相关的leetcode题目
  • 九.测试TOP-K对于海量数据的处理
  • 十.总结

一.堆的基本知识点

1.知识点

1.堆的知识点:

堆的知识点
堆的逻辑结构是一颗完全二叉树
堆的物理结构是一个数组
也就是说,给我们是一个数组,可是我们要把它想象成一个完全二叉树来做
通过下标父子结点关系
leftchild = parent * 2 + 1;
rightchild = parent * 2 + 2;
parent = (child - 1) / 2;(child可以是左孩子,也可以是右孩子)

下面我们通过一张图片来更加深刻地理解堆
在这里插入图片描述

堆的两个特性

1.结构性:用数组表示的完全二叉树
2.有序性:任意节点的关键字是其子树所有结点的最大值
3.堆的两种分类
最大堆(MaxHeap):也称为大顶堆(最大值)
最小堆(MinHeap):也称为小顶堆(最小值)
3.大堆:要求树中所有的父亲都大于等于孩子
小堆:要求所有的父亲都小于等于孩子
堆只有两种:大堆,小堆,其余的都不是堆,注意有些选择题常考堆的判别
大堆:堆顶数据是最大的
小堆:堆顶数据是最小的

二.堆的实现

1.堆的结构

上面我们说过,堆的物理结构是一个数组,逻辑结构是一个完全二叉树,所以堆的实际结构类似于顺序表,只不过我们的处理方式类似于二叉树

那么我们就可以用顺序表那样的结构来表示堆了
于是我们可以写出这样的代码

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
//堆的初始化
void HeapInit(HP* php);
//堆的销毁
void HeapDestroy(HP* php);
//堆的打印
void HeapPrint(HP* php);
//取堆顶数据
HPDataType HeapTop(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//返回堆的元素大小
int HeapSize(HP* php);
void HeapInit(HP* php)
{
	assert(php);
	//这里我们将容量初始化为0,当然,初始化为别的一些数值也可以,这里没有强制要求
	php->a = NULL;
	php->size = php->capacity = 0;
}

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

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

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

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

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

剩下的一些常见的接口:

//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的删除
void HeapPop(HP* php);

在这里,我们要先说明一下:

1.在堆中插入一个数据后,我们不能改变堆的特性,
即:

原来是小堆,插入数据后还是小堆
原来是大堆,插入数据后还是大堆

2.我们的删除操作所要删除的值是堆顶元素.
即数组的第一个元素,
删除操作依然不能改变堆的特性

要想实现堆的插入
首先我们需要先学习一个算法:向上调整算法

2.向上调整算法与堆的插入

假设我们现在有一个小堆(所有的父亲都小于他们所对应的孩子)
我们要插入一个元素
在这里插入图片描述
向上调整算法整体思路:
child不断向上跟父亲比,如果比父亲小,跟父亲交换,向上迭代
当该节点调整到父亲小于该节点时,或者该节点调整到数组的首元素位置时调整结束

所以我们可以写出这样的代码

//向上调整算法
//[0,child]区间内向上调整
void AdjustUp(HPDataType* a,int child)
{
	int parent = (child - 1) / 2;
	//终止条件:孩子等于0,大于0就继续调整
	//不要拿父亲作为条件,父亲和孩子都等于0的时候,parent = (0-1)/2还是0,死循环了
	//while(parent>=0)
	while (child > 0)
	{
		//建小堆if(a[child]<a[parent])
		//建大堆if(a[child]>a[parent])
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

其中,向上调整算法的时间复杂度为O(log2(N)),
最多调整高度次,因为是完全二叉树,所以高度约等于O(log2(N))
实现了向上调整算法之后,我们就可以完成插入了

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 (NULL == tmp)
		{
			perror("malloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	//插入
	php->a[php->size] = x;
	php->size++;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

2.向下调整算法与堆的删除

假设我们现在有一个小堆
在这里插入图片描述
向下调整算法整体思路:
前提:根节点的左子树和右子树都是小堆
child为左右孩子中较小者
如果父亲大于child,交换父亲和child,父亲和孩子向上迭代

于是我们可以写出这样的代码

//向下调整算法(小堆)
//[root,n)区间内向下调整
void AdjustDown(HPDataType* a, int root,int n)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		//建小堆:if(...&& a[child]>a[child+1])
		//建大堆:if(...&& a[child]<a[child+1])
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		//建小堆:if(a[parent]>a[child])
		//建大堆:if(a[parent]<a[child])
		if (a[parent] > a[child])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

实现了向下调整算法的代码后,我们可以写出删除的代码
其中,向下调整算法的时间复杂度也为O(log2(N)),
最多调整高度次,因为是完全二叉树,所以高度约等于O(log2(N))

//删除堆顶元素
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	Swap(&(php->a[0]), &(php->a[php->size - 1]));
	php->size--;
	AdjustDown(php->a, 0, php->size);
}

三.整体代码

Heap.h

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

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
//初始化堆
void HeapInit(HP* php);
//销毁堆
void HeapDestory(HP* php);
//打印堆
void HeapPrint(HP* php);
//插入X继续保持堆形态
void HeapPush(HP* php, HPDataType x);
//删除堆顶元素
void HeapPop(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//返回堆顶元素
HPDataType HeapTop(HP* php);
//返回堆的元素大小
int HeapSize(HP* php);

Heap.c

#include "Heap.h"
//初始化堆
void HeapInit(HP* php)
{
	php->a = NULL;
	php->capacity = php->size = 0;
}
//销毁堆
void HeapDestory(HP* php)
{
	free(php);
	php->capacity = php->size = 0;
}
//打印堆
void HeapPrint(HP* php)
{
	int i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

void Swap(HPDataType* h1, HPDataType* h2)
{
	HPDataType tmp = *h1;
	*h1 = *h2;
	*h2 = tmp;
}

//向上调整算法
//区间范围:[0,child]
void AdjustUp(HPDataType* a,int child)
{
	int parent = (child - 1) / 2;
	//终止条件:孩子等于0,大于0就继续调整
	//不要拿父亲作为条件,父亲和孩子都等于0的时候,parent = (0-1)/2还是0,死循环了
	//while(parent>=0)
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//插入X继续保持堆形态(小堆:父亲小于孩子)

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 (NULL == tmp)
		{
			perror("malloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}


//向下调整算法(小堆)
//区间范围:[root,n)
void AdjustDown(HPDataType* a, int root,int n)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

//删除堆顶元素
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	Swap(&(php->a[0]), &(php->a[php->size - 1]));
	php->size--;
	AdjustDown(php->a, 0, php->size);
}
//判断是否为空
bool HeapEmpty(HP* php)
{
	return php->size == 0;
}
//返回堆顶元素

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}

//返回堆的元素大小
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

四.利用回调函数避免对向上和向下调整算法的修改

从上面的讲解中,对于向上调整算法和向下调整算法来说,如果想要实现从小堆到大堆的转换,还需要改一下代码,(尽管只需要改一下比较符号即可),

但是那样的话我们就需要再去实现针对于建大堆的向上调整算法和向下调整算法,
而且还需要根据不同需要去调用不同函数,过于麻烦

所以有什么方法可以避免这种修改吗?
在C语言中,我们可以利用回调函数来完成,对于回调函数来说,大家可以看我的这篇博客
征服C语言指针系列(3)
里面有详细的讲解

1.向上调整算法的修改

void AdjustUp(HPDataType* a, int child, int(*cmp_up)(HPDataType* p1, HPDataType* p2))
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//小堆:if(a[parent]>a[child])
		//也就是说cmp_up(...,...)这个函数返回值为正数,则建的是小堆
		//否则,建的是大堆
		if (cmp_up(&a[parent], &a[child]) > 0)
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

2.向下调整算法的修改

void AdjustDown(HPDataType* a, int root,int n, int(*cmp_down)(HPDataType* p1, HPDataType* p2))
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		//小堆:if(a[child]>a[child+1])
		//也就是说cmp_down(...,...)这个函数返回值为正数,则建的是小堆
		//否则,建的是大堆
		if (child + 1 < n && cmp_down(&a[child], &a[child + 1]) > 0)
		{
			child++;
		}
		//小堆:if(a[parent]>a[child])
		//也就是说cmp_down(...,...)这个函数返回值为正数,则建的是小堆
		//否则,建的是大堆
		if (cmp_down(&a[parent], &a[child]) > 0)
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
函数调用者自行实现两个函数
int cmp_down(HPDataType* p1, HPDataType* p2);

int cmp_up(const HPDataType* p1, const HPDataType* p2);
//使用函数指针来做一个回调函数
cmp_up:向上调整
cmp_down:向下调整
//建小堆(父亲小于孩子)
int cmp_up(const HPDataType* p1, const HPDataType* p2)
{
	return *p1 - *p2;
}

//建大堆
int cmp_up(const HPDataType* p1, const HPDataType* p2)
{
	return *p2 - *p1;
}

//建小堆:父亲小于孩子

int cmp_down(HPDataType* p1, HPDataType* p2)
{
	return *p1 - *p2;
}

//建大堆
int cmp_down(HPDataType* p1, HPDataType* p2)
{
	return *p2 - *p1;
}

3.插入元素和删除元素函数的修改

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		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, cmp_up);
}
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

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

经过了上面的修改,我们就可以在不改变代码的情况下,实现大堆或者小堆了

五.建堆

上面的代码可以让我们从无到有建立堆
但是如果我们要把一个数组改造成堆,而且不能浪费其他空间,只能在原数组上改造,那该怎么办呢?
这里我们需要建堆
这里以建小堆为例

1.自顶向下的建堆方式(利用向上调整算法)

根据上文可知进行向上调整算法后,数组中[0,child]区间就变为小堆了,所以我们可以用一个for循环来扩展这个区间让这个区间从[0,1]一直扩到[0,n-1]
于是我们可以写出如下代码

	for (int i = 1; i < n; i++)
	{
		AdjustUp(arr, i, cmp_up);
	}

下面给大家画张图看一看:
在这里插入图片描述

2.自底向上的建堆方式(利用向下调整算法)

既然向上调整算法可以建堆,那么向下调整算法呢?
答案是:也可以建堆
我们可以从最后一个非叶子节点往上建堆,先让下面变成小堆,再让上面变成小堆
那么我们该如何求出最后一个非叶子节点呢?

根据堆的特性,我们得出过如下结论:
parent=(child-1)/2;
parent自然是非叶子节点,那么我们只需要求出最后一个parent不就行了吗?
我们又知道最后一个child的下标一定是n-1
所以得出最后一个parent的下标是(n-1-1)/2;

根据上文我们得出向下调整算法可以使得
数组中区间[root,n)范围内变为小堆
那么我们就可以逐步扩大这个范围,让这个范围从[(n-1-1)/2,n)一直扩大到[0,n)

所以我们可以写出如下代码

	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, i, n, cmp_down);
	}

下面给大家画张图看一看:
在这里插入图片描述

3.两种方法建堆的时间复杂度

//向下调整算法
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, i, n, cmp_down);
	}
//向上调整算法
	for (int i = 1; i < n; i++)
	{
		AdjustUp(arr, i, cmp_up);
	}

可能大家看到这两个代码后的反应是:
因为向上和向下调整算法的时间复杂度都是log(2)N
所以这两种建堆的时间复杂度不都是O(Nlog2(N))吗?
答案是:并不是这样,
向下调整算法建堆的时间复杂度:O(N)
向上调整算法建堆的时间复杂度:O(N
log(2)N)
下面我们来用数学公式证明一下(这里需要用到错位相减法)

1.向下调整算法建堆的时间复杂度

在这里插入图片描述

2.向上调整算法建堆的时间复杂度

在这里插入图片描述
前面已经介绍了如何把一个普通的数组改造成堆
那么接下来我们来看堆的两大重要应用:
堆排序和TOP-K问题

六.堆排序

1.算法思想

1.那么排升序我们要建什么堆呢?
答案是:大堆,这个答案确实挺出人意料的,但是为什么呢?
下面我们来详细解释一下这个原因
1.建小堆为什么不可以(不建议):
2.建大堆为什么可以(建议):

因为:
堆排序是属于选择排序的一种.
如果是建小堆,最小数在堆顶,已经被选出来了,
那么在剩下的数中再去选数,但是剩下的树结构都乱了,需要重新建堆才能选出下一个数,
建堆的时间复杂度是O(N),我们在讲解堆排序的最后会给大家证明这个建堆的时间复杂度.
那么这样不是不可以,但是堆排序就没有效率优势了并且建堆选数还不如直接遍历选数

其次,如何选次小的数呢?
第二个数去做根了,剩下的树关系全乱了,再重新建堆,
建堆的时间复杂度:O(N),而建堆选数排序,时间复杂度:O(N^2)
并且建堆选数还不如直接遍历选数

下面我给大家画图来演示一下:
在这里插入图片描述

2.建大堆:
步骤:
1.第一个和最后一个交换,然后把交换后的那个较大的数(即位于数组末尾的那个数)不看做堆里面
2.前n-1和数进行向下调整算法,选出次大的数放到根节点,再跟倒数第二个位置交换

2.代码实现

代码如下:

void HeapSort(int* a,int n)
{
	int i = 0;
	//这里用向下调整算法来建堆,因为时间复杂度只有O(N)
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustUp(a, end, 0);
		--end;
	}
}

下面给大家画图演示一下,能帮助大家有更好的理解

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

3.时间复杂度

void HeapSort(int* a,int n)
{
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustUp(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustUp(a, end, 0);
		--end;
	}
}

向下调整算法最坏的情况:进行高度次,即log(2)N次,所以while循环最坏进行了(n-1)log(2)N次,而for循环的建堆的时间复杂度为O(N)
所以整体的时间复杂度为O(N
log(2)N)
整个效率相对来说是很高的

4.稳定性

堆排序是不稳定的
因为建堆的时候有可能发生以下的类似情况
在这里插入图片描述

七.TOP-K问题

可能很多小伙伴有这么一个疑问:什么是TOP-K问题啊?
TOP-K问题:
就是求数据集合中前K个最大或者最小的元素,一般情况下数据量都比较大
比如:
全国企业500强,专业前10名,全国高考前100名等等…
那么我们如何利用堆来解决TOP-K问题呢?

假设有10亿个数据,内存存不下,数据在文件中
找出最大的前K个,  K==100

1.读取文件的前100个数据,在内存数组中建立一个小堆
2.再依次读取剩下的数据,跟堆顶数据比较,大于堆顶,就替换它进堆,向下调整
3.所有数据读取完毕,堆里面的数据就是最大的前100

那么我们要建小堆还是大堆呢?
答案是:找最大的前K个就建小堆
反之,建大堆

为什么不建议建大堆:

大堆只能找出最大的一个数,
而且最大的数会挡在根节点的位置,后面的数完全无法进堆,那就完蛋了

小堆的优势
而小堆的话,只有第K个大的数才会挡在根节点的位置,而更大的数会往堆的下面跑
时间复杂度O(N*logK),而且K相对于N来说可以忽略不计,所以可以认为是O(N)
时间复杂度:O(K),K:堆的大小,而K相对于N来说可以忽略不计,所以可以认为是O(1)

下面我们结合一道leetcode题目来实现一下这个代码

八.一道与TOP-K相关的leetcode题目

最小K个数

void Swap(int*p1,int*p2)
{
     int tmp =*p1;
     *p1 = *p2;
     *p2 = tmp;
}
//前k个数建大堆
//向下调整算法
 void AdjustDown(int*a,int n,int parent)
 {
     int minchild = parent*2+1;
     while(minchild<n)
     {
         if(minchild+1<n&&a[minchild+1]>a[minchild])
         {
             minchild++;
         }
         if(a[minchild]>a[parent])
         {
             Swap(&a[minchild],&a[parent]);
             parent = minchild;
             minchild = parent*2+1;
         }
         else
         {
             break;
         }
     }
 }
int* smallestK(int* arr, int arrSize, int k, int* returnSize){
    if(k==0)
    {
        *returnSize=0;
        return NULL;
    }
    int* ret=(int*)malloc(sizeof(int)*k);
    for(int i=0;i<k;i++)
    {
        ret[i]=arr[i];
    }
    //前k个数建大堆
    for(int i=(k-1-1)/2;i>=0;i--)
    {
        AdjustDown(ret,k,i);
    }
    for(int i=k;i<arrSize;i++)
    {
        if(ret[0]>arr[i])
        {
            ret[0]=arr[i];
            AdjustDown(ret,k,0);
        }
    }
    *returnSize=k;
    return ret;
}

在这里插入图片描述

九.测试TOP-K对于海量数据的处理

下面我们来测试一下TOP-K对于大数据的处理功能

//创建一个文件,并且随机生成一些数字
void CreateDataFile(const char* filename, int N)
{
	FILE* Fin = fopen(filename, "w");
	if (Fin == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}
	srand(time(0));
	for (int i = 0; i < N; i++)
	{
		fprintf(Fin, "%d ", rand() % 10000);
	}
}
void PrintTopK(const char* filename, int k)
{
	assert(filename);
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	//读前k个数
	for (int i = 0; i < k; i++)
	{
		//空格和换行默认是多个值之间的间隔
		fscanf(fout, "%d", &minheap[i]);
	}
	//建k个数的堆
	for (int j = (k - 1 - 1) / 2; j >= 0; j--)
	{
		AdjustDown(minheap, j, k, cmp_down);
	}
	//读取后N-K个
	int x = 0;
	while(fscanf(fout,"%d",&x)!=EOF)
	{
		if (x > minheap[0])
		{
			minheap[0] = x;
			AdjustDown(minheap, 0, k, cmp_down);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");
	free(minheap);
	fclose(fout);
}
int main()
{
	//CreateDataFile("data.txt", 1000000);
	//找前10个最大的数
	PrintTopK("data.txt", 10);
	return 0;
}

在这里插入图片描述
我们已经提前修改了文件当中的值,
现在文件当中的最大的前10个值是1000000到1000009
运行后的结果:
在这里插入图片描述
可见堆针对于TOP-K的强大之处

十.总结

本文介绍了
1.堆的知识点
2.用C语言实现了堆
3.并且用函数指针实现了回调函数对向上和向下调整算法的代码进行了优化
4.实现了建堆
5.用数学公式推导出了两种建堆方法的时间复杂度
6.实现了堆排序
7.分析了堆排序的时间复杂度和稳定性
8.介绍了TOP-K的代码实现并解决了一道leetcode题目
9.测试了TOP-K对于大数据的处理功能

以上就是<<数据结构-堆的实现及应用>>的讲解,希望能对大家有所帮助,谢谢大家!

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

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

相关文章

vue学习之列表渲染

列表渲染 创建 demo8.html,内容如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</…

LeetCode算法心得——判断能否在给定时间到达单元格(动态模拟)

大家好&#xff0c;我是晴天学长&#xff0c;这是一个动态模拟题&#xff0c;跟大佬相比&#xff0c;我的有点繁琐了&#xff0c;但是也算是锻炼到自己的一些细节问题&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 1) .判断能否在给定时间到达单元…

Pytorch面试题整理(2023.09.10)

1、pytorch如何微调fine tuning&#xff1f; 在加载了预训练模型参数之后&#xff0c;需要finetuning 模型&#xff0c;可以使用不同方式finetune。 局部微调&#xff1a;加载了模型参数后&#xff0c;只想调节最后几层&#xff0c;其他层不训练&#xff0c;也就是不进行梯度…

【Endnote】如何出现“作者(年份) found that ....”的格式?

如何出现“作者&#xff08;年份&#xff09; found that ....”的格式&#xff1f; 非常简单&#xff01;先用endnote插入文献后&#xff0c;默认显示&#xff1a; 然后&#xff0c;重点来了&#xff0c;点开这个&#xff1a; 然后&#xff0c;将此处的default改成Display as…

unique_ptr的大小探讨

unique_ptr大小和删除器有很大关系&#xff0c;具体区别看如下代码的分析。不要让unique_ptr占用的空间太大&#xff0c;否则不会达到裸指针同样的效果。 #include <iostream> #include <memory> using namespace std;class Widget {int m_x;int m_y;int m_z;publ…

国产化改造之Mysql迁移方案:Mysql Galera Cluster

一、背景 因某业务系统OS国产化改造&#xff0c;现需将生成环境Mysql 主从迁移到新部署的BCLinux OS主机上&#xff1b;如果保障业务不断&#xff0c;平滑迁移并成功割接将是本次方案的重要方向&#xff0c;现场环境涉及需迁移数据780G左右&#xff0c;目标主机OS版本&#xff…

day35 线程

程序&#xff1a;是为了完成特定的任务&#xff0c;用某种语言编写的一组有序指令的集合&#xff0c;是一段静态的代码 进程&#xff1a;是程序的一次执行过程 线程&#xff1a;线程是进程中的一个执行单元 线程是调度和执行的单位 处理器和线程间的关系 创建线程(重点) 创建…

时序分解 | MATLAB实现MVMD多元变分模态分解信号分量可视化

时序分解 | MATLAB实现MVMD多元变分模态分解信号分量可视化 目录 时序分解 | MATLAB实现MVMD多元变分模态分解信号分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MVMD多元变分模态分解 可直接替换Excel运行包含频谱相关系数图 Matlab语言 1.算法新颖小众&…

LTGNet-超分辨率OCTA图像分割

目录 一、摘要 二、引言 三、方法 A. Reference-based框架 B. Learnable Texture Generator 四、实验 五、总结 一、摘要 研究背景&#xff1a;光学相干断层血管成像(OCTA)是一种新的视网膜微血管成像方式&#xff0c;已广泛应用于临床。 高分辨率OCT血管造影对于定性和…

Brief. Bioinformatics2023 | 利用深度学习和分子动力学模拟设计抗菌肽

文章标题&#xff1a;Designing antimicrobial peptides using deep learning and molecular dynamic simulations 代码&#xff1a;https://github.com/gc-js/Antimicrobial-peptide-generation 一、问题 PandoraGAN使用手动策划的130个高活性肽的训练数据集&#xff0c;其…

第11章_瑞萨MCU零基础入门系列教程之SysTick

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

QTabWidget当tab位置在左右时,设置文字方向朝上

QTabWidget当tab位置在左右时&#xff0c;设置文字方向朝上解决方案 方案一&#xff1a;调用setTabButton()方法方案二&#xff1a;重写QTabBar方案三&#xff1a;重写QProxyStyle 当用QTabWidget控件时&#xff0c;默认是下方显示&#xff1a; 如果想左侧或右侧显示tab页&…

upload-labs文件上传漏洞通关

一、环境搭建 upload-labs是一个使用php语言编写的&#xff0c;专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。 下载地址&#xff1a;https://github.com/c0ny1/upload-labs/releases 在 win 环境下 直接解压到phpstudy下即可 二、通关 &#xff08;一&#xff09;16关…

【leetcode 力扣刷题】删除字符串中的子串or字符以满足要求

删除字符串中的子串或者字符以满足题意要求 1234. 替换子串得到平衡字符串680. 验证回文串917. 仅仅反转字母 1234. 替换子串得到平衡字符串 题目链接&#xff1a;1234. 替换子串得到平衡字符串 题目内容&#xff1a; 题目中给出了平衡字符串的定义——只有’Q’&#xff0c;…

【C++】详解std::thread

2023年9月10日&#xff0c;周日下午开始 2023年9月10日&#xff0c;周日晚上23:35完成 虽然这篇博客我今天花了很多时间去写&#xff0c;但是我对std::thread有了一个完整的认识 不过有些内容还没完善&#xff0c;以后有空再更新.... 目录 头文件类的成员类型方法(construc…

GDB用法(三)

预备 测试代码参照GDB用法(二) 命令历史 可以将命令历史保存到文件中 (show history) 展示当前gdb中history的设置信息 设置expansion (set history expansion) 打开历史扩展 能使用历史处理命令对历史数据进行处理, 暂不细究 (show history expansion) 展示历史扩展配置…

《JDK17新特性和代码案例演示》

《JDK17新特性和代码案例演示》 &#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全…

sqli --【1--10】

Less-1&#xff08;联合查询&#xff09; 1.查看是否有回显 2.查看是否有报错 3.使用联合查询&#xff08;字符注入&#xff09; 3.1判断其列数 3.2 判断显示位置 3.3敏感信息查询 Less-2&#xff08;联合查询&#xff09; 1.查看是否有回显 2.查看是否有报错 3.使用…

[学习笔记]词向量模型-Word2vec

参考资料&#xff1a; 【word2vec词向量模型】原理详解代码实现 NLP自然语言处理的经典模型Word2vec 论文背景知识 词的表示方法 One-hot Representation&#xff1a;独热表示 简单&#xff0c;但词越多&#xff0c;向量越长。且无法表示词与词之间的关系。 论文储备知识-pr…

Ae 效果:CC Scatterize

模拟/CC Scatterize Simulation/CC Scatterize CC Scatterize&#xff08;CC 散射&#xff09;主要用于模拟将对象散射为颗粒状的效果。 CC Scatterize 的工作原理是&#xff1a;对源图像的 Alpha 通道先进行融解 Dissolve&#xff0c;从而产生随机颗粒&#xff0c;因此改变了源…