与第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的实现代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
#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 char* const red= " \033[0;40;31m ";
const char* const green= " \033[0;40;32m ";
const char* const 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函数来建立一个堆,并且和我们自己的代码进行测试比较:
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.3节的堆结构是一种隐式数据结构( implicit data structure) ,用完全二叉树表示的堆在数组中是隐式存贮的(即没有明确的指针或其他数据能够重构这种结构)。由于没有存贮结构信息,这种描述方法空间利用率很高,事实上没有空间浪费。尽管堆结构的时间和空间效率都很高,但它不适合于所有优先队列的应用,尤其是当需要合并两个优先队列或多个长度不同的队列时。因此需要借助于其他数据结构来实现这类应用,左高树(leftist tree)就能满足这种要求。
考察一棵二叉树 ,它有一类特殊的节点叫做外部节点( external node), 用来代替树中的空子树,其余节点叫做内部节点(internal node)。增加了外部节点的二叉树被称为扩充二叉树(extended binary tree),图9-6a 给出了一棵二叉树,其相应的扩充二叉树如图 9-6b 所示,外部节点用阴影框表示,为了方便起见,这些节点用 a~f标注。
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。
最大左高树的实现代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
#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] = { 7, 9, 1, 8, 11};
H.Initialize(a, 5);
cout << " One tree in postorder is " << endl;
H.Output();
int b[ 4] = { 2, 6, 4, 9};
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.1 堆排序
9.5.2 机器调度
模拟代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class JobNode{
friend void LPT(JobNode*, int, int);
friend int main();
public:
operator int() const {
return time;
}
int ID,time;
};
class MachineNode{
friend void LPT(JobNode*, int, int);
public:
operator int() const{
return avail;
}
private:
int ID,avail;
};
// template <class T>
// void LPT(T a[],int n,int m){
void LPT(JobNode a[], int n, int m){
if(n<=m){
cout<< " Schedule one job per machine. "<<endl;
return ;
}
sort(a,a+n);
priority_queue<MachineNode,vector<MachineNode>,greater<MachineNode> > pq;
MachineNode x;
for( int i= 1;i<=m;i++){
x.avail= 0;
x.ID=i;
pq.push(x);
}
for( int i=n- 1;i>= 0;i--){
x=pq.top();
cout<< " Schedule job "<<a[i].ID<< " on machine "<<x.ID<< " from "<<x.avail<< " to "<<(x.avail+a[i].time)<<endl;
pq.pop();
x.avail+=a[i].time;
pq.push(x);
}
}
int main(){
const int n = 10;
JobNode a[n];
for ( int i = 0; i < n; i++) {
a[i].time = 2 * i * i;
a[i].ID = i+ 1;
}
LPT(a,n, 3);
}
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
#include <vector>
#include <queue>
#include <cstdlib>
#include <algorithm>
#include " BinaryTree.h "
using namespace std;
template < class T>
class Huffman{
friend BinaryTree< int> HuffmanTree(T a[], int);
public:
operator T() const {
return weight;
}
// private:
BinaryTree< int> tree;
T weight;
};
template < class T>
BinaryTree< int> HuffmanTree(T a[], int n){
vector<Huffman<T> > w(n);
BinaryTree< int> z,zero;
for( int i= 0;i<n;i++){
z.MakeTree(i,zero,zero);
w[i].weight=a[i];
w[i].tree=z;
}
priority_queue<Huffman<T>,vector<Huffman<T> >, greater<Huffman<T> > > H(w.begin(),w.end());
Huffman<T> x,y;
while(H.size()> 1){
// for(int i=1;i<n;i++){
x=H.top();
H.pop();
if(!H.empty()) {
y=H.top();
H.pop();
}
z.MakeTree( 0,x.tree,y.tree);
x.weight+=y.weight;
x.tree=z;
H.push(x);
}
x=H.top();
return x.tree;
}
int main(){
const int n = 10;
int a[n];
BinaryTree< int> x;
for ( int i = 0; i < n; i++)
a[i] = 2*(i+ 1);
x = HuffmanTree(a,n);
x.PostOutput();
}
这儿有一个介绍huffman编码的:http://coolshell.cn/articles/7459.html
这儿介绍huffman编码的扩展:http://www.cnblogs.com/xkfz007/archive/2012/08/30/2664220.html