堆排序算法——Java实现

堆排序

1、堆

    如图6-1所示,(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。表示堆的数组A包括两个属性:A.length(通常)给出数组元素的个数,A.heap-size表示有多少个堆元素存储在该数组中。也就是说,虽然A[1..A.length]可能都存有数据,但只有A[1..A.heap-size]中存放的是堆的有效元素,这里,0<=A.heap-size<=A.length。树的根节点是A[1],这样给定一个结点的下标i,我们很容易计算得到它的父结点、左孩子和右孩子的下标:

PARENT(i)
<span style="white-space:pre">	</span>return『i/2』

LEFT(i)
<span style="white-space:pre">	</span>return 2i
RIGHT(i)
<span style="white-space:pre">	</span>return 2i+1

    在大多数计算机上,通过将i的值左移一位,LEFT过程可以在一条指令内计算出2i。采用类似方法,在RIGHT过程中可以通过将i的值左移1位并在低位加1,快速计算得到2i+1.至于PARENT过程,则可以通过把i的值右移1位计算得到『i/2』。在堆排序的实现中,这三个函数通常是以“宏”或者“内联函数”的方式实现的。

二叉堆可以分为两种形式:最大堆和最小堆。在这两种堆中,结点的值都要满足堆的性质,但一些细节定义则有所差异。在最大堆中,最大堆性质是指除了根以外的所有结点i都要满足:

A[PARENT(i)]>=A[i]

也就是说,某个结点的值至多与其父结点一样大。因此,堆中的最大元素存放在根节点中;并且,在任一子树中,该子树所包含的所有结点的值都不大于该子树根结点的值。

下面我们开始介绍一些基本过程,并说明如何在排序算法中应用它们。

  • MAX-HEAPIFY过程:其时间复杂度为O(lgn),它是维护最大堆性质的关键。
  • BUILD-MAX-HEAP过程:具有线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆。
  • HEAPSORT过程:其时间复杂度为O(lgn),功能是对一个数组进行原址排序。
  • MAX-HEAP-INSERT、HEAP-EXTRACT-MAX、HEAP-INCREASE-KEY和HEAP-MAXIMUM过程:时间复杂度为O(lgn),功能是利用对实现一个优先队列。

2、维护堆的性质

MAX-HEAPIFY是用于维护最大堆性质的重要过程。它的输入为一个数组A和一个下标i。在调用MAX-HEAPIFY的时候,我们假定根结点为LEFT(i)和RIGHT(i)的二叉树都是最大堆,但这时A[i]有可能小于其孩子,这样就违背了最大堆的性质。MAX-HEAPIFY通过让A[i]的值在最大堆中“逐级下降”,从而使得以下标i为根结点的子树重新遵循最大堆的性质。

MAX-HEAPIFY(A,i)
<span style="white-space:pre">	</span>l=LEFT(i)
<span style="white-space:pre">	</span>r=RIGHT(i)
<span style="white-space:pre">	</span>if l<=A.heap-size and A[l]>A[i]
<span style="white-space:pre">		</span>largest=l
<span style="white-space:pre">	</span>else largset=l
<span style="white-space:pre">	</span>if r<=A.heap-size and A[r]>A[largest]
<span style="white-space:pre">		</span>largest=r;
<span style="white-space:pre">	</span>if largest!=i
<span style="white-space:pre">		</span>exchange A[i] with A[largest]
<span style="white-space:pre">		</span>MAX-HEAPIFY(A,largest)

    下图6-2图示了MAX-HEAPIFY的执行过程。在程序的每一步中,从A[i]、A[LEFT(i)]和A[RIGHT(i)]中选出最大的,并将其下标存储在largest中。如果A[i]是最大的,那么以i为根结点的子树已经是最大堆,程序结束。否则,最大元素是i的某个孩子结点,则交换A[i]和A[largest]的值。从而使i及其孩子都满足最大堆的性质。在交换后,下标为largest的结点的值是原来的A[i],于是以该结点为根的子树又有可能会违反最大堆的性质。因此,需要对该子树递归调用MAX-HEAPIFY。


3、建堆

    我们可以用自底向上的方法利用过程MAX-HEAPIFY把一个大小为n=A.length的数组A[1..n]转换为最大堆。我们知道,子数组A(『n/2』+1..n)中的元素都是树的叶结点。每个叶结点都可以看成只包含一个元素的堆。过程BUILD-MAX-HEAP对树中的其他结点都调用一次MAX-HEAPIFY。

BUILD-MAX-HEAP(A)
<span style="white-space:pre">	</span>A.heap-size=A.length
<span style="white-space:pre">	</span>for i=[A.length/2] downto 1
<span style="white-space:pre">		</span>MAX-HEAPIFY(A,i) 
   图6-3给出了BUILD-MAX-HEAP过程的一个例子。

4、堆排序算法

    初始时候,堆排序算法利用BUILD-MAX-HEAP将输入数组A[1..n]建成最大堆,其中n=A.length。因为数组中的最大元素总在根结点A[1]中,通过把它与A[n]进行互换,我们可以让该元素放到正确的位置。这时候,如果我们从堆中去掉结点n(这一操作可以通过减少A.heap-size的值来实现),剩余的结点中,原来根的孩子结点仍然是最大堆,而新的根结点可能会违背最大堆的性质。为了维护最大堆的性质,我们要做的是调用MAX-HEAPIFY(A,1),从而在A[1..n-1]上构造一个新的最大堆。堆排序算法会不断重复这一过程,直到堆的大小从n-1降到2。

HEAPSORT(A)
<span style="white-space:pre">	</span>BUILD-MAX-HEAP(A)
<span style="white-space:pre">	</span>for i=A.length downto 2
<span style="white-space:pre">		</span>exchange A[i] with A[i]
<span style="white-space:pre">		</span>A.heap-size=A.heap-size - 1
<span style="white-space:pre">		</span>MAX-HEAPIFY(A,1)
     图6-4给出了一个在HEAPSORT的第1行建立初始最大堆之后,堆排序操作的一个例子。图6-4显示了第2~5行for循环第一次迭代开始前最大堆的情况和每一次迭代之后最大堆的情况。

5、Java代码实现

后续补充。。。。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章