您的位置:首页 > 其它

堆结构的运用

2016-07-29 00:00 337 查看
堆通常用来表示元素集合。小根堆的任何父结点的值都小于或等于其子结点的值。最底层的叶结点尽可能地靠左分布,树中不存在空闲的位置。堆可以用二叉树或一维数组来表示。用数组x[1...n]表示时,value(i)=x[i], leftchild(i)=2*i, rightchild(i)=2*i+1, parent(i)=i/2,根为x[1]。我们可能把堆定义为heap(t,u),对任意的2t<=i<=u有x[i/2]<=x[i]。
(1)堆的向上筛选:当x[1...n-1]是堆时,在x
中放置一个任意的元素后,x[1...n]可能不是堆,需要向上筛选(通过交换该结点与其父结点来实现),以建成堆heap(1,n)。

void siftup(n)
//前置条件:n>0 && heap(1,n-1)
//后置条件:heap(1,n)
i=n;
loop
if i==1
break;
p=i/2;
if x[p]<=x[i] //父结点小于子结点时,直接退出循环
break;
swap(p,i);  //否则交换两个结点的值
i=p;  //继续向上筛选,直到根结点


因为堆具有logn层,故运行时间为O(logn)。
(2)堆的向下筛选:当x[1...n]是堆时,给x[1]分配一个新值后x[2...n]仍是堆heap(2,n),但x[1...n]可能不再是堆,需要向下筛选(通过交换该结点与子结点),重新建成堆heap(1,n)。

void siftdown(n)
//前置条件:heap(2,n) && n>=0
//后置条件:heap(1,n)
i=1;
loop
c=2*i; //寻找其左子结点
if c>n  //若没有子结点,退出循环
break;
//让c指向左右子结点中的较小者
if c+1<=n && x[c+1]<x[c]
c++;
if x[i]<=x[c]  //父结点小于子结点时,直接退出循环
break;
swap(c,i);  //否则交换两个结点的值
i=c;  //继续向下筛选,直到根结点


算法的运行时间为O(logn)。
(3)堆排序:我们采用大根堆,只要把上面算法中的<和>运算互换即可。先扫描元素,通过不断地向上筛选来建立堆heap(1,n)。接着利用堆来建立有序序列。前段为未排序部分,后段为已排序部分。每建一次堆,堆顶就是未排序部分的最大元素,把它与未排序部分的末尾元素交换,这样已排序部分长度就增1。由于堆顶分配了一个新值,重新向下筛选以建成新堆。

void heapsort(n)
for(i=2;i<=n;i++)
siftup(i);
for(i=n;i>=2;i--)
swap(1,i);
siftdown(i-1);


算法使用了n-1次siftup和n-1次siftdown,故运行时间为O(nlogn)。
(4)优先级队列的实现:优先级队列的insert在集合中插入一个新元素,extractmin删除集合中最小的元素。当然我们可以用数组或链表之类的线性结构来实现,但用堆结构实现更高效。用x[1...n]表示n元集合,insert函数插入新元素时,n加1并将新元素放在x
处,由于满足heap(1,n-1),故向上筛选即可调整成新堆heap(1,n)。对extractmin函数,只要将x
移动到x[1],n减1,就删除了最小元x[1]。由于仍满足heap(2,n),故向下筛选即可调整成新堆heap(1,n)。两个函数的运行时间均为O(logn)。为简便,实现时我们集中于算法本身,没有考虑错误处理、资源析构、拷贝构造等问题。标准模板库中提供了优先级队列结构priority_queue。

template<typename T>
class priqueue{
private:
int n,maxsize;
T *x;
void swap(int i,int j){
T t=x[i];
x[i]=x[j];
x[j]=t;
}
public:
priqueue(int m){
maxsize=m;
x=new T[maxsize+1];
n=0;
}
void insert(T t){
int i,p;
x[++n]=t; //在末尾插入新元素
//插入元素后再向上筛选,以调整成小根堆
for(i=n;i>1 && x[p=i/2]>x[i];i=p)
swap(p,i);
}
T extractmin(){
int i,c;
T t=x[1];
x[1]=x[n--];  //把x
移动到x[1]处,即删除了最小元
//堆顶x[1]分配了一个新值后再向下筛选,以调整成小根堆
for(i=1;(c=2*i)<=n;i=c){
if(c+1<=n && x[c+1]<x[c])
c++;
if(x[i]<=x[c])
break;
swap(c,i);
}
return t; //返回最小元
}
};


(5)堆结构的一些应用例子:
构造哈夫曼编码:选择集合中的两个最小结点,将其归并为一个新结点。这可通过两次extractmin调用和一次insert调用来实现。如果输入是有序,则可在线性时间内完成。
计算大型浮点数集合的和:如果简单地把较小和较大的浮点数相加可能会丢失精度。较好的办法是每次把集合中最小的两个数相加,类似于构造哈夫曼编码的过程。
在存有10亿个数的文件中找出最大的100万个数:用一个含有一百万个元素的堆来存放结果,并采用多趟策略。先读取前100万个数到堆数组中,并建成堆,堆顶为最小元。后续每读一个数就跟堆顶元素比较,若小于堆顶元,就什么也不做;若大于堆顶元,就用这个数替换掉堆顶元素,并向下筛选重新调整成堆。然后读下一个数。
将多个较小的有序文件归并为一个较大的文件:用堆表示每个文件中的下一个元素,从而实现对有序文件的归并。迭代步骤从堆中选出最小的元素,将其后继插入堆中。n个文件中下一个待输出的元素可以在O(logn)时间内选出。
关键算法设计思想:堆结构、优先级队列、排序策略。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: