TopK问题与堆排序

Posted .阿Q.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TopK问题与堆排序相关的知识,希望对你有一定的参考价值。

目录

1、堆的实现

(1)定义堆

(2)初始化堆

(3)交换父子结点

(4)打印堆内数据

(5)堆是否为空

(6)取堆顶数据

(7)销毁堆

(8)✳向上调整

(9)✳数据插入堆(拿最大堆举例,即:插入后仍是保持大堆)

对于插入堆来说,最大堆、最小堆都是向上调整

(10)✳向下调整

(11)✳删除堆顶数据(拿最大堆举例,即:删除后仍保持是大堆)

对于删除堆顶数据来说,最大堆、最小堆都是需要向下调整

※向下、向上调整的时间复杂度log2_N

2、建堆的应用

(1)TopK问题

实现TopK算法(最小的K个数)-- 建立小堆

(2)堆排序

堆排序  -  (向下调整建x堆+x堆)

排升序  -  (向下调整建大堆+大堆)

排降序  -  (向下调整建小堆+小堆)

建堆的时间复杂度O(N)证明


1、堆的实现

因为堆是一棵完全二叉树,所以使用数组结构,不会浪费空间。

所以,使用数组结构实现堆

代码以大堆,作为插入堆、删除根节点为例子,后续不再说明:


(1)定义堆

可以看到,堆的结构定义和顺序表类似

typedef int HPDataType;
typedef struct Heap

	HPDataType* a;
	int size;
	int capacity;
HP;

(2)初始化堆

//初始化堆
void HeapInit(HP* hp)

	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;

(3)交换父子结点

//交换父子结点数据
void Swap(HPDataType* px, HPDataType* py)

	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;

(4)打印堆内数据

//打印堆内数据
void HeapPrint(HP* hp)

	for (int i = 0; i < hp->size; ++i)
	
		printf("%d ", hp->a[i]);
	
	printf("\\n");

(5)堆是否为空

//堆为空
bool HeapEmpty(HP* hp)

	assert(hp);

	return hp->size == 0;

(6)取堆顶数据

//获取堆顶数据
HPDataType HeapTop(HP* hp)

	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->a[0];

(7)销毁堆

//销毁堆
void HeapDestroy(HP* hp)

	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;

(8)✳向上调整

插入堆如何实现呢?

注意:插入数据后,还要保证堆依旧是大堆(此处以大堆为列)

分析:


所以,需要向上调整

  1. 先将元素插入到堆的末尾,即最后一个孩子之后
  2. 插入之后如果堆的性质遭到破坏,则将新插入节点顺着其双亲往上调整到合适位置即可


向上调整的执行逻辑:

//向上调整(大堆)
void AdjustUp(int* a, int child)

	assert(a);

	int parent = (child - 1) / 2;
	//while (parent >= 0)   //这样写有Bug!
	while (child > 0)
	
		if (a[child] > a[parent])
		
			/*HPDataType tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;*/
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		
		else
		
			break;
		
	

(9)✳数据插入堆(拿最大堆举例,即:插入后仍是保持大堆)

//往堆插入数据
void HeapPush(HP* hp, HPDataType x)

	assert(hp);
	//不够就扩容
	if (hp->size == hp->capacity)
	
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		
			printf("realloc fail!\\n");
			exit(-1);
		

		hp->a = tmp;
		hp->capacity = newcapacity;
	

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

	AdjustUp(hp->a, hp->size - 1);

对于插入堆来说,最大堆、最小堆都是向上调整

区别:

最大堆向上调整,插入后的结点数据,要不改变最大堆(ps:定义  最大堆是每个节点的数据值都大于等于其子树数据值),影响了插入节点到根节点那段路径上的结点( 产生变化!):

  • 将插入结点作为child,与其父亲parent比较,如果a[child] > a[parent],就交换节点数据值,直到child是根节点,才截至!

