【数据结构】二叉树-堆实现及其堆的应用(堆排序topK问题)

news2024/9/21 2:36:07

文章目录

  • 一、堆的概念及结构
  • 二、堆的实现
    • 1.结构的定义
    • 2.堆的初始化
    • 3.堆的插入
    • 4.堆的向上调整
    • 5.堆的删除
    • 6.堆的向下调整
    • 7.取出堆顶元素
    • 8.返回堆的元素个数
    • 9.判断堆是否为空
    • 10.打印堆中的数据
    • 11.堆的销毁
  • 三、完整代码
    • 1.Heap.h
    • 2.Heap.c
    • 3.test.c
  • 四、堆排序
    • 1.堆排序
    • 2.建堆
    • 3.选数
    • 4.完整代码
  • 五、topK问题

一、堆的概念及结构

如果有一个关键码的集合K = {k0,k1,k2…kn-1},把它的所有元素按完全二叉树顺序存储方式存储在一个一维数组中,并满足:Ki <= K 2*i+1 且Ki <= K2*i+2(Ki >= K2*i+1 且 Ki >= K2*i+2),i = 0,1,2…则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

堆的性质:

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

堆总是一棵完全二叉树

二、堆的实现

1.结构的定义

由于堆的元素是按完全二叉树的顺序存储方式存储在一个数组中,所以堆的结构和顺序表的结构一样

ypedef int HPDataType;   //数据类型重定义

typedef struct Heap
{
	HPDataType* a;   //指向动态开辟的数组
	int size;        //记录数组元素是个数
	int capacity;    //记录容量,容量满时扩容
}HP;

2.堆的初始化

堆的初始化和顺序表的初始化方式一样,我们可以先开辟一块空间也可以不开辟,在插入数据的时候进行开辟,我们这里先不开辟空间

//初始化堆
void HeapInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->size = php->capacity = 0;
}

3.堆的插入

堆的插入我们需要注意两个地方:

1.由于堆只会在数组的尾部插入数据,所以我们不需要将CheckCapacity(检查容量)单独封装一个函数

2.由于我们在插入数据之后要保持堆的形态(大根堆或小根堆),所以我们需要对堆进行向上调整(调整数组里的数据,使其保持堆的形态),向上调整的过程其实也是建堆的过程

//堆的插入 --  插入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, newCapacity * sizeof(HPDataType));
		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);
}

4.堆的向上调整

这里我们以小根堆为例,如图,假设现在我们已经有了一个小根堆,现在我们在数组的最后(堆尾)插入一个数据,那么就可能出现两种情况:

在这里插入图片描述

1.插入的数据大于父亲节点,此时我们的堆仍然保存小根堆的结构,所以不需要进行调整,比如我们在上面的堆中插入30:

在这里插入图片描述

2.插入的数据小于父亲节点,这时我们就需要进行向上调整,直到根节点的大小小于父亲节点的大小(即小根堆),调整的次数由节点的大小决定,可能调整1次,也可能调整到根节点,比如我们插入10:

在这里插入图片描述

//交换两个节点
void Swap(HPDataType* p1, HPDataType* p2)
{
	assert(p1 && p2);

	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//堆的向上调整 --小根堆
void AdjustUp(HPDataType* a, int child)
{
	assert(a);

	int parent = (child - 1) / 2; //找到父节点

	//while (parent >= 0)   当父亲为0时,(0 - 1) / 2 = 0;又会进入循环
	while (child > 0)   //当调整到跟节点的时候不再继续调整
	{
		//当子节点小于父节点的时候交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);

			//迭代
			child = parent;
			parent = (child - 1) / 2;
		}
		//否则直接跳出循环
		else
		{
			break;
		}
	}
}

对于上面的代码我们需要注意循环结束的条件,如果我们使用parent >= 0这个来判断结束时,当父亲为0时,(0 - 1) / 2 = 0;又会进入循环,所以我们选择以孩子节点作为结束的条件:child > 0

