读书笔记之:数据结构,算法与应用(4)

第9章 优先队列
与第6章FIFO结构的队列不同,优先队列中元素出队列的顺序由元素的优先级决定。从优先队列中删除元素是根据优先权高或低的次序,而不是元素进入队列的次序。
可以利用堆数据结构来高效地实现优先队列。堆是一棵完全二叉树,可用8.4节所介绍的公式化描述方法来高效存储完全二叉树。在高度和重量上取得平衡的左高树很适合于用来实现优先队列。本章的内容涵盖了堆和左高树。
在本章的应用部分,利用堆开发了一种复杂性为O(nlogn)的排序算法,称为堆排序。在第2章所介绍的对n个元素进行排序的算法,其复杂性均为O(n^2)。虽然第3章介绍的箱子排序和基数排序算法的运行时间为Θ(n),但算法中元素的取值必须在合适的范围内。堆排序是迄今为止所讨论的第一种复杂性优于O(n^2)的通用排序算法,第 14章将讨论另一种与堆排序具有相同复杂性的排序算法。从渐进复杂性的观点来看,堆排序是一种优化的排序算法,因为可以证明,任何通用的排序算法都是通过成对比较元素来获得Ω(nlogn)复杂性的(见14.4.2节)。
本节所考察的另外两个应用是机器调度和生成霍夫曼编码。机器调度问题属于NP-复杂问题,对于这类问题不存在具有多项式时间复杂性的算法。而第2章提到的大量事实表明,只有具有多项式时间复杂性的算法才是可行的,因此,经常利用近似算法或启发式算法来解决NP-完全问题,这些算法能在合理的时间内完成,但并不能保证找到最佳结果。
1. 最大树(最小树)
每个节点的值都大于(小于)或等于其子节点(如果有的话)值的树。
最大堆(最小堆)是最大(最小)的完全二叉树
2. 最大堆插入操作
插入策略从叶到根只有单一路径,每一层的工作需耗时Θ(1),因此实现此策略的时间复杂性为O(height)=O(logn)
删除操作
删除策略产生了一条从堆的根节点到叶节点的单一路径,每层工作需耗时Θ(1),因此实现此策略的时间复杂性为O(height)=O(logn)
最大堆的初始化
通过在初始为空的堆中执行n次插入操作来构建非空的堆,插入操作所需总时间为O(nlogn) ,也可利用不同的策略在Θ(n)时间内完成堆的初始化
类MaxHeap的实现代码如下:
View Code
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <iterator>
#include <cstdio>
using namespace std;
template <class T>
class MaxHeap{
    public:
        MaxHeap(int sz=10);
        MaxHeap(T a[],int N );
        ~MaxHeap(){
            delete [] heap;
        }
        int Size()const{
            return CurrentSize;
        }
        T Max(){
            if(CurrentSize==0)
                throw "OutofBounds";
            return heap[1];
        }
        MaxHeap<T>& Insert(const T& x);
        MaxHeap<T>& DeleteMax(T& x);
        void Initialize(T a[],int ArraySize);
        ostream& Output(ostream& out)const {
            for(int i=1;i<=CurrentSize;i++)
                out<<heap[i]<<' ';
        }
        void Adjust(T b[],int m,int n);
        bool Equal(T a[],int n);
    private:
        void Swap(T& a,T& b);
        int CurrentSize,MaxSize;
        T* heap;
};
template <class T>
MaxHeap<T>::MaxHeap(int sz)
{
    MaxSize=sz;
    heap=new T[MaxSize+1];
    CurrentSize=0;
}
template <class T>
MaxHeap<T>::MaxHeap(T a[],int ArraySize)
{
    MaxSize=ArraySize;
    heap=new T[ArraySize+1];
    for(int i=1;i<=ArraySize;i++)
        heap[i]=a[i-1];
    CurrentSize=ArraySize;
    for(int i=CurrentSize/2;i>=1;i--){
        Adjust(heap,i,CurrentSize);
    }

}
template <class T>
MaxHeap<T>& MaxHeap<T>::Insert(const T& x)
{
    if(CurrentSize==MaxSize)
        throw "NoMem";
    int i=++CurrentSize;
    while(i>1&&x>heap[i/2]){
        heap[i]=heap[i/2];
        i/=2;
    }
    heap[i]=x;
    return *this;
}
template <class T>
MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
{
    if(CurrentSize==0)
        throw "OutofBounds";
    x=heap[1];
    heap[1]=heap[CurrentSize--];
    Adjust(heap,1,CurrentSize);

    return *this;
}
template <class T>
void MaxHeap<T>::Swap(T& a,T& b){
    T tmp=a;
    a=b;
    b=tmp;
}
template <class T>
void MaxHeap<T>::Adjust(T b[],int m,int n){
    int j=m,k=2*m;
    while(k<=n){
        if(k<n&&b[k]<b[k+1])
            k++;
        if(b[j]<b[k])
            Swap(b[j],b[k]);
        j=k;
        k*=2;
    }
}
template <class T>
void MaxHeap<T>::Initialize(T a[],int ArraySize)
{
    if(MaxSize>=ArraySize){
        for(int i=1;i<=ArraySize;i++)
            heap[i]=a[i-1];
    }
    else{
        delete [] heap;
        MaxSize=ArraySize;
        heap=new T[ArraySize+1];
        for(int i=1;i<=ArraySize;i++)
            heap[i]=a[i-1];
    }
    CurrentSize=ArraySize;
    for(int i=CurrentSize/2;i>=1;i--){
        Adjust(heap,i,CurrentSize);
    }
}
template <class T>
bool MaxHeap<T>::Equal(T a[],int N){
    if(CurrentSize!=N)
        return false;
    for(int i=1;i<=CurrentSize;i++)
        if(heap[i]!=a[i-1])
            return false;
    return true;
}
template <class T>
ostream& operator<<(ostream& out,MaxHeap<T>& mh){
    mh.Output(out);
    return out;
}
void test1(){
    MaxHeap<int> H(4);
    int x;
    H.Insert(10).Insert(20).Insert(5);
    cout << "Elements in array order" << endl;
    cout<<H<<endl;
    try {H.Insert(15);
             cout << "Insert of 15  succeeded" << endl;
                  H.Insert(30);
                       cout << "Insert of 30  succeeded" << endl;}
    catch (...)
           {cout << "An insert has failed"  << endl;}
    cout << "Elements in array order" << endl;
    cout<<H<<endl;
    cout << "The max element is " << H.Max() << endl;
    H.DeleteMax(x);
    cout << "Deleted max element " << x << endl;
    H.DeleteMax(x);
    cout << "Deleted max element " << x << endl;
    cout << "Elements in array order" << endl;           
    cout<<H<<endl;

}
const charconst red="\033[0;40;31m";
const charconst green="\033[0;40;32m";
const charconst normal="\033[0m";
void test2(){
    const int N=20;
    int a[N];
    for(int j=0;j<10;j++){
        for(int i=0;i<N;i++)
            a[i]=rand()%100;
        cout<<"Orig:";
        copy(a,a+N,ostream_iterator<int>(cout," "));
        cout<<endl;

        MaxHeap<int> H(a,N);
        cout<<"Heap:";
        cout<<H<<endl;

        cout<<"Stnd:";
        make_heap(a,a+N);
        copy(a,a+N,ostream_iterator<int>(cout," "));
        if(H.Equal(a,N))
            printf("%sOK%s\n",green,normal);
        else
            printf("%sNo%s\n",red,normal);

        cout<<endl;
    }
}
int main(){
    test2();
}