最小堆向上调整,插入后的结点数据,要不改变最小堆(ps:定义  最小堆是每个节点的数据值都小于等于其子树数据值),影响了插入节点到根节点那段路径上的结点( 产生变化!):

  • 将插入结点作为child,与其父亲parent比较,如果a[child] < a[parent],就交换节点数据值,直到child是根节点,才截至!

(10)✳向下调整

分析:

删除堆顶数据,还要依旧保持是大堆,则需要向下调整

  1. 将堆顶元素与堆中最后一个元素进行交换
  2. 删除堆中最后一个元素
  3. 将堆顶元素向下调整到满足堆特性为止


向下调整过程:

注意:向下调整的循环结束条件:(大堆为例!)

  1. 父亲  >=  大的孩子,则停止      即:父亲比两个孩子都大
  2. 调整到叶子结点         (叶子特征:没有左孩子,即:左孩子下标超出数组范围,就不存在了)
//向下调整(大堆)
void AdjustDown(int* a, int n, int parent)

	assert(a);

	int child = parent * 2 + 1;//左孩子
	while (child < n)
	
		//选出大孩子
		if (child + 1 < n && a[child + 1] > a[child])
			child++;
		
		//交换 并 迭代 parent 和 child
		if (a[parent] < a[child])
		
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		
		else
		
			break;
		
	

(11)✳删除堆顶数据(拿最大堆举例,即:删除后仍保持是大堆)

//删除堆顶数据
void HeapPop(HP* hp)

	assert(hp);
	assert(!HeapEmpty(&hp));

	Swap(&hp->a[0], &hp->a[hp->size - 1]);//堆顶数据与末尾数据交换
	hp->size--;

	AdjustDown(hp->a, hp->size, 0);

对于删除堆顶数据来说,最大堆、最小堆都是需要向下调整

需要注意的是,根节点,也就是堆顶结点,我们直接删除是不行的!!   

需要将最后一个结点与根节点位置互换,因为我们删除尾结点(堆本质上是数组嘛~~~~~)是方便的!!!

然后再进行“  根结点  ” 向下调整...................

区别:

最大堆删除堆顶数据,看是否此时还仍然符合最大堆的要求,若不符合,那么就需要向下调整,调成符合最大堆的形式。

考虑到完全二叉树中,任意一个父亲节点左孩子存在,而右孩子不一定存在(细品!!!),所以我们需要判断,避免造成非法访问数组元素。如果右孩子存在,即:child + 1 < n ,右孩子大于左孩子,即:a[child + 1] > a[ child ](选出大孩子)。那么就child指向右孩子。

(即:该代码段是用child指向左右孩子中较大孩子)

如果说,a[child] > a[parent],那么就交换父子结点,然后继续向下调整。

最小堆删除堆顶数据,看是否此时还仍然符合最小堆的要求,若不符合,那么就需要向下调整,调成符合最小堆的形式。

考虑到完全二叉树中,任意一个父亲节点左孩子存在,而右孩子不一定存在(细品!!!),所以我们需要判断,避免造成非法访问数组元素。如果右孩子存在,即:child + 1 < n ,右孩子小于左孩子,即:a[child + 1] < a[ child ](选出小孩子)。那么就child指向右孩子。

(即:该代码段是用child指向左右孩子中较小孩子)

如果说,a[child] < a[parent],那么就交换父子结点,然后继续向下调整。

测试  【插入、删除大根堆】 的效果:

※向下、向上调整的时间复杂度log2_N

所以,向上向下调整都需要调整树的高度h次,即:时间复杂度为:O(log2_N)

堆的实现总代码:

//Heap.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//实现大堆
typedef int HPDataType;
//定义堆
typedef struct Heap

	HPDataType* a;
	int size;
	int capacity;
HP;


//初始化堆
void HeapInit(HP* hp);

//交换父子结点数据
void Swap(HPDataType* px, HPDataType* py);

//打印堆内数据
void HeapPrint(HP* hp);

//堆为空
bool HeapEmpty(HP* hp);

//销毁堆
void HeapDestroy(HP* hp);

//数据插入堆
void HeapPush(HP* hp, HPDataType x);

//向上调整
void AdjustUp(int* a, int child);

