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

目录


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)/2 i 结点的左右子结点下标分别为(2i+1) (2i+2) 。如下图(b)所示。

物理结构

  如果堆的根结点为 L[1] i 结点的父结点下标就为i/2 i 结点的左右子结点下标分别为(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. 《大话数据结构》       清华大学出版社   程杰 著
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
堆排序是一种常见的排序算法,具有稳定性高、效率高等优点。下面介绍一下使用Java实现的小根堆排序。 首先,我们需要定义一个小根堆类,用于存储待排序的数据。该类需要包含以下几个方法: 1. `Heap()`:构造函数,用于初始化小根堆。 2. `insert(int val)`:插入操作,将一个新的元素插入到小根堆中。 3. `deleteMin()`:删除操作,删除小根堆中的最小元素,并返回该元素的值。 4. `size()`:获取小根堆中元素的个数。 5. `isEmpty()`:判断小根堆是否为空。 接下来,我们就可以使用小根堆对待排序的数据进行排序了。具体的步骤如下: 1. 将待排序的数据存入小根堆中。 2. 依次从小根堆中删除最小元素,并将其存入数组中。 3. 最后,将数组反转,即可得到排序后的结果。 下面是具体的Java代码实现: ```java public class HeapSort { public static void heapSort(int[] arr) { Heap heap = new Heap(arr.length); for (int i = 0; i < arr.length; i++) { heap.insert(arr[i]); } for (int i = 0; i < arr.length; i++) { arr[i] = heap.deleteMin(); } // 反转数组 reverse(arr); } // 反转数组 private static void reverse(int[] arr) { int left = 0; int right = arr.length - 1; while (left < right) { int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } } // 小根堆类 static class Heap { private int[] heap; private int size; public Heap(int capacity) { heap = new int[capacity + 1]; size = 0; } public void insert(int val) { if (size == heap.length - 1) { throw new RuntimeException("Heap is full"); } int i = ++size; while (i != 1 && val < heap[i / 2]) { heap[i] = heap[i / 2]; i /= 2; } heap[i] = val; } public int deleteMin() { if (isEmpty()) { throw new RuntimeException("Heap is empty"); } int min = heap[1]; int last = heap[size--]; int i = 1; int child = 2; while (child <= size) { if (child < size && heap[child] > heap[child + 1]) { child++; } if (last > heap[child]) { heap[i] = heap[child]; i = child; child *= 2; } else { break; } } heap[i] = last; return min; } public int size() { return size; } public boolean isEmpty() { return size == 0; } } } ``` 使用该算法对数组进行排序: ```java int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; HeapSort.heapSort(arr); System.out.println(Arrays.toString(arr)); // 输出 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值