【注意】如果我们需要建大根堆,只需要把交换的条件修改一下即可:

//当子节点小于父节点的时候交换
if (a[child] > a[parent])

5.堆的删除

对于堆的删除有明确的规定,我们只能删除堆顶的元素,但是顺序表头删又存在下面两个问题:

1.顺序表头删需要挪动数据,效率低下O(N)

2.头删之后堆中各节点的父子关系全被破坏了

对于上面的两个问题,我们采用如下的解决方案:

1.我们在删除之前先将堆顶的元素和堆尾的元素进行交换,然后–size(删除数组的最后一个元素/堆尾元素),这个月就相当于删除了堆顶的元素,并且时间复杂度从O(N)提升到了O(1)

2.由于我们把堆尾的元素交换到了堆顶,堆的结构被破坏,所以我们需要设计一个向下调整的算法来继续保持堆的形态:

//删除堆顶元素 --找次大或者次小 -- logN
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	//首先交换堆顶和堆尾的元素
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除堆顶的元素
	php->size--;
	//向下调整,保持堆的形态
	AdjustDown(php->a, php->size, 0);
}

6.堆的向下调整

堆的向下调整和堆的向下调整刚好相反,我们以小根堆为例,我们调整的思路如下:1.找出子节点中较小的节点;

2.比较父节点和较小节点的大小,如果父节点比子节点大就交换两个节点,反之说明现在的形态已经是堆,不需要进行调整了;3.交换之后,原来的子节点称为新的父节点,然后继续执行1,2步骤,直到调整为堆的结构:

在这里插入图片描述

//堆的向下调整 --小根堆
void AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);

	int minchild = parent * 2 + 1;
	while (minchild < n)
	{
		//找出那个较小的孩子
		if (a[minchild] > a[minchild + 1] && minchild + 1 < n)
		{
			minchild++;
		}
		//当子节点小于父节点的时候交换
		if (a[minchild] < a[parent])
		{
			Swap(&a[minchild], &a[parent]);

			//迭代
			parent = minchild;
			minchild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

和向上调整类似,如果我们想要调整为大堆,也只需要改变交换条件即可:

// 找出较大的节点
if (a[maxchild] > a[maxchild + 1] && axchild + 1 < n)
// 如果父节点小于子节点就交换
if (a[maxchild] > a[parent])

7.取出堆顶元素

堆顶元素就是数组的第一个元素

//获取堆顶的元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}

8.返回堆的元素个数

/返回堆的元素个数
int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

9.判断堆是否为空

//判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

10.打印堆中的数据

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

11.堆的销毁

//堆的销毁
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

三、完整代码

1.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 HeapDestroy(HP* php);
//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的向上调整
void AdjustUp(HPDataType* a, int child);
//删除堆顶元素
void HeapPop(HP* php);
//堆的向下调整
void AdjustDown(HPDataType* a, int n, int parent);
//获取堆顶的元素
HPDataType HeapTop(HP* php);
//判断堆是否为空
bool HeapEmpty(HP* php);
//返回堆的元素个数
int HeapSize(HP* php);
//打印堆中的数据
void HeapPrint(HP* php);

2.Heap.c

#include "Heap.h"

//初始化堆
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;
}

//堆的插入 --  插入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, newCapacity * sizeof(HPDataType));
		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);
}

//交换两个节点
void Swap(HPDataType* p1, HPDataType* p2)
{
	assert(p1 && p2);

	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//堆的向上调整 --小根堆
void AdjustUp(HPDataType* a, int child)
{
	assert(a);

	int parent = (child - 1) / 2; //找到父节点

	//while (parent >= 0)   当父亲为0时,(0 - 1) / 2 = 0;又会进入循环
	while (child > 0)   //当调整到跟节点的时候不再继续调整
	{
		//当子节点小于父节点的时候交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);

			//迭代
			child = parent;
			parent = (child - 1) / 2;
		}
		//否则跳出循环
		else
		{
			break;
		}
	}
}
//删除堆顶元素 --找次大或者次小 -- logN
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	//首先交换堆顶和堆为的元素
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除堆顶的元素
	php->size--;
	//向下调整,保持堆的形态
	AdjustDown(php->a, php->size, 0);
}

