Head并不是STL中的容器组件,它是优先级队列(priority queue)的底层实现,优先级队列允许用户以任何次序将任何元素推入容器内,但是取出时一定是从优先权最高的元素开始取,最大二叉堆正是具有这样的特性,适合作为priority queue的底层机制。
堆堆分为大堆小堆小堆指的是每个父节点的值都比子节点小,相反为大堆;故可以实现堆结构,但是要在插入时候实现自动调整。
调整函数:
SiftDown()//向下调整:根节点先下来,把最后一个节点移到根节点,然后进行调整,重新满足小堆特点,以此类推实现排序
SiftUp()//向上调整:指插入新数据后依然满足大小堆
下面分别用两种方法实现上面两个函数(都采用小堆方式):
#include<iostream>
using namespace std;
#define DEFAULT_SIZE 20
template<class Type>
class Heap
{
public:
Heap()
{
heap = new Type[DEFAULT_SIZE];
memset(heap, 0, sizeof(Type)*DEFAULT_SIZE);
cursize = 0;
maxsize = DEFAULT_SIZE;
}
virtual ~Heap()
{
delete []heap;
heap = NULL;
cursize = maxsize = 0;
}
public:
virtual bool Insert(const Type &x) = 0;
virtual bool Remove(Type &x) = 0;
bool IsEmpty()const
{
return cursize == 0;
}
bool IsFull()const
{
return cursize >= maxsize;
}
int size()const
{return cursize;}
Type& operator[](int i)
{
return heap[i];
}
protected:
virtual void SiftUp(int start) = 0;
virtual void SiftDown(int start, int n) = 0;
protected:
Type *heap;
int cursize;
int maxsize;
};
//MinHeap
template<class Type>
class MinHeap : public Heap<Type>
{
public:
bool Insert(const Type &x)
{
if(IsFull())
{
cout<<"堆已满,不能插入数据."<<endl;
return false;
}
heap[cursize] = x;
SiftUp(cursize);
cursize++;
return true;
}
bool Remove(Type &x)
{
x = heap[0];
heap[0] = heap[--cursize];
SiftDown(0, cursize-1);
return true;
}
protected:
void SiftUp(int start)
{
int j = start;
int i = (j-1) / 2;
while(j > 0)
{
if(heap[j] < heap[i])
{
Type tmp = heap[j];
heap[j] = heap[i];
heap[i] = tmp;
}
j = i;
i = (j-1) / 2;
}
}
void SiftDown(int start, int n)
{
int i = start;
int j = 2*i+1;
while(j <= n)
{
if(j+1<=n && heap[j]>heap[j+1])
j = j+1;
if(heap[i] > heap[j])
{
Type tmp = heap[i];
heap[i] = heap[j];
heap[j] = tmp;
}
i = j;
j = 2*i+1;
}
}
};
void main()
{
int ar[] = {41,435,14,45,54,15,51,65,76,8,1};
//int ar[] = {8,5,9,2,1};
int n = sizeof(ar) / sizeof(int);
int i;
MinHeap<int> mp;
for( i=0; i<n; ++i)
{
mp.Insert(ar[i]);
}
for(i=0; i<mp.size(); ++i)
{
cout<<mp[i]<<" ";
}
cout<<endl;
int x;
i = 1;
while(i <= 3)
{
mp.Remove(x);
cout<<x<<"删除"<<" ";
cout<<endl;
++i;
}
for(i=0; i<mp.size(); ++i)
{
cout<<mp[i]<<" ";
}
cout<<endl;
}
上述方法采用子节点若小于父节点就交换的算法,效率相对低,下面采用另一种算法:
//stl_heap.h
#pragma once
#ifndef _STL_HEAP_H
#define _STL_HEAP_H
#include<iostream>
using namespace std;
#define DEFAULT_SIZE 20
template <class T>
class Heap
{
public:
Heap()
{
heap = new T[DEFAULT_SIZE];
memset(heap , 0 , sizeof(T)*DEFAULT_SIZE);
cursize = 0;
maxsize = DEFAULT_SIZE;
}
virtual bool Insert(const T& value)= 0 ;
virtual bool Remove(T &x) = 0;
bool IsFull(){return cursize >= maxsize;}
bool IsEmpt(){return cursize == 0;}
int size(){return cursize;}
T& operator[](int i){return heap[i];}
virtual ~Heap()
{
delete []heap;
heap = NULL;
cursize = maxsize = 0;
}
protected:
virtual void SiftUp(int start) = 0;
virtual void SiftDown(int start , int n) = 0;
protected:
T* heap;
int cursize;
int maxsize;
};
template<class T>
class MinHeap : public Heap<T>
{
public:
bool Insert(const T &x)
{
if(IsFull())
{
cout<<"Heap is FULL."<<endl;
return false;
}
else
{
heap[cursize] = x;
SiftUp(cursize);
cursize++;
return true;
}
}
bool Remove(T &x)
{
x = heap[0];
heap[0] = heap[--cursize];
SiftDown(0, cursize-1);
return true;
}
protected:
void SiftUp(int start)
{
int first = start;
T tmp = *(heap + first);
int parent = (first-1) / 2;
while(parent >= 0 && *(heap + parent) > tmp)
{
*(heap + first) =*(heap +parent) ;
*(heap + parent) = tmp;
first = parent;
parent = (first-1) / 2;
}
}
void SiftDown(int start, int n)
{
int first = start;
T tmp = *(heap+first);
int child = 2*first +1;
while(child <= n && *(heap+first) > *(heap + child))
{
if(child + 1<=n && *(heap+child) > *(heap+child+1))
child = child + 1;
*(heap+first) = *(heap+child);
*(heap+child) = tmp;
first = child;
child = 2*first+1;
}
}
};
#endif
//主函数
#include"stl_heap.h"
void main()
{
int ar[] = {41,435,14,45,54,15,51,65,76,8,1};
//int ar[] = {8,5,9,2,1};
int n = sizeof(ar) / sizeof(int);
int i;
MinHeap<int> mp;
for( i=0; i<n; ++i)
{
mp.Insert(ar[i]);
}
for(i=0; i<mp.size(); ++i)
{
cout<<mp[i]<<" ";
}
cout<<endl;
int x;
i = 1;
while(i <= 3)
{
mp.Remove(x);
cout<<x<<"删除"<<" ";
cout<<endl;
++i;
}
for(i=0; i<mp.size(); ++i)
{
cout<<mp[i]<<" ";
}
cout<<endl;
}
第二种方法,采用先存储新插入或删除值,满足条件子父结点进行覆盖,最后在循环后的下表位置进行赋值即可。
在编写第二种方法时候遇到的问题:中间值tmp的定义误用了引用返回,造成了插入的值总是第一个,在一步步调试后发现问题,加深了对引用返回的认识,引用返回即使用的相同的空间,故肯定不能进行交换值。
需要注意的陷阱:
Stl_head的底层是借助矢量来实现通过make_head来创建堆,
其中push_back方法仅仅是在尾部插入,并且不会立即进行调整堆的结构,必须调用push_head才会进行调整;
pop_back是真实的删除,pop_heap不会真正的进行数据删除。