堆排序(JAVA)

看了几篇博客,以自己的理解默写了一遍自己理解的堆排序,然后本地跑了一遍,可以正常排序,因为怕以后看不懂,注释写的很啰嗦

/**
 * 堆排序
 * 堆一般指一个完全二叉树,根据其特性,可以分为大根堆,小根堆
 * 大根堆:任意父节点的值大于或等于2个孩子节点的值
 * 小根堆:任意父节点的值小于或等于2个孩子节点的值
 * 就是利用堆的这样的特性用来排序,所以叫做堆排序
 * 比较常用的就是大根堆了,因为根结点是堆的最大节点,所以可以类似于冒泡,选择排序那样,依次得到最大节点,次最大节点,完成一个序列的排序
 * 每次都从根结点把堆的最大值交换到堆低,然后调整堆为新的大根堆,不过这时候堆的长度得减1,依次下去就可以完成排序
 * 小根堆也一样,可以不断选择最小值,然后不断调整小根堆,获得次最小,然后也是一个排好序的序列
 * 一颗完全二叉树,其层次遍历的结果可以顺序存在一个数组里
 * 其父节点i的位置和左右孩子的位置关系是:左孩子:2*1+1;右孩子:2*1+2;节点的父节点位置:(i-1)/2
 * 
 */
public class HeapSort {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] array = {87,45,78,32,17,65,53,9,122,133};
		heapSort(array);
		for(int i : array){
			System.out.print(i+" ");
		}

	}
	/**
	 * 堆排序
	 * 使用大根堆的特性,每次获取堆顶的最大值,放到堆底,然后堆的长度减1,调整堆使其再次恢复大根堆特性,依次循环,直到只剩下一个节点为止
	 * @param data
	 * @return
	 */
	public static int[] heapSort(int[] data){
		if(data == null || data.length <= 1) return data;
		//构建大根堆
		buildMaxHeap(data);
		for(int i = data.length -1; i > 0; i--){//自减为0后,表示堆还剩一个节点没有排序
			//交换堆顶、底的值
			int temp = data[i];
			data[i] = data[0];
			data[0] = temp;//可能破坏了大根堆的特性,指定根结点进行调整]
			adjustMaxHeap(data, 0, i);//此时已经确定了最大的节点到堆底,所以堆的长度得减1
		}
		return data;
	}
	/**
	 * 构建大根堆
	 * 思路:
	 * 从右向左,从下到上不断调根结点为大根堆,起始点为最后一个父节点:(data.length-1-1)/2,终点为
	 * 树的根结点 0
	 * @param data
	 * @return
	 */
	public static int[] buildMaxHeap(int[] data){
		if(data == null || data.length <= 1) return data;		
		for(int i = (data.length-1-1)/2; i >= 0; i--){
			adjustMaxHeap(data, i, data.length);//构建大根堆时,长度一直不变
		}
		return data;
	}
	/**
	 * 调整大根堆,使其保持大根堆的特性,自上而下地向下调整,直到堆底
	 * @param data 需要调整的堆
	 * @param headIndex 需要调整的堆的根节点的位置
	 * @param length 需要调整的堆的长度
	 * @return
	 */
	public static void adjustMaxHeap(int[] data, int headIndex, int length){
		//判断要调整的根结点的合法性,最后一个根结点的位置是(length-1-1)/2
		if(headIndex <= (length-1-1)/2){
			//位置合法,开始调整大根堆,判断其是否大于或等于左右孩子的最大值,是则不需要调整,直接跳出循环,否则与最大值交换,然后继续往下调整
			//一直调整所需要比较和交换值的子节点到堆底结束,即可以执行到data.length -1位置的节点
			//因为调整大根堆使,可能会不停的那较大的孩子节点的值覆盖父节点,所以需要保存当前父节点的值
			int temp = data[headIndex];
			for(int i = 2*headIndex+1; i < length; i = 2*i+1){//每次循环开始,即需要确认一个根结点headIndex是否需要调整,使其保持大根堆的性质
				//取左右孩子最大值的位置
				//i < data.length -1 这个条件的限制是为了确定i是左孩子的情况下,那么一定存在右孩子,如果i不满足条件,那么就不会有右孩子了
				if(i < length -1 && data[i] < data[i+1]){
					//i是左孩子,并且值比右孩子小,那么最大值的位置就是右孩子
					i++;
				}
				//此时已经得到左右孩子的最大位置,判断是否需要调整堆				
				if(temp >= data[i]){
					//根结点的值已经大于或等于左右节点,不需要调整堆
					break;
				}else{
					//需要调整					
					data[headIndex] = data[i];//将最大的值赋值到根结点
					//原headIndex的位置的值已经确定,这时候需要向下继续调整,因为我们还不知道 temp因该放在哪,即无论放在哪里都可能破坏
					//大根堆的结构,所以需要依次向下调整,直到不需要调整为止,才能保证大根堆的特性
					//现在temp可能会被放在刚刚赋值过去的i上,所以以i为根结点,继续调整大根堆
					headIndex = i;//以headIndex为根结点,并且假设将temp的值也迁移到这个新的headIndex上(可以这么想,因为每次都是temp与左右孩子较大值取比较的)
					//继续寻找左孩子的位置,即i = 2*i+1
				}
			}
			//此时循环结束,可能是headIndex的值本来就不需要调整,
			//或者进行调整后,因为把最大孩子节点的值赋值到根结点后,更新较大孩子节点的位置为headIndex,此时的headIndex的值不需要调整
			//或者headIndex的位置已经超出最后一个父亲节点的位置
			//那么headIndex就可以存放调节父节点的值temp了
			data[headIndex] = temp; //指定父节点调整大根堆完成
		}
	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值