算法导论------堆排序heapsort

来源:互联网 发布:大数据对旅游业的影响 编辑:程序博客网 时间:2024/04/30 14:41

目录

  • 1.堆排序概述
  • 2.二叉堆
  • 3.二叉堆的存储
  • 4.维护堆的性质
    • 4.1递归实现maxHeapify函数
    • 4.2非递归实现maxHeapify函数
  • 5.建堆操作
    • 5.1从下向上、从右向左建堆(C实现)
    • 5.2从上向下、从左向右建堆(C实现)
  • 6.堆排序
  • 7.堆排序C代码实现
  • 8.参考资料

1.堆排序概述

  简单选择排序是:假设排序序列为L[1...n],第i趟排序从L[i...n]中选择关键字最小(大)元素与L[i]交换,每一趟排序可以确定一个元素到最终的位置上,这样经过n1趟排序就可以使得整个排序序列有序。可惜的是,这样操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟的比较中已经做过了,因而比较的次数较多。
  如果每趟排序可以在找到最小(大)元素的同时,根据比较结果对其它记录做出调整(逆序交换),那么简单选择排序的效率就会大大提高。于是Floyd和Williams在1964年发明了Heap Sort(堆排序)方法,对简单选择排序进行改进,所以堆排序也是选择排序算法中的一种。
  堆排序是一种基于完全二叉树的树形选择排序 方法。在排序的过程中将待排序列看成是一颗完全二叉树的顺序存储结构,树上的每一个结点对应数组中的一个元素,可以利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序序列中构建“二叉堆”,简称“建堆”操作。从而在二叉堆的根部找到关键字最大(小)的元素。这种叫“堆”的数据结构可以保存每趟排序过程中的中间比较结果。堆按性质可分为大顶堆和小顶堆。如果想让序列按升序(降序)排序,就需要将待排序的n个元素构造成大顶堆(小顶堆)。此时堆顶即为最大值(最小值),将它和堆数组的尾元素交换,堆数组长度减1。然后将剩余的n-1个待排元素重新建堆,从而得到n个元素中的次大元素,将它和堆数组的尾元素交换,堆数组长度减1。反复迭代,最终得到一个有序的序列。
  堆排序具有空间原址性(in space),即任何时候都只需要常数个额外的元素空间存储临时数据。


2.二叉堆

  (二叉)堆是具有下列性质的完全二叉树:

每个结点的值L[i]都大于或等于其左孩子L[left(i)]和右孩子L[right(i)]结点的值,称为大顶堆(最大堆);
每个结点的值L[i]都小于或等于其左孩子L[left(i)]和右孩子L[right(i)]结点的值,称为小顶堆(最小堆);

堆 堆的性质 最大堆 L[PARENT(i)]>=L[i] 最小堆 L[PARENT(i)]<=L[i]

从二叉堆性质中,可以看到根节点一定是所有结点的最小(大)值。较小(大)的结点和根结点的距离较近。

逻辑结构


3.二叉堆的存储

  二叉堆在内存中是用数组存储的。
  如果堆的根结点为L[0]i结点的父结点下标就为(i1)/2i结点的左右子结点下标分别为(2i+1)(2i+2)。如下图(b)所示。

物理结构

  如果堆的根结点为L[1]i结点的父结点下标就为i/2i结点的左右子结点下标分别为(2i)(2i+1)。如下图(c)所示。

物理结构


4.维护堆的性质

  堆排序算法中,最关键的就是构造初始堆。需要编写一个维护大顶堆性质的函数Max_Heapify。当输入一个数组L和一个下标i,然后调用Max_Heapify时,比较L[i]L[left(i)]L[right(i)]三者的大小;如果L[i]小于其孩子结点,就违背了大顶堆的性质,让L[i]和较大的孩子结点交换,实现在大顶堆中“降级”,从而使L[i]结点遵循大顶堆的性质。但交换后,以L[i]为根的子树又有可能违反最大堆的性质,因此对该子树递归的调用Max_Heapify函数。如调用Max_Heapify(L,2)后的递归过程。

维护堆的性质
伪代码

4.1递归实现maxHeapify函数

