定义
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)。
二叉堆可视化链接
堆属性
- 堆是一个完全二叉树
- 堆中的每个结点的值总是不大于或不小于其父结点的值。 即对于最大堆,父节点的值比每个子结点的值都大,所以在最大堆中根节点存放堆的最大值;对于小根堆同理存放最小值。
注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。–唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。
堆常用于:构建优先队列、快速找出集合中最值、堆排序。
堆结构
堆可以用数组来实现,虽然看起来可能有点奇怪,但是它在时间和空间上都很高效。
堆用数组存储,不需要存储额外的指针,那怎么找到子结点?
其实在数组中,每个结点在数组中的位置和它的父节点以及子结点的索引之间存在一个映射关系。
如果 i 是节点的索引(从0开始),那么下面的公式就给出了它的父节点和子节点在数组中的位置:
parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
right(i) 就是简单的 left(i) + 1。左右节点总是处于相邻的位置。在小根堆中,父结点总是小于等于子结点的值(array[parent(i)] <= array[i]
),可以验证如下,(结点的值刚好也是它的下标值)
注意:不是每个堆都是有序数组!要将堆转为有序数组必须要进行堆排序。
堆的操作
向下调整算法 shiftDown
如果一个节点比它的子节点小(最大堆)或者大(最小堆),那么需要将它向下移动。这个操作也称作“堆化(heapify)”。
/// <summary>
/// 堆的向下调整 主要用于删除堆顶后调整堆
/// </summary>
/// <param name="arr">存储堆的一维数组首地址</param>
/// <param name="n">堆的长度,即元素的个数length</param>
/// <param name="cur">当前结点的下标 下标范围[0,n-1]</param>
void shiftDown(int arr[], int n, int cur)
{ // 小根堆
int temp = arr[cur];
int child = cur * 2 + 1;
while (child < n)
{
// 找出左右子结点的更小的值,用child标记
if (child + 1 < n && arr[child] > arr[child + 1])
{
child += 1;
}
// 比较child和cur的值,如果不满足堆则交换
if (arr[child] < arr[cur])
{
arr[cur] = arr[child];
arr[child] = temp;
cur = child;
child = cur * 2 + 1;
}
else
{
break;
}
}
}
向上调整算法 shiftUp
如果一个节点比它的父节点大(最大堆)或者小(最小堆),那么需要将它同父节点交换位置。
/// <summary>
/// 堆的向上调整 主要用于插入元素后的调整
/// </summary>
/// <param name="arr">存储堆的一维数组首地址</param>
/// <param name="n">堆的长度,即元素的个数length</param>
/// <param name="cur">当前结点的下标 下标范围[0,n-1]</param>
void shiftUp(int arr[], int n, int cur)
{
if (cur >= n)
{
return;
}
int parent = (cur - 1) / 2;
int temp = arr[cur];
while (parent >= 0 && arr[parent] > temp)
{// 如果已经满足父结点小于子结点即可结束,否则循环向上调整至满足条件
// 小根堆
arr[cur] = arr[parent];
arr[parent] = temp;
cur = parent;
parent = (cur - 1) / 2;
}
}
堆的插入 insert
在堆的尾部添加一个新的元素,然后使用 shiftUp 来修复堆。
void insert(int arr[], int n, int value)
{
// 在堆的尾部添加一个新的元素,然后使用 shiftUp 来修复
// 如果不是尾部插入,需要移动元素,重新构建堆
// 这里没有考虑内存越界情况,在初始arr应该尽量大
arr[n] = value;
shiftUp(arr, n + 1, n);
}
堆的创建 buildHeap
堆创建有两种时间复杂度, O ( n l o g 2 n ) O(nlog_2^n) O(nlog2n)和 O ( n ) O(n) O(n),分别利用shiftUp和shiftDown。
- 向堆中逐个插入元素,然后利用shiftUp调整位置,时间复杂度较高 O ( n l o g 2 n ) O(nlog_2^n) O(nlog2n)
- 将数组直接视为一个完全二叉树,从第一个非叶子结点开始利用shiftDown调整位置,直到根节点,时间复杂度为 O ( n ) O(n) O(n)。
堆创建可视化网址 (点击exc后,左侧有一个箭头,创建)
堆排序
创建堆后,根据升序降序需求选择大顶堆或小顶堆; 将堆顶元素与末尾元素交换,将最大(小)元素"沉"到数组末端; 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
注意:如果把排序后的数组继续看为一个堆,那么大根堆排序后变为小根堆,小根堆排序后变为大根堆
堆的删除
堆元素的删除: 删除的结点直接和堆的最后一个叶子结点交换,向下调整堆,从而改变堆的大小
完整代码
这里用C++实现,C的实现参见参考数据结构:堆(Heap)
#include <iostream>
using namespace std;
#define MIN_HEAP 0 // 小根堆
#define MAX_HEAP 1 // 大根堆
#define DESCEND_SORT 0 // 降序 构建小跟堆
#define ASCEND_SORT 1 // 升序 构建大根堆
#define HEAP_INIT_SIZE 64 // 默认大小
#define HEAP_INCREMENT 16 // 存储空间分配增值
class Heap
{
public:
Heap(); // 空堆 堆默认为大根堆
Heap(int data[], int len, int type);
~Heap();
bool insert(int value); // 插入结点
int remove(int value);
int remove_atindex(int index); // 删除index上的结点 默认index=0
int front() // 获取堆顶元素 但不删除
{
return this->heap_[0];
}
bool replace(int index, int value); // 替换index位置的值
void sort(); // 默认按照小根堆-降序 大根堆-升序 排序后会改变堆类型
void show(); // 打印堆
int length()
{
return this->length_;
}
private:
void shift_up__(int cur);
void shift_down__(int cur);
bool insert__(const int value);
int delete__(int index);
int search__(const int value);
// 类成员
int type_; // 堆的类型 MIN_HEAP、MAX_HEAP
int length_; // 堆的长度 指当前堆的长度
int size_; // 堆的大小 指当前堆的内存最多可容纳多少个结点
int *heap_; // 指向数据
};
Heap::Heap()
: type_(MAX_HEAP),
length_(0),
size_(HEAP_INIT_SIZE)
{
this->heap_ = new int[HEAP_INIT_SIZE];
if (!this->heap_)
{
exit(-1);
}
}
// data初始数组 len数组长度 type堆类型-默认大根堆
Heap::Heap(int data[], int len, int type = MAX_HEAP)
: type_(type),
length_(len),
size_(HEAP_INIT_SIZE)
{
if (type != MAX_HEAP && type != MIN_HEAP)
{
std::cout << "Heap() type must be MAX_HEAP or MIN_HEAP!" << endl;
exit(-1);
}
if (len > HEAP_INIT_SIZE)
{
this->size_ = len + HEAP_INCREMENT;
}
this->heap_ = new int[this->size_];
if (!this->heap_)
{
exit(-1);
}
for (int i = 0; i < len; ++i)
{ // 复制数据到开辟的内存中
this->heap_[i] = data[i];
}
// 生成堆
for (int i = len / 2 - 1; i >= 0; --i)
{
shift_down__(i);
}
}
Heap::~Heap()
{
if (this->heap_)
{
delete[] this->heap_;
}
}
bool Heap::insert(int value)
{ // 检查空间是否充足 并处理
if (this->size_ < this->length_ + 1)
{
this->size_ += HEAP_INCREMENT;
int *temp = new int[this->size_];
if (!temp)
{
exit(-1);
}
for (int i = 0; i < this->length_; ++i)
{
temp[i] = this->heap_[i];
}
delete[] this->heap_;
this->heap_ = temp;
}
return this->insert__(value);
}
int Heap::remove(int value)
{ // 判断是否出界 出界返回0
int index = this->search__(value);
if (index < 0)
{
return 0;
}
return delete__(index);
}
int Heap::remove_atindex(int index)
{ // 判断是否出界 出界返回0
if (index < 0 || index >= this->length_)
{
return 0;
}
return delete__(index);
}
bool Heap::replace(int index, int value)
{
if (index < 0 || index >= this->length_)
{
return false;
}
// 将更小的换入小根堆 shift-up,大根堆shift-down
if (this->type_ == MIN_HEAP && this->heap_[index] > value)
{
this->heap_[index] = value;
this->shift_up__(index);
}
else if (this->type_ == MIN_HEAP && this->heap_[index] < value)
{
this->heap_[index] = value;
this->shift_down__(index);
}
else if (this->type_ == MAX_HEAP && this->heap_[index] > value)
{
this->heap_[index] = value;
this->shift_down__(index);
}
else if (this->type_ == MAX_HEAP && this->heap_[index] < value)
{
this->heap_[index] = value;
this->shift_up__(index);
}
return true;
}
void Heap::sort()
{
int temp = 0;
int len = this->length_;
for (int i = len - 1; i > 0; --i)
{
this->length_ -= 1;
temp = this->heap_[0];
this->heap_[0] = this->heap_[i];
this->heap_[i] = temp;
shift_down__(0);
}
this->length_ = len;
this->type_ = !this->type_; // 排序会改变堆的类型
}
void Heap::show()
{
std::cout << "heap: ";
for (int i = 0; i < this->length_; ++i)
{
std::cout << this->heap_[i] << "\t";
}
std::cout << endl;
}
void Heap::shift_up__(int cur)
{
int parent = (cur - 1) / 2;
int temp = this->heap_[cur];
while ((!this->type_ && parent >= 0 && this->heap_[parent] > temp) || (this->type_ && parent >= 0 && this->heap_[parent] < temp))
{
this->heap_[cur] = this->heap_[parent];
this->heap_[parent] = temp;
cur = parent;
parent = (cur - 1) / 2;
}
}
void Heap::shift_down__(int cur)
{
int temp = this->heap_[cur];
int child = cur * 2 + 1;
while (child < this->length_)
{
// 找出左右子结点的更小(大)的值,用child标记
if (!this->type_ && child + 1 < this->length_ && this->heap_[child] > this->heap_[child + 1])
{ // 小根堆找更小
child += 1;
}
else if (this->type_ && child + 1 < this->length_ && this->heap_[child] < this->heap_[child + 1])
{ // 大根堆找更大
child += 1;
}
// 比较child和cur的值,如果不满足堆则交换
if ((!this->type_ && this->heap_[child] < this->heap_[cur]) || (this->type_ && this->heap_[child] > this->heap_[cur]))
{
this->heap_[cur] = this->heap_[child];
this->heap_[child] = temp;
cur = child;
child = cur * 2 + 1;
}
else
{
break;
}
}
}
bool Heap::insert__(const int value)
{
this->heap_[this->length_] = value;
this->length_ += 1;
this->shift_up__(this->length_ - 1);
return true;
}
int Heap::delete__(int index = 0)
{
int temp = this->heap_[index];
this->length_ -= 1;
this->heap_[index] = this->heap_[this->length_];
this->shift_down__(index);
this->shift_up__(index);
return temp;
}
int Heap::search__(const int value)
{
int i = 0;
for (; i < this->length_; ++i)
{
if (this->heap_[i] == value)
{
return i;
}
}
return -1;
}
int main()
{
int a[] = {62, 41, 30, 28, 16, 22, 13, 19, 17, 15, 52}; // 11
int b[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7}; // 10
int c[] = {5, 10, 8, 7, 3, 2}; //6
int d[] = {17, 35, 55, 77, 96, 13, 60, 65, 24, 68, 80, 9, 52, 54, 61}; //15
Heap heap_a(d, 15, MAX_HEAP);
heap_a.show();
heap_a.sort();
heap_a.show();
std::cout << heap_a.remove(9) << endl;
std::cout << heap_a.remove(100) << endl;
heap_a.show();
heap_a.replace(0, 100);
heap_a.show();
return 0;
}
参考:
1.数据结构:堆(Heap)
2.数据结构之堆
说明:图片来自网络和以上博客,如有侵权请联系删除