算法分析(一)堆排序原理及java实现

一、堆排序思想

以下以大根堆为例:

1、先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区

2、 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key

3、由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。

4、……

5、直到无序区只有一个元素为止。

二、堆排序步骤

  1. 构建最大堆。
  2. 选择顶,并与第0位置元素交换
  3. 由于步骤2的的交换可能破环了最大堆的性质,第0不再是最大元素,需要调用maxHeap调整堆(沉降法),如果需要重复步骤2

三、具体实现细节

    1. 构建一个最大堆。对于给定的包含有n个元素的数组A[n],构建一个最大堆(最大堆的特性是,某个节点的值最多和其父节点的值一样大。这样,         堆中的最大元素存放在根节点中;并且,在以某一个节点为根的子树中,各节点的值都不大于该子树根节点的值)。从最底下的子树开始,调整           这个堆结构,使其满足最大堆的特性。当为了满足最大堆特性时,堆结构发生变化,此时递归调整对应的子树。 
    2. 堆排序算法,每次取出该最大堆的根节点(因为根节点是最大的),同时,取最末尾的叶子节点来作为根节点,从此根节点开始调整堆,使其满足最大堆的特性。 
    3. 重复上一步操作,直到堆的大小由n个元素降到2个。 

    伪代码如下:

    建堆;

    循环(条件:堆不为空){

      取出堆顶元素;

      将最后一个元素移动到堆顶位置;

      调整使之再次成为堆;

    }

四、演示图如下:



五、java实现

package com.deppon.tps.module;

public class HeapSortTest {
	 public static void main(String[] args) {  
		 Comparable[] data5 = new Comparable[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };  
	        print(data5);  
	        sort(data5);  
	        System.out.println("排序后的数组:");  
	        print(data5);  
	    }  
	 public static void sort(Comparable[] data) {  
	        // 构建最大堆  
	        buildMaxHeap(data);  
	        // 循环,每次把根节点和最后一个节点调换位置  
	        for (int i = data.length; i > 1; i--) {  
	            Comparable tmp = data[0];  
	            data[0] = data[i - 1];  
	            data[i - 1] = tmp;  
	  
	            // 堆的长度减少1,排除置换到最后位置的根节点  
	            maxHeapify(data, 1, i - 1);  
	        }  
	    }  
	  
	    // 根据输入数组构建一个最大堆  
	    private static void buildMaxHeap(Comparable[] data) {  
	        for (int i = data.length / 2; i > 0; i--) {  
	            maxHeapify(data, i, data.length);  
	        }  
	    }  
	  
	    //堆调整,使其生成最大堆  
	    private static void maxHeapify(Comparable[] data, int parentNodeIndex, int heapSize) {  
	        // 左子节点索引  
	        int leftChildNodeIndex = parentNodeIndex * 2;  
	        // 右子节点索引  
	        int rightChildNodeIndex = parentNodeIndex * 2 + 1;  
	        // 最大节点索引  
	        int largestNodeIndex = parentNodeIndex;  
	  
	        // 如果左子节点大于父节点,则将左子节点作为最大节点  
	        if (leftChildNodeIndex <= heapSize && data[leftChildNodeIndex - 1].compareTo(data[parentNodeIndex - 1]) > 0) {  
	            largestNodeIndex = leftChildNodeIndex;  
	        }  
	  
	        // 如果右子节点比最大节点还大,那么最大节点应该是右子节点  
	        if (rightChildNodeIndex <= heapSize && data[rightChildNodeIndex - 1].compareTo(data[largestNodeIndex - 1]) > 0) {  
	            largestNodeIndex = rightChildNodeIndex;  
	        }  
	  
	        // 最后,如果最大节点和父节点不一致,则交换他们的值  
	        if (largestNodeIndex != parentNodeIndex) {  
	            Comparable tmp = data[parentNodeIndex - 1];  
	            data[parentNodeIndex - 1] = data[largestNodeIndex - 1];  
	            data[largestNodeIndex - 1] = tmp;  
	  
	            // 交换完父节点和子节点的值,对换了值的子节点检查是否符合最大堆的特性  
	            maxHeapify(data, largestNodeIndex, heapSize);  
	        }  
	    }  
	    public static void print(Comparable[] data) {  
	        for (int i = 0; i < data.length; i++) {  
	            System.out.print(data[i] + "\t");  
	        }  
	        System.out.println();  
	    } 
}
测试结果为:
5 3 6 2 19 4 8 7
排序后的数组:
1 2 34 5 6 7 8 9

六、堆排序算法分析

优点:

  1. 堆排序的效率与快排、归并相同,都达到了基于比较的排序算法效率的峰值(时间复杂度为O(nlogn))
  2. 除了高效之外,最大的亮点就是只需要O(1)的辅助空间了,既最高效率又最节省空间,只此一家了
  3. 堆排序效率相对稳定,不像快排在最坏情况下时间复杂度会变成O(n^2)),所以无论待排序序列是否有序,堆排序的效率都是O(nlogn)不变(注意这里的稳定特指平均时间复杂度=最坏时间复杂度,不是那个“稳定”,因为堆排序本身是不稳定的)

缺点:(从上面看,堆排序几乎是完美的,那么为什么最常用的内部排序算法是快排而不是堆排序呢?)

  1. 最大的也是唯一的缺点就是——堆的维护问题,实际场景中的数据是频繁发生变动的,而对于待排序序列的每次更新(增,删,改),我们都要重新做一遍堆的维护,以保证其特性,这在大多数情况下都是没有必要的。(所以快排成为了实际应用中的老大,而堆排序只能在算法书里面顶着光环,当然这么说有些过分了,当数据更新不很频繁的时候,当然堆排序更好些...)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值