排序算法之堆排序

堆的概念:

n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质): 
    (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ )

满足(1)的称为小根堆,满足(2)的称为大根堆。
    若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
image_thumb[9]image_thumb[8]

可以发现,大顶堆的根节点一定是这组数据中值最大的节点,也就是说,如果要对一组数据进行排序,只需先将这组建成大顶堆,就选出了其中的最大值。

思想:

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

堆排序的关键在于建堆,要按如下步骤完成(大顶堆):

    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;

    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];

    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

排序过程:

重复一下两步:

     1)初始化堆:将R[1..n]构造为堆;

     2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

    因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

建堆(初始化堆)

下面举例说明:

     给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。

    首先根据该数组元素构建一个完全二叉树,得到

       image
然后需要构造初始堆,则从最后一个非叶节点开始调整,每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。具体调整过程如下:

             å æåº-æ建å 
20和16交换后导致16不满足堆的性质,因此需重新调整

           å æåº-æ建å 2

这样就得到了初始堆。

堆排序:

将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

               æåº

从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为 nlogn。堆排序为不稳定排序,不适合记录较少的排序。
 

java代码实现:

package fanzhenhua.sort;

import java.util.Arrays;

public class HeapSort {

	public static void heapSort(DataWrap[] data){
		/*
		 * 1.需要进行n-1次的建堆过程,当无序数组的元素只剩下一个的时候,整棵二叉树已经有序了
		 * 2.建堆后需要交换第一个元素和无序数组的最后一个元素
		 * */
		for(int i=0;i<data.length-1;i++){
			buildHeap(data,data.length-1-i);
			swap(data,0,data.length-1-i);
			System.out.println(Arrays.toString(data));
		}
	}
	
	public static void buildHeap(DataWrap[] data,int maxIndex){
		//每次建立大根堆的时候都是从最后一个叶子节点的父节点开始判断,直到所有非叶子节点都判断完毕后大根堆便构建成功
		//每次对父节点的判断时需从本身以及子节点中选择最大值进行交换,交换后还必须被交换的子节点(如果该节点有孩子)符合大根堆
		
		//判断每一个父节点
		for(int i=(maxIndex-1)/2;i>=0;i--){
			int k=i;//保存父节点k
			//k*2+1取的是k节点的左孩子节点,如果k节点存在孩子节点,则必须判断k节点所在的子树是否为大根堆
			while(k*2+1<=maxIndex){
				int bigNum=k*2+1;//保存k节点的左孩子节点
				if(bigNum<maxIndex){//如果bigNum的值小于maxIndex说明k节点存在右孩子节点
					//判断k节点的左右孩子的大小,并保存值较大的孩子节点的索引
					if(data[bigNum].compareTo(data[bigNum+1])<0){
						bigNum++;//指向右孩子节点
					}
				}
				//接下来判断k节点的值是否大于孩子节点中的最大值
				if(data[k].compareTo(data[bigNum])<0){
					//如果小于的话则交换,构建k节点所在子树为大根堆
					swap(data,k,bigNum);
					
					//交换完成后必须保存k值为被交换的孩子节点 的索引,防止交换后出现不满足大根堆的子树(交换后k节点的孩子节点也存在子树)			
					//k值改变,第一次while循环判断时条件肯定不成立,但是后面的循环会成立,所以必须重新记录k值。
					k=bigNum;					
				}else{//否则的话说明k节点值最大,k所在的子树已经符合大根堆
					break;//退出while循环,继续判断下一个叶子节点的父节点
				}
			}
		}
	}
	//交换元素
	public static void swap(DataWrap[] data,int beg,int end){
		DataWrap temp=data[beg];
		data[beg]=data[end];
		data[end]=temp;
	}
	
	public static void main(String[] args){
		DataWrap[] data = { 
				new DataWrap(21, "")
				,new DataWrap(30, "")
				,new DataWrap(49, "")
				,new DataWrap(30, "*")
				,new DataWrap(16, "")
				,new DataWrap(9, "") };
 
		System.out.println("排序之前:" + Arrays.toString(data));
 
		heapSort(data);
 
		System.out.println("排序之后:" + Arrays.toString(data));

	}
}

堆排序算法分析:

时间复杂度:O(nlog2n)

空间复杂度:O(1)

是否为稳定排序:不稳定排序

参考文章:

https://blog.csdn.net/bruce_6/article/details/38704301

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值