邓俊辉《数据结构》学习笔记-第十章 优先级队列(自用)
stack(LIFO)和queue(FLFO)都是PQ的特例——优先级取决于元素的插入次序
steap和queap也是PQ的特例——插入和删除的位置受限
1.基本实现
1.1 接口规范
template <typename T> struct PQ{
virtual void insert(T) =0;//按优先级插入词条 ,=0为纯虚函数
virtual T getMax() =0;//取出优先级最高的词条
virtual T delMax() =0;//删除优先级最高的词条
};
1.2 实现探索
(1)基于vector(效率低不可取)
(2)基于list(效率低不可取)
(3)基于BBST(过犹不及,功能太强大,浪费资源)
2.实现——完全二叉堆Complete Binary Heap)
完全二叉树
2.1 性质
2.1.1 结构性——血肉
实现: PQ_ComplHeap=PQ+vector
template <typename T> class PQ_ComplHeap:public PQ<T>,public vector<T>{
protected:
Rank percolateDown(Rank n,Rank i);//下滤
Rank percolateUp(Rank i);//上滤
void heapity(Rank n);//Floyd建堆算法
public:
PQ_ComplHeap(T* A,Rank n)//批量构造
{copyFrom(A,0,n);heapify(n);}
void insert(T);
T getMax(){return this->_elem[0];}
T delMax();
};
2.1.2 堆序性(堆顶即为最大元)——灵魂
2.2 操作
2.2.1 插入(结构性保持,堆序性却不一定)与上滤(比较一次)
插入)直接在向量末尾插入即可,此时结构性位置,堆序性遭到了破坏
template <typename T> void PQ_ComplHeap<T>::insert(T e){
vector<T>::insert(e);
percolateUp(this->_size-1);
}
上滤)若插入的比其逻辑意义上的父亲大,则互换位置,如此往复
template <typename T>
Rank PQ_ComplHeap<T>::percolateUp(Rank i){
while(ParentValid(i)){//只要有父亲(尚未抵达堆顶),则
Rank j=Parent(i);//将i之父记作j
if(It(this->_elem[i],this->_elem[j])) break;//一旦父子不再逆序,上滤即完成
swap(this->_elem[i],this->_elem[j]);//否则,交换父子位置,并上升一层 (O(3logn)
i=j;
}
return i;
}
改进:直接将插入词条备份,下移父节点后,再将备份的词条放入合适的位置(logn+2)
2.2.2 删除(堆序性保持,结构性却不一定)与下滤(比较两次)
删除(logn)物理摘除最大元素后,将末元素与首元素交换,此时结构性恢复,但是堆序性遭到了破坏
template <typename T> T PQ_ComplHeap<T>::delMax(){
T maxElem=this->_elem[0];
this->_elem[0]=this->_elem[--this->_size];//摘除堆顶,用末元素取代
percolateDown(this->_size,0);//对新堆顶实施下滤
return maxElem;//返回此前备份的最大词条
}
下滤与更大的孩子节点交换位置
template <typename T>
Rank PQ_ComplHeap<T>::percolateDown(Rank n,Rank i){
Rank j;//i及其孩子中,能作为父亲的秩
while(i!=(j=properParent(this->_elem,n,i))){//只要i不是j,则
swap(this->_elem[i],this->_elem[j]);//换位 (O(3logn)
i=j;//并继续考察i
}
return i;
}
同样可以做优化
2.2.3 批量建堆
(1)自上而下的上滤(O(nlogn):来源于对所有节点深度的求和)
template <typename T> void PQ_ComplHeap<T>::heapify(Rank n){//蛮力
for(int i=1;i<n;i++)
percolateUp(i);
}
(2)自下而上的下滤(O(n):来源于对所有节点高度的求和)
template <typename T> void PQ_ComplHeap<T>::heapify(Rank n){//蛮力
for(int i=LastInternal(n);i<n;i--)//i=[n/2](取下)-1
percolateDown(n,i); //只需要考虑内部节点
}
2.3 应用
2.3.1 堆排序
template <typename T> void PQ_ComplHeap<T>::heapsort(Rank lo,Rank hi){
PQ_ComplHeap<T> H(this->_elem+lo,hi-lo);//待排序区间建堆,O(n)
while(!H.empty())//反复摘除最大元素并归入已排序后缀,直至堆空
this->_elem[--hi]=H.delMax();//等效于堆顶与末元素兑换后下滤
}
3.左式堆(堆合并)——满足堆序性,不满足非本质的结构性
堆合并方法探索
(1)A.insert(B.delMax())——O(m*log(n+m)
(2)union(A+B).heapify(n+m)——O(m+n)
(3)左式堆——O(logn)
1.条件:节点分布偏左,合并操作偏右
2.引入空节点长度(npl)
3.左倾性&左式堆
4.右侧链
4.模板类
template <typename T> class PQ_LeftHeap:public PQ<T>,public BinTree<T>{
public:
void insert(T);
T getMax(){
return this->root->data;
}
T delMax();
};
5.算法实现
合并
template <typename T>
static BinNodePosi(T) merge(BinNodePosi(T) a,BinNodePosi(T) b){
if(!a) return b;//递归基,a为空,返回b
if(!b) return a;//递归基,b为空,返回a
if(It(a->data,b->data)) swap(b,a);//此时ab非空,一般情况:首先确保b不大
a->rc=merge(a->rc,b);//a的右子堆与b合并
a->rc->parent=a;并//更新父子关系
if(!a->rc||a->lc>npl<a->rc->npl)//若有必要
swap(a->lc,a->rc);//交换a的左右子堆,以确保右子堆的npl不大
a->npl=a->rc?a->rc>npl+1:1;//更新a的npl
return a;//返回堆顶
插入
template <typename T> void PQ_LeftHeap<T>::insert(T e){
BinNodePosi(T) v=new BinNode<T>(e);
this->_root=merge(this->_root,v);
this->_root->parent=0;
this->_size++;
}
删除
template <typename T> T PQ_LeftHeap<T>::delMax(){
BinNodePosi(T) lHeap=this->_root->lc;
BinNodePosi(T) rHeap=this->_root->rc;
T e=this->_root->data;//备份堆顶处的最大元素
delete this->_root;
this->_size--;
this->_root=merge(lHeap,rHeap) ;
if(this->_root) this->_root->parent=0;
return e;
}