优先级队列的实现

优先级队列:队列里面的所有元素都有相应的权值,元素的删除顺序由这些权值决定。
优先级队列的实现一般用堆来实现其效率比一般的实现要高。
要弄清楚堆我们得先弄清楚下面的定义:
一颗大根树(小根树):是这样一棵树,其中每个节点的值都大于(小于)或等于其子节点(如果有子节点的话)的值。
大根堆:一个大根堆(小根堆)即是大根树(小根树)也是完全二叉树。
由于大根堆为完全二叉树,所以我们能用数组来实现,并且不用担心浪费内存资源。完全二叉树的性质之前已经讲过了。

template<class T>
void maxHeap<T>::push(const T& theElement)
{//把元素theElement加入堆
//必要时加长数组长度
if(heapSize==arrayLength-1)
{
    changeLength1D(heap,arrayLength,2*arrayLength)//这个函数很早之前就写过了
    arrayLength*=2;
}
//位元素theElement寻找插入位置
//currentNode从新叶子向上移动
int currentNode=++heapSize;//注意这是前++
while(currentNode!=1&&heap[currentNode/2]<theElement)//这里的currentNode/2为父节点位置,完全二叉树的性质,前面有讲
{//不能把theElement元素插在heap[currentNode]
heap[currentNode]=heap[currentNode/2];//如果父节点的元素比子节点小那么就交换位子
currentNode/=2;//更新当前节点的位置
}
heap[currentNode]=theElement;
}

以上为大根堆的插入代码,大根堆是一个父节点元素大于子节点的元素的树,如果要在其中插入一个元素后任然保持大根堆的性质,那么就得将该元素插入到适当的位子,首先将该元素插入到最后一个叶节点的位子,然后将其与父节点进行比较,如果小于父节点则插入该位置,否则交换位置,该元素成为新的父节点,然后再次与其新的父节点进行比较,直到其插入当前位置,或者成为树根为止。一次操作最多将插入的元素变为根为止,所以最多需要树高height次操作。

template<class T>
void maxHeap<T>::pop()
{//删除最大元素,如果堆为空则抛出异常
if(heapSize==0)
    throw queueEmpty();
//删除最大元素
heap[1].~T();
//删除最后一个元素,然后重新建堆
T lastElement=heap[heapSize--];//注意这是后--
//从根开始为最后一个元素寻找位置
int currentNode=1,child=2;
while(child<=heapSize)
{//heap[child]应该是currentNode的更大的孩子
if(child<heapSize&&heap[child]<heap[child+1])
    child++;//首先选出根节点较大的那个孩子的位置
if(lastElement>=heap[child])
    break;
heap[currentNode]=heap[child];//将根节点较大孩子变为新的根节点
currntNode=child;//判断孩子位置是否是最后节点的插入位置
child*=2;//该child节点位置的左子节点位子。
}
heap[currentNode]=lastElement;
}

以上是删除大根堆中最大元素并保持大根堆性质的程序,首先删除根元素,保存并删除最后一个叶元素,然后为则最后一个叶元素寻找其新的插入位置,首先将其 插入根位置,然后与根的子节点进行比较看其适不适合该位置,如果适合则插入该位子,否则,将适合的子节点位置与该元素进行交换,然后再判断该元素是否适合新的位置。这个操作同插入最多消耗height时间。

template<class T>
void maxHeap<T>::initialize(T *theHeap,int theSize)
{//在数组中建立大堆gen
delete [] heap;
heap=theHeap;
heapSize=theSize;
//堆化
for(int root=heapSize/2;root>=1;root--)//root位置为大根堆的第一个父节点
{
    T rootElement=heap[root];
    //为元素rootElement寻找位置
    int child=2*root;//完全树性质有说这个。
    while(child<=heapSize)
    {
        if(child<heapSize&&heap[child]<heap[child+1])//如果只有左孩子这里child=heapSize
            child++;
        if(rootElement>=heap[child])//如果该节点比其子节点大,则处理下一个节点
            break;
        heap[child/2]=heap[child]//否则交换
        child*=2;//移到下一层,这一步很重要,因为子节点变后,该子节点与其子节点的位置关系也需要重新进行判断
    }
    heap[child/2]=rootElement;
}

}

以上为大根堆的初始化程序,负责将一个非大根堆数组初始化为大根堆数组。
首先处理的是最后一个拥有子节点的节点,将其与子节点进行比较,然后交换顺序,然后在处理与该节点相邻的上一个节点,直到根节点。需要注意的是,当某个节点拥有至少二层子节点时,每当其与子节点进行交换后都要重新考虑其子节点与其孙节点的顺序。以上程序是通过一个while循环进行的处理。

以上的堆结构是一种隐式数据结构,用完全二叉树表示的堆在数组中是隐式存在的。虽然空间和时间效率都很高但是却并不适合用于将不同长度的数进行合并操作,下面介绍左高树的概念。
外部节点:在一棵树的所有叶节上添加其左孩子和右孩子(如果本身具有左或右则仅添加缺失的那一个)添加的这部分节点称为外部节点,其他的节点称为内部节点。
s(x):从节点x到其子树的外部节点的所有路径中最短的一条。
高度优先左高树:当且仅当其任何一个内部节点的左孩子的s值都大于或等于右孩子的s值。
高度优先左高树的性质:
另x为HBLT的一个内部节点,则
(1)以x为根的子树的节点数目至少为2^s(x)-1.
(2)若以x为根的子树有m个节点,那么s(x)最多有log2(m+1).
(3)从x到一外部节点的最右路径的长度为s(x)。
衍生:若一颗HBLT(高度优先左高树)同时还是大根树或者小根树,那么这棵树被称为最大HBLT和最小HBLT。