//删除堆顶数据
void HeapPop(HP* hp);

//向下调整
void AdjustDown(int* a, int n, int parent);

//获取堆顶数据
HPDataType HeapTop(HP* hp);
//Heap.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

//初始化堆
void HeapInit(HP* hp)

	assert(hp);

	hp->a = NULL;
	hp->size = hp->capacity = 0;

//销毁堆
void HeapDestroy(HP* hp)

	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;

//交换父子结点数据
void Swap(HPDataType* px, HPDataType* py)

	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;

//获取堆顶数据
HPDataType HeapTop(HP* hp)

	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->a[0];

向上调整(大堆)
//void AdjustUp(int* a, int child)
//
//	assert(a);
//
//	int parent = (child - 1) / 2;
//	//while (parent >= 0)
//	while (child > 0)
//	
//		if (a[child] > a[parent])
//		
//			/*HPDataType tmp = a[child];
//			a[child] = a[parent];
//			a[parent] = tmp;*/
//			Swap(&a[child], &a[parent]);
//
//			child = parent;
//			parent = (child - 1) / 2;
//		
//		else
//		
//			break;
//		
//	
//
//向上调整(小堆)
void AdjustUp(int* a, int child)

	assert(a);

	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	
		if (a[child] < a[parent])
		
			/*HPDataType tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;*/
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		
		else
		
			break;
		
	

//往堆插入数据
void HeapPush(HP* hp, HPDataType x)

	assert(hp);
	//不够就扩容
	if (hp->size == hp->capacity)
	
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		
			printf("realloc fail!\\n");
			exit(-1);
		

		hp->a = tmp;
		hp->capacity = newcapacity;
	

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

	AdjustUp(hp->a, hp->size - 1);

//判断堆是否为空
bool HeapEmpty(HP* hp)

	assert(hp);

	return hp->size == 0;

//堆的数据个数
int HeapSize(HP* hp)

	assert(hp);

	return hp->size;

//打印堆内数据
void HeapPrint(HP* hp)

	for (int i = 0; i < hp->size; ++i)
	
		printf("%d ", hp->a[i]);
	
	printf("\\n");

向下调整(大堆)
//void AdjustDown(int* a, int n, int parent)
//
//	assert(a);
//
//	int child = parent * 2 + 1;//左孩子
//	while (child < n)
//	
//		//选出大孩子
//		if (child + 1 < n && a[child + 1] > a[child])
//			child++;
//		
//		//交换 并 迭代 parent 和 child
//		if (a[parent] < a[child])
//		
//			Swap(&a[parent], &a[child]);
//			parent = child;
//			child = parent * 2 + 1;
//		
//		else
//		
//			break;
//		
//	
//
//向下调整(小堆)
void AdjustDown(int* a, int n, int parent)

	assert(a);

	int child = parent * 2 + 1;//左孩子
	while (child < n)
	
		//选出小孩子
		if (child + 1 < n && a[child + 1] < a[child])
			child++;

		//交换 并 迭代 parent 和 child
		if (a[parent] > a[child])
		
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		
		else
		
			break;
		
	

//删除堆顶数据
void HeapPop(HP* hp)

	assert(hp);
	assert(!HeapEmpty(&hp));

	Swap(&hp->a[0], &hp->a[hp->size - 1]);//堆顶数据与末尾数据交换
	hp->size--;

	AdjustDown(hp->a, hp->size, 0);
//Test.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

//在N个数中找出最大的前K个最大值 (或者最小的前K个最小值)

int main()

	int a[] =  70,56,30,25,15,10,75 ;
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	
		HeapPush(&hp, a[i]);
	
	HeapPrint(&hp);

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

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

	//销毁堆
	HeapDestroy(&hp);

	return 0;

2、建堆的应用

  1. topk问题
  2. 堆排序算法

(1)TopK问题

首先想想为什么要用堆实现Topk问题?在N非常大的时候,其他算法为什么不行?


以选最大的K个数为例,建立小堆。


