首页 > 代码库 > 排序算法之堆排序

排序算法之堆排序

3. 堆排序

      已经介绍过了两种相对简单的排序算法(插入排序、合并排序)之后,我们将介绍一种稍微难一些的排序算法——堆排序(heapsort)。

      堆排序在运行时间上与合并排序相似,同时又是一种原地(in place)排序算法(在任何时候,数组中只有常数个元素存储在输入数组以外),结合了插入排序和合并排序两种排序算法的优点。

 

3.1 堆

      (二叉)堆数据结构是一种数组对象,它可以被视为一棵完全二叉树。数中每个节点与数组中存放该节点值的那个元素对应(如图3-1,3-2所示)。



         一个最大堆可以看作一棵二叉树(如图3-1所示),也可以看作一个数组(如图3-2所示)。

  • 二叉树中:

      圆圈中的数字表示数中每个节点存储的值,节点上方的数字表示对应的数组下标。

  •  数组中:

      数组上下的连线表示父子关系,且父节点总是在子节点的左边。

 

这里需要简单的补充一些知识:

      最大堆,堆中的最大元素存放在根节点中,并且,在以某一个节点为根的字数中,各节点的值都不大于该子树根节点的值。图3-1所示的二叉树就可以称之为最大堆。

 

3.2 建堆

      建堆是指将“原数组所等价的堆”转换为“最大堆”的过程。

      从最后一个子树(即从以标号为i/2的节点为父节点的子树)开始,自底向上地建立最大堆。以原数组为{4,1,3,2,16,9,10,14,8,7}为例,其具体过程如图3-3所示。


      过程详细解释如下(若了解过程,可跳过此段):

      A):一个包含10个元素的数组,将其表示为堆的形式。从父节点标号为5(10/2=5)的子树开始,自底向上构建最大堆。由于16>7,故此时该子树符合最大堆性质,故不做修改。然后,将目标父节点标号减一变为4,即对父节点标号为4的子树构建最大堆。

      B):对于父节点标号为4的子树,由于14>8>2,故将节点标号为4(其值为2)与节点标号为8(其值为14)的值互换。值互换之后,父节点标号为4的子树符合最大堆性质,故此子树的最大堆构建完成。然后,将目标父节点标号减一变为3,即对父节点标号为3的子树构建最大堆。

      C):对于父节点标号为3的子树,由于10>9>3,故将节点标号为3(其值为3)与节点标号为7(其值为10)的值互换。值互换之后,父节点标号为3的子树符合最大堆性质,故此子树的最大堆构建完成。然后,将目标父节点标号减一变为2,即对父节点标号为3的子树构建最大堆。

      D):对于父节点标号为2的子树,由于16>14>1,故将节点标号为2(其值为1)与节点标号为5(其值为16)的值互换。与之前不同的是,此时值互换之后,父节点标号为2的子树并不满足最大堆性质,故将目标父节点变换至标号为5的节点。由于7>1,故将节点标号为5(其值为1)与节点标号为10(其值为7)的值互换。值互换之后,父节点标号为2的子树满足最大堆性质。故继续如C中所示,将目标父节点变为1,即对父节点标号为1的子树(即整个二叉树)构建最大堆。


      E):对于父节点标号为1的子树,由10>4>1,故将节点标号为1(其值为4)与节点标号为2(其值为16)的值互换。与D中相同,值互换之后,父节点标号为1的子树并不满足最大堆性质,故将目标父节点变化至标号为2的节点。由于14>7>4,故将节点标号为2(其值为4)与节点标号为4(其值为14)的值互换。值互换之后,父节点标号为1的子树仍然不满足最大堆特征,故将目标父节点变换至标号为4的节点。由于8>4>2,故将节点标号为4(其值为4)与节点标号为9(其值为8)的值互换。值互换之后,父节点标号为1的子树(即整个二叉树)满足最大堆性质。


         至此,建立最大堆的过程完成。


3.3 堆排序

      对于数组A[1]……A[n],通过构建最大堆可知,数组中最大的元素在A[1],则可以通过把A[1]与A[n]互换,使该数字达到最终正确的位置。现在,如果从堆中“去掉”节点n,则可以较容易地构建数组A[1]……A[n-1]的最大堆(即只需要重新构建目标父节点标号为1的子树)。此后不断重复这一过程,直至堆的大小降为1,此时,即完成了最终的排序过程。其具体过程如图3-6所示。


      以a)到b)的转换过程为例,对该过程进行细部详解,如图3-7所示。



C代码实现如下:

#include <stdio.h>