重量优先左高树:当且仅当其任何一个内部节点的左孩子的w值都大于或者等于其右孩子的w值。若是大根树或者小根树,则称为最大WBLT和最小WBLT.
w(x):是以节点x为根的子树的内部节点数目,若x是外部节点,则他的重量为0,若x是内部节点则它的重量为其孩子节点的重量之和加1。(加一是因为算其本身的重量,外部节点不算重量)

template<class T>
void maxHblt<T>::initialize(T* theElement,int theSize)
{//用数组theElement[1:theSize]建立左高树
arrayQueue<binaryTreeNode<pair<int,T>>*>q(theSize);//这里的binaryTreeNode已经在二叉树的链表实现里面进行了定义。
erase();//使*this为空
//初始化树的队列
for(int i=1;i<=theSize;i++)
{//建立只有一个节点的树
    q.push(new binaryTreeNode<pair<int ,T>>(pair<int,T>(1,theElement[i])));//这里是将树高初始化为1,根树树高就是1.
}
//从队列中重复取出两棵树合并
for(i=1;i<=theSize-1;i++)
{//从队列中删除两棵树合并
binaryTreeNode<pair<int,T>>*b=q.front();
q.pop();
binaryTreeNode<pair<int,T>>*c=q.front();
q.pop();
meld(b,c)
//把合并后的树插入队列
q.push(b);  
}
    if(theSize>0)
        root=q.front();
    treeSize=theSize;
}

以上为最大左高树的初始化函数,其输入为要进行树化的数组和其尺寸大小。
注意:这里的最大左高树并不是完全二叉树,所以并不满足其性质。所以这里我们并不能用数组进行存取,所以我们在这里用二叉树节点对数据进行存储,二叉树节点里面有三个数据,elment是一个pair对象,里面包含一个T型数据和一个int型数据代表以该节点为根节点的树s值,还有两个指针分别指向左,右子节点。
我们首先将数据存到二叉树节点里面,然后将这些节点装进一个队列,然后每次取其中的前两个节点进行合并成左高树,然后将该树的根节点push到队列尾部,循环thesize-1次后队列中将只剩下一个左高树节点,就是我们形成的左高树根节点。

template<class T>
void maxHblt<T>::meld(binaryTreeNode<pair<int,T>>* &x,binaryTreeNode<pair<int,T>>* &x)
{//合并分别以*x和*y为根的两颗左高树
//合并后的左高树以x为根,返回x的指针
if(y==NULL)//如果y为空则树x不变
    return;
if(x==NULL)
{x=y;return;}//如果x为空,则将y作为新的树根
//x和y都不为空,必要时交换x和y
if(x->element.second<y->element.second)
    swap(x,y);//在合并的时候我们一般将根较大的值放在左边,保证大根堆的性质。
meld(x->rightChild,y);
//如有需要,交换x的子树,然后设置x->element.first的值
if(x->element==NULL)
{//左子树为空,交换子树
    x->leftChild=x->rightChild;
    x->rightChild=NULL;
    x->element.first=1;//对于左高树而言,左子树只有在右子树为空的时候才能为空,所以只有在x为根树的时候才会执行这段代码,在根树右子树为空后,其s为1.
}
else{//只有左子树的s值较小时才交换
if(x->leftChild->element.first<x->rightChild->element.first)
    swap(x->leftChild,x->rightChild);
x->element.first=x->rightChild->element.first+1;    
}
}

以上为将两个左高树进行合并的代码,初始化函数里面使用了这个代码,这个代码首先判断两个左高树是否为空,然后对其进行简单的交换处理,或者不处理,如果都不为空,则首先选取合成左高树的根节点,选取标准是谁的根节点大,谁就是新左高树的根节点,这是为了保证形成的左高树满足大根树的特点(注意不是大根堆),然后将根节点的右子树与另一个子树进行合并(这里使用了迭代算法,且对于这种迭代我们可以先仅仅考虑最外层)如果根节点的左子树为空,则交换左右子树的位置,否则比较左右子树的s值,将s值较小的那个放到右子树的位置。

void maxHblt<T>::meld(maxHblt<T>& theHblt)
{//把左高树*this和theHblt合并
melt(root,theHblt.root);
treeSize+=theHblt.treeSize;
theHblt.root=NULL;
theHblt.treeSize=0;
}

以上代码将两个左高树合并,其实就是调用了之前的melt然后对一些变量进行改变而已。

template<class T>
void maxHblt<T>::push(const T& theElement)
{//把元素theElement插入左高树
//建立只有一个节点的左高树
binaryTreeNode<pair<int,T>>(pair<int,T>(1,theElement));
//将左高树q和原树合并
meld(root,q);
treeSize++; 
}

插入只是将元素初始化为节点后在使用meld函数进行合并

template<class T>
void maxHblt<T>::pop()
{//删除最大元素
if(root==NULL)
    throw queueEmpty();
//树不空
binaryTreeNode<pair<int,T>>*left=root->leftChild;
binaryTreeNode<pair<int,T>>*right=root->rightChild;
delete root;
root=left;//左边的数据要大(因为是大根树),所以将其设为新的根
melt(root,right);
treeSize--;
}

删除只是将树的根删除然后再将树的左子树和右子树合并成一个新的树。

以上我们实现了最大左高树以及大根堆的算法,大根堆由于满足完全二叉树的性质,我们能用数组直接实现这样既节省空间,同时又很效率,但是其不能实现两个大根堆的合并操作,所以我们又实现了最大左高树,其的核心就是树的合并,无论数据的删除,插入都是利用左高树的合并来实现的。

猜你喜欢

转载自blog.csdn.net/Du_Shuang/article/details/81283630