服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - 数据结构之堆的具体使用

数据结构之堆的具体使用

2022-09-29 16:40小倪同学 -_- C/C++

本文主要介绍了数据结构之堆的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

的概念及结构

数据结构之堆的具体使用

数据结构之堆的具体使用

 

定义堆

实现堆的功能首先要定义堆的结构体

typedef int HPDataTpye;

typedef struct Heap
{
	HPDataTpye* a;		//存储数据
	int size;           //保存元素个数
	int capacity;       //存储容量
}HP;

 

堆的初始化

思路:

  • 先开辟一块空间,将传入的数据存放到堆的结构体中
  • 将堆中数据建堆排序
  • 将堆结构中容量,元素个数初始化

开辟空间不难,那么如何建堆呢?

这里有两种思路,一是从上往下调整,二是从下往上调整

思路一:
从上往下调整

将传入的结点当做父节点,比较其两个子节点,将子节点与父节点比较,如果不满足堆的条件就交换,并将原先子节点的位置当成父节点,重复上述操作。如果满足堆的条件就结束操作。(注意:该程序是建立在左右子树都为大堆基础上的)

数据结构之堆的具体使用

代码如下

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
// 条件:左右子树都是小堆/大堆
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 选出左右孩子中小 or 大的那个
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}

		// 1、如果小 or 大的孩子比父亲小 or 大,则交换,继续往下调整
		// 2、如果小 or 大 的孩子比父亲大 or 小,则结束调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

思路二:
从下往上建

将传入的结点当做子节点,找到其父结点并与之比较,不满足堆的条件就交换,并将原父结点的位置当成子节点重复之前操作。满足堆的条件则退出程序。(注意:该程序建立在除传入的子节点外,其余结点都满足堆条件基础上的)

数据结构之堆的具体使用

代码实现

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)  不对的 parent不会小于0
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

初始化总体代码

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

// 条件:左右子树都是小堆/大堆
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 选出左右孩子中小 or 大的那个
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}

		// 1、如果小 or 大的孩子比父亲小 or 大,则交换,继续往下调整
		// 2、如果小 or 大 的孩子比父亲大 or 小,则结束调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)  不对的 parent不会小于0
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapInit(HP* php, HPDataTpye* a, int n)
{
	assert(php);
	//开辟空间
	php->a = (HPDataTpye*)malloc(sizeof(HPDataTpye)*n);
	if (php->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	//转移数据
	memcpy(php->a, a, sizeof(HPDataTpye)*n);
	//建堆排序
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}

	php->capacity = n;
	php->size = n;
}

 

插入数据

思路:

  • 检查是否满容量,满了就扩容
  • 插入数据,并将size+1

代码:

void HeapPush(HP* php, HPDataTpye x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		HPDataTpye* tmp = (HPDataTpye*)realloc(php->a, 2 * php->capacity*sizeof(HPDataTpye));
		if (php->a == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

 

判空

思路:

判空只需判断其元素个数是否为0即可

代码:

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

 

删除堆顶的数据

思路:

  • 先判空处理
  • 将堆顶数据和最后一个叶结点数据交换
  • 从上往下调整堆

数据结构之堆的具体使用

代码:

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);
}

 

获取堆顶数据

思路:

先判空,再取出堆顶数据

代码:

HPDataTpye HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}

 

获取元素个数

直接返回size

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

 

打印

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

 

销毁堆

将开辟的空间释放,并将size,capacity赋值为0

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

 

Topk问题

问:如何取出一组数据中最大的前K个值

有人会想到把所有数据建大堆,取出堆顶数据再删除该数据,重复操作K次
操作如下

void TestHeap()
{
	int a[] = { 27, 37, 28, 18, 19, 34, 65, 4, 25, 49, 15 };
	HP hp;
	HeapInit(&hp, a, sizeof(a) / sizeof(int));
	HeapPrint(&hp);

	printf("\n");

	int k = 0;
	scanf("%d", &k);
	printf("找出数组中最小的前%d个:", k);
	while (!HeapEmpty(&hp)&&k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");
}

数据结构之堆的具体使用

如果该组数据个数为一万,十万呢?

这时用该方法不但耗费时间而且十分耗内存,那有没有时间复杂符度较小的用堆实现的方法呢?
答案是有的,那就是Topk算法

Topk基本思路如下:

用数据集合中前K个元素来建堆
求前k个最大的元素,则建小堆
求前k个最小的元素,则建大堆用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

代码实现

void PrintTopK(int* a, int n, int k)
{
	HP hp;
	HeapInit(&hp, a, k);

	for(int i = k; i < n; i++)
	{
		if (a[i]>HeapTop(&hp))
		{
			HeapPop(&hp);
			HeapPush(&hp, a[i]);
		}
	}
	HeapPrint(&hp);
	HeapDestroy(&hp);
}

检测

这里利用随机数来检测

void TestTopk()
{
	int n = 100000;
	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);
}

运行结果

数据结构之堆的具体使用

 

代码总结

Heap.h 头文件

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include<time.h>

typedef int HPDataTpye;

typedef struct Heap
{
	HPDataTpye* a;
	int size;
	int capacity;
}HP;

