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号位的元素跟孩子们进行比对,如果有限度更低,执行下沉操作
下沉操作有两个流程:
- 寻找左右孩子排序优先度更高的孩子
- 当前节点与优先度更高的孩子进行对比,如果孩子节点的优先度更大,执行下沉操作
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;
有三种排序方法:
- 按照该数据结构的成员a来排序
- 按照该数据结构的成员b来排序
- 按照该数据结构的成员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