其中主要包括3个主要函数:Insert,Delete和Initialize。

而Delete和Initialize操作主要是调用Adjust函数来完成的。这个函数是对堆进行调节的。

在STL中有一个对应的容器priority_queue,就是堆的实现,具体见这

这个容器的操作如下:

同时,在STL中也提供了几个函数来实现堆的建立,插入和删除。

函数说明:

std::make_heap将[start, end)范围进行堆排序,默认使用less<int>, 即最大元素放在第一个。

std::pop_heap将front(即第一个最大元素)移动到end的前部,同时将剩下的元素重新构造成(堆排序)一个新的heap。

std::push_heap对刚插入的(尾部)元素做堆排序

std::sort_heap将一个堆做排序,最终成为一个有序的系列,可以看到sort_heap时,必须先是一个堆(两个特性:1、最大元素在第一个 2、添加或者删除元素以对数时间),因此必须先做一次make_heap.

为了测试程序的正确,本文的测试代码中就调用了make_heap函数来建立一个堆,并且和我们自己的代码进行测试比较:

void test2(){
    const int N=20;
    int a[N];
    for(int j=0;j<10;j++){
        for(int i=0;i<N;i++)
            a[i]=rand()%100;
        cout<<"Orig:";
        copy(a,a+N,ostream_iterator<int>(cout," "));
        cout<<endl;

        MaxHeap<int> H(a,N);
        cout<<"Heap:";
        cout<<H<<endl;

        cout<<"Stnd:";
        make_heap(a,a+N);
        copy(a,a+N,ostream_iterator<int>(cout," "));
        if(H.Equal(a,N))
            printf("%sOK%s\n",green,normal);
        else
            printf("%sNo%s\n",red,normal);

        cout<<endl;
    }
}

 测试结果如下:

 上图中第7个给出的测试结果为No,其实这个也是正确的,因为给出的原始的随机数组中包含两个最大值97,在最后一步中两种方法选择了不同子树中的97,导致最后的结果不一样,但是都是正确的。

 