//堆的向下调整 --小根堆
void AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);

	int minchild = parent * 2 + 1;
	while (minchild < n)
	{
		//找出那个较小的孩子
		if (a[minchild] > a[minchild + 1] && minchild + 1 < n)
		{
			minchild++;
		}
		//当子节点小于父节点的时候交换
		if (a[minchild] < a[parent])
		{
			Swap(&a[minchild], &a[parent]);

			//迭代
			parent = minchild;
			minchild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//获取堆顶的元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}
//判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}
//返回堆的元素个数
int HeapSize(HP* php)
{
	assert(php);

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

3.test.c

#include "Heap.h"

int main()
{
	int a[10] = { 15, 18, 19, 25, 28, 34, 65, 49, 27, 37 };

	HP hp;

	//初始化堆
	HeapInit(&hp);
	//建堆
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);
	}

	//插入元素
	HeapPush(&hp, 10);
	HeapPrint(&hp);

	//删除堆顶元素
	HeapPop(&hp);
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	//打印堆的元素
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;
}

【总结】

1.堆是二叉树顺序存储结构的一个具体体现,堆中的每个节点的值总是不大于或不小于父节点的值(大堆/小堆),堆总是一棵完全二叉树,堆使用顺序表进行存储

2.堆中父节点下标的计算公式:(n-1)/2;左孩子下标:n*2+1;右孩子下标:n*2+2;

3.堆只能在尾部插入数据,且插入数据后需要保证堆的结构,所以在插入数据之后我们需要进行向上调整,向上调整的时间复杂度为O(logN)(log以2为底)

4.堆只能在头部删除数据,且删除数据后需要保证堆的结构,又因为顺序表在头部删除数据需要挪动数据,效率很低而且会破坏堆的结构,所以在堆删除数据时会先将堆尾的数据和堆顶的数据进行交换,然后–size(删除数组最后一个元素/队尾元素),再进行向下调整,向下调整的时间复杂度为O(logN)(log以2为底)

四、堆排序

1.堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。时间复杂度:O(N*logN)空间复杂度:O(1)

2.建堆

堆排序的第一步就是建堆,建堆有两种方法:向上调整建堆和向下调整建堆

**向下调整建堆:**从最后一个非叶子节点(即最后一个叶子节点的父节点)开始向下调整,直到调整到根节点

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

向下调整建堆的时间复杂度:

在这里插入图片描述

调整次数 = 每一层节点个数 * 这一层节点最坏向下调整次数

T(N) = 2^0*(h-1) + 2^1*(h-2) + 2^2*(h-3) + 2^3*(h-4) + …+2^(h-2)*1

错位相减法:

2*T(N) = 2^1*(h-1) + 2^2*(h-2) + 2^3*(h-3) + … + 2^(h-2)*2 + 2^(h-1)*1

T(N) = 2^0*(h-1) + 2^1*(h-2) + 2^2*(h-3) + 2^3*(h-4) + …+2^(h-2)*1

两式相减得:

T(N) = -2^0*(h-1) + 2^1 + 2^2 + … +2^(h-2) + 2^(h-1)

T(N) = -h + 2^0 + 2^1 + 2^2 + … +2^(h-2) + 2^(h-1)

T(N) = -h + 2^h-1

高度为h,节点数量为N的完全二叉树,2^h-1=N,h = log(N+1)(log以2为底)

T(N) = N - log(N+1)(log以2为底)

所以,向下调整建堆的时间复杂度为O(N)

**向上调整建堆:**把数组的第一个元素作为堆的根节点,然后在堆尾一次插入其余元素,每插入一个元素就向上调整一次,从而保证堆的结构:

在这里插入图片描述

**向上调整建堆的时间复杂度:**由于堆的完全二叉树,而满二叉树又是完全二叉树的一种,所以此处为了简化计算,使用满二叉树来计算时间复杂度(时间复杂度本身看来就是近似值,多几个节点不影响最终结果)

在这里插入图片描述

我们知道:调整次数 = 每一层节点个数 * 这一层节点最坏向下调整次数

T(N) = 2^1*1 + 2^2*2 + 2^3*3 + …2^(h-2)*(h-2) + 2^(h-1)*(h-1)

精确算,还是用错位相减法

高度为h,节点数量为N的完全二叉树,2^h-1=N,h = log(N+1)(log以2为底)

算大概就算最后一层:2^(h-1)*(h-1)

​ 2^(h-1)*(h-1) * 2/2

​ 2^h*(h-1)/2

​ (N+1)*(log(N+1))/2

所以向上调整的时间复杂度为O(N*logN)

综合上面两种建堆的方式,我们选择向下调整建堆,所以建堆的时间复杂度为O(N);

3.选数

现在我们已经完成了建堆,那么接下来就需要进行选数,假设我们需要排升序,那么方法一共有三种:

1.建小堆,开辟一个和原数组同等大小的新数组中,每次取出堆顶元素(最小元素)放在新的数组中,然后挪动数组中的数据,最后排好序了以后再将新数组的数据覆盖到原数组;

缺点:每次挪动数据的效率很低,且挪动数据会造成堆中的其余元素的父子关系混乱,需要重新建堆,而建堆的时间复杂度也是O(N),所以排N个数,时间复杂度为O(N*N),空间复杂度为O(N)

2.建小堆,我们借鉴Pop数据的方法,先将堆顶的元素放在新的数组中,然后交换堆顶和队尾的元素,然后进行向下调数组的前n-1个数据,直到排好序,最后将新数组中的元素覆盖到原数组中;

缺点:虽然此方法可以让我们每次都拿到数组中最小的元素,但是需要开辟额外的空间,时间复杂度为O(N*lonN),空间复杂度为O(N)

3.建大堆,先将堆顶和队尾的数据进行交换,使得数组中最大的元素处于数组的末尾,然后向下调整前n-1个元素,使得次大的数据位于堆顶,然后重复前面的步骤,把次大的数据存放到最大的数据之前,直到数组有序;

优点:没有额外的空间消耗,且效率达到了O(N*logN)

综合上面的三种选数的方法:选数的时间复杂度为O(N*logN),空间复杂度为O(N)

4.完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <assert.h>


//空间复杂度O(1)
//时间复杂度O(N*logN)

typedef int HPDataType;
//交换两个节点
void Swap(HPDataType* p1, HPDataType* p2)
{
	assert(p1 && p2);
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//堆的向上调整 --小根堆
void AdjustUp(HPDataType* a, int child)
{
	assert(a);

	int parent = (child - 1) / 2; //找到父节点

	//while (parent >= 0)   当父亲为0时,(0 - 1) / 2 = 0;又会进入循环
	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 AdjustDown(HPDataType* a, int n, int parent)
{
	assert(a);

	int minchild = parent * 2 + 1;
	while (minchild < n)
	{
		//找出那个较小的孩子
		if (a[minchild] > a[minchild + 1] && minchild + 1 < n)
		{
			minchild++;
		}

		//if (a[minchild] > a[parent])  大根堆
		//当子节点小于父节点的时候交换
		if (a[minchild] < a[parent])
		{
			Swap(&a[minchild], &a[parent]);

			//迭代
			parent = minchild;
			minchild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* a, int n)
{
	/* 建堆 -- 向上调整建堆 - O(N*logN)
	for (int i = 1; i < n; ++i)
	{
		AdjustUp(a, i);
	}*/

	// 大思路:选择排序,依次选数,从后往前排
	// 升序 -- 大堆
	// 降序 -- 小堆
	//建堆 -- 向下调整建堆 - O(N)

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

	int i = 1;
	while (i < n)
	{
		Swap(&a[0], &a[n - i]);    // 交换堆尾和堆顶的数据
		AdjustDown(a, n - i, 0);  //向下调整
		++i;
	}
}

int main()
{
	int a[] = { 15, 1, 19, 25, 8, 34, 65, 4, 27, 7 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

五、topK问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大,比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。

N个数,找前K个最大的,如何处理?

1.排序 --O(N*logN)

2.堆选数

(1)建大堆:建N个数的大堆,选K次即可(Pop K次) O(N)+O(N*logK)

(2)建小堆:假设N很大,K很小,比如N=100亿,K=100,那么(1)方法就不行了

N很大的时候,内存就存不下了,就只能存在磁盘中

100亿整数=40G

400亿Byte

1G=1024MB

1024MB=1024*1024KB

1024*1024KB=1024*1024*1024Byte

时间复杂度为O(K)+O(logK*(N-K)) 空间复杂度 O(K)

思路:前K个数,建K个数的小堆,依次遍历后续N-K个数,比堆顶的数据大,就替换堆顶数据,向下调整建堆

最佳的方式就是用堆来解决,基本思路如下:

第一步,用数据集合中的前K个元素来建堆–前K个最大元素,则建小堆,前K个最小元素,则建大堆;

第二步,用剩余的N-K个元素依次与堆顶的元素进行比较,前K大的元素,则大于堆顶元素则就替换堆顶数据,进行向下调整前K小的元素,则小于堆顶的元素替换堆顶数据,进行向下调整;

#include <stdio.h>
#include <stdlib.h>

// 交换两个节点
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

// 向下调整 --建小堆
void AdjustDown(int a[], int n, int parent)
{
	int minchild = parent * 2 + 1; // 找到左孩子(左孩子 + 1得到右孩子)
	while (minchild < n)  // 调整到数组尾时不在调整
	{
		if (minchild + 1 < n && a[minchild + 1] < a[minchild])
		{
			minchild += 1;
		}

		if (a[parent] > a[minchild])
		{
			Swap(&a[parent], &a[minchild]);
		}
		else
		{
			break;
		}
	}

	// 迭代
	parent = minchild;
	minchild = parent * 2 + 1;
}

int* TopK(int* a, int n, int k)
{
	// 开辟K个元素的空间

	int* minHeap = (int*)malloc(sizeof(int) * k);
	if (minHeap == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	// 将数组的前K个元素
	for (int i = 0; i < k; i++)
	{
		minHeap[i] = a[i];
	}
	// 建小堆 --向下调整建堆:O(N)
	// n-1找到最后一个叶子节点,该节点-1/2找到倒数第一个父节点
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(minHeap, k, i);
	}
	// 取N-K个元素与堆顶元素比较,如果大于堆顶元素,就如堆
	for (int i = k; i < n; i++)
	{
		if (minHeap[0] < a[i])
		{
			minHeap[0] = a[i];
			AdjustDown(minHeap, k, 0);
		}
	}
	return minHeap;
}

int main()
{
	int arr[] = { 15,1,19,25,8,34,65,4,27,7 };
	int n = sizeof(arr) / sizeof(arr[0]);
	// TopK问题--前K个最大的元素
	int k = 3;
	int* ret = TopK(arr, n, k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", ret[i]);
	}
	free(ret);
	ret = NULL;
	return 0;
}
```c

;
	minchild = parent * 2 + 1;
}

int* TopK(int* a, int n, int k)
{
	// 开辟K个元素的空间

	int* minHeap = (int*)malloc(sizeof(int) * k);
	if (minHeap == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	// 将数组的前K个元素
	for (int i = 0; i < k; i++)
	{
		minHeap[i] = a[i];
	}
	// 建小堆 --向下调整建堆:O(N)
	// n-1找到最后一个叶子节点,该节点-1/2找到倒数第一个父节点
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(minHeap, k, i);
	}
	// 取N-K个元素与堆顶元素比较,如果大于堆顶元素,就如堆
	for (int i = k; i < n; i++)
	{
		if (minHeap[0] < a[i])
		{
			minHeap[0] = a[i];
			AdjustDown(minHeap, k, 0);
		}
	}
	return minHeap;
}

int main()
{
	int arr[] = { 15,1,19,25,8,34,65,4,27,7 };
	int n = sizeof(arr) / sizeof(arr[0]);
	// TopK问题--前K个最大的元素
	int k = 3;
	int* ret = TopK(arr, n, k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", ret[i]);
	}
	free(ret);
	ret = NULL;
	return 0;
}

在这里插入图片描述

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

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

相关文章

Shopee、ebay、亚马逊等跨境卖家了解测评的一篇干货

随着时代的发展&#xff0c;大家越来越喜欢网购&#xff0c;国外也有亚马逊、沃尔码、阿里国际、速卖通、ebay、shopee、Lazada、ozon、temu等等&#xff0c;而国外这些平台也有很大的市场&#xff0c;跨境电商也随时诞生&#xff0c;而当今社会环境实体生意越来越难做&#xf…

DAMA认证|数据治理产业上规模需要做到“三化”

数据治理是开启数据安全体系化建设的第一步&#xff0c;需要从产业层面做大做强&#xff0c;支撑数据安全整体框架&#xff0c;为数据流通提供安全保障&#xff0c;推动促进数字化产业进一步发展。 规模化发展是数据治理产业的瓶颈&#xff0c;行业数字化业务的复杂性和过多的定…

k8s安装tekton,编写task

文章目录一、官方安装二、国内资源安装安装tekton安装dashboard安装CLI三、demo编写task.yaml编写taskRun.yaml使用tkn命令查看参考文章一、官方安装 地址&#xff1a;https://tekton.dev/docs/installation/pipelines/#installing-tekton-pipelines-on-kubernetes 注意&#…

Spring MVC 源码之MultipartResolver 组件

MultipartResolver 组件&#xff0c;内容类型( Content-Type )为 multipart/* 的请求的解析器&#xff0c;主要解析文件上传的请求。例如&#xff0c;MultipartResolver 会将 HttpServletRequest 封装成 MultipartHttpServletRequest 对象&#xff0c;便于获取参数信息以及上传…

【NVMEM子系统】二、NVMEM驱动框架

个人主页&#xff1a;董哥聊技术我是董哥&#xff0c;嵌入式领域新星创作者创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01;文章目录1、前言2、驱动框架3、源码目录结构4、用户空间下的目录结构1、前言 NVMEM SUBSYSTEM&#xff0c;该子系…

视频片段怎么做成gif图?快试试这2种方法

动态gif图片作为当下非常常用的表情包&#xff0c;其丰富的内容生动的画面深受大众喜爱。那么&#xff0c;当我们想要将电影或是电视剧中的某一片段做成gif动态图片的时候&#xff0c;要如何操作呢&#xff1f;接下来&#xff0c;给大家分享两招视频转化gif的小窍门–使用【GIF…

【力扣-Python-1】两数之和(easy)

https://leetcode.cn/problems/two-sum/题目描述给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出和为目标值 target 的那两个整数&#xff0c;并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答…

uboot下实现U盘自动升级程序的思路分析(基于USB系统、eMMC系统、FAT32文件系统)

1、常见的升级方式 1.1、应用程序升级 优点&#xff1a;在图形化界面操作&#xff0c;只需要选中升级文件并点击升级即可&#xff0c;操作简单&#xff1b; 缺点&#xff1a;应用程序必须能正常启动&#xff0c;当程序出现bug就不能升级&#xff0c;可靠性差&#xff1b; 总结…

旺店通与金蝶云星空对接集成采购入库单接口

旺店通旗舰奇门与金蝶云星空对接集成采购入库单查询连通销售退货新增V1(12-采购入库单集成方案-P)数据源系统:旺店通旗舰奇门旺店通是北京掌上先机网络科技有限公司旗下品牌&#xff0c;国内的零售云服务提供商&#xff0c;基于云计算SaaS服务模式&#xff0c;以体系化解决方案…

Prometheus集群分布式架构浅析

集群行为是一种常见于自然界中鱼群、鸟群、蜂群等低等群居生物的集体行为&#xff0c;受此启发形成了无人机集群的概念。无人机集群不是多无人机间的简单编队&#xff0c;而是通过必要的控制策略使之产生集群协同效应&#xff0c;从而具备执行复杂多变、危险任务的能力。目前无…

【C++】AVLTree——高度平衡二叉搜索树

文章目录一、AVL树的概念二、AVL树节点的定义三、AVL树的插入四、AVL树的旋转1.左单旋2.右单旋3.左右双旋4.右左双旋五、进行验证六、AVLTree的性能个人简介&#x1f4dd; &#x1f3c6;2022年度博客之星Top18;&#x1f3c6;2022社区之星Top2;的&#x1f947;C/C领域优质创作者…

JVM类加载子系统

1、类加载子系统在内存结构中所处的位置通过内存结构图&#xff0c;我们先知道类加载子系统所处的位置&#xff0c;做到心中有图。2、类加载器作用类加载器子系统负责从文件系统或者网络中加载Class文件&#xff0c;class文件在文件开头有特定的文件标识。ClassLoader只负责cla…

anaconda创建环境为空、修改默认环境位置

无论是用navigator还是命令行创建环境都无法指定python版本conda create -n test python3.9其实就是没有路径&#xff0c;添加几个镜像就好&#xff1a;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels ht…

BUUCTF-[安洵杯 2019]crackMe1

题目下载&#xff1a;下载 这道题涉及到SM4加密和变表base64。 SM4简单了解&#xff1a;SM4算法过程_不是小白才怪的博客-CSDN博客_sm4算法 先运行一下程序&#xff0c; 发现有一个Messagebox&#xff0c;并且内容是hooked。 载入IDA&#xff0c;使用IDA的插件Findcrypt查…

ChatGPT:“抢走你工作的不会是 AI ,而是先掌握 AI 能力的人”

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; ChatGPT&#xff1a;“抢走你工作的不会是 AI &#xff0c;而是先掌握 AI 能力的人” ChatGPT&#xff1a;美国OpenAI 研发的聊天机器人程序&#xff0c;人工智能技术…

Springboot启动过程分析

Springboot启动过程分析 SpringBoot的版本是v3.0.2&#xff0c;下面进行详细的分析。 一、SpringBoot启动流程的主干 示例程序入口如下所示&#xff1a; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApp…

【网络知识】TCP和UDP详解

TCP和UDP 文章目录UDP协议概述TCP协议概述TCP报文段TCP连接的建立两天内完成下面的参考博客&#x1f60a;点此到文末惊喜↩︎ UDP协议 概述 TCP协议 概述 定义 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种传输层通信协议&…

Python 之 Pandas DataFrame 数据类型的简介、创建的列操作

文章目录一、DataFrame 结构简介二、DataFrame 对象创建1. 使用普通列表创建2. 使用嵌套列表创建3 指定数值元素的数据类型为 float4. 字典嵌套列表创建5. 添加自定义的行标签6. 列表嵌套字典创建 DataFrame 对象7. Series 创建 DataFrame 对象三、DataFrame 列操作1. 选取数据…

【LeetCode】剑指 Offer(5)

目录 写在前面&#xff1a; 题目&#xff1a; 题目的接口&#xff1a; 解题思路1&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 解题思路2&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a;…

臻和科技再冲刺港交所上市:近三年亏损14亿元,有股东提前退出

近日&#xff0c;臻和科技集团有限公司&#xff08;下称“臻和科技”&#xff09;再次递交招股书&#xff0c;准备在港交所主板上市。据贝多财经了解&#xff0c;这已经是臻和科技第二次冲刺港交所上市。在此之前&#xff0c;臻和科技曾于2022年9月26日递表&#xff0c;后选择了…