堆排序的算法分析

堆排序算法分析

什么是堆

我们这里讨论的堆是一种数据结构,而不是垃圾收集存储机制。(二叉)堆一个数组,它可以被看成一个近似的完全二叉树,即一棵树上的每一个结点对应数组中的某一个元素,除了最底层外,该树是完全填满的,而且是从左往右填充。堆具有三个重要的属性,即父结点左孩子右孩子。堆又分为两种,即最大堆和最小堆,最大堆是指所有的父结点都要大于子结点,故根结点的“值”最大(这里堆中存储的不一定是数值,可以是任何一种对象,只要实现了相应的equals()方法即可,下文中将以简单的int数值类型为例来讨论和编程);而最小堆就相反,因而根结点最小。它们的性质可以用下面两个数学表达式来表示:

最大堆性质:A[PARENT(i)]>=A[i]

最小堆性质:A[PARENT(i)]<=A[i]

(注意:这里的i是除了根结点以外的所有结点)

如果把堆看成一棵树,可以定义一个堆中的结点的高度就是结点到叶结点最长简单路径上边的个数,因而可以把堆的高度定义为根结点的高度。故此,我们可以得到:

一个具有n个元素的堆的高度为logn;

高度为h的堆中,元素个数最多为2^(h+1)-1个,元素个数最少为2^h个。

 因此,我们可以联想到,最大堆通过将根元素提取到结尾几乎可以对应到降序排列,而最小堆通过将根元素提取到结尾几乎可以对应到升序排列。下面我们使用最大堆来讨论。

堆的元素处理

根据堆的三个重要的属性,我们可以得到父结点和子结点的获取方法,下面用C语言实现:

/*
	返回父节点和子节点索引
*/
//返回父结点
int parent(int index)
{
	return (index-1)/2;
}

//返回左孩子
int left(int index)
{
	return (index<<1)+1;
}

//返回右孩子
int right(int index)
{
	return (index+1)<<1;
}

除了获取三个属性之外,我们还应该获取堆的大小,即元素的个数,下面用C语言实现:

/*
	返回堆的大小,指针操作
*/
int heapSize(int *A)
{
	int count=0;
	while(*(A+count)!=‘\0‘) {
		count++;
	}
	return count;
}

堆的性质维护

由于要满足最大堆的性质,因此当添加一个新元素时,我们需要让它满足父结点大于子结点的性质,故堆的性质需要我们去维护,因而,当我们检查结点i是否满足要求时,我们应该检查结点i是否比它的孩子结点都要大,如果比它们大,则可以不用操作;反之,应该将该结点与孩子结点进行交换到新的结点上,进而再次检查该新结点是否满足要求,操作方法同上,直到该结点没有孩子结点为止。

       维护堆的性质基本思路如上所述,下面用C语言实现:

/*
	维护堆的性质,即把不符合要求的元素重新组织
	时间复杂度为logn
*/
void maxHeapify(int *A,int index,int size)
{
	int L=left(index);
	int R=right(index);
	int tmp;
	int largestIndex=index;
	if(L<size && *(A+index)<*(A+L)) {
		largestIndex=L;
	}
	if(R<size && *(A+largestIndex)<*(A+R)) {
		largestIndex=R;
	}
	if(index!=largestIndex) {
		tmp=*(A+index);
		*(A+index)=*(A+largestIndex);
		*(A+largestIndex)=tmp;
		maxHeapify(A,largestIndex);
	}
}

如上所示,维护堆的时间复杂度为堆的高度成正比,即logn。类似的,也可以编写一个minHeapify函数来实现最小堆。

堆的建立

       堆的建立即使堆的每个元素都能满足最大堆的性质的要求,因为叶子结点没有孩子结点,因而叶子结点无法调用maxHeapify函数,故每个叶子结点都可以看成一个只包含一个元素的堆。

       堆的建立可以使用以下一段C语言代码来实现:

/*
	对除没有子结点的结点进行堆的维护操作,进而
	使得一个数组变成一个最大堆
	时间复杂度为nlogn
*/
void buildMaxHeap(int *A)
{
	int size=heapSize(A);
	//找出不是叶子结点的最后一个索引
	int need=size/2-1;
	int i;
	for(i=need;i>=0;i--) {
		maxHeapify(A,i,size);
	}
}

类似的,可以编写一个buildMinHeap来建立最小堆。

堆排序算法

通过上面的步骤,我们已经把一个数组建立成为一个最大堆,然而最大堆并不一定是有序堆,因为最大堆只保证了父结点大于子结点,并没有保证左孩子一定大于右孩子,因此,堆排序就是要使左孩子大于右孩子。但是,我们始终相信,根元素是最大的,因此,我们想先找最大的比较方便,把最大的元素先扔到结尾的叶子结点上,而之前的叶子结点将跑到根结点上并且要满足堆的性质进行维护即可。

据此,我们可以设计如下算法来实现:

/*
	堆排序程序
	通过根元素始终是最大的,因此我们倒过来找
	从最大的元素依次往最小的元素去排序
*/
void heapSort(int *A)
{
	buildMaxHeap(A);
	int size=heapSize(A);
	int i;
	int tmp;
	for(i=size-1;i>=0;i--) {
		tmp=*A;
		*A=*(A+i);
		*(A+i)=tmp;
		size--;
		maxHeapify(A,0,size);
	}
}