建立K个数的小堆,最终达到选出N个数中最大的K个数,在这个过程中需要注意的是:

  1. 选最大的K个数,是建立小堆。因为小堆的堆顶数据最小的,而用剩下的N-K个数去和最小的比较...然后大于它...就取代,就会保证每次选出的K个数都是到目前为止最大的K个数。
  2. 因为K<<N,所以通过该方法实现了仅仅用K个空间就可以实现N个数据中的Topk问题。空间节约了,而且没有多余去管其他的N-K个数具体的大小,针对性的解决了要求的K个数。

实现TopK算法(最小的K个数)-- 建立小堆

TopK算法(Test.c文件)

//Test.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

//从N个数中,选出K个最大的数
//(只有建立 【小堆】 一种办法,大堆不行)
void PrintTopK(int* a, int n, int k)

	HP hp;
	HeapInit(&hp);
	// 1. 建堆--用a中的K个数创建一个小堆
	for (int i = 0; i < k; ++i)
	
		HeapPush(&hp, a[i]);//小堆的HeapPush
	

	// 2. 将剩余N-K个元素依次与堆顶数据比较,比他大,就替换他,进堆
	for (int i = k; i < n; ++i)
	
		if (a[i] > HeapTop(&hp))
		
			//方法1
			//HeapPop(&hp);//小堆的HeapPop
			//HeapPush(&hp, a[i]);

			//方法2
			hp.a[0] = a[i];
			AdjustDown(hp.a, hp.size, 0);//==>为了保持是小堆
		
	
	HeapPrint(&hp);

	//HeapDestroy(&hp);

void TestTopk()

	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand(time(0));
	for (int i = 0; i < n; ++i)
	
		//随机生成数字,%100w就保证了数字小于100w
		a[i] = rand() % 1000000;
	
	//手动设置10个大于100w的数字(  即:要选出的K个最大数  )
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
	PrintTopK(a, n, 10);

int main()

	TestTopk();

	return 0;
//Heap.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//实现大堆
typedef int HPDataType;
//定义堆
typedef struct Heap

	HPDataType* a;
	int size;
	int capacity;
HP;


//初始化堆
void HeapInit(HP* hp);

//交换父子结点数据
void Swap(HPDataType* px, HPDataType* py);

//打印堆内数据
void HeapPrint(HP* hp);

//堆为空
bool HeapEmpty(HP* hp);

//销毁堆
void HeapDestroy(HP* hp);

//数据插入堆
void HeapPush(HP* hp, HPDataType x);

//向上调整
void AdjustUp(int* a, int child);

//删除堆顶数据
void HeapPop(HP* hp);

//向下调整
void AdjustDown(int* a, int n, int parent);

//获取堆顶数据
HPDataType HeapTop(HP* hp);
//Heap.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

//初始化堆
void HeapInit(HP* hp)

	assert(hp);

	hp->a = NULL;
	hp->size = hp->capacity = 0;

//销毁堆
void HeapDestroy(HP* hp)

	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;

//交换父子结点数据
void Swap(HPDataType* px, HPDataType* py)

	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;

//获取堆顶数据
HPDataType HeapTop(HP* hp)

	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->a[0];

//向上调整(小堆)
void AdjustUp(int* a, int child)

	assert(a);

	int parent = (child - 1) / 2;
	//while (parent >= 0)
	while (child > 0)
	
		if (a[child] < a[parent])
		
			/*HPDataType tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;*/
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		
		else
		
			break;
		
	

//往堆插入数据
void HeapPush(HP* hp, HPDataType x)

	assert(hp);
	//不够就扩容
	if (hp->size == hp->capacity)
	
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		
			printf("realloc fail!\\n");
			exit(-1);
		

		hp->a = tmp;
		hp->capacity = newcapacity;
	

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

	AdjustUp(hp->a, hp->size - 1);

//判断堆是否为空
bool HeapEmpty(HP* hp)

	assert(hp);

	return hp->size == 0;

//堆的数据个数
int HeapSize(HP* hp)

	assert(hp);

	return hp->size;

