读书笔记之:数据结构,算法与应用(4)
与第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的实现代码如下:
#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标注。
[高度优先左高树] 当且仅当一棵二叉树的任何一个内部节点,其左孩子的 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。
最大左高树的实现代码如下:
#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 机器调度
模拟代码如下:
#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);
}
实现代码如下:
#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