堆排序

首先知道什么是完全二叉树
       如果一棵具有n个结点的深度为k的二叉树,它的每一个结点都与深度为k的满二叉树中编号为1~n的结点一一对应,
这棵二叉树称为完全二叉树。这个其实很好理解!一个数组对应的完全二叉树,即父结点为i位置的话,左孩子的结点位置为2*i+1,右孩子的结点位置为2*i+2。


然后知道什么是大根堆和小根堆
       堆是一种特殊的树形数据结构,其每个结点都有一个值,通常提到的堆都是指一颗完全二叉树,所有父结点的值小于两个子结点的值,叫小根堆;所有父结点的值大于两个子结点的值,叫大根堆。


给定一个数组,如何实现大根堆
       首先遍历数组,确保0~i的元素是大根堆,形成一个子树时,验证子结点是否比父结点小,如果大则与父结点交换,并继续与上一层的父结点比较(子结点i的父结点是(i-1)/2),直到根结点。以{2,1,3,6,0,4}为例,2为根结点,1和3分别为左右子结点,此时形成的完全二叉树右子结点比父结点大,所以二者交换,此时父结点为根结点,就不需要继续向上比较了,当前的数组修改为{3,1,2,6,0,4};接下来,6和0分别是父结点1的左右子结点,但是6比1大,所以将6与1交换位置,此时数组修改为 {3,6,2,1,0,4},由于6不是根结点,继续向上一层比较,6也比3大,所以6和3交换,此时的数组修改为{6,3,2,1,0,4};接下来4是父结点2的左孩子,4比2大,所以交换,因为4不比6大,所以不交换,此时的数组修改为{6,3,4,1,0,2}结点,遍历结束,就得到最终的大根堆。

 

堆排序:
    利用大根堆,给定一个数组实现大根堆,然后根结点与最后一个结点交换,这样最后一个结点就是最大值,再把最后一个结点从大根堆中去掉;然后利用heapFy()重新生成大根堆,一直重复上述过程,直到大根堆只剩一个结点为止,就完成排序了。时间复杂度 O(N*logN)。

值得注意的是,Java 5 之后提供了一个优先级队列PriorityQueue,它是Java的一种集合,默认实现的就是小根堆。

public class HeapSort {
	public static void heapSort(int[] arr){
		if(arr == null || arr.length < 2)
			return;
		//遍历数组,确保0~i是大根堆
		for (int i = 0; i < arr.length; i++) {
			heapInsert(arr,i);
		}
		
		
		int heapSize = arr.length;
		while(heapSize > 1){
			swap(arr,0,--heapSize);	//调换第一个元素和最后一个元素,将最大值放到最后
			heapFy(arr,0,heapSize);
		}
		
	}
	
	public static void heapInsert(int[] arr,int index){ //生成大根堆
		//确定当前结点的值是否比其父结点大
		while(arr[index] > arr[(index - 1)/2]){ //当index就是根结点时,(index-1)/2  -1/2其实也是0,二者相等while还是不执行
			swap(arr,(index - 1)/2,index);
			index = (index - 1)/2;
		}
	}
	
	public static void swap(int[] arr, int a, int b){
		int temp = arr[a];
		arr[a] = arr[b];
		arr[b] = temp;
	}
	
	/**
	 * 当大根堆的某个数突然变小时,下面方法可以修正。
	 * index是改变元素的位置,heapSize是堆的范围,针对不一定堆就是整个数组的情况
	 * 
	 * 具体过程是:得到index左右子结点的最大值跟index比较,如果index要小,就交换;然后继续与下一层的进行比较。
	 */
	public static void heapFy(int[] arr,int index,int heapSize){
		int left = index*2 + 1;
		while(left < heapSize){ //不取等的原因是,size从1开始。
//			if(left + 1 < heapSize){
//				int large = arr[left] > arr[left+1] ? left : left + 1;
//				if(arr[index] < arr[large]){
//					swap(arr,index,large);
//					index = large;
//					left = index*2 + 1;
//				}else{ //如果改变后的index位置值还是比两个子结点大,则直接结束
//					break;
//				}
//			}else{
//				if(arr[index] < arr[left]){
//					swap(arr,index,left);
//					index = left;
//					left = index*2 + 1;
//				}else{
//					break;
//				}
//					
//			}
			
			//上面写得太繁琐了
			int large = left + 1 < heapSize && arr[left] < arr[left+1] ? left + 1 : left;
			if(arr[index] < arr[large]){
				swap(arr,index,large);
				index = large;
				left = index*2 + 1;
			}else{ //如果改变后的index位置值还是比两个子结点大,则直接结束
				break;
			}
			
			
		}
	}
	
	
	//打印数组元素
	public static void printArray(int[] arr){
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
	}
	
	public static void main(String[] args){
		int[] arr = {2,1,3,6,0,4};
		heapSort(arr);
		printArray(arr);
	}
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值