编程语言
首页 > 编程语言> > C++下定义一个通用的堆数据结构

C++下定义一个通用的堆数据结构

作者:互联网

快速导航

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

部分基础知识

什么是堆?

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

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

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

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

如图所示,我们使用一个int[]数组实现小顶堆,来存放数据{3,8,15,31,25}
在这里插入图片描述

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

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

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

堆的元操作

入堆操作

当我们要向堆中添加新的元素,将新元素增加到数组的末尾,并将添加节点的数据与其父亲节点的大小进行对比,如果新增节点的数据根据排序准则,大于其父亲节点,交换两者的位置,如此递归至根节点或者满足排序添加即可

以整形数组为例

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. 当前节点与优先度更高的孩子进行对比,如果孩子节点的优先度更大,执行下沉操作
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;

在这里插入图片描述
在这里插入图片描述

根据以上基础知识,我们可以基本实现一个堆的构建

扩展

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

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

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

有三种排序方法:

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

假如要建立三种数据排序的堆,代码繁琐复杂,在此,定义一种通用的堆模板

头文件

创建头文件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

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

其次,在该类中,定义cmp函数指针,通过对函数指针的设置,我们可以为不同的排序方式进行设置

实现文件

在实现文件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];
		element[0] = element[--heapsize];
		int cur = 0;
		int child;
		while (cur * 2 + 1 < heapsize)
		{
			if (cur * 2 + 2 == heapsize)
				child = cur * 2 + 1;
			else
				child = cmp(element[cur * 2 + 1], element[cur * 2 + 2]) ? cur * 2 + 1 : cur * 2 + 2;
			if (cmp(element[child], element[cur]))
				SWAP(element[child], element[cur]);
			else
				break;
			cur = child;
		}
		return ret;
	}
}

void* Heap::top()
{
	return heapsize == 0 ? nullptr : element[0];
}

主程序测试

在main.cpp中我们写一个测试程序,来检验此功能
我们随机为TYPEA赋10个初始值,将他们按照三种排序规则入堆

TYPEA a[10] = { {15,0.4,'g'},{7,0.1,'a'},{10,3.2,'d'},{8,2.5,'h'},{1,4.4,'v'},
				{6,6.2,'r'},{16,8.5,'l'}, {12,7.2,'s'},{0,0.8,'q'},{4,2.9,'m'}};
#include<stdio.h>
#include"Heap.h"

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

TYPEA a[10] = { {15,0.4,'g'},{7,0.1,'a'},{10,3.2,'d'},{8,2.5,'h'},{1,4.4,'v'},
					{6,6.2,'r'},{16,8.5,'l'}, {12,7.2,'s'},{0,0.8,'q'},{4,2.9,'m'} };

bool compare_a(void* _a, void* _b)
{
	TYPEA* a = (TYPEA*)_a;
	TYPEA* b = (TYPEA*)_b;
	return a->a < b->a;
}

bool compare_b(void* _a, void* _b)
{
	TYPEA* a = (TYPEA*)_a;
	TYPEA* b = (TYPEA*)_b;
	return a->b < b->b;
}

bool compare_c(void* _a, void* _b)
{
	TYPEA* a = (TYPEA*)_a;
	TYPEA* b = (TYPEA*)_b;
	return a->c < b->c;
}

int main()
{
	Heap heap;
	//按照TYPEA中a成员小优先进行堆的初始化
	printf("TYPEA ->a smaller fisrt\n");
	heap.init(compare_a);
	for (int i = 0; i < 10; i++)
	{
		heap.push(&a[i]);
	}
	while (heap.top() != nullptr)
	{
		TYPEA* pop = (TYPEA*)(heap.pop());
		printf("a=%d,b=%.2f,c=%c\n", pop->a, pop->b, pop->c);
	}
	//按照TYPEA中b成员小优先进行堆的初始化
	printf("TYPEA ->b smaller fisrt\n");
	heap.init(compare_b);
	for (int i = 0; i < 10; i++)
	{
		heap.push(&a[i]);
	}
	while (heap.top() != nullptr)
	{
		TYPEA* pop = (TYPEA*)(heap.pop());
		printf("a=%d,b=%.2f,c=%c\n", pop->a, pop->b, pop->c);
	}
	//按照TYPEA中c成员小优先进行堆的初始化
	printf("TYPEA ->c smaller fisrt\n");
	heap.init(compare_c);
	for (int i = 0; i < 10; i++)
	{
		heap.push(&a[i]);
	}
	while (heap.top() != nullptr)
	{
		TYPEA* pop = (TYPEA*)(heap.pop());
		printf("a=%d,b=%.2f,c=%c\n", pop->a, pop->b, pop->c);
	}
	return 1;
}

程序运行结果如下:
在这里插入图片描述
在这个例子中,我们可以看到,通过修改compare函数,可以实现不同排序方法的堆

标签:数据结构,cur,int,void,TYPEA,C++,pop,下定义,节点
来源: https://blog.csdn.net/qq_35779286/article/details/114482639