您的位置:首页 > 理论基础 > 数据结构算法

第4篇 堆排序

2017-09-11 20:24 162 查看
优先队列:删除最大的元素和插入元素。优先队列的使用和队列(删除最老的元素)以及栈(删除最新的元素)类似。

1)基于二叉堆数据结构的一种优先队列的经典实现方法,用数组保存元素并按照一定条件排序,以实现高效地(对数级别)删除最大元素和插入元素操作。

2)一种名为堆排序的重要排序算法来自于基于堆排序的优先队列的实现。

问题:输入N个字符串,每个字符串都对应着一个整数,任务找到最大的(或者最小的)M个整数。

解答:1)将输入排序然后从中找到M个最大的元素。时间NlogN,空间N。

2)将每个新的输入和已知的M个最大元素比较,但除非M较小,否则这种比较代价会很高昂。时间MN,空间N。

3)调用堆实现的优先队列,时间NlogM,空间M。

二叉堆(The binary heap):可以很好地实现优先队列的基本操作。

堆有序:当一颗二叉树的每个结点都大于等于它的两个子结点时。

二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不使用数组的第一个位置)。用它们将能够实现对数级别的插入元素和删除最大元素的操作。

堆的算法:

1)由下至上的堆有序化(上浮)

void swim(int k){
while(k >1 && a[k/2] < a[k]){
swap(a[k/2],a[k]);
k = k/2;
}
}


2) 由上至下的堆有序化(下沉)

void sink(int k){
while(2*k <= N){
int j = 2*k;
if(j < N && a[j] < a[j+1]) j++;
if(a[k] >= a[j]) break;
swap(a[k],a[j]);
k = j;
}
}


3)插入元素:将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置。

void insert(int v){
a[++N] = v;
swim(N);
}


4)删除最大元素:把数组顶端删除最大的元素,并将数组的最后一个元素放到顶端,减小堆并让这个元素下沉到合适的位置。

void delMax(){
int max = a[1];
swap(a[1],a[N--]);
a[N+1] = NULL;
sink(1);
return max;
}


优先队列由一个基于堆的完全二叉树表示时,对于一个含有N个元素的基于堆的优先队列,插入元素操作只需不超过(lgN+1)次比较,删除最大元素的操作需要不超过2lgN次比较。

另外可以构建3叉堆,n叉堆,一样的原理。

堆排序:

堆排序可以分为两个阶段:①在堆的构造阶段中,我们将原始数组重新组织安排进一个堆中。②然后在下沉排序阶段,我们从堆中按递减顺序取出所有元素并得到排序结果。

1)堆的构造

方法一:NlogN成正比的时间。从坐至右遍历数组,用swim()保证扫描指针左侧的所有元素已经是一颗堆有序的完全数即可。

方法二:从右至左用sink()函数构造子堆。因为如果一个结点的两个子结点都已经是堆了,那么在该结点上调用sink()可以将它们变成一个堆。因为堆提供了一种从未排序部分找到最大元素的有效方法。

void sort(int *a,int N)
{
for (int k = N/2; k >= 1; k--)
sink(a, k, N);
while (N > 1)
{
swap(a[1],a[N--]);
sink(a, 1, N);
}
}


2)下沉排序

堆排序的主要工作都是在第二阶段完成的。这里将堆中的最大元素删除,然后放入堆缩小后数组中空出的位置。

3)先下沉后上浮

在下沉中总是直接提升较大的子结点直到到达堆底,然后再使元素上浮到正确的位置。

堆排序的重要地位

时间和空间的使用都保证使用~2NlgN比较和常数。

当空间非常紧时(例如,在嵌入式中系统或低成本移动设备)它很受欢迎因为它可以被实现只有几十行(即使是机器代码),同时仍然提供最优性能。

然而,它很少用于现代系统的典型应用程序中,因为它的缓存性能很差。

完整测试代码

#include <iostream>
using namespace std;

using Fp = void(*)(int *a, int count);
//堆排序统一接口
//注意:堆排序中,数组的第一个位置不用
void HeapSort(int *a, int count);
//下沉子程序
void Sink(int *a, int k, int count);
//交换子程序
void MySwap(int &a, int &b);

const int Cnt = 11;

int main(int argc, char *argv[])
{

int a[Cnt] = { 0, 23, 1, 44, 54, 76, 782, 23, 12, 43, 34 };

cout << "排序前:" << endl;
for (int i = 0; i < Cnt; ++i){
cout << a[i] << " ";
}
cout << endl;

Fp fp = HeapSort;
fp(a, Cnt);

cout << "排序后:" << endl;
for (int i = 0; i < Cnt; ++i){
cout << a[i] << " ";
}
cout << endl;

return 0;
}

void MySwap(int &a, int &b){
int tmp = a;
a = b;
b = tmp;
}

void HeapSort(int *a, int count){
count--;
//堆的构造阶段
for (int k = count / 2; k >= 1; k--)
Sink(a, k, count);
while (count > 1){
MySwap(a[1], a[count--]);
Sink(a, 1, count);
}
}

void Sink(int *a, int k, int count){
while (2 * k <= count){
int j = 2 * k;
if (j < count&&a[j] < a[j + 1]) j++;
if (a[k] >= a[j]) break;
MySwap(a[k], a[j]);
k = j;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息