堆排序原理及实现

堆排序是利用堆的性质对记录序列进行排序的一种排序方法。堆排序是选择类排序

堆的定义

堆是满足下列性质的数列{K0,K1,K2,……,K(n-1)}:

  • (1)小根堆:Ki <= K(2i+1),且Ki <= K(2i+2)(0<=i<=n/2-1);
  • 或者(2)大根堆:Ki >= K(2i+1),且Ki >= K(2i+2)(0<=i<=n/2-1);

其中,Ki相当于二叉树的非叶子节点,K(2i+1)是左孩子节点,K(2i+2)是右孩子节点。

若将此数列看成是一颗完全二叉树,则堆是空数列或是满足下列特性的完全二叉树:

  1. 其左、右子树分别是堆;
  2. 当左、右子树不空时,根结点的值小于(或大于)左、右子树根结点的值。



堆(大根堆)排序基本思想及步骤

基本思想:

  1. 建堆:先将待排序序列文件R[0……n-1]构建成一个大根堆,此堆为初始的无序区;
  2. 交换:将堆顶记录(无序区的最大记录R[0])和无序区的最后一个记录R[n-1]交换,无序区记录个数减去1,有序区记录个数加1,由此得到新的无序区R[0……n-2]和有序区R[n-1];
  3. 调整:将当前无序区R[0……n-2]调整为大根堆;
  4. 重复交换操作:将堆顶记录和无序区最后一个记录交换,无序区记录个数减去1,有序区记录个数加1;
  5. 重复调整操作:将当前无序区调整为大根堆;
  6. ……
  7. 直到无序区只有一个元素为止。

假设待排序序列为{7, 3, 5, 1, 6},要将其按升序排序,其堆排序具体过程如下:

步骤一:建初始堆。(升序使用大根堆,降序使用小根堆)

初始的无序序列逻辑及物理存储结构如下:


从最后一个非叶子结点开始,其下标为i = arr.length/2-1 = 1,即从arr[1]处开始,看其左、右孩子结点的值,找出左、右孩子结点中值最大的结点,记住其下标,然后与其父结点交换;然后,从左往右,从下向上,依次进行调整。直到将其调整成大根堆。


步骤二:交换。将堆顶记录和无序区最后一个记录交换。


圈出来的部分就是新的无序区,即需要下次调整为大根堆的部分。此时下标为4的arr[4]就是有序区。

步骤三:调整。将新的无序区调整为大根堆。

从最后一个非叶子结点(新的无序区的最后一个非叶子结点)开始,其下标为i = (arr.length-1)/2-1 = 1,即从arr[1]处开始,看其左、右孩子结点的值,找出左、右孩子结点中值最大的结点,记住其下标,然后与其父结点交换;然后,从左往右,从下向上,依次进行调整。直到将其调整成大根堆。

步骤四:然后重复交换、调整、交换、调整……操作,直到无序区只有一个记录。


圈出来的是新的无序区,是下一次需要调整的新的无序区。

最终的堆排序结果为下图:


此时,堆排序结束。

代码实现:

public class HeapSort {
	
	/**
	 * 调整一个非叶子结点及它的左、右孩子这三个结点为一个大根堆
	 * @param arr
	 * @param i
	 * @param length
	 */
	private static void adjust(int[] arr, int i, int length) {
		//先保存第一个非叶子结点记录
		int temp = arr[i];
		//从左孩子结点开始
		for(int j = 2*i+1; j < length; j = 2*j+1) {
			//有右孩子,且左孩子记录小于右孩子记录
			if(j+1 < length && arr[j] < arr[j+1]) {
				//用j来保存孩子结点中比较大的孩子结点的下标
				j++;
			}
			//用孩子结点中比较大的孩子的记录和父结点记录做比较
			if(temp < arr[j]) {
				//父结点记录保存最大记录
				arr[i] = arr[j];
				//记录孩子结点的下标,要把父结点的记录赋值给该孩子记录
				i = j;
			} else {
				break;
			}
		}
		arr[i] = temp;
	}
	
	private static void swap(int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	
	/**
	 * 大根堆排序
	 * @param arr
	 */
	public static void heapSort(int[] arr) {
		if(null == arr) {
			return;
		}
		/**
		 * 构建大根堆
		 */
		for(int i = arr.length/2-1; i >= 0; i--) {
			adjust(arr, i, arr.length);
		}
		/**
		 * 交换 + 调整
		 */
		for(int i = arr.length-1; i >= 0; i--) {
			//先交换堆顶记录和无序区最后一个记录
			swap(arr, 0, i);
			//调整无序区为大根堆
			adjust(arr, 0, i);
		}
	}
	
	private static void showArr(int[] arr) {
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
	}
	
	public static void main(String[] args) {
		int[] arr = {7, 3, 5, 1, 6};
		/**
		 * 堆排序
		 */
		heapSort(arr);
		showArr(arr);
	}

}

运行结果:


效率:

时间复杂度:O(nlogn);

空间复杂度:O(1);

稳定性:不稳定排序。

猜你喜欢

转载自blog.csdn.net/weixin_39651041/article/details/79949482