排序算法从入门到精通之八--堆排序

排序算法从入门到精通之八–堆排序

1. 堆排序简介

本文要介绍另一种排序算法,即堆排序。像归并排序(而不像插入顺序)一样,堆排序的运行时间为O(n*ln(n)).像插入排序(而不像归并排序)一样,它是一种原地排序算法,在任何时候,数组中只有常数个元素存储在输入数组以外。这个堆排序就把归并排序和插入排序两种排序算法的优点结合起来。

2. 堆的结构与表示

堆排序使用一个数组表示一个完全二叉树。这里,一个结点数为N的完全二叉树,其非叶子结点数为N/2,叶子结点数为N-(N/2)。

算法导论的伪代码中,数组的下标是从1开始的。若A[]表示一个数组,整个树的根结点放在A[1]。对于某个下标为i的结点,则这个结点的的双亲结点,子结点的下标具有如下关系
双亲结点的下标,PARENT(i)=i/2
左子结点的下标,LEFT(i)=i2
右子结点的下标,RIGHT(i)=i
2+1

C语言的下标从0开始,若采用上述表示法,则浪费一个元素的空间。这与这个系列的排序函数的接口不统一。故我们采用下述二叉树的表示法。

1.整个树的根结点放在A[0]
2. 对于下标为i的结点,其双亲结点和子结点的下标之间的关系如下
双亲结点的下标,PARENT(i)=(i-1)/2.
左子结点的下标,LEFT(i)=i2+1
右子结点的下标,RIGHT(i)=i
2+2
3. 若这个数组具有N个元素,则最后一个元素的下标是N-1,则其双亲结点的下标为(N-1-1)/2=N/2-1. 故最后一个非叶子结点的下标是N/2-1

3. 堆的性质和堆化

堆分为大顶堆和小顶堆。
对于大顶堆,堆中的每一个非叶子结点,其左子结点的和右子结点的值须不大于自身。
对于小顶堆,堆中的每一个非叶子结点,其左子结点的和右子结点的值须不小于自身。
容易看出,若堆中元素的值各不相同,则大顶堆的根结点的值是整个数组中最大的,而小顶堆的根结点的值是整个数组中最小的。
若数组中的元素不满足堆的性质,则需将某些结点与子结点交换,以满足堆的性质,这个过程叫做堆化

4. 堆排序算法

堆排序算法分为两步。
第一步,将整个数组堆化,若需增序排列,构建大顶堆,否则,构建小顶堆.
第二步,不断删除堆顶元素,放在堆的后面,并对剩下的元素堆化。如此往复,直至堆中仅为一个元素。
这一步可以这样理解,对于长度为N的数组,分为两部分,前半部分是一个大顶堆(或小顶堆),后半部分是一个有序数组.
开始时,前半部分长度为N,后半部分长度为0,随着排序过程的进行,前半部分(堆)的长度越来越短,而后半部分则越来越长.
当堆中元素仅为一个元素时,堆中那个元素必小于或者等于后半部分第一个元素,这样,整个数组都变成一个有序数组.

#include <stdio.h>
#include <stdlib.h>
#include "sorts.h"

#define PARENT(i) ((i)-1)/2)
#define LEFT(i)   ((i)*2+1)
#define RIGHT(i)  ((i)*2+2)
#define SWAP(T,a,b)  {T t=a; a=b; b=t; } 

//从i结点开始,做maxHeapify,这是一个递归函数
static void maxHeapify(ELE_TYPE A[],int i,int size) 
{
    int l = LEFT(i);      // i的左子结点
    int r = RIGHT(i);     // i的右子结点
    int maxNodeIdx = i;   // 值最大的那个结点的下标,初化为i
    
    if ( l<size && A[l] > A[maxNodeIdx])
        maxNodeIdx = l;

    if ( r<size && A[r] > A[maxNodeIdx])
        maxNodeIdx = r;

    if (maxNodeIdx != i)    // 最大值不是当前结点,交换之
    {
        SWAP(ELE_TYPE,A[i],A[maxNodeIdx]);
        maxHeapify(A, maxNodeIdx, size);  //继续对子结点做堆化操作,保证子树也是大顶堆
    }
}

// 使长度为n的数组A变为一个大顶堆。其时间复杂度为O(n)
static void buildMaxHeap(ELE_TYPE A[], int n)
{
    // 从最后一个非叶子节点开始,对每一个非叶子节点做maxHeapify
    int lastNonLeafNode=n/2-1;
    for (int i = lastNonLeafNode; i >= 0; i--)  
        maxHeapify(A, i, n);
}

//堆排序算法
void heap_sort(ELE_TYPE arr[], int len)  
{
    if (len<2)
        return ;

    buildMaxHeap(arr,len);  //将整个数组变成一个大顶堆
    int heapSize=len;
    do
    {
        // 将根节点(最大值)与堆的最后一个元素arr[heapSize-1]交换
        SWAP(ELE_TYPE,arr[0],arr[heapSize-1]);
        
        heapSize--;  // 堆(待排序数组)长度减一
        // 此时,arr[heapSize]变成有序数的的第一个元素

        maxHeapify(arr, 0, heapSize);  //保持堆结构
    }while (heapSize>1);
}

void test_heap_sort()
{
	ELE_TYPE arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
	int len; 
 
	printf("Original data are:");
	len = (int) sizeof(arr) / sizeof(arr[0]);
	print_array(arr, len);
 
	heap_sort(arr, len);
	printf("The data after sorted are:");
	print_array(arr, len);	
}

下一篇 排序算法从入门到精通之九–性能测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值