首页 > 代码库 > 排序之堆排序
排序之堆排序
一、什么是堆?
1. 定义:二叉堆数据结构是一种数组对象,它通常可视为一棵完全二叉树。这样讲估计比较抽象,来看下它的逻辑定义:有一个包含n个元素的数组A[n]={k1,k2...ki...kn},当且仅当满足下列关系时称之为堆:
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)。
通俗来讲,即一个数组中的元素之间存在如上的关系,就是一个堆。
2. 二叉堆的分类
二叉堆有两种:最大堆和最小堆。在最大堆中,最大堆特性是指对于每个结点i,都有父结点的值大于等于其所有子结点的值;反之,在最小堆中,父结点的值应小于等于子结点的值。
根据最大堆和最小堆的定义,可从中发现:在最大堆中,根结点为所有结点中值最大的点,最小堆中根结点为所有结点中值最小的点。在堆排序算法中,一般使用最大堆,最小堆通常在构造优先队列时使用。
二、建立堆
在堆排序算法中,首先需要将一个乱序的数组建成一个堆,然后在堆的基础上进行排序。我们以待排序数组A[7] = {1,3,4,6,2,9,7}为例,将其建成一个最大堆。我们以完全二叉树的形式将整个过程表示出来,应该会更加直观。
数组的初始状态(圈外编号为该结点在数组中所处的下标,圈内值为该结点的值):
观察这个完全二叉树,要使这个完全二叉树的每个父结点都要大于或等于其所有子结点,我们该从哪个结点开始入手呢?乍一看,感觉有种触一发而动全身的感觉,无从下手啊!在此,可借鉴算法中常用的一种思想,先局部在整体。要使整个二叉树满足条件最大堆的条件,那么就必须使该树中的任意子树都要满足这个条件。我们以下面这个子树为例:
父结点的值为1,小于其所有子结点,因此,需要将其与子结点置换。可以发现,结点1和2的值都要大于1,那么置换哪个结点呢?当然置换值较大的那个啦,不然将值小的置换上去依然不能满足堆的条件啊!置换完后二叉树变为:
大家可以发现,置换之后又出现了情况,虚线区域内的子树不满足最大堆的要求,那么就得按照上面的要求再对该子树进行置换。
到此,是不是有点感觉了?这是不是有点递归的意思了?看下代码:
|
上面的代码主要解决针对的是树中的一个结点,对于整个树,则需要遍历树中的每个结点(一般自底向上),代码如下:
|
这样,整个最大堆就建好了!
为了更加清楚的表示这个过程,还是用二叉树表示下吧!
步骤一:根据代码可知,从底向上(从数组的角度看,是从数组的尾部向首部移动),即从结点6开始。由于其没有子结点,因此不做任何操作。以此类推,实际的工作从结点2开始,显而易见,结点2需要和其子结点置换。
步骤二:遍历到结点1,依然需要置换。
步骤三:遍历到结点0,依然需要置换。
步骤四:由于结点2的值发生了改变,需要查看该子树,发现需要置换。
此时,发现没有需要置换的结点,最大堆建造完毕!
二、堆排序
堆建好之后,对堆进行从小到大的排序就变得很简单了。第一次建堆之后,数组中元素变为:
每次建堆之后,数组中元素0都为最大值,因此,将元素0和元素6对调,则数组变为:
此时,元素0~5组成的数组不是最大堆,则重新对这六个元素建堆,又得到这六个元素中的最大值,将元素0与元素5对调,以此类推,直到只剩一个元素建堆,即可得到一个从小到大的有序数组。代码如下:
|
测试方法:
|
排序之堆排序