算法分析之——heap-sort堆排序

堆排序是一种原地排序算法,不使用额外的数组空间,运行时间为O(nlgn)。本篇文章我们来介绍一下堆排序的实现过程。
要了解堆排序,我们首先来了解一个概念,完全二叉树。堆是一种完全二叉树或者近似完全二叉树。什么是完全二叉树呢?百度百科上给出定义:完全二叉树:除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点。下面用两个小图来说明完全二叉树与非完全二叉树。(图片来自百度,大家可以忽略水印…)
完全二叉树
二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。

堆排序也是基于分治思想的,主要有以下三步:

  1. 初始化,从第一个非叶结点开始遍历,使以其为根的树为大根堆;
  2. 交换堆顶元素与堆尾元素,筛选出最大的值。调整新的堆为大根堆。
  3. 重复2,每次筛选出堆中的最大元素,堆排序完成。
    本次,我们举例的数组如下:数组长度length=10;
    待排序数组
    对应的堆结构:
    待排序的堆

从第一个非叶结点开始,建初堆

void BUILD_MAX_HEAP(int A[],int length)
{
	int i;
	for(i=((length/2)-1);i>=0;i--)//length/2-1,为第一个非叶结点
		MAX_HEAPIFY(A,i);
		
}

保持堆的性质

void MAX_HEAPIFY(int A[],int i)
{
	int l,r,largest,middle;
	l=LEFT(i);
	r=RIGHT(i);
	if(l<heap_size && A[l]>A[i])
		largest = l;
	else 
		largest= i;
	if(r<heap_size && A[r]>A[largest])
		largest = r;
	if(largest!=i)
	{
		middle=A[largest];
		A[largest]=A[i];
		A[i]=middle;
    	MAX_HEAPIFY(A,largest);

	}
}

堆排序的具体实现

void heap_sort(int A[],int length)
{
	BUILD_MAX_HEAP(A,length);

	int i,middle;
	for(i=length-1;i>0;i--)
	{
		middle=A[0];
		A[0]=A[i];
		A[i]=middle;
		heap_size--;
		MAX_HEAPIFY(A,0);

	}	
}

下面为程序执行的简单的过程,分析不够全面,但是足以说明问题。
程序的图

1.分析步骤4中的for循环,BUILD_MAX_HEAP(A,length);即建初堆的过程。i从length/2-1循环到0,即从4循环到0。(4为第一个非叶结点)
(1)i=4;i=4MAX_HEAPIFY(A,i);MAX_HEAPIFY(A,4);
<1>计算左叶子节点的编号l=LEFT(i)=(2i+1)=9; 计算右叶子节点的编号r=RIGHT(i)=(2i+2)=10;
注:此处计算左右叶子节点的编号时,要注意数组是从0还是从1开始的;若从0开始,左叶子节点为(2i+1),右叶子节点为(2i+2);若从1开始,左叶子为2i;右叶子为2i+1

<2>判断左右叶子节点与根节点的大小,将其中节点编号的较大值赋值给largest;
heap_size为堆的大小,开始heap_size=length=9

if(l<heap_size && A[l]>A[i])
		largest = l;
	else 
		largest= i;
	if(r<heap_size && A[r]>A[largest])
		largest = r;

当i=4时,largest=4

<3>判断largest是否等于根节点,若不为根节点,说明其中左叶节点或者右叶节点比根节点的值大,则此时交换根节点与largest节点的值。

if(largest!=i)
	{
		middle=A[largest];
		A[largest]=A[i];
		A[i]=middle;
    	MAX_HEAPIFY(A,largest);

	}

因为此处largest=i,因此不执行这一步,执行下一次for循环

(2)i=3;i=3MAX_HEAPIFY(A,i);MAX_HEAPIFY(A,3);
<1>计算左叶子节点的编号l=LEFT(i)=(2i+1)=7; 计算右叶子节点的编号r=RIGHT(i)=(2i+2)=8;

<2>判断左右叶子节点与根节点的大小,将其中节点编号的较大值赋值给largest;
largest=7

<3>判断largest是否等于根节点,若不为根节点,说明其中左叶节点或者右叶节点比根节点的值大,则此时交换根节点与largest节点的值。交换

<4>执行MAX_HEAPIFY(A,largest);MAX_HEAPIFY(A,7);将以largest为根的树调整为大根堆

(3)i=2;i=2步骤与2中的<1>~<4>相同。largest=6,发生交换。此处不再分析。
l=6

(4)i=1;时分析过程参考步骤与2中的<1>~<4>。执行后的结果i1

(5)i=0;i=0

2.步骤6中的for循环分析,即筛选出最大的值,缩小堆的规模,保持堆的性质的过程。
length=10,i从length-1到1,即从9循环到1

for(i=length-1;i>0;i--)
	{
		middle=A[0];
		A[0]=A[i];
		A[i]=middle;
		heap_size--;
		MAX_HEAPIFY(A,0);

	}	

(1)i=9;交换A[i]与A[0],此时i是堆的最末的那个元素,A[0]是堆顶元素,即最大的元素,将最大元素交换到堆尾。并且堆的规模缩小一个,即此时待重新排序的堆是红框框起来的部分i9,此时执行MAX_HEAPIFY(A,0);上面已经分析,此处不再赘述。这里写图片描述

(2)i=8;交换A[i]与A[0];heap_size–;MAX_HEAPIFY(A,0);这里写图片描述

(3)下面的循环不再举例,我们可以看出,每次都筛选出当前堆中最大的元素。

3.最后给出程序运行的截图:
结果

程序源代码下载地址:堆排序实现代码

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值