堆排序

1、排序原理

首先对要排序的数组元素的进行比较把它初始化成一个大根堆的形势,此时这个堆的堆顶既原数组的第一个元素是最大的,把它与最后一个元素交换位置,然后在把除了最后一个元素的所有元素重新排列成大根堆,拿第一个和此时的最后一个交换位置,依次执行直到剩余的最后一个元素的下标为0是结束,此时的结果就是排序后的结果。

2、实现步骤:

a)初始化稳定堆

a.1)先找出堆的一些规律,因为这些堆是完全二叉树的形势,所有他的父节点和左右孩子节点的关系应该是(假设父节点是k,则左孩子节点是left = (2 * k + 1),右孩子节点是right= left + 1);

a.2)开始把这个数组的元素按照堆的形势整理,先取第一个元素既k = 0;此时k既是父节点,然后找到他的两个孩子节点,用他们三个进行比较把他们三个组成的堆调整成大根堆;

a.3)如果成功了,就取出第二个元素既k = 1;继续上面的步骤,直到取出一个父节点,而他的左孩子节点或者右孩子节点不小于数组的长度时,结束取值,既不需要去取父节点了;此时堆的初步排序结束;

a.4)因为上面的那一步骤是以每一个最小的大根堆进行排序的(既只根据父节点,两个孩子节点排序,没有管孩子节点是否还有孩子节点)所有很可能后面的排序的时候会把前面的打乱,所有此时应该对上一步骤进行递归运算,把刚才排好的堆进行重新排序,直到所有的堆都已经是稳定堆时结束。(既每一次排序的时候如果有的堆不是稳定堆就要重新排序,当有一次递归的时候不需要排序了,说明此时的堆已经是稳定堆了结束递归);

b)开始排序

b.1)因为经过a步骤堆的最顶端已经是最大值,此时就把那个值和要排序的数组的最后一个值交换位置;

b.2)因为每次交换位置后就已经把数组的最大值排好序了,这个最大值就已经固定了,不需要排序了,所以,此时应该把要排序的长度减少1;

b.3)交换位置后此时的堆已经不是稳定堆,所以要重新整理堆,就把此时的顶当作父节点与他的两个孩子节点比较整理这个堆,如果父节点是和左孩子节点交换位置,那么左孩子节点就不一定是稳定堆了,(但是右孩子节点一定是稳定堆,不需要去整理它)此时就把刚才的左孩子节点当作父节点去整理他自己的两个孩子节点依次类推,如果交换的是右孩子节点,则方法相同,直到没有孩子节点或者交换后的堆依然是稳定堆此时结束整理;

b.4)不断的循环排序的几个步骤,直到要排序的长度小于2的时候结束,此时得到的结果就是排序后的结果。

3、实现代码:


 


 

package sort;

import java.util.Arrays;

public class HeapSortTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = {50, 30, 60, 38, 423, 42, 5423};
		HeapSort(a);
		System.out.println("排序完成后的结果 :" + Arrays.toString(a));
		
	}
	private static void HeapSort(int[] a) {
		int length = a.length;
		//先把堆进行初始化
		initHeap(a);
		System.out.println("堆初始化后的结果: " + Arrays.toString(a));
		System.out.println();
		//用初始化后的堆进行排序
		while(length > 1){
			//每次排序开始的时候要先把数组的最后一个值和第一个值交换位置
			System.out.println("每一次堆整理完成后的结果: " + Arrays.toString(a));
			exchange(a, length - 1, 0);
			System.out.println("每一次交换位置后的结果: " + Arrays.toString(a));
			System.out.println();
			/*交换位置后数组的需要排序的最后一个元素已经是最大值,不需要再改动了,
			 * 此时需要把length减一,只需要对前面的这些元素进行排序就行了
			 */			
			length--;
			System.out.println("每一次需要排序的数组的长度 : " + length);
			int k = 0;
			while(k < length){
				int left = 2 * k + 1;
				int right = left + 1;
				//假设左孩子节点的下标是最大值的下标
				int max = left;
				//先判断是否有右孩子结点
				if (left < length) {
					if (right < length ) {
						//如果有去和左孩子比较大小
						if (a[right] > a[left]) {
							//如果右孩子节点大于左孩子就把右孩子的下标赋值给max
							max = right;
						}
					}
					//用最大值和父节点比较大小
					if (a[max] > a[k]) {
						//如果父节点小就交换位置
						exchange(a, max, k);
						//如果完成交换说明这个孩子所在的堆被改变了,
						//不一定是稳定堆,就要把这个堆重新排序,
						//此时,把这个节点的下标赋值给k,对这个堆进行排序,使其稳定
						k = max;
					}else {
						//否则就退出循环
						break;
					}
				}else {
					//如果左孩子节点的下标超过了数组长度,说明没有左孩子了,这次排序结束了
					break;
				}
			}
		}
	}
	/*
	 * 初始化堆
	 */
	public static void initHeap(int[] a){
		//做一个标记来确定何时结束递归
		boolean isExchange = false;
		int length = a.length;
		int k = 0;
		for (; k < length; k++) {
			//k(父节点),left(左孩子节点), right(右孩子结点)他们三个就是一个小的堆
			//每次把这个堆进行排序使其稳定;当其稳定后就把k加1继续排序下一个堆。
			int left = 2 * k + 1;
			int right = left + 1;
			int max = left;
			if (left < length) {
				if (right < length) {
					if (a[right] > a[left]) {
						max = right;
					}
				}
				
				if (a[max] > a[k]) {
					exchange(a, max, k);
					isExchange = true;
				}
			}else {
				break;
			}
			
		}
		if (isExchange) {
			//当这一次方法执行完后,有需要交换的元素说明堆还没完全初始化,
			//继续递归,直到不需要交换元素,说明初始化完成
			initHeap(a);
		}
		
	}
	/*
	 * 交换位置
	 */
	public static void exchange(int[] a, int max, int k){
		a[max] ^= a[k];
		a[k] ^= a[max];
		a[max] ^= a[k];
	}
}

