【数据结构】带你了解什么是堆以及堆的应用

堆的概念及结构

如果有一个关键码的集合K = { K₁,K₂ ,K₃ ,…,Kⁿ﹣₁ },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:双亲节点小于或等于孩子(双亲节点大于或等于孩子)则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
通俗的来讲:堆就是一种物理结构是数组但逻辑结构是二叉树的一种数据结构,它的特点是充分体现在逻辑结构上的,每一个双亲节点都大于或等于它的孩子(大堆);每一个双亲节点都小于或等于它的孩子(小堆)

堆的性质:

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

堆的逻辑结构(物理结构是一个数组)

【数据结构】带你了解什么是堆以及堆的应用_第1张图片

堆的实现

//Heap.h文件

#include 
#include 
#include 
#include 

typedef int HPDataType;
typedef struct HeapNode
{
	HPDataType* data;
	int size;
	int capacity;
}HP;

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

//销毁
void HeapDestory(HP* php);

//插入
void HeapPush(HP* php, HPDataType x);

//删除
void HeapPop(HP* php);

//返回堆顶的数据
HPDataType HeapTop(HP* php);

//返回堆的大小
int HeapSize(HP* php);

//判空
bool HeapEmpty(HP* php);

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

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

//交换
void Swap(HPDataType* v1, HPDataType* v2);
//Heap.c文件

#include "Heap.h"

void HeapInit(HP* php)
{
	assert(php);
	php->data = NULL;
	php->size = 0;
	php->capacity = 0;
}


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

void Swap(HPDataType* v1, HPDataType* v2)
{
	HPDataType tmp = *v1;
	*v1 = *v2;
	*v2 = tmp;
}


void AdjustUp(HPDataType* data, int child)
{
	assert(data);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//按小堆来,如果双亲大于孩子,就交换
		if (data[parent] > data[child])
		{
			/*HPDataType tmp = data[parent];
			data[parent] = data[child];
			data[child] = tmp;*/


			Swap(&data[parent], &data[child]);

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


void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//扩容
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->data, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail!\n");
			return;
		}
		php->data = tmp;
		php->capacity = newCapacity;
	}

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

	//向上调整
	AdjustUp(php->data, php->size - 1);
}


void AdjustDown(HPDataType* data, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && data[child + 1] < data[child])
		{
			child++;
		}
		if (data[child] < data[parent])
		{
			Swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;

		}
		else
		{
			break;
		}

	}
}

//删除堆顶的数据
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	//第一个和最后一个交换
	Swap(&php->data[0], &php->data[php->size - 1]);
	php->size--;
	//向下调整
	AdjustDown(php->data, php->size, 0);
}

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

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

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

【数据结构】带你了解什么是堆以及堆的应用_第2张图片

堆的应用

一、堆排序

给一个数组进行排序,首先应该先建一个堆,排升序建大堆,排降序建小堆,创建好堆后,将堆顶元素和最后一个元素交换,这样就把最大的或最小的数放在了最后。再把除了最后一个数之外的数据向下调整建堆,再交换,这就就把次大的或次小的数放在了倒数第二个位置。循环这个过程,这个数组中的数据就会按降序或升序变得有序。

代码如下(按降序实现):

#include 
void Swap(int* v1, int* v2)
{
	int tmp = *v1;
	*v1 = *v2;
	*v2 = tmp;
}

void AdjustDown(int* arr, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] < arr[child])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);

			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

void HeapSort(int* arr, int n)
{
	//建堆
	//升序——建大堆
	//降序——建小堆
	
	//向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		//交换
		Swap(&arr[0], &arr[end]);

		//向下调整
		AdjustDown(arr, end, 0);
		end--;
	}
}

int main()
{
	int arr[] = { 2,5,6,1,4,7,0,9,8,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	HeapSort(arr, sz);

	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果:

【数据结构】带你了解什么是堆以及堆的应用_第3张图片

 堆排序的时间复杂度:O(N*log₂N)

二、TopK问题

N个数找最大的前K个
正常思路:把N个数建成一个大堆,Pop K次,就可以找出最大的K个数
但是当N非常大时,上面的思路就解决不了了。
解决思路:
  1. 前K个数建小堆
  2. 后面N-K个数依次与堆顶的数比较,如果比堆顶的数大,就替换它进堆(覆盖堆顶的值,向下调整)
  3. 最后这个小堆的值就是最大的前K个数

代码实现:


#include 
#include 
#include 

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;

			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void CreateData()
{
	FILE* fin = fopen("data.txt", "w");
	if (fin == NULL)
	{
		perror("fopen fail\n");
		return;
	}

	int n = 1000;
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(int k)
{
	FILE* fout = fopen("data.txt", "r");
	if (fout == NULL)
	{
		perror("fopen fail\n");
		return;
	}


	int* a = (int*)malloc(sizeof(int) * k);
	if (a == NULL)
	{
		perror("malloc fail\n");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &a[i]);
	}

	//建小堆——向下调整
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}

	while (!feof(fout))
	{
		int x = 0;
		fscanf(fout, "%d", &x);

		if (a[0] < x)
		{
			a[0] = x;
			AdjustDown(a, k, 0);

		}

	}

	//排序
	int end = k - 1;
	while (end > 0)
	{
		int tmp = a[0];
		a[0] = a[end];
		a[end] = tmp;

		AdjustDown(a, end, 0);
		end--;
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);

	}
	printf("\n");

	fclose(fout);
}

int main()
{
	CreateData();
	PrintTopK(5);
	return 0;
}
以上代码实现的是往文件中写入1000个一百万以内的随机数据,找出1000个数据的前5个大的数。为了方便观察,在文件中随机改5个数据,改成超过一百万的数,这样top5就是大于一百万的这5个数。
【数据结构】带你了解什么是堆以及堆的应用_第4张图片

 运行结果:

【数据结构】带你了解什么是堆以及堆的应用_第5张图片

你可能感兴趣的:(数据结构,算法)