9.4  左高树
9.3节的堆结构是一种隐式数据结构( implicit data structure),用完全二叉树表示的堆在数组中是隐式存贮的(即没有明确的指针或其他数据能够重构这种结构)。由于没有存贮结构信息,这种描述方法空间利用率很高,事实上没有空间浪费。尽管堆结构的时间和空间效率都很高,但它不适合于所有优先队列的应用,尤其是当需要合并两个优先队列或多个长度不同的队列时。因此需要借助于其他数据结构来实现这类应用,左高树(leftist tree)就能满足这种要求。
考察一棵二叉树,它有一类特殊的节点叫做外部节点( external node),用来代替树中的空子树,其余节点叫做内部节点(internal node)。增加了外部节点的二叉树被称为扩充二叉树(extended binary tree),图9-6a 给出了一棵二叉树,其相应的扩充二叉树如图 9-6b 所示,外部节点用阴影框表示,为了方便起见,这些节点用 a~f标注。
[高度优先左高树] 当且仅当一棵二叉树的任何一个内部节点,其左孩子的 s 值大于等于右孩子的s 值时,该二叉树为高度优先左高树( height-biased leftist tree, HBLT)。
[最大HBLT ] 即同时又是最大树的HBLT;[最小HBLT ] 即同时又是最小树的HBLT。
9.4.2 最大HBLT的插入
最大HBLT的插入操作可借助于最大 HBLT的合并操作来完成。假设将元素 x 插入到名为 H的最大HBLT中,如果建造一棵仅有一个元素 x 的最大HBLT然后将它与H进行合并,结果得到的最大HBLT将包括H中的全部元素及元素 x。因此插入操作只需先建立一棵仅包含欲插入元素的HBLT,然后将它与原来的HBLT合并即可。
9.4.3 最大HBLT的删除
根是最大元素,如果根被删除,将留下分别以其左右孩子为根的两棵 HBLT的子树。将这两棵最大HBLT合并到一起,便得到包含除删除元素外所有元素的最大 HBLT,所以删除操作可以通过删除根元素并对两个子树进行合并来实现。
9.4.4 合并两棵最大HBLT
具有n个元素的最大HBLT,其最右路径的长度为O(logn)。合并操作仅需遍历欲合并的HBLT的最右路径。由于在两条最右路径的每个节点上只需耗时 O(1),因此将两棵HBLT进行合并具有对数复杂性。通过以上观察,在我们所设计的合并算法中,仅需移动右孩子。
合并策略最好用递归来实现。令 A、B 为需要合并的两棵最大 HBLT,如果其中一个为空,则将另一个作为合并的结果,因此可以假设两者均不为空。为实现合并,先比较两个根元素,较大者作为合并后的HBLT的根。假定A 具有较大的根,且其左子树为 L,C 是由A 的右子树与B 合并而成的 HBLT。A与B合并所得结果即是以 A 的根为根,L 与C 为左右子树的最大 HBLT。如果L 的s 值小于C 的s 值,则C 为左子树,否则L 为左子树。
9.4.5 初始化最大 HBLT
通过将n个元素插入到最初为空的最大HBLT中来对其进行初始化,所需时间为 O(logn)。为得到具有线性时间的初始化算法,首先创建n个最大HBLT,每个树中仅包含 n 个元素中的某一个,这 n 棵树排成一个 FIFO队列,然后从队列中依次删除两个 HBLT,将其合并,然后再加入队列末尾,直到最后只有一棵 HBLT。

最大左高树的实现代码如下:

