首页 > IT职业 > IT职场 > 堆(heap)的原理与实现详解【Van0512】
本文主要包括的内容:
先吐个槽O(∩_∩)O:
网站上的编辑器我总是不太会用,排版总是让我很揪心,大概是写得太少了吧。慢慢习惯吧,希望每一篇文章的排版都能进步一点点。言归正传。
堆(heap)
是一种优先队列(priority queue)。
取出元素的顺序是按照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。
特性:
主要操作:建堆、判空、判满、插入、删除堆顶元素。
直接生成堆的时间复杂性是O(nlog(n))。
在线性时间复杂度O(n)下建堆:
这边提供一份C语言的代码,主要关注它的原理:
/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
int Parent, Child;
ElementType X;
X = H->Data[p]; /* 取出根结点存放的值 */
for( Parent=p; Parent*2Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Data[Child]Data[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
}
void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性 */
/* 这里假设所有H->Size个元素已经存在H->Data[]中 */
int i;
/* 从最后一个结点的父节点开始,到根结点1 */
for( i = H->Size/2; i>0; i-- )
PercDown( H, i );
} 重要算法:上虑、下虑。
在C++中,提供了一种容器适配器(container adaptor)来实现这种数据结构,放在头文件 queue 中。
具体使用方式:
#include
using namespace std;
...
priority_queue priQue; 主要操作有:
bool empty() //Test whether container is empty.
int size() //Return size.
void push(elem) //Insert element.
void emplace(elem) //Construct and insert element.
void pop() //Remove top element.
elemType top() //Access top element. priority_queue 默认按照严格弱序(strict weak ordering)排序规则生成最大堆,若要生成最小堆,看下面这个例子:
#include
#include
#include
#include
using namespace std;
int main(void) {
priority_queue priQue;
for (int i = 0; i b;
}
};
在下面的一行代码中,把 greater 改成 greater 即可,头文件 functional 也可删除。
*/
priority_queue, greater> minHeap;
for (int i = 0; i 但许多时候,常常有其他的需求。为此,我们就需要手动建一个堆,然后就可以在堆上增加一些函数,提供我们想要的功能。
写一个最小堆类:
typedef int ElemType;
class MinHeap {
public:
MinHeap(int capacity); //建堆,传入堆的容量。
~MinHeap();
bool isEmpty() const;
bool isFull() const;
bool insertElem(ElemType &elem);
ElemType deleteTop();
private:
ElemType *arr;
int capacity;
int len; //堆中元素个数,是堆的最后一个元素的索引。
};
/*
数组长度为堆容量+1。
0号元素作为“哨兵”,在最小堆中,设置为比所有元素都小的值。
1号元素作为根节点,那么父子节点下标有如下关系:
leftChild = parent * 2;
rightChild = parent * 2 + 1;
parent = Child/2;
*/
MinHeap::MinHeap(int capacity) {
arr = new ElemType[capacity + 1];
this->capacity = capacity;
len = 0;
arr[0] = INT_MIN; //定义在头文件 climits 里面
}
MinHeap::~MinHeap() {
delete[] arr;
arr = NULL;
}
bool MinHeap::isEmpty() const {
return 0 == len;
}
bool MinHeap::isFull() const {
return len == capacity;
}
bool MinHeap::insertElem(ElemType &elem) {
if (isFull()) return false;
/*
上滤。
如果父节点的数据值比elem大,那么把父节点的数据值拷贝到要插入的子节点中。
把子节点的下标更新为父节点,继续。
若父节点的数据值比elem小,那么elem就可以插入。
*/
int child = ++len;
for (; arr[child / 2] > elem; child /= 2)
arr[child] = arr[child / 2]; //上滤 child = parent
arr[child] = elem;
return true;
}
ElemType MinHeap::deleteTop() {
if (isEmpty()) return INT_MIN; //error
ElemType minItem = arr[1];
/*
下滤。
模拟将最后一个元素从根节点的位置向下层节点过滤的过程。
*/
ElemType tmp = arr[len--];
int parent = 1;
for (int child = 2 * parent; child arr[child + 1]) child++; //child指向数据值较小的子节点。
if (tmp 比如,如果要打印从一个给定索引到根节点的路径,就可以在 MinHeap 中增加一个函数,代码如下:
class MinHeap {
public:
void printPath(int index);
}
void MinHeap::printPath(int index) {
if (index > len) {
cout 1) {
index /= 2;
cout 好了,就写到这里吧。
慕友若有什么高见,可在下面回复。
祝开心。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2568/viewspace-2807382/ ,如需转载,请注明出处,否则将追究法律责任。