简化代码:

因为初始化堆和排序的方法非常相似,为了节省代码量,可以把初始化堆和排序的方法合并在一起;

<pre class="java" name="code">package heapSort;

import java.util.Arrays;

public class HeapSortTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = {50, 30, 60, 38, 423, 42, 5423};
		heapSort(a, a.length, false);
		System.out.println("排序完成后的结果 :" + Arrays.toString(a));
		
	}
	/*
	 * 初始化堆以及排序,用isHeap来表示初始化堆是否完成
	 */
	public static void heapSort(int[] a, int length, boolean isHeap){
		
		//做一个标记来确定何时结束递归
		boolean isExchange = false;
		int k = 0;
		for (; k < length;) {
			//k(父节点),left(左孩子节点), right(右孩子结点)他们三个就是一个小的堆
			//每次把这个堆进行排序使其稳定;
			int left = 2 * k + 1;
			int right = left + 1;
			int max = left;
			if (left < length) {
				if (right < length) {
					if (a[right] > a[left]) {
						max = right;
					}
				}
				
				if (a[max] > a[k]) {
					exchange(a, max, k);
					isExchange = true;
				}
			}else {
				break;
			}
			//当其稳定后就改变k值继续排序下一个堆。
			if (!isHeap) {
				//如果是初始化堆,就需要比较每一次加入的堆
				k ++;
			}else {
				//如果不是初始化堆则只需要比较刚才交换位置的那个堆即可
				k = max;
			}
		}
		//如果初始化堆完成了if就不需要继续执行了,直接执行else
		if (!isHeap && isExchange) {
			//当这一次方法执行完后,有需要交换的元素说明堆还没完全初始化,
			//继续递归,直到不需要交换元素,说明初始化完成
			heapSort(a, length,false);
		}else {
			if (length > 1) {
				System.out.println("每一次堆整理完成后的结果: " + Arrays.toString(a));
				exchange(a, length - 1, 0);
				System.out.println("每一次交换位置后的结果: " + Arrays.toString(a));
				System.out.println();
				/*交换位置后数组的需要排序的最后一个元素已经是最大值,不需要再改动了,
				 * 此时需要把length减一,只需要对前面的这些元素进行排序就行了
				 */			
				length--;
				System.out.println("每一次需要排序的数组的长度 : " + length);
				heapSort(a, length, true);
			}
		}
	}
	/*
	 * 交换位置
	 */
	public static void exchange(int[] a, int max, int k){
		a[max] ^= a[k];
		a[k] ^= a[max];
		a[max] ^= a[k];
	}
}

 

结果:

堆初始化后的结果: [5423, 423, 60, 38, 30, 42, 50]

每一次堆整理完成后的结果: [5423, 423, 60, 38, 30, 42, 50]
每一次交换位置后的结果: [50, 423, 60, 38, 30, 42, 5423]

每一次需要排序的数组的长度 : 6
每一次堆整理完成后的结果: [423, 50, 60, 38, 30, 42, 5423]
每一次交换位置后的结果: [42, 50, 60, 38, 30, 423, 5423]

每一次需要排序的数组的长度 : 5
每一次堆整理完成后的结果: [60, 50, 42, 38, 30, 423, 5423]
每一次交换位置后的结果: [30, 50, 42, 38, 60, 423, 5423]

每一次需要排序的数组的长度 : 4
每一次堆整理完成后的结果: [50, 38, 42, 30, 60, 423, 5423]
每一次交换位置后的结果: [30, 38, 42, 50, 60, 423, 5423]

每一次需要排序的数组的长度 : 3
每一次堆整理完成后的结果: [42, 38, 30, 50, 60, 423, 5423]
每一次交换位置后的结果: [30, 38, 42, 50, 60, 423, 5423]

每一次需要排序的数组的长度 : 2
每一次堆整理完成后的结果: [38, 30, 42, 50, 60, 423, 5423]
每一次交换位置后的结果: [30, 38, 42, 50, 60, 423, 5423]

每一次需要排序的数组的长度 : 1
排序完成后的结果 :[30, 38, 42, 50, 60, 423, 5423]

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值