数据结构之堆(优先队列)c++
什么是堆?
堆(优先队列)是“特殊”的队列,取出元素的顺序是按照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。
堆可以看成是一颗完全二叉树。既可以使用链表实现也可以使用数组实现。但是使用数组实现比较方便。其使用数组实现的一个堆如下图所示。
对应数组(在用数组为存储方式的结构中,数组下标为0的地方是不存放任何数据的)
堆可以分为2种,一个是最大堆,另一个是最小堆
最大堆:父节点的值要比其两个子节点(如果存在)大。
最小堆:父节点的值要比其两个子节点(如果存在)小。
下面我们给出堆的结构描述以及堆的操作:
为了应对不同数据类型,在这里我们使用模板类,
template <class T>
class Heap
{
public:
//方便用户选择创建大堆还是小堆
enum Way
{
GREATER,//大
LESS//小
};
private:
T *array_heap; //堆
size_t current_size; //当前容量
size_t max_size; //最大容量
Way flag; //堆的标志(大堆/小堆)
public:
explicit Heap(const int &Size);
~Heap();
//输出堆内数据
void print();
//堆是否为空
bool empty() const;
//堆是否已满
bool full() const;
//出堆
T delHeap();
//插入数据
void insert(const T &Data, Way way);
//构造堆
void createHeap(const T *Data_Array, const size_t &Size, Way way = Heap<T>::GREATER);
private:
//小于
bool less(const T &Left, const T &Right);
//大于
bool greater(const T &Left, const T &Right);
//比较
bool compare(const T &Left, const T &Right, Way way);
//调整堆
void adjust();
//调整堆
void adjust(Way way);
public:
T &operator[](const int &location);
};
下面我们给出部分函数的定义(类外实现)
template <typename T>
Heap<T>::Heap(const int &Size) : max_size(Size), current_size(0), flag(Heap<T>::GREATER)
{
//申请内存 0号位置不使用
this->array_heap = new T[Size + 1];
//内存是否申请成功
assert(this->array_heap);
//初始化数据
for (int i = 0; i < Size; ++i)
{
this->array_heap[i] = 0;
}
};
template <typename T>
Heap<T>::~Heap()
{
if (this->array_heap)
{
delete[] this->array_heap;
}
this->array_heap = nullptr;
this->current_size = this->max_size = 0;
}
//堆是否为空
template <typename T>
bool Heap<T>::empty() const
{
return (this->current_size == 0);
}
//堆是否已满
template <typename T>
bool Heap<T>::full() const
{
return (this->current_size == this->max_size + 1);
}
template <typename T>
T &Heap<T>::operator[](const int &Location)
{
return (this->array_heap[Location]);
}
//小于
template <typename T>
bool Heap<T>::less(const T &Left, const T &Right)
{
return (Left < Right);
}
//大于
template <typename T>
bool Heap<T>::greater(const T &Left, const T &Right)
{
return (Left > Right);
}
//比较
template <typename T>
bool Heap<T>::compare(const T &Left, const T &Right, Heap<T>::Way way)
{
if (way == this->GREATER)
{
return this->greater(Left, Right);
}
else
{
return (this->less(Left, Right));
}
}
//输出堆内数据
template <typename T>
void Heap<T>::print()
{
for (auto i = 1; i <= this->current_size; ++i)
{
std::cout << this->array_heap[i] << ' ';
}
std::cout << std::endl;
}
堆的构造
我们使用数组来构造一个堆,为了处理多个数据,我们的构造选择接受一个数组元素来初始化我们的堆(此函数为类内实现)
//构造堆
void createHeap(const T *Data_Array, const size_t &Size, Way way = Heap<T>::GREATER)
{
this->flag = way; //标识堆
if (Size > this->max_size)
{
return;
}
for (auto i = 0; i < Size; ++i)
{
this->insert(Data_Array[i], way);
}
}
堆的插入
大家应该已经注意到了,在我们构造堆的函数中,核心的操作其实就是插入,
***注意事项:***将元素插入到堆中的时候,不能改变堆的性质。
插入操作:
插入元素到堆中,首先我们将元素插入到数组最后一个元素的下一个位置(假设下标为i),接下来我们要对该堆进行一个调整。
我们以大堆为例,假设我们要插入是数据是10,如图所示:
我们把10放在数组的最后面,我们可以看到此时10>9,这肯定已经违反了大堆的性质,所以我们要对其进行调整,调整的方式为,将10与他的父节点(parent)进行比较,如果值大于父节点,我们就把该节点与父节点进行交换,然后继续和其父节点比较,直到有序为止(注意:在调整过程中父节点下标不能到零,即>1)。(父节点下标的计算公式为:父节点下标=当前下标 / 2)
此时我们发现10<16(父节点),堆已经有序,所以不需要调整了
下面我们来看插入操作的代码实现:
//插入数据
void insert(const T &Data, Way way)
{
//堆是否已满
if (this->full())
{
return;
}
//把数据插入到末尾
this->array_heap[++this->current_size] = Data;
//调整堆
this->adjust(way);
}
下面是插入操作的核心,即调整堆,让堆变得有序,
//调整堆
void adjust(Way way)
{
auto curPos = this->current_size; // curPos指向堆中最后一个节点位置
auto max = this->array_heap[curPos]; //保存当前节点数据
// curPos > 1 防止访问到0号位置
while (curPos > 1 && this->compare(max, this->array_heap[curPos / 2], way))
{
this->array_heap[curPos] = this->array_heap[curPos / 2]; //向下过滤节点
curPos = curPos / 2;
}
//将原数据插入
this->array_heap[curPos] = max;
}
在此,为了提高效率,我们并不使用交换两个数据元素的方式
这种调整堆的方式又称为向下过滤节点
堆的删除
删除操作针对与对顶元素也就是根节点,我们采用的删除方式是,拿掉根节点,把末尾节点放到原根节上面,如图所示:
此时我们发现,根节点的值明显小于其左右子节点的值(1<16,1<12),已经不满足大堆的性质,在此时,我们需要对该堆的进行调整,让他变得有序。
此时我们所用的调整方式与插入时不一样
这次我们要将根节点的值与其子节点进行比较,对其进行向下调整,即
将根节点与左右子节点进行比较,把大的值往上移动,直到堆有序为止(子节点下标=父节点下标 * 2)
如图所示:
此时堆已经调整完毕
下面我们来看代码实现
//出堆
template <typename T>
T Heap<T>::delHeap()
{
//判断堆是否为空
if(this->empty())
{
return false;
}
//保存根节点元素
auto item = this->array_heap[1];
//将末尾元素放到根节点上面,堆的大小减一
this->array_heap[1] = this->array_heap[this->current_size--] ;
//调整堆
this->adjust();
//返回根节点元素
return item;
}
下面是调整堆的代码实现
//调整堆
template <typename T>
void Heap<T>::adjust()
{
//记录父节点和子节点下标位置
size_t parent, child;
//保存根节点元素
auto item=this->array_heap[1];
for(parent=1;parent*2<this->current_size;parent=child)
{
child=parent*2;
//比较左节点和右节点大小
if(child<this->current_size&&this->compare(this->array_heap[child+1],this->array_heap[child],this->flag))
{
++child;
}
//向下过滤节点
if(this->compare(this->array_heap[child],item,this->flag))
{
this->array_heap[parent]=this->array_heap[child];
}
}
//插入数据
this->array_heap[parent]=item;
}
下面我们来进行测试:
int main()
{
int arr[10] = {1,3,2,5,4,6,7,9,8,10};
Heap<int> heap1(10);
std::cout<<"最大堆"<<std::endl;
//最大堆
heap1.createHeap(arr,10,Heap<int>::GREATER);
std::cout<<"堆内元素"<<std::endl;
heap1.print();
//删除
heap1.delHeap();
std::cout<<"删除后堆内元素"<<std::endl;
heap1.print();
//插入
heap1.insert(20,Heap<int>::GREATER);
std::cout<<"插入后堆内元素"<<std::endl;
heap1.print();
Heap<int>heap2(10);
std::cout<<"最小堆"<<std::endl;
//最小堆
heap2.createHeap(arr,10,Heap<int>::LESS);
std::cout<<"堆内元素"<<std::endl;
heap2.print();
//删除
heap2.delHeap();
std::cout<<"删除后堆内元素"<<std::endl;
heap2.print();
//插入
heap2.insert(20,Heap<int>::LESS);
std::cout<<"插入后堆内元素"<<std::endl;
heap2.print();
return 0;
}
这是输出结果
最后这是完整的代码(分文件)
头文件
#ifndef HEAP_H_
#define HEAP_H_
#include <assert.h>
#include <iostream>
template <class T>
class Heap
{
public:
enum Way
{
GREATER,
LESS
};
private:
T *array_heap; //堆
size_t current_size; //当前容量
size_t max_size; //最大容量
Way flag; //堆的表示(大堆/小堆)
public:
explicit Heap(const int &Size);
~Heap();
//输出堆内数据
void print();
//堆是否为空
bool empty() const;
//堆是否已满
bool full() const;
//出堆
T delHeap();
//插入数据
void insert(const T &Data, Way way)
{
if (this->full())
{
return;
}
//把数据插入到末尾
this->array_heap[++this->current_size] = Data;
//调整堆
this->adjust(way);
}
//构造堆
void createHeap(const T *Data_Array, const size_t &Size, Way way = Heap<T>::GREATER)
{
this->flag = way; //标识堆
if (Size > this->max_size)
{
return;
}
for (auto i = 0; i < Size; ++i)
{
this->insert(Data_Array[i], way);
}
}
private:
//小于
bool less(const T &Left, const T &Right);
//大于
bool greater(const T &Left, const T &Right);
//比较
bool compare(const T &Left, const T &Right, Way way);
//调整堆
void adjust();
//调整堆
void adjust(Way way)
{
auto curPos = this->current_size; // curPos指向堆中最后一个节点位置
auto max = this->array_heap[curPos]; //保存当前节点数据
// curPos > 1 防止访问到0号位置
while (curPos > 1 && this->compare(max, this->array_heap[curPos / 2], way))
{
this->array_heap[curPos] = this->array_heap[curPos / 2]; //向下过滤节点
curPos = curPos / 2;
}
//将原数据插入
this->array_heap[curPos] = max;
}
public:
T &operator[](const int &location);
};
template <typename T>
Heap<T>::Heap(const int &Size) : max_size(Size), current_size(0), flag(Heap<T>::GREATER)
{
//申请内存 0号位置不使用
this->array_heap = new T[Size + 1];
//内存是否申请成功
assert(this->array_heap);
//初始化数据
for (int i = 0; i < Size; ++i)
{
this->array_heap[i] = 0;
}
};
template <typename T>
Heap<T>::~Heap()
{
if (this->array_heap)
{
delete[] this->array_heap;
}
this->array_heap = nullptr;
this->current_size = this->max_size = 0;
}
//堆是否为空
template <typename T>
bool Heap<T>::empty() const
{
return (this->current_size == 0);
}
//堆是否已满
template <typename T>
bool Heap<T>::full() const
{
return (this->current_size == this->max_size + 1);
}
template <typename T>
T &Heap<T>::operator[](const int &Location)
{
return (this->array_heap[Location]);
}
//小于
template <typename T>
bool Heap<T>::less(const T &Left, const T &Right)
{
return (Left < Right);
}
//大于
template <typename T>
bool Heap<T>::greater(const T &Left, const T &Right)
{
return (Left > Right);
}
//比较
template <typename T>
bool Heap<T>::compare(const T &Left, const T &Right, Heap<T>::Way way)
{
if (way == this->GREATER)
{
return this->greater(Left, Right);
}
else
{
return (this->less(Left, Right));
}
}
//出堆
template <typename T>
T Heap<T>::delHeap()
{
//判断堆是否为空
if(this->empty())
{
return false;
}
//保存根节点元素
auto item = this->array_heap[1];
//将末尾元素放到根节点上面,堆的大小减一
this->array_heap[1] = this->array_heap[this->current_size--] ;
//调整堆
this->adjust();
//返回根节点元素
return item;
}
//调整堆
template <typename T>
void Heap<T>::adjust()
{
//记录父节点和子节点下标位置
size_t parent, child;
//保存根节点元素
auto item=this->array_heap[1];
for(parent=1;parent*2<this->current_size;parent=child)
{
child=parent*2;
//比较左节点和右节点大小
if(child<this->current_size&&this->compare(this->array_heap[child+1],this->array_heap[child],this->flag))
{
++child;
}
//向下过滤节点
if(this->compare(this->array_heap[child],item,this->flag))
{
this->array_heap[parent]=this->array_heap[child];
}
}
//插入数据
this->array_heap[parent]=item;
}
//输出堆内数据
template <typename T>
void Heap<T>::print()
{
for (auto i = 1; i <= this->current_size; ++i)
{
std::cout << this->array_heap[i] << ' ';
}
std::cout << std::endl;
}
#endif
main文件
#include<iostream>
#include "heap.h"
int main()
{
int arr[10] = {1,3,2,5,4,6,7,9,8,10};
Heap<int> heap1(10);
std::cout<<"最大堆"<<std::endl;
//最大堆
heap1.createHeap(arr,10,Heap<int>::GREATER);
std::cout<<"堆内元素"<<std::endl;
heap1.print();
//删除
heap1.delHeap();
std::cout<<"删除后堆内元素"<<std::endl;
heap1.print();
//插入
heap1.insert(20,Heap<int>::GREATER);
std::cout<<"插入后堆内元素"<<std::endl;
heap1.print();
Heap<int>heap2(10);
std::cout<<"最小堆"<<std::endl;
//最小堆
heap2.createHeap(arr,10,Heap<int>::LESS);
std::cout<<"堆内元素"<<std::endl;
heap2.print();
//删除
heap2.delHeap();
std::cout<<"删除后堆内元素"<<std::endl;
heap2.print();
//插入
heap2.insert(20,Heap<int>::LESS);
std::cout<<"插入后堆内元素"<<std::endl;
heap2.print();
return 0;
}
好了这就是以上数据结构之堆的所有内容,希望大家喜欢。
喜欢的话麻烦大家点个赞~