//打印堆内数据
void HeapPrint(HP* hp)

	for (int i = 0; i < hp->size; ++i)
	
		printf("%d ", hp->a[i]);
	
	printf("\\n");

//向下调整(小堆)
void AdjustDown(int* a, int n, int parent)

	assert(a);

	int child = parent * 2 + 1;//左孩子
	while (child < n)
	
		//选出小孩子
		if (child + 1 < n && a[child + 1] < a[child])
			child++;

		//交换 并 迭代 parent 和 child
		if (a[parent] > a[child])
		
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		
		else
		
			break;
		
	

//删除堆顶数据
void HeapPop(HP* hp)

	assert(hp);
	assert(!HeapEmpty(&hp));

	Swap(&hp->a[0], &hp->a[hp->size - 1]);//堆顶数据与末尾数据交换
	hp->size--;

	AdjustDown(hp->a, hp->size, 0);

测试效果 

(2)堆排序

堆排序 - 升序  -  空间复杂度为O(N)的写法    (不推荐)

堆排序算法(Test.c文件)

该算法是建立小堆,并且用了HeapPush等建堆操作;所以是额外开辟使用了空间,并且还得事先实现堆以及堆的操作才能做。

而堆本身就是数组,那么能否直接对数组a进行构建堆?(不用堆的任何相关操作~)

//Test.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

//堆排序 - 升序  - 空间复杂度( O(N) )  
//降序同理,Push、Pop小堆变大堆即可
void HeapSort(int* a, int n)

	HP hp;
	HeapInit(&hp);
	//建立一个小堆
	for (int i = 0; i < n; ++i)
	
		HeapPush(&hp, a[i]);//小堆的HeapPush
	

	//Pop  N 次
	for (int i = 0; i < n; ++i)
	
		a[i] = HeapTop(&hp);
		HeapPop(&hp);//小堆的HeapPop
	
	//HeapDestroy(&hp);

int main()

	int a[] =  70,56,30,25,15,10,75 ;
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	
		printf("%d ", a[i]);
	
	printf("\\n");

	HeapSort(a, sizeof(a) / sizeof(a[0]));

	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	
		printf("%d ", a[i]);
	
	printf("\\n");
	return 0;

该程序空间复杂度是O(N),那么能否优化到O(1)呢?