void maxHeapify(int arr[],int i,int heapsize) {//递归实现    int left=2*i+1;    int right=2*i+2;    int largest;    if (left<=heapsize  && arr[left]>arr[i])        largest=left;        else            largest=i;    if (right<=heapsize  && arr[right]>arr[largest])        largest=right;    if(largest!=i) {        swap(&arr[largest],&arr[i]);        maxHeapify(arr,largest,heapsize);    }}

4.2非递归实现maxHeapify函数

void maxHeapify(int arr[],int i,int heapsize) {//非递归实现    int left=2*i+1;    int right=2*i+2;    int largest;    while(left<=heapsize) {        if (arr[left]>arr[i])            largest=left;        else            largest=i;        if (right<=heapsize  && arr[right]>arr[largest])            largest=right;        if(largest!=i) {            swap(&arr[largest],&arr[i]);            i=largest;        //从上向下维护堆性质;所以要更新根节点、左右节点的索引;            left=2*i+1;            right=2*i+2;        } else {            break;        }    }}

5.建堆操作

  对一个长度为length的数组arr[0...(length1)]而言,结点索引为(length1)的父亲结点的索引为i=((length1)1)/2。子数组arr[(i+1)...(length1)]中的元素都是叶结点。每个叶结点都可看成是只包含一个元素的堆。我们从索引为i的结点开始,从右向左、从下向上地调用maxHeapify,把数组arr[0...(length1)]转换成一个大顶堆。

这里写图片描述

5.1从下向上、从右向左建堆(C实现)

void buildMaxHeap(int arr[],int heapsize) {//实现1    for(int i=(heapsize-1)/2; i>=0; i--) {//从下向上,从右向左维护堆性质        maxHeapify(arr,i,heapsize);    }}

5.2从上向下、从左向右建堆(C实现)

  当数组仅有arr[0]时,不要执行操作,就是一个天然的大顶堆;然后把arr[1...(length1)]的元素逐个插入到堆的尾部,每插入一个元素,都要对已有的大顶堆进行维护。不再展开详解。仅给出代码实现。

void buildMaxHeap(int arr[],int heapsize) { //实现2    if(heapsize==0)        return;    for(int i=1;i<=heapsize;i++){        int index=i;        int parent=floor((index-1)/2);      //floor函数定义在<math.h>中;        while(parent>=0  && arr[parent]<arr[index]){            swap(&arr[parent],&arr[index]); //arr[0]元素父节点arr[-1],循环中断;            index=parent;                       parent=floor((index-1)/2);  //不加floor函数,可以把arr[0]的父节点看成是自身;        }    }}

6.堆排序

  将待排序的length个元素构造成大顶堆。此时堆顶arr[0]即为最大值,将它和堆数组的尾元素arr[length1]交换。然后将arr[length1]从堆中去掉,剩余待排元素重新建堆,从而得到次最大元素,将它和堆数组的尾元素交换。反复迭代,直到堆中元素的数目只剩一个,则必然是最小的元素。所以从arr[length1]遍历到arr[1]即可。最终得到一个有序的序列。


这里写图片描述

//从arr[length-1]到arr[1]依次调用buildMaxHeap,最终实现排序void heapSort(int arr[],int len) {    buildMaxHeap(arr,len-1);    for(int i=len-1; i>0; i--) {        swap(&arr[0],&arr[i]);        //buildMaxHeap(arr,i-1);此处无需从新建堆,从新维护堆性质即可        maxHeapify(arr,0,i-1);    }}

7.堆排序C代码实现

#include <stdio.h>#include <stdlib.h>int arrtest[100];void swap(int *a,int *b) {    int temp=*a;    *a=*b;    *b=temp;}void maxHeapify(int arr[],int i,int heapsize) {      //维护堆的性质,可以用非递归实现    int left=2*i+1;    int right=2*i+2;    int largest;    if (left<=heapsize  && arr[left]>arr[i])        largest=left;        else            largest=i;    if (right<=heapsize  && arr[right]>arr[largest])        largest=right;    if(largest!=i) {        swap(&arr[largest],&arr[i]);        maxHeapify(arr,largest,heapsize);    }}void buildMaxHeap(int arr[],int heapsize) {          //建堆    for(int i=(heapsize-1)/2; i>=0; i--) {        maxHeapify(arr,i,heapsize);    }}void heapSort(int arr[],int len) {                   //堆排序    buildMaxHeap(arr,len-1);    for(int i=len-1; i>0; i--) {        swap(&arr[0],&arr[i]);        //buildMaxHeap(arr,i-1);//此处无需从新建堆,从新维护堆性质即可        maxHeapify(arr,0,i-1);    }}void PrintArray(int arr[],int length) {             //打印数组,用于查看排序效果    printf("[");    for(int i=0; i<length; i++) {        if(i==length-1)            printf("%d]\n",arr[i]);        else            printf("%d ,",arr[i]);    }}int main() {    int num=0;    printf("请输入数组元素个数:");    scanf("%d",&num);    for(int i=0; i<num; i++) {                         //读入待排序数据        scanf("%d",&arrtest[i]);    }    printf("排序前数据为:");    PrintArray(arrtest,num);    heapSort(arrtest,num);    printf("排序后数据为:");    PrintArray(arrtest,num);    return 0;}

8.参考资料

  1. 《2013数据结构联考复习指导》 王道论坛  组编
  2. 《算法导论 (原书第3版)》  机械工业出版社
  3. 《大话数据结构》       清华大学出版社   程杰 著
1 0
原创粉丝点击