void Swap(int* px, int* py);
void AdjustDown(int* a, int n, int parent);
void AdjustUp(int* a, int child);


//void HeapInit(HP* php);
//初始化
void HeapInit(HP* php, HPDataTpye* a, int n);
// 插入x,保持他继续是堆
void HeapPush(HP* php, HPDataTpye x);
//判空
bool HeapEmpty(HP* php);
// 删除堆顶数据,删除后保持他继续是堆
void HeapPop(HP* php);
// 获取堆顶的数据,也就是最值
HPDataTpye HeapTop(HP* php);
//获取堆中元素个数
int HeapSize(HP* php);
//打印
void HeapPrint(HP* php);
//销毁堆
void HeapDestroy(HP* php);

Heap.c 函数文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

// 条件:左右子树都是小堆/大堆
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 选出左右孩子中小 or 大的那个
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}

		// 1、如果小 or 大的孩子比父亲小 or 大,则交换,继续往下调整
		// 2、如果小 or 大 的孩子比父亲大 or 小,则结束调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	//while (parent >= 0)  不对的 parent不会小于0
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapInit(HP* php, HPDataTpye* a, int n)
{
	assert(php);
	//开辟空间
	php->a = (HPDataTpye*)malloc(sizeof(HPDataTpye)*n);
	if (php->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	//转移数据
	memcpy(php->a, a, sizeof(HPDataTpye)*n);
	//建堆排序
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}

	php->capacity = n;
	php->size = n;
}

// 插入x,保持它继续是堆
void HeapPush(HP* php, HPDataTpye x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		HPDataTpye* tmp = (HPDataTpye*)realloc(php->a, 2 * php->capacity*sizeof(HPDataTpye));
		if (php->a == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

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

// 删除堆顶数据,删除后保持他继续是堆
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);
}

// 获取堆顶的数据,也就是最值
HPDataTpye HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];
}


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

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

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

test.c 测试文件

#include "Heap.h"

void TestHeap()
{
	int a[] = { 27, 37, 28, 18, 19, 34, 65, 4, 25, 49, 15 };
	HP hp;
	HeapInit(&hp, a, sizeof(a) / sizeof(int));
	HeapPrint(&hp);

	printf("\n");

	int k = 0;
	scanf("%d", &k);
	printf("找出数组中最小的前%d个:", k);
	while (!HeapEmpty(&hp)&&k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");
}

void PrintTopK(int* a, int n, int k)
{
	HP hp;
	HeapInit(&hp, a, k);

	for(int i = k; i < n; i++)
	{
		if (a[i]>HeapTop(&hp))
		{
			HeapPop(&hp);
			HeapPush(&hp, a[i]);
		}
	}
	HeapPrint(&hp);
	HeapDestroy(&hp);
}

void TestTopk()
{
	int n = 100000;
	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);
}

int main()
{
	//TestHeap();
	TestTopk();

	return 0;
}

到此这篇关于数据结构之堆的具体使用的文章就介绍到这了,更多相关数据结构 堆内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/qq_56663697/article/details/120156354

延伸 · 阅读

精彩推荐
  • C/C++C语言实现Flappy Bird小游戏

    C语言实现Flappy Bird小游戏

    这篇文章主要为大家详细介绍了C语言实现Flappy Bird小游戏,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    一个全栈游戏开发者10272021-07-15
  • C/C++详解C++ cout格式化输出完全攻略

    详解C++ cout格式化输出完全攻略

    这篇文章主要介绍了详解C++ cout格式化输出完全攻略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    RioTian7272021-10-07
  • C/C++C语言入门篇--学习选择,if,switch语句以及代码块

    C语言入门篇--学习选择,if,switch语句以及代码块

    本篇文章是基础篇,适合c语言刚入门的朋友,本文主要带大家学习一下C语言的选择,if,switch语句及代码块,帮助大家快速入门c语言的世界,更好的理解c语...

    yycure9042021-12-23
  • C/C++C++ 类和对象基础篇

    C++ 类和对象基础篇

    类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(...

    夜行过客8142021-08-12
  • C/C++C++中关键字Struct和Class的区别

    C++中关键字Struct和Class的区别

    这篇文章主要介绍了C++中关键字Struct和Class的区别,本文用大量实例讲解了Struct和Class的区别,需要的朋友可以参考下...

    果冻想10772021-02-04
  • C/C++C++实现大数乘法算法代码

    C++实现大数乘法算法代码

    这篇文章主要介绍了C++实现大数乘法算法代码的相关资料,需要的朋友可以参考下...

    C++教程网10772021-02-22
  • C/C++C语言中对数组赋值的三种形式

    C语言中对数组赋值的三种形式

    这篇文章主要给大家介绍了关于C语言中对数组赋值的3种形式,文中通过示例代码介绍的非常详细,对大家学习或者使用C语言具有一定的参考学习价值,需...

    onClick1238172021-08-02
  • C/C++C语言动态内存分配函数的实现

    C语言动态内存分配函数的实现

    这篇文章主要介绍了C语言动态内存分配函数的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    爱上小公举11232021-11-05