经典排序算法之实现(四)

五、堆排序

      1、算法描述

   堆排序是一种树形选择排序,是对直接选择排序的有效改进。堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

    2、堆排序过程

既然是堆排序,自然需要先建立一个堆,而建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。调堆的过程应该从最后一个非叶子节点开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。那么调堆的过程如下图,数组下标从0开始,A[3] = 5开始。分别与左孩子和右孩子比较大小,如果A[3]最大,则不用调整,否则和孩子中的值最大的一个交换位置,在图1中是A[7] > A[3] > A[8],所以A[3]与A[7]对换,从图1.1转到图1.2。

所以建堆的过程是:

void BuildHeap(int A[], int hLen){   
	int i;  
	int begin = hLen/2 - 1;  
	//最后一个非叶子节点  
	for (i = begin; i >= 0; i--){          
		AdjustHeap(A, hLen, i);    
	}  
}


调堆:如果初始数组是非降序排序,那么就不需要调堆,直接就满足堆的定义,此为最好情况,运行时间为Θ(1);如果初始数组是如图1.5,只有A[0] = 1不满足堆的定义,经过与子节点的比较调整到图1.6,但是图1.6仍然不满足堆的定义,所以要递归调整,一直到满足堆的定义或者到堆底为止。如果递归调堆到堆底才结束,那么是最坏情况,运行时间为O(h) (h为需要调整的节点的高度,堆底高度为0,堆顶高度为floor(logn) )。

建堆完成之后,堆如图1.7是个大根堆。将A[0] = 8 与 A[heapLen-1]交换,然后heapLen减一,如图2.1,然后AdjustHeap(A, heapLen-1, 0),如图2.2。如此交换堆的第一个元素和堆的最后一个元素,然后堆的大小heapLen减一,对堆的大小heapLen的堆进行调整,如此循环,直到heapLen==1为止,最后结果如图3所示





       3、算法代码

#include<iostream>
using namespace std;

int LeftChild(int i);
int RightChild(int i);
void BuildHeap(int A[], int hLen);
void AdjustHeap(int A[], int hLen, int i) ;
void HeapSort(int A[], int aLen);
int n,*a;

void main(){
	cout<<"请输入要排序的数字序列的长度:";
	cin>>n;
	cout<<"请输入需要排序的数字序列:";
	a = new int[n];
	for(int i = 0; i < n; i++){
		cin>>a[i];
	}
	HeapSort(a,n);
	cout<<"排序结果是:";
	for(int i = 0; i < n; i++){
		cout<<a[i]<<"  ";
	}
	cout<<endl;
	delete []a;
}

/*  输入:数组A,堆的大小hLen  
    功能:建堆   */   
void BuildHeap(int A[], int hLen){   
	int i;  
	int begin = hLen/2 - 1;  
	//最后一个非叶子节点  
	for (i = begin; i >= 0; i--){          
		AdjustHeap(A, hLen, i);    
	}  
}

/*   输入:数组A,堆的长度hLen,以及需要调整的节点i   
     功能:调堆    */  
void AdjustHeap(int A[], int hLen, int i) {  
	int left = LeftChild(i);  //节点i的左孩子   
	int right = RightChild(i); //节点i的右孩子节点  
	int largest = i;  
	int temp;  
	while(left < hLen || right < hLen) {  
		if (left < hLen && A[largest] < A[left])  {  
			largest = left;  
		}  
		if (right < hLen && A[largest] < A[right]){  
			largest = right;  
		}  
		//如果最大值不是父节点  
		if (i != largest)   { 
			temp = A[largest]; 
			//交换父节点和和拥有最大值的子节点交换 
			A[largest] = A[i];  
			A[i] = temp;  
			i = largest;         //新的父节点,以备迭代调堆 
			left = LeftChild(i);  //新的子节点  
			right = RightChild(i);  
		}  else  {  
			break;  
		} 
	}  
}

void HeapSort(int A[], int aLen) { 
	 int hLen = aLen;  
	 int temp;  
	 BuildHeap(A, hLen);      //建堆  
	 while (hLen > 1)  {  
		 temp = A[hLen-1];    //交换堆的第一个元素和堆的最后一个元素 
		 A[hLen-1] = A[0];  
		 A[0] = temp;  
		 hLen--;        //堆的大小减一 
		 AdjustHeap(A, hLen, 0);  //调堆 
	 } 
}

int LeftChild(int i){
	return 2*i;
}

int RightChild(int i){
	return 2*i+1;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值