View Code
#include <iostream>
#include <queue>
using namespace std;
template <class T>
class MaxHBLT;
template <class T>
class HBLTNode{
    friend class MaxHBLT<T>;
    public:
    HBLTNode(const T& e,const int sh){
        data=e;
        s=sh;
        lchild=rchild=0;
    }
    private:
    int s;
    T data;
    HBLTNode<T>* lchild,*rchild;
};
template <class T>
class MaxHBLT{
    public:
        MaxHBLT(){ root=0; }
        ~MaxHBLT(){
            Free(root);
        }
        T Max(){
            if(!root)
                throw "OutofBounds";
            return root->data;
        }
        MaxHBLT<T>& Insert(const T& x);
        MaxHBLT<T>& DeleteMax(T& x);
        MaxHBLT<T>& Meld(MaxHBLT<T>& x){
            Meld(root,x.root);
            x.root=0;
            return *this;
        }
        void Initialize(T a[],int n);
        void Output(){
            Output(root);
            cout<<endl;
        }
    private:
        void Free(HBLTNode<T>* t);
        void Meld(HBLTNode<T>*& x,HBLTNode<T>* y);
        void Swap(HBLTNode<T>* &x,HBLTNode<T>* &y);
        void Output(HBLTNode<T>* x)const;
        HBLTNode<T>* root;
};
template <class T>
void MaxHBLT<T>::Free(HBLTNode<T>* t){
    if(t){
        Free(t->lchild);
        Free(t->rchild);
        delete t;
    }
}
template <class T>
void MaxHBLT<T>::Swap(HBLTNode<T>* &x,HBLTNode<T>* &y){
    HBLTNode<T>* t=x;
    x=y;
    y=t;
}
template <class T>
void MaxHBLT<T>::Output(HBLTNode<T>* x)const{
    if(x){
        cout<<"<"<<x->data<<","<<x->s<<"";
        Output(x->lchild);
        Output(x->rchild);
    }
}
template <class T>
void MaxHBLT<T>::Meld(HBLTNode<T>* &x,HBLTNode<T>* y){
    if(!y)
        return ;
    if(!x){
        x=y;
        return ;
    }
    if(x->data<y->data)
        Swap(x,y);
    Meld(x->rchild,y);
    if(!x->lchild){
        x->lchild=x->rchild;
        x->rchild=0;
        x->s=1;
    }
    else{
        if(x->lchild->s<x->rchild->s)
            Swap(x->lchild,x->rchild);
        x->s=x->rchild->s+1;
    }
}
template <class T>
MaxHBLT<T>& MaxHBLT<T>::Insert(const T& x){
    HBLTNode<T>* q=new HBLTNode<T>(x,1);
    Meld(root,q);
    return *this;
}
template <class T>
MaxHBLT<T>& MaxHBLT<T>::DeleteMax(T& x){
    if(!root)
        throw "OutofBounds";
    x=root->data;
    HBLTNode<T>*L=root->lchild;
    HBLTNode<T>*R=root->rchild;
    delete root;
    root=L;
    Meld(root,R);
    return *this;
}
template <class T>
void MaxHBLT<T>::Initialize(T a[],int n){
    queue<HBLTNode<T>*> qu;
    Free(root);
    for(int i=0;i<n;i++){
        HBLTNode<T>* x=new HBLTNode<T>(a[i],1);
        qu.push(x);
    }
    HBLTNode<T> *b,*c;
    for(int i=0;i<n-1;i++) {
        b=qu.front();
        qu.pop();
        if(!qu.empty())
            c=qu.front();
        qu.pop();
        Meld(b,c);
        qu.push(b);
    }
    if(n){
        root=qu.front();
        qu.pop();
    }
}
int main(){
    MaxHBLT<int> H, J;
    int a[5] = {791811};
    H.Initialize(a,5);
    cout << "One tree in postorder is" << endl;
    H.Output();
    int b[4] = {2649};
    J.Initialize(b,4);
    cout << "Other tree in postorder is" << endl;
    J.Output();
    H.Meld(J);
    cout << "After melding, the tree in postorder is" << endl;
    H.Output();
    int w, x, y, z;
    H.DeleteMax(w).DeleteMax(x).DeleteMax(y).DeleteMax(z);
    cout << "After deleting four elements, the tree is" << endl;
    H.Output();
    cout << "The deleted elements, in order, are" << endl;
    cout << w << "  " << x << "  " << y << "  " << z << endl;
    H.Insert(10).Insert(20).Insert(5);
    cout << "Leftist tree in postorder" << endl;
    H.Output();
    H.Insert(15).Insert(30).Insert(2);
    cout << "Leftist tree in postorder" << endl;
    H.Output();
    cout << "The max element is " << H.Max() << endl;
    H.DeleteMax(x);
    cout << "Deleted max element " << x << endl;
    cout << "Leftist tree in postorder" << endl;
    H.Output();
    H.DeleteMax(x);
    cout << "Deleted max element " << x << endl;
    cout << "Leftist tree in postorder" << endl;
    H.Output();
    while (true) {// empty out
        try {H.DeleteMax(x);
            cout << "Deleted " << x << endl;}
        catch (...) {break;}
    }
}
9.5 应用
9.5.1 堆排序
9.5.2 机器调度