优先级队列:按照事先约定的优先级,可以始终高效查找并访问优先级最高数据项的数据结构。
可以将堆继承于向量的数据结构,优先级队列ADT的size(),insert(),getMax(),delMax()四个接口。
堆的成员:词条也是向量的元素,有大小N确定。
应用接口:可反复使用delMax接口实现排序算法。
如何确保insert(),getMax(),delMax()的时间复杂度均可达到o(logn);
无论是向量还是列表均无法实现该要求,无需保证全体词条之间的全序关系。
有限偏序集的极值必然存在,故因此借助堆结构来实现,也就是树结构特例。
完全二叉堆的其中一种:完全二叉树,条件二:根节点均不大于或不小于子节点。
大顶堆:优先级最高的词条在堆顶为大顶堆。优先级最低的词条在堆顶,为小顶堆。
基于向量结构实现堆结构。完全二叉树可用向量实现。节点之间的关系由位置RANK确定。
1.词条插入堆中:将词条添加到向量末尾,再对堆执行上滤操作。
2.获取优先级最高或最低的词条:返回向量的首单元。
3.删除优先级最高的的词条:堆顶词条=末尾词条,舍弃末尾词条,对新堆顶实现下滤。
堆的上滤操作:判断该元素是否有父亲;
若有,看是否逆序,若逆序,则调换
直到不再逆序或达到堆顶。
由于完全二叉堆中元素均按照层次遍历存储在向量结构中,所以各节点在物理上连续,可利用RANK判断父子关系。
只要i>0,则有父节点=i/2-1;
i*2+1满足[0,N)之间,则合法,且左孩子为i*2+1
i*2+2满足[0,N)之间,则合法,且右孩子为i*2+2
堆的下滤操作: 判断i是否有两个孩子,有,则与最大的那个交换,
若只有一个左孩子,则与左孩子看最大的那个交换。
建堆:给定一组词条,如何高效地将他们建成堆。
方法一:insert()插入法,O(NLOGN);
方法二:将所有词条插入向量中,自顶向下对每个词条进行上滤操作。O(LOGN).深度
方法三:Floyd算法,自下而上,依次对每一个内部节点进行下滤。O(N)。高度
方法三优于方法二的原因是:在完全二叉树树中,深度小的点,远远少于高度小的节点。
堆的构建:
#define Rank int
#parent(i) (i-1)>>1
#define InHeap(n,i) -1<i && i<n
#define LastInternal(n) Parent(i);
#define orderNot(i,j) i<j or j<i 大顶堆与小顶堆之区别。
#define parentvalid(i) 0<i
#define best(heap,n,i) RchildValid(n,i) ? big(heap,big(heap,i,Lchild(i),Rchild(i))):
Lchildvalid(n,i)?big(heap ,i,Lchild(i)):i
template<typename T>class Heap:{
private:
vector<T>heap;
protcted:
RANK downfind(rank n,rank i);
Rank upfind(rank i);
void heapify(rank n);
void copyFrom(T const*A,rank lo,rank hi);
public:
Headp(){}
Heap(T*A,rank n){copyFrom(A,0,n);heapify(n);}
void insert(T);
T getMax();
T delmax();
};
template<typename T>Heap<T>::copyFrom(T const*A,rank lo,rank hi){
while(lo<hi){
heap.clear();
heap.push_back(A[lo++]);
}
heapify(hi);
}
template<typename T>Heap<T>::heapify(rank n){
for(int i=LastInternal(n);InHeap(n,i);i--){
downfind(n,i);
}
}
template<typename T>rank Heap<T>::upfind(rank i){
while(parentvalid(i)){
rank j=parent(i);
if(orderNot(heap[i],heap[j])) break;
swap(i,j);i=j;
}
return i;
}SWAP在utility中。
template<typename T>rank Heap<T>::downfind(rank n,rank i){
rank j;
while(i!=(j=(best(heap,n,i)))){
swap(heap[i],heap[j]);i=j;
}
return i;
}
堆排序:反复调用delmax()方法,实际运行时间往往高于O(nlogn)时间。
也就证明堆结构优于向量结构。
左式堆:除了标准的插入和删除操作,堆结构的另一个常见操纵为合并。由列表构成。
方法一:反复取出堆的最大词条并插入另外一个堆中。
建堆方法;
优先级容器priority_queue<int,vector<T>,less<T>>。priority_queue 模板有 3 个参数,其中两个有默认的参数;第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,它定义了一个用来决定元素顺序的断言。当然,如果指定模板的最巵一个参数,就必须提供另外的两个模板类型参数。
std::vector<int> values{21, 22, 12, 3, 24, 54, 56};
std::priority_queue<int,vector<T>,less<T>> numbers {std::less<int>(),values};
方法如下:
- push(const T& obj):将obj的副本放到容器的适当位置,这通常会包含一个排序操作。
- push(T&& obj):将obj放到容器的适当位置,这通常会包含一个排序操作。
- emplace(T constructor a rgs...):通过调用传入参数的构造函数,在序列的适当位置构造一个T对象。为了维持优先顺序,通常需要一个排序操作。
- top():返回优先级队列中第一个元素的引用。
- pop():移除第一个元素。
- size():返回队列中元素的个数。
- empty():如果队列为空的话,返回true。
- swap(priority_queue<T>& other):和参数的元素进行交换,所包含对象的类型必须相同。
- priority_queue 是一个堆。在底层,一个 priority_queue 实例创建了一个堆它可以自动保持元素的顺序;但我们不能打乱 priority_queue 的有序状态,因为除了第一个元素,我们无法直接访问它的其他元素。如果需要的是一个优先级队列,这一点非常有用。
make_heap():堆(heaps)不是容器,而是一种特别的数据组织方式。堆一般用来保存序列容器。默认使用的是 < 运算符,可以生成一个大顶堆。类似sort()。例如:
std::make_heap(std::begin(numbers), std:rend(numbers),greater<>());
添加元素和移除元素到堆:push_heap()会默认最后一个元素是新添加到堆中的,所以会执行上滤或下滤操作。
- numbers.push_back(11); // Result: 12 10 3.5 6.5 8 2.5 1.5 6 11
- std::push_heap(std::begin(numbers), std::end(numbers));
- std::pop_heap(std::begin(numbers),std::end(numbers));
- // Result:10 8 3.5 6.5 6 2.5 1.5 12
- numbers.pop_back();// Result:10 8 3.5 6.5 6 2.5 1.5
- std::is_heap(std::begin(numbers),std::end(numbers),std::greater<>())。
STL 提供的最后一个操作是 sort_heap(),它会将元素段作为堆来排序。如果元素段不是堆,程序会在运行时崩溃。所以需要判断该向量是否为堆。这个函数有以两个迭代器为参数的版本,迭代器指向一个假定的大顶堆(用 less<> 排列),然后将堆中的元素排成降序。结果当然不再是大顶堆。尽管堆并不是全部有序的,但任何全部有序的序列都是堆。