一、堆定义(Heap)
n个元素的序列{k1,k2,...,kn},当且仅当任意ki 满足以下关系时,称之为堆(也是完全二叉树):
1.当k[i] <= k[2i] && k[i] <= k[2i+1] 时,称为小顶堆,即每个结点的值都小于等于其左右孩子结点的值;
2.当k[i] >= k[2i] && k[i] >= k[2i+1] 时,称为大顶堆,即每个结点的值都大于等于其左右孩子结点的值;
二、堆构建(Build)
1.存储方式:顺序表
2.理论基础:a).此数组元素构成一颗完全二叉树;b).对于一个含有 n 个结点的完全二叉树,其最后 一个结点时第 floor(n/2) 个结点的孩子结点;
3.堆构建过程:从第 floor(n/2) 个结点开始,对此子树根及其孩子结点进行调整,使之成为小根堆(或大根堆);然后依次向前对各个结点子树进行调整;最终完成对整个二叉树的堆化;
4.图解,以序列{49,39,65,97,76,13,27,69}为例:
三、堆排序(Sort)
1.排序方式:在构建完成的小根堆基础上,输出根结点(堆顶),并将其与堆尾结点交换位置,同时堆尾指针前移(即将输出结点移除跟堆,如下图中“切断”),重新调整为小根堆;如此,取后序根结点,再交换,再调整,循环往复,直至所有结点都取出。
2.小根堆排序结束后,将输出序列的升序排列{13,27,39,49,65,69,76,97},小根堆最终状态:转化成了大根堆结构。
3.小根堆排序结束后,若需再次输出排序,需对其重建根堆。
4.用途:可实现不需要对 M个元素全排序的情况下,输出前 N个最小(大)元素;
**最小(大)Top N 问题:
缓存一个 N 长度的数组,遍历 M 个元素;
当 i < N 时,向数组中插入元素;
当 i = N 时,向数组中插入元素,并构建 大(小)根堆;
当 i > N 时,若元素 小于(大于) 根元素,替换根元素,并从根节点处重建根堆;否则,不做处理;
当 遍历完 M 个元素 后,对根堆排序,输出即是。
5.性质:不稳定排序例如序列 { 55,65,49,97,76,13,27,49 }在排完序后为 { 13,27,49,49,55,65,76,97 },其中下划线49在排序后却比正确序数的49先输出;
6.图解
四、代码(类模板实现)
1.实现了二叉堆的类模板化,可根据需求,创建小根堆或者大根堆;
2.实现了堆元素结点的对象化,结点除关键字Key以外,还可携带其它属性;
3.对外调用还需实现 元素关键字比较接口(函数指针)作为初始化参数 int (*KeyCompare)(ElemType *, ElemType *);
4.实现了堆的元素插入、堆创建、堆调整、移除堆顶、堆排序等方法;
5.代码中包含的"ObjArrayList.h"头文件,代码在之前博文"数据结构之顺序列表(支持对象元素)"中。
//文件名:"BiHeap.h"
#pragma once
#ifndef BIHEAP_H_
#define BIHEAP_H_
#include <iostream>
#include "ObjArrayList.h"
using namespace std;
/*
. 二叉堆 类模板 Binary Heap
. 实现:
. 1.以对象数组形式实现
. 2.小根堆、大根堆,及堆排序
*/
template <typename ElemType>
class BiHeap
{
public:
enum Type //堆类型
{
MIN, //小顶堆
MAX //大顶堆
};
private:
ElemType ** arr; //对象数组
int length; //数组长度
int num; //已入堆的元素个数
int heapTail; //堆尾指针
int isBuild; //是否建堆 0|否 1|已建
int type; //堆类型
int (*KeyCompare)(ElemType *, ElemType *); //实现关键字比较接口
void _Heapify(int start, int end); //堆化(重新调整成堆)
void _RemoveRoot(); //移除堆顶元素
public:
BiHeap(int type, int size, int(*KeyCompare)(ElemType *, ElemType *)); //构造函数
~BiHeap(); //析构函数
void Insert(ElemType * e); //顺序插入堆元素
void Insert(ObjArrayList<ElemType> list); //顺序列表插入堆元素
void Build(); //建堆
ElemType * GetRoot(); //获取堆顶元素
ObjArrayList<ElemType> * Sort(); //返回排序序列
};
template <typename ElemType>
BiHeap<ElemType>::BiHeap(int type, int size, int(*KeyCompare)(ElemType *, ElemType *))
{
/*
. 构造函数
. 入参:
. int type: 堆类型
. int size: 堆大小
. int (*KeyCompare)(int, int): 堆关键字比较接口
*/
//初始化参数
this->type = type;
this->length = size;
this->num = 0;
this->heapTail = -1;
this->isBuild = 0;
this->KeyCompare = KeyCompare;
//初始化数组,(元素对象存放于堆上,也可存放栈上,此处统一堆上)
this->arr = (ElemType **) malloc(sizeof(ElemType *) * size);
}
template <typename ElemType>
BiHeap<ElemType>::~BiHeap()
{
/*
. 析构函数
*/
//释放数组空间,元素空间外部释放
delete[] this->arr;
//释放函数指针
delete this->KeyCompare;
}
template <typename ElemType>
void BiHeap<ElemType>::Insert(ElemType * e)
{
/*
. 顺序插入堆元素
*/
//1.堆上限判断
//已入堆的元素个数 小于堆长度时,可插入;否则不可
if (this->num >= this->length)
return;
//2.插入
this->arr[this->num] = e;
//3.元素计数,标记堆尾
this->num++;
this->heapTail++;
//4.重置建堆标识
this->isBuild = 0;
}
template <typename ElemType>
void BiHeap<ElemType>::Insert(ObjArrayList<ElemType> list)
{
/*
. 顺序列表插入堆元素
*/
for (int i = 0; i < list->Length(); i++)
{
Insert(list->Get(i));
}
}
template <typename ElemType>
void BiHeap<ElemType>::_Heapify(int start, int end)
{
/*
. 堆化(重新调整成堆)
. 算法:
. 1.游标从子树根结点start 向下调整为指定堆类型
. 2.比较数根结点与左右孩子结点的 key ,调整其位置
. 2.1.如(小根堆):
. 当 Key(arr[root]) > min{ Key(arr[lchild]), Key(arr[rchild]) }时,需交换 arr[root] 与 arr[min{}] 的位置
. 否则不需交换;
. 2.2.如(大根堆):
. 当 Key(arr[root]) < max{ Key(arr[lchild]), Key(arr[rchild]) }时,需交换 arr[root] 与 arr[max{}] 的位置
. 否则不需交换;
. 3.调整完位置后,将游标start 向下移,移至与根结点交换的子树结点位置
. 4.重复上述步骤,直至 end位置 结束。
. 时间复杂度:O(log2(n))
*/
//1.保存起始根结点元素(以start 为起始位置的子树根结点)
ElemType * startElem = this->arr[start];
//2.从子树根结点start 向下调整为指定堆类型
for (int i = 2 * start + 1; i <= end; i = 2 * i + 1)
{
//2.1.选出左右子树较小 或 较大的一个
//注:"i + 1 < end" 防止数组越界
if (i + 1 <= end &&
(this->type == MIN && KeyCompare(this->arr[i], this->arr[i + 1]) > 0 )
|| (this->type == MAX && KeyCompare(this->arr[i], this->arr[i + 1]) < 0 ))
{
i = i + 1; //i++;
}
//2.2.1.若为小根堆,根结点key 小于 min{key(lc), key(rc)} 则停止向下调整
//2.2.2.若为大根堆,根结点key 大于 max{key(lc), key(rc)} 则停止向下调整
if ((this->type == MIN && KeyCompare(startElem, this->arr[i]) < 0)
|| (this->type == MAX && KeyCompare(startElem, this->arr[i]) > 0))
{
break;
}
//2.3.交换结点位置
this->arr[start] = this->arr[i];
//2.4.将游标start 移至交换的子结点处
start = i;
}
//3.将起始根结点元素 放到最后游标处
this->arr[start] = startElem;
}
template <typename ElemType>
void BiHeap<ElemType>::_RemoveRoot()
{
/*
. 移除堆顶元素
*/
//堆尾指针 < 0 时,表示元素全部排序输出,不可再输出
if (this->heapTail < 0)
{
return;
}
//1.将堆顶(根)元素与堆尾元素交换位置
//cout << "(" << this->heapTail << ")";
ElemType * root = this->arr[0];
this->arr[0] = this->arr[this->heapTail];
this->arr[this->heapTail] = root;
//2.堆尾指针 向前移动
this->heapTail--;
}
template <typename ElemType>
void BiHeap<ElemType>::Build()
{
/*
. 建堆
*/
//建堆前数组输出
//1.从二叉堆的最后一颗子树(即最后一个结点的父节点处),向上调整
//注:堆化的对象是子树(有孩子),叶子结点无法堆化
for (int i = this->num / 2 - 1; i >= 0; i--)
{
_Heapify(i, this->num - 1);
}
//2.标记为 堆已建
this->isBuild = 1;
}
template <typename ElemType>
ElemType * BiHeap<ElemType>::GetRoot()
{
/*
. 获取堆顶元素
*/
//只有建堆完成才可取根结点
if (this->isBuild == 0)
return NULL;
//元素全部排序输出后,若再需重取,需重建堆
if (this->heapTail < 0)
{
this->heapTail = this->num - 1;
Build();
}
//1.取堆顶元素
ElemType * root = this->arr[0];
//2.移除堆顶元素(堆尾指针前移)
_RemoveRoot();
//3.重新调整堆,使其堆化
_Heapify(0, this->heapTail);
//4.返回
return root;
}
template <typename ElemType>
ObjArrayList<ElemType> * BiHeap<ElemType>::Sort()
{
/*
. 返回排序序列
*/
ObjArrayList<ElemType> * list = new ObjArrayList<ElemType>();
for (int i = 0; i < this->num; i++)
{
list->Add(GetRoot());
}
return list;
}
#endif // !BIHEAP_H_
//文件名:"Heap_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "BiHeap.h"
using namespace std;
int main()
{
//1.初始化二叉堆,并实现元素KEY 比较接口(匿名函数)
BiHeap<int> * h = new BiHeap<int>(BiHeap<int>::MIN, 8,
[](int * e1, int *e2) {
if (*e1 > *e2)
return 1;
else if (*e1 == *e2)
return 0;
else
return -1;
});
//2.向二叉堆插入元素
h->Insert(new int(49));
h->Insert(new int(39));
h->Insert(new int(65));
h->Insert(new int(97));
h->Insert(new int(76));
h->Insert(new int(13));
h->Insert(new int(27));
h->Insert(new int(69));
//3.构建二叉堆
h->Build();
//4.获得排序后列表
cout << "排序后:" << endl;
ObjArrayList<int> * sortedList = h->Sort();
for (int i = 0; i < sortedList->Length(); i++)
{
cout << *sortedList->Get(i) << " ";
}
//多次排序输出测试(需重建顶堆,内部已实现)
cout << endl;
for (int i = 0; i < 32; i++)
{
cout << *h->GetRoot() << " ";
if ((i+1) % 8 == 0)
cout << endl;
}
return 0;
}