堆的构建及应用~C语言简单实现

目录

堆的实现

堆的创建

 建堆时间复杂度

堆的插入

堆的删除

堆的代码实现

堆的初始化

  堆的销毁

堆的插入

 堆的删除

获取堆顶的数据 

 堆的数据个数

堆的判空 

堆的打印 

向上调整

向下调整 

堆的应用

堆排序

TOP-K问题


堆(Heap)计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。

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

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

堆的实现

堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整
成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算
法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的
子树开始调整,一直调整到根节点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6};


 

 建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的
就是近似值,多几个节点不影响最终结果):

 因此:建堆的时间复杂度为O(N)

堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调
整算法。

 

堆的代码实现

先看看具体需要实现哪些功能

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;//大小
	int capacity;//最大容量
}Heap;
//堆的初始化
void HeapInit(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
//堆的打印
void HeapPrint(Heap*hp);
//向上调整(大堆/小堆)
void AdjustUp(HPDataType* a, int n, int child);
//向下调整(大堆/小堆)
void AdjustDown(HPDataType*a,int n,int root);
//交换
void swap(HPDataType*a, HPDataType*b);

下面来看看具体实现

堆的初始化

void HeapInit(Heap* hp, HPDataType* a, int n)
{
	assert(hp&&a);
	hp->a = (HPDataType*)malloc(sizeof(HPDataType)*n);
	hp->size = n;
	hp->capacity = n;
	for (int i = 0; i < n; i++)
	{
		hp->a[i] = a[i];
	}
	for (int i=(n-2)/2;i>=0;i--)
	{
		AdjustDown(hp->a,hp->size,i);
	}
}

  堆的销毁

void HeapDestory(Heap* hp)
{
	assert(hp);
	hp->size = 0;
	hp->capacity = 0;
	free(hp->a);
	hp->a = NULL;
}

堆的插入

void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//扩容
	if (hp->size == hp->capacity)
	{
		hp->capacity *= 2;
		hp->a = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * hp->capacity);
	}
	hp->a[hp->size] = x;
	hp->size++;
	//向上调整
	AdjustUp(hp->a,hp->size,hp->size-1);
}

 堆的删除

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	//先把第一个和最后一个交换
	swap(&hp->a[0], &hp->a[hp->size - 1]);
	//删掉最后一个
	hp->size--;
	//向下调整
	AdjustDown(hp->a,hp->size,0);
}

获取堆顶的数据 

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}

 堆的数据个数

// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

堆的判空 

// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0 ;
}

堆的打印 

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

向上调整

//向上调整(大堆)
void AdjustUp(HPDataType* a, int n, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child>0&&a[child]>a[parent])
	{
		swap(&a[parent],&a[child]);
		child = parent;
		parent = (child - 1) / 2;
	}
}
//向上调整(小堆)
void AdjustUp(HPDataType* a, int n, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child>0&&a[child]<a[parent])
	{
		swap(&a[parent],&a[child]);
		child = parent;
		parent = (child - 1) / 2;
	}
}

向下调整 

//向下调整(大堆)
void AdjustDown(HPDataType*a,int n,int root)
{
	assert(a);
	int parent = root;
	int child = root*2+1;
	if (child+1<n&&a[child+1] > a[child ])
	{
		child++;
	}
	while (child<n&&a[child]>a[parent])
	{
		swap(&a[child],&a[parent]);
		parent = child;
		child = 2 * parent + 1;
		if (child<n && a[child+1] > a[child ])
		{
			child++;
		}
	}
}
//向下调整(小堆)
void AdjustDown(HPDataType*a,int n,int root)
{
	assert(a);
	int parent = root;
	int child = root*2+1;
	if (child+1<n&&a[child+1] < a[child ])
	{
		child++;
	}
	while (child<n&&a[child]<a[parent])
	{
		swap(&a[child],&a[parent]);
		parent = child;
		child = 2 * parent + 1;
		if (child<n && a[child+1] <a[child ])
		{
			child++;
		}
	}
}

交换 

//交换
void swap(HPDataType*a, HPDataType*b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

堆的应用

堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆
降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

 下面是借用大堆来实现排升序的代码

//建大堆排升序
void HeapSort(int *a,int n)
{
	//先建大堆
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a , n, i);//大堆
	}
	//再排序
	for (int i = n-1; i > 0; i--)
	{
		swap(&a[0], &a[i]);
		AdjustDown(a, i, 0);//大堆
	}
}

TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
 

//建小堆来取最大前K个数
void PrintTopK(int* a, int n, int k)
{
	assert(a);
	Heap hp;
// 创建一个K个数的小堆
	HeapInit(&hp,a,k);
	for (int i=k;i<n;i++)
	{
		if (HeapTop(&hp) < a[i])
		{
			HeapPop(&hp);
			HeapPush(&hp, a[i]);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
}
//测试
void TestTopk()
{
int n = 10000;
int* a = (int*)malloc(sizeof(int)*n);
srand(time(0));
for (size_t 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[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);
}


版权声明:本文为qq_58259923原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。