【堆】C++下定义通用的堆模板

程序员在写程序时,最常遇到的就是排序问题
对于N个待排序的数据结构,使用冒泡插入等排序方法,时间复杂度为O(N2),往往不能满足要求,因此需要使用更加高效的排序方法(归并,二分等)将时间复杂度降低为O(logN)
因此,本文在这里介绍堆排序的方法,与堆的通用数据结构代码,以供参考

1.堆基础知识

什么是堆?

定义:堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

要点1:堆可以类比为完全二叉树
要点2:父亲节点的键值或索引总是小于(或者大于)左右孩子的键值或索引
要点3:堆是一种递归的数据存储结构
要点4:父亲节点比孩子节点小的叫做小顶堆,父亲节点比孩子节点大的叫做大顶堆

我们可以看到,对于堆,可以类比为完全二叉树,根据这一特性,我们就可以使用数组来实现一个堆。使用数组来实现堆的话,可以使用元素在数组中的索引来快速确认其父亲节点,孩子节点,关系如下:

左孩子索引=当前节点索引*2+1
右孩子索引=当前节点索引*2+2
父亲节点索引=(当前节点索引-1)/2

如图所示,我们使用一个int[]数组实现小顶堆,来存放数据{3,8,15,31,25}
图1 堆的逻辑结构与数据结构

图1 堆的逻辑结构与数据结构

由于堆的定义,我们不难发现,同一节点的左右孩子的大小关系是无法确定的

要点5:同一节点的左右孩子节点的大小关系不定

那么如何实现一个堆呢?接下来介绍堆的原操作

堆的原操作

入堆操作

当我们要向堆中添加新的元素,需执行以下流程:

  1. 将新元素增加到数组的末尾
  2. 将添加的数据与其父亲节点的数据进行比较,如果新增节点的数据根据排序准则先于其父亲节点,交换两者的位置
  3. 递归比较至根节点结束或者满足排序准则结束

以整形数组为例,假设我们向图1的堆中增加一个元素1,流程如下:
在这里插入图片描述

图2 向堆中增加数字1

在这里插入图片描述

图3 上浮操作1次

在这里插入图片描述

图4 上浮操作2次

可以看到,向堆中增加元素是一个上浮的过程,将当前节点与父亲节点进行对比,如果优先度更高,向上调整。 时间复杂度为O(logN)

实现代码如下:

int a[100]={
   3,8,15,31,25};
int push=1;
int heapsize=5;//当前堆有5个元素
//首先将push放入数组队尾
a[heapsize++]=push;
//比较a[5]与其父亲节点
int cur=heapsize-1;
int father=(cur-1)/2;
while(cur>0&&a[cur]<a[father])
{
   
	//孩子节点应该排序在父亲节点之上,交换
	int tmep=a[cur];
	a[cur]=a[father];
	a[father]=temp;
	
	cur=father;
	father=(father-1)/2;
}

出堆操作

同样以上面为例,当一个堆要弹出顶端的元素时,我们先将0号索引位置的元素记录下来,然后将堆尾的元素放在0号位置,然后将目前0号位的元素跟孩子们进行比对,如果优先度更低,执行下沉操作

下沉操作有两个流程:

  1. 寻找左右孩子排序优先度更高的孩子
  2. 当前节点与优先度更高的孩子进行对比,如果孩子节点的优先度更高,执行下沉操作

流程图如下:
在这里插入图片描述

图5 弹出堆顶的元素

在这里插入图片描述

图6 下沉调整1次

代码如下:

int pop=a[0];

a[0]=a[--heapsize];
int cur=0;
int child;
while(cur*2+1<heapsize)
{
   
	//首先寻找孩子节点中优先度高的
	if(cur*2+2==heapsize)//如果没有右孩子,跟左孩子比对即可
		child=cur*2+1;
	else
		child=a[cur*2+1]<a[cur*2+2]?cur*2+1:cur*2+2;
	if(a[child]<a[cur])//孩子优先度更高,交换
	{
   
		int temp=a[child];
		a[child]=a[cur];
		a[cur]=temp;
	}
	cur=child;
}
return pop;

根据以上,我们基本可以实现一个数组构建的堆

2.扩展到存储复杂数据结构的堆

上述我们论述了一个整形数组的堆排序的方法,但是在实际中,需要排序的数据结构往往多样且复杂,比较优先度的逻辑也不尽相同,因此我们需要构建一个通用的堆模板来满足多变的情况

假设我们有这样一种数据结构

typedef struct
{
   
	int a;
	double b;
	char c;
}TYPEA;

有三种排序方法:

  1. 按照该数据结构的成员a来排序
  2. 按照该数据结构的成员b来排序
  3. 按照该数据结构的成员c来排序

假如要建立三种不同排序准则的堆,代码繁琐复杂。因此在这里,笔者归纳整理了一种通用的堆模板

头文件Heap.h

创建头文件Heap.h

#ifndef _Heap_H_
#define _Heap_H_

#define MAX_HEAPSIZE 100 //堆最大容量
//交换函数指针的宏
#define SWAP(a,b) do{void *temp=a;a=b;b=temp;}while(0)

class Heap
{
   
private:
	bool (*cmp)(void* a, void* b);//排序的函数指针
	int heapsize;//当前堆中元素的个数
public:
	void* element[MAX_HEAPSIZE];//堆中元素的指针数组
	void init(bool (*compare_function)(void* a, void* b));//堆的初始化
	void push(void* a);//入堆操作
	void* pop();//出堆操作
	void* top();//返回堆顶元素的操作
};

#endif

该类中 使用指针数组void* element[MAX_HEAPSIZE]来替换之前的整形数组,其中element[i]指向要排序的数据结构,指针数组的好处在于,我们可以用指针来指向不同的数据结构,从而满足对不同的数据结构进行排序

其次,在该类中,定义bool (*cmp)(void* a, void* b)函数指针,通过函数指针,我们可以为不同的排序方式初始化不同的堆

其他API

使用void init(bool (*compare_function)(void* a, void* b))函数来初始化堆
使用void push(void* a)函数将元素a的指针入堆
使用void* pop()函数将堆顶的元素弹出
使用void* top()函数返回堆顶的元素(不弹出)

实现文件Heap.cpp

在实现文件Heap.cpp中,对每个函数进行定义

#include"Heap.h"

void Heap::init(bool(*compare_function)(void* a, void* b))
{
   
	heapsize = 0;
	cmp = compare_function;
	return;
}

void Heap::push(void* a)
{
   
	element[heapsize++] = a;
	int cur = heapsize - 1;
	int father = (cur - 1) / 2;
	while (cur > 0 && cmp(element[cur], element[father]))
	{
   
		SWAP(element[cur], element[father]);
		cur = father;
		father = (father - 1) / 2;
	}
	return;
}

void* Heap::pop()
{
   
	if (top() == nullptr)
		return nullptr;
	else
	{
   
		void* ret = element[0
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值