(即:不能用Heap避免了HeapPush开辟新空间,直接对数组a进行操作


堆排序 - 升序  -  空间复杂度为 O(1) 的写法    (推荐!) 

考虑到原来使用了HeapPush建堆,新开辟了N个空间。所以空间复杂度是O(N),而优化到O(1)即优化建堆的操作。

所以,优化建堆的操作就是对数组a直接操作,不开辟新空间。其有两种方式:

  1. 向上调整  (依次加入)
  2. 向下调整  (倒着走)

那么建堆完成后,如何实现升序呢?

 所以,排升序,用大堆+向下调整建堆方式

堆排序  -  (向下调整建x堆+x堆)

堆排序:

  1. 排升序,建大堆
  2. 排降序,建小堆

*注释:大小堆,在堆排序中的区别就体现在AdjustDown函数实现的是大堆还是小堆

void HeapSort(int* a, int n)

	//把a构建成堆(向下调整)
	//注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整

	int end = n - 1;//最后一个节点
	for (int i = (end - 1) / 2; i >= 0; i--)
	
		AdjustDown(a, n, i);
	

	//实现升序
	end = n - 1;//最后一个数
	for (int i = end; i > 0; i--)
	
		Swap(&a[i], &a[0]);
		AdjustDown(a, i, 0);
	

排升序  -  (向下调整建大堆+大堆)

//向下调整(大堆)
void AdjustDown(int* a, int n, int parent);
//堆排序 - 升序  -(向下调整建大堆+大堆)
void HeapSort(int* a, int n)

	//把a构建成堆(向下调整)
	//注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整

	int end = n - 1;//最后一个节点
	for (int i = (end - 1) / 2; i >= 0; i--)
	
		AdjustDown(a, n, i);
	

	//实现升序
	end = n - 1;//最后一个数
	for (int i = end; i > 0; i--)
	
		Swap(&a[i], &a[0]);
		AdjustDown(a, i, 0);
	

//向下调整(大堆)
void AdjustDown(int* a, int n, int parent)

	assert(a);

	int child = parent * 2 + 1;//左孩子
	while (child < n)
	
		//选出大孩子
		if (child + 1 < n && a[child + 1] > a[child])
			child++;

		//交换 并 迭代 parent 和 child
		if (a[parent] < a[child])
		
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		
		else
		
			break;
		
	

整体代码

//堆排序 - 升序 -(向下调整建大堆+大堆)
void HeapSort(int* a, int n)

	//建堆操作:
	//方法1:
	//把a构建成堆(向上调整)
	/*for (int i = 0; i < n; i++)
	
		AdjustUp(a, i);
	
	*/

	//方法2:
	//把a构建成堆(向下调整)
	//注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整

	int end = n - 1;//最后一个节点
	for (int i = (end - 1) / 2; i >= 0; i--)
	
		AdjustDown(a, n, i);
	

	//实现升序
	end = n - 1;//最后一个数
	for (int i = end; i > 0; i--)
	
		Swap(&a[i], &a[0]);
		AdjustDown(a, i, 0);
	

int main()

	int a[] =  70,56,30,25,15,10,75,33,50,69 ;
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	
		printf("%d ", a[i]);
	
	printf("\\n");

	HeapSort(a, sizeof(a) / sizeof(a[0]));

	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	
		printf("%d ", a[i]);
	
	printf("\\n");
	return 0;

 同理,堆排序 - 排降序是建立小堆

排降序  -  (向下调整建小堆+小堆)

//向下调整(小堆)
void AdjustDown(int* a, int n, int parent);
//堆排序 - 降序 
void HeapSort(int* a, int n)

	//把a构建成堆(向下调整)
	//注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整

	int end = n - 1;//最后一个节点
	for (int i = (end - 1) / 2; i >= 0; i--)
	
		AdjustDown(a, n, i);
	

	//实现降序
	end = n - 1;//最后一个数
	for (int i = end; i > 0; i--)
	
		Swap(&a[i], &a[0]);
		AdjustDown(a, i, 0);
	

//向下调整(小堆)
void AdjustDown(int* a, int n, int parent)

	assert(a);

	int child = parent * 2 + 1;//左孩子
	while (child < n)
	
		//选出小孩子
		if (child + 1 < n && a[child + 1] < a[child])
			child++;

		//交换 并 迭代 parent 和 child
		if (a[parent] > a[child])
		
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		
		else
		
			break;
		
	

整体代码:

//堆排序 - 降序 
void HeapSort(int* a, int n)

	//建堆操作:
	//方法1:
	//把a构建成堆(向上调整)
	/*for (int i = 0; i < n; i++)
	
		AdjustUp(a, i);
	
	*/

	//方法2:
	//把a构建成堆(向下调整)
	//注意:需要找到 [最后一个节点] 的 [父亲节点] ,从那开始,向下调整

	int end = n - 1;//最后一个节点
	for (int i = (end - 1) / 2; i >= 0; i--)
	
		AdjustDown(a, n, i);
	

	//实现降序
	end = n - 1;//最后一个数
	for (int i = end; i > 0; i--)
	
		Swap(&a[i], &a[0]);
		AdjustDown(a, i, 0);
	

int main()

	int a[] =  70,56,30,25,15,10,75,33,50,69 ;
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	
		printf("%d ", a[i]);
	
	printf("\\n");

	HeapSort(a, sizeof(a) / sizeof(a[0]));

	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	
		printf("%d ", a[i]);
	
	printf("\\n");
	return 0;

建堆的时间复杂度O(N)证明

以上是关于TopK问题与堆排序的主要内容,如果未能解决你的问题,请参考以下文章

TopK问题与堆排序

最大堆与堆排序和优先队列

排序4-堆排序与海量TopK问题

topK问题

topK问题

转白话经典算法系列之七 堆与堆排序