//求取父节点的标号
int parent(int index){
	return (index-1)/2;
}
//求取左子节点的标号
int left(int index){
	return (2*index+1);
}
//求取右子节点的标号
int right(int index){
	return (2*index+2);
}
//通过地址传递的方式,交换两数字的值
void exchange(int &a, int &b){
	int temp = a;
	a = b;
	b = temp;
}
//建立最大堆子树
void maxHeapify(int a[], int index, int length){
	int indexLeft = left(index);
	int indexRight = right(index);
	int largest = index;
	//若存在左子节点,且左子结点的值大于其父节点的值,则将最大值对应的标号变为左子结点标号
	if( (indexLeft<length)&&(a[indexLeft]>a[index]) ){
		largest = indexLeft;
	}
	//若存在右子节点,且右子节点的值大于其父节点的值,则将最大值对应的标号变为右子结点标号
	if( (indexRight<length)&&(a[indexRight]>a[largest]) ){
		largest = indexRight;
	}
	//若父节点的值不是最大值,则将父节点的值与最大值对应的子节点的值交换。并对该子节点构成的树构建最大堆
	if( largest!=index ){
		exchange(a[index], a[largest]);
		maxHeapify(a, largest, length);
	}
}
//建立最大堆
void buildMaxHeap(int a[], int length){
	int lastParent = length/2 - 1;
	//从最后一个父节点开始,自底向上构建最大堆
	for(int i=lastParent; i>=0; i--){
		maxHeapify(a, i, length);
	}
}
//堆排序
void heapsort(int a[], int length){
	buildMaxHeap(a, length);
	for(int sizeHeap=length; sizeHeap>1; sizeHeap--){
		exchange(a[0], a[sizeHeap-1]);
//由于数组的最后一位此时已不属于堆,故堆的大小为asizeHeap-1
		maxHeapify(a, 0, sizeHeap-1);
	}
}

int main(int argc, char** argv){
	int a[] = {4,1,3,2,16,9,10,14,8,7};
	int length = sizeof(a)/sizeof(a[0]);
	//显示原数列
	for(int i=0; i<length; i++){
		printf("%d ", a[i]);
	}
	printf("\n");
	//进行堆排序
	heapsort(a, length);
	//显示排序后数列
	for(int i=0; i<length; i++){
		printf("%d ", a[i]);
	}
	printf("\n");
}

C++代码实现:

#include <iostream>
#include <vector>
using namespace std;

//求取父节点标号
int parent(int index){
	return index/2 - 1;
}
//求取左子结点标号
int left(int index){
	return index*2 + 1;
}
//求取右子节点标号
int right(int index){
	return index*2 + 2;
}
//通过地址传递的方式交换两值
void exchange(int &a, int &b){
	int temp = a;
	a = b;
	b = temp;
}
//建立最大堆子树
void maxHeapify(vector<int> &a, int index, int length){
	int indexLeft = left(index);
	int indexRight = right(index);
	int largest = index;
	vector<int>::iterator it = a.begin();
	//若存在左子节点,且左子结点的值大于其父节点的值,则将最大值对应的标号变为左子结点标号
	if( (indexLeft<length) && (*(it+indexLeft)>*(it+index)) ) {
		largest = indexLeft;
	}
	//若存在右子节点,且右子节点的值大于其父节点的值,则将最大值对应的标号变为右子结点标号
	if( (indexRight<length) && (*(it+indexRight)>*(it+largest)) ) {
		largest = indexRight;
	}
	//若父节点的值不是最大值,则将父节点的值与最大值对应的子节点的值交换。并对该子节点构成的树构建最大堆
	if( largest!=index ) {
		exchange(*(it+index), *(it+largest));
		maxHeapify(a, largest, length);
	}
}
//建立最大堆
void buildMaxHeap(vector<int> &a) {
	int lastParent = a.size()/2 - 1;
	for(int i=lastParent; i>=0; i--) {
		maxHeapify(a, i, a.size());
	}
}
//进行堆排序
void heapsort(vector<int> &a){
	buildMaxHeap(a);
	vector<int>::iterator it = a.begin();
	for(int sizeHeap=a.size(); sizeHeap>1; sizeHeap--) {
		exchange( *it, *(it+sizeHeap-1) );
		//由于数组的最后一位此时已不属于堆,故堆的大小为asizeHeap-1
		maxHeapify(a, 0, sizeHeap-1);
	}
}

int main(int argc, char** argv){
	int a[] = {4,1,3,2,16,9,10,14,8,7};
	int length = sizeof(a)/sizeof(a[0]);
	vector<int> aVec(a, a+length);
	//显示原排序
	for(vector<int>::iterator i=aVec.begin(); i<aVec.end(); i++ ){
		cout<<*(i)<<" ";
	}
	cout<<endl;
	heapsort(aVec);
	//显示排序完成情况
	for(vector<int>::iterator i=aVec.begin(); i<aVec.end(); i++ ){
		cout<<*(i)<<" ";
	}
	cout<<endl;
	return 0;
}