因此堆排序的时间复杂度为nlogn。

堆排序算法的意义

堆排序是一种优秀的算法,后面我们会看到尽管快速排序的算法性能要优于堆排序,但是堆这一数据结构仍然有重要的实际应用——高校的优先队列,实际问题例如如何动态保留成绩在倒数100名的学生的信息。

时间: 2024-10-25 11:25:30

堆排序的算法分析的相关文章

堆排序算法分析

(1) 最大堆和最小堆(max_heap and min_heap) 注意:(二叉)堆是一个数组,数组的下标随机存取是从0开始的,<算法导论>上计算左右孩子节点的坐标是从1开始考虑的,与此略有不同!! 堆实际上是一棵完全二叉树,其任何一非叶节点满足性质: Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2] 即任何一非叶节点的关键字不大于或者不小于其

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

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

算法分析-堆排序 Heap Sort

堆排序的是集合了插入排序的单数组操作,又有归并排序的时间复杂度,完美的结合了2者的优点. 堆的定义 n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系之一时,称之为堆. 情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小顶堆) 情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大顶堆) 其中i=1,2,…,n/2向下取整; 若将和此序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终

算法分析与设计复习

算法分析与设计复习 2016年初,研一上学期期末考试前,复习并总结算法分析与设计科目的内容.复习过程参照<算法导论>中文第2版,同时参照PPT,章节划分根据PPT内容 概要: 第一章 概述 第二章 插入排序&分治策略 第三章 复杂度分析 第四章 堆与堆排序 第五章 快速排序 第六章 线性时间排序 第一章 概述 算法的应用范围 算法在诸如生物等诸多领域有其应用 算法的意义 算法在很多情况下让不可能完成的事情变成了可能,让处理的很慢的过程变快. 一个铺垫 一串不全为0的数,怎么取能拿到一段

算法----堆排序(heap sort)

堆排序是利用堆进行排序的高效算法,其能实现O(NlogN)的排序时间复杂度,具体算法分析可以点击堆排序算法时间复杂度分析. 算法实现: 调整堆: void sort::sink(int* a, const int root, const int end) { int i=root; while(2*i +1 <= end) { int k = 2*i+1; if(k+1<=end && a[k]<a[k+1]) k++; if(a[k] < a[i]) break;

最大(小)堆和堆排序简介

(注:本文的相关叙述和图片摘自<数据结构与算法分析新视角>(周幸妮等),因此本文只是我的一个复习记录,详细的论述请参考该书.) 1. 最大(小)堆 对于一个完全二叉树来说,如果所有的结点(叶子结点除外)的值都大于(小于)其左右孩子结点的值,那么这个完全二叉树就被成为一个大(小)根堆.如下图所示.按照堆的定义可以发现,堆顶结点(二叉树的根结点)一定对应整个序列中的最大(小)记录.这样一来,可以设计一种排序思路,每次将堆的堆顶记录输出,同时调整剩余的记录,使它们从新排成一个堆.重复这个过程,就能最

【算法导论】堆排序

(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树.二叉堆可以分为两种形式:最大堆和最小堆.若将记录按从大到小排列,建“小”顶堆.若将记录按从小到大排,建“大”顶堆. 说明:在堆排序算法中,我们使用的是最大堆,最小堆通常用于构造优先队列. 算法分析:时间复杂度是O(nlogn).堆排序属于原址排序:任何时候都只需要常数个额外的元素空间存储临时数据.堆排序是不稳定的排序算法. 1 #include <stdio.h> 2 #define LEFT(i) 2 * i 3 #define RIG

数据结构:堆排序

「仅为草稿,尚未详解」 堆排序(C语言版) 走进堆排序 什么是堆 堆实质就是一颗完全二叉树,其任何一非叶子节点满足下列性质. i=1,2,3...n/2 说明: 既然是完全二叉树,我们就可以用数组来表示! 堆根据上面的性质又分为: 从中不难发现,大顶堆从上往下依次键值减小,小顶堆从上向下键值增大. 什么是堆排序 ? 对一组待排序记录的关键字,首先把它们按堆的定义建成小(大)顶堆 ? 然后输出堆顶的最小(大)关键字所代表的记录,再对剩余的关键字建堆,以便得到次小(大)的关键字 ? 如此反复进行,直

《数据结构与算法分析—C语言描述》pdf

下载地址:网盘下载 内容简介 编辑 <数据结构与算法分析:C语言描述(原书第2版)>内容简介:书中详细介绍了当前流行的论题和新的变化,讨论了算法设计技巧,并在研究算法的性能.效率以及对运行时间分析的基础上考查了一些高级数据结构,从历史的角度和近年的进展对数据结构的活跃领域进行了简要的概括.由于<数据结构与算法分析:C语言描述(原书第2版)>选材新颖,方法实用,题例丰富,取舍得当.<数据结构与算法分析:C语言描述(原书第2版)>的目的是培养学生良好的程序设计技巧和熟练的算