参考博客: https://blog.youkuaiyun.com/weixin_41698717/article/details/107789354
参考博客: https://blog.youkuaiyun.com/zxhio/article/details/61195042
本章问题
1.左式堆有什么性质特点
空节点路径长度: npl(null path length)。npl(x)既等于x到外部节点的最近距离,同时也等于以x为根的最大满子树的高度。
左式堆:对于堆中每一个节点X,左儿子的零路径长至少与右儿子的零路径长一样大
即任意节点x,满足npl( lc(x) ) >= npl( rc(x) )
最右侧通路:rPath( x )。从x沿着右侧分支一直前行到空节点,每个点的npl值等于其最右侧通路的长度。r为根节点,rPath( r )终点是深度最小的外部节点。
有一个左式树的定理
在右路径上有r个节点的左式树必然有
2
r
2^r
2r-1个节点
首先右路径是这棵树的最右边的那一条路径,我们用递归的思想去验证,
考虑最右边的节点,根据性质一棵树的右节点必定有左节点与之匹配,所以这棵树至少有 r
个(可能左兄弟还有左儿子)左儿子,
所以树上至少有 2r−1个节点。
2.左式堆如何进行合并操作
现在开始思考左式堆的合并操作了,那么肯定就要用到它的性质
X->left->npl >= X->right->npl;
根据上面的定理,一个含有n个节点的左式树有一条右路径至多含有 log(n+1)个节点,这样可以确保树的右路径的长度,所以对每次合并操作都是对树的右节点进行操作,朝着右路径进行,
总结
1.就是两棵树先从树根开始比较,值更大的节点成为值更小的节点的右儿子,
同时满足小根堆的特点(儿子比父节点的值更大,反之则反)
2.终止条件是一棵树到达了两棵树的右路径的最后一个节点,中间过程有左儿子npl小于右儿子npl的时候,交换左右儿子。
3.之后再在每层merge操作中,判断是否需要交换a的左、右子堆,以确保右子堆的npl不大
3.左式堆如何进行插入操作
左式堆的插入操作为创建一个节点即只有一个根节点的树与需要插入的树进行Merge操作。
4.左式堆如何进行删除操作
删除操作,最小(大)的元素处在树根的位置,只需要把根节点删除就可以,然后对左右子树进行Merge操作。
10.1 pq_leftheap.h ADT接口
操作接口 | 功能描述 |
---|---|
getMax( ) | 获取非空左式堆中优先级最高的词条 |
merge(a,b) | 根据相对优先级确定适宜的方式,合并以a和b为根节点的两个左式堆 |
delMax( ) | 基于合并操作的词条删除算法(当前队列非空) |
insert( ) | 基于合并操作的词条插入算法 |
10.1.1 pq_leftheap类模板
#include "pq.h" //引入优先级队列ADT
#include "bintree.h" //引入二叉树节点模板类
template <typename T>
class PQ_LeftHeap : public PQ<T>, public BinTree<T> { //基于二叉树,以左式堆形式实现的PQ
/*DSA*/friend class UniPrint; //演示输出使用,否则不必设置友类
public:
PQ_LeftHeap() { } //默认构造
PQ_LeftHeap ( T* E, int n ) //批量构造:可改进为Floyd建堆算法
{ for ( int i = 0; i < n; i++ ) insert ( E[i] ); }
void insert ( T ); //按照比较器确定的优先级次序插入元素
T getMax(); //取出优先级最高的元素
T delMax(); //删除优先级最高的元素
}; //PQ_LeftHeap
10.1.2 merge(a,b)
template <typename T> //根据相对优先级确定适宜的方式,合并以a和b为根节点的两个左式堆
static BinNodePosi(T) merge ( BinNodePosi(T) a, BinNodePosi(T) b ) {
if ( ! a ) return b; //退化情况
if ( ! b ) return a; //退化情况
if ( a->data <= b->data ) swap ( a, b ); //一般情况:首先确保b不大
a->rc = merge ( a->rc, b ); //将a的右子堆,与b合并
a->rc->parent = a; //并更新父子关系
if ( !a->lc || 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; //返回合并后的堆顶
} //本算法只实现结构上的合并,堆的规模须由上层调用者负责更新
10.1.3 delMax( )
//delMax
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 ( _root ) _root->parent = NULL; //若堆非空,还需相应设置父子链接
return e; //返回原根节点的数据项
}
10.1.4 insert(e )
//insert
template <typename T> void PQ_LeftHeap<T>::insert ( T e ) { //基于合并操作的词条插入算法
BinNodePosi(T) v = new BinNode<T> ( e ); //为e创建一个二叉树节点
this->_root = merge ( this->_root, v ); //通过合并完成新节点的插入
// _root->parent = NULL; //既然此时堆非空,还需相应设置父子链接
this->_size++; //更新规模
}
10.2 pq_leftheap.h
#pragma once
#include "pq.h" //引入优先级队列ADT
#include "bintree.h" //引入二叉树节点模板类
template <typename T>
class PQ_LeftHeap : public PQ<T>, public BinTree<T> { //基于二叉树,以左式堆形式实现的PQ
/*DSA*/friend class UniPrint; //演示输出使用,否则不必设置友类
public:
PQ_LeftHeap() { } //默认构造
PQ_LeftHeap ( T* E, int n ) //批量构造:可改进为Floyd建堆算法
{ for ( int i = 0; i < n; i++ ) insert ( E[i] ); }
void insert ( T ); //按照比较器确定的优先级次序插入元素
T getMax(); //取出优先级最高的元素
T delMax(); //删除优先级最高的元素
}; //PQ_LeftHeap
//getMax
template <typename T> T PQ_LeftHeap<T>::getMax() //获取非空左式堆中优先级最高的词条
{ return this->_root->data; } //按照此处约定,堆顶即优先级最高的词条
//swap
void swap( BinNodePosi(int) &a,BinNodePosi(int) &b){
auto tmp = a;
a = b;
b = tmp;
}
//merge
template <typename T> //根据相对优先级确定适宜的方式,合并以a和b为根节点的两个左式堆
static BinNodePosi(T) merge ( BinNodePosi(T) a, BinNodePosi(T) b ) {
if ( ! a ) return b; //退化情况
if ( ! b ) return a; //退化情况
if ( a->data <= b->data ) swap ( a, b ); //一般情况:首先确保b不大
a->rc = merge ( a->rc, b ); //将a的右子堆,与b合并
a->rc->parent = a; //并更新父子关系
if ( !a->lc || 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; //返回合并后的堆顶
} //本算法只实现结构上的合并,堆的规模须由上层调用者负责更新
//delMax
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 ( _root ) _root->parent = NULL; //若堆非空,还需相应设置父子链接
return e; //返回原根节点的数据项
}
//insert
template <typename T> void PQ_LeftHeap<T>::insert ( T e ) { //基于合并操作的词条插入算法
BinNodePosi(T) v = new BinNode<T> ( e ); //为e创建一个二叉树节点
this->_root = merge ( this->_root, v ); //通过合并完成新节点的插入
// _root->parent = NULL; //既然此时堆非空,还需相应设置父子链接
this->_size++; //更新规模
}
10.3 pq_leftheap.h测试
#include "pq_leftheap.h"
#include <iostream>
using namespace std;
int main(){
int A[] = { 2, 5, 1, 7, 18};
PQ_LeftHeap<int> lh1( A, 5);
//getMax()
cout << lh1.getMax() << endl;//18
//delMax
cout << lh1.delMax() << endl;//18
//insert
lh1.insert( 11 );
cout << lh1.getMax() << endl;//11
//merge
int B[] = { 12, 5, 21, 6, 9};
PQ_LeftHeap<int> lh2(B, 5);
//PQ_LeftHeap<int> lh( merge( lh1.root() , lh2.root()));
cout << merge( lh1.root() , lh2.root())->data << endl;//21
cout << lh2.delMax() << endl;//21
cout << lh2.delMax() << endl;//12
cout << lh2.delMax() << endl;//11,可见lh2合并入lh2中了
cout << lh2.delMax() << endl;//9
cout << lh2.delMax() << endl;//7
}