本博客不讲解具体的原理,仅仅给出一种优先队列较为一般化的,可重用性更高的一种实现方法。我所希望的是能过带来一种与使用STL相同的使用体验,因为学习了STL源码之后深受STL代码的影响,对每个ADT都希望能过给出一种高效,可重用,更一般的实现方法,即使我的代码在STL的priority_queue面前仅仅只是三流水平,但也足够吧二叉堆这种数据结构演绎好了。为了更一般化,我抛弃C语言的函数指针,改用STL相同的仿函数来实现更加方便的自定义优先级比较函数,于此同时我也兼容了已有类型的原有优先级比较仿函数。首先给出maxPriorityQueue的ADT以及默认的优先级比较仿函数,默认为大根堆,小根堆重载优先级比较仿函数即可。
#ifndef MAXPRIORITYQUEUE_H #define MAXPRIORITYQUEUE_H #include <cstring> template<class T>
struct Compare { bool operator () (const T& a, const T& b) { return a < b; } }; template<>
struct Compare<char*> { bool operator () (char* s1, char* s2) { return strcmp(s1, s2) < 0; } }; template<class T>
class maxPriorityQueue { public: ~maxPriorityQueue() {} virtual bool empty() const = 0; virtual int size() const = 0; virtual T& top() = 0; virtual void pop() = 0; virtual void push(const T& theElement) = 0; }; #endif
对于ADT的设计与实现,在C++中,相应ADT的纯虚类是必不可少的,它是ADT的逻辑定义,是设计数据结构的基础,上述代码给出了maxPriorityQueue的定义以及默认优先级比较仿函数,对于仿函数的使用方法,可在下面的maxHeap类中找到。
#include "maxPriorityQueue.h" #include <cstdlib> #include <cstdio> #include <algorithm> #ifndef MAX_HEAP_H #define MAX_HEAP_H
/* 用法:大根堆重载小于号,小根堆重载大于号 */ template<class T, class Cmp = Compare<T> >
class maxHeap : maxPriorityQueue<T> { public: maxHeap(int Size = 10) { heap = new T[Size]; Heapsize = 0; Arraylength = Size; } maxHeap(maxHeap<T, Cmp>& theHeap); ~maxHeap(); void Initialize(T* theHeap, int heapsize); bool empty() const; int size() const; void push(const T& x); void pop(); T& top() { return heap[1]; } private: void changelength1(); protected: T* heap; int Heapsize; int Arraylength; Cmp elecmp; }; template<class T, class Cmp>
void maxHeap<T, Cmp>::changelength1() { T* newHeap = new T[Arraylength * 2]; std::copy(heap, heap + Arraylength, newHeap); delete [] heap; heap = newHeap; } template<class T, class Cmp>
bool maxHeap<T, Cmp>::empty() const { return Heapsize == 0; } template<class T, class Cmp>
int maxHeap<T, Cmp>::size() const { return Heapsize; } template<class T, class Cmp>
void maxHeap<T, Cmp>::push(const T& x) { if(Heapsize == Arraylength - 1) { changelength1(); Arraylength *= 2; } int currentNode = ++Heapsize; while(currentNode != 1 && elecmp(heap[currentNode / 2], x)) { heap[currentNode] = heap[currentNode / 2]; currentNode /= 2; } heap[currentNode] = x; } template<class T, class Cmp>
void maxHeap<T, Cmp>::pop() { if(Heapsize == 0){ printf("error at function maxHeap<T, Com>::pop()\n"); exit(-1); } heap[1].~T(); T lastElement = heap[Heapsize--]; int currentNode = 1, child = 2; while(child <= Heapsize) { if(child < Heapsize && elecmp(heap[child], heap[child + 1])) child++; if(!elecmp(lastElement, heap[child])) break; heap[currentNode] = heap[child]; currentNode = child; child *= 2; } heap[currentNode] = lastElement; } template<class T, class Cmp>
void maxHeap<T, Cmp>::Initialize(T* theHeap, int heapsize) { delete [] heap; heap = new T[heapsize + 1]; std::copy(theHeap, theHeap + heapsize + 1, heap); Heapsize = heapsize; Arraylength = heapsize + 1; for(int root = Heapsize / 2; root >= 1; root--) { T rootElement = heap[root]; int child = root * 2; while(child <= Heapsize) { if(child < Heapsize && elecmp(heap[child], heap[child + 1])) child++; if(!elecmp(rootElement, heap[child])) break; heap[child / 2] = heap[child]; child *= 2; } heap[child / 2] = rootElement; } } template<class T, class Cmp> maxHeap<T, Cmp>::~maxHeap() { delete [] heap; } template<class T, class Cmp> maxHeap<T, Cmp>::maxHeap(maxHeap<T, Cmp>& theHeap) { Arraylength = theHeap.Arraylength; Heapsize = theHeap.Heapsize; elecmp = theHeap.elecmp; heap = new T[Arraylength]; std::copy(theHeap.heap, theHeap.heap + Arraylength, heap); } #endif
例如maxHeap中保护成员elecmp(意思可以理解为Element Comparison)为一个仿函数的具体类,Cmp为仿函数的类型,使用方法为
Cmp elecmp;
elecmp(a, b)
虽然这个类叫做maxHeap,但它不仅支持大根堆,还支持小根堆,maxHeap中没有一些clear() 等操作,也没有制作相关的迭代器,因为heap理论上来讲不需要遍历,但是其他的数据结构我都会设计相关的迭代器,只是有时候会偷点懒。
TIP:1. 内存扩展函数应定义为私有或保护函数,不推荐定义为友函数。
2. Initialize操作更准确的复杂度为Θ(n)。
3. 在头文件中不推荐声明STL的命名空间。
最后欢迎学习交流,做题时遇到优先队列的题我的maxHeap过不了但priority_queue过得了,第一个我请杯奶茶,我自认为比不可能有bug。
参考文献:[1] [美]Sartaj Sahni著,王立柱 刘志红 译,《数据结构、算法与应用(C++语言描述, 第二版)》,2021.3.26