五十道编程小题目 --- 28 八大排序算法 java 之 04堆排序

4.选择序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进。

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足


时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆(Java数组从0开始,i为0到n-1),则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

(a)大顶堆序列:(96, 83,27,38,11,09)

  (b)  小顶堆序列:(12,36,24,85,47,30,53,91)

1.堆

  堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:

  

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Key[i] <= key[2i+1] && Key[i] <= key[2i+2]  
  2.  或者  
  3. Key[i] >= Key[2i+1] && key >= key[2i+2]  

     即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。

   

堆分为大顶堆和小顶堆,

满足 Key[i] >= Key[2i+1] && key >= key[2i+2] 称为大顶堆,满足 Key[i] <= key[2i+1] && Key[i] <= key[2i+2] 称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。


2.堆排序的思想

   利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

    其基本思想为(大顶堆):

    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;

    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 

    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    操作过程如下:

     1)初始化堆:将R[1..n]构造为堆;

     2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

    因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。


    下面举例说明:

     给定一个整形数组a[]={16,7,3,20,17,8}(小顶堆),对其进行堆排序。

首先根据该数组元素构建一个完全二叉树,得到



然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:


20和16交换后导致16不满足堆的性质,因此需重新调整




这样就得到了初始堆。


即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。



此时3位于堆顶不满堆的性质,则需调整继续调整








这样整个区间便已经有序了。

    从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. import java.util.Random;  
  2.   
  3. public class HeapSort {  
  4.   
  5.     public static int[] heapSort(int[] arr) {  
  6.   
  7.         System.out.println("----------------建堆----------------");  
  8.         int lastIndex = 0;  
  9.         for (int i = 0; i < arr.length; i++) {  
  10.             lastIndex = arr.length - 1 - i;  
  11.               
  12.             createHeap(arr, lastIndex); // 建初始堆(大顶堆)  
  13.             isHeap(arr, lastIndex);// 检查堆还有没有不满足性质的  
  14.             swap(arr, 0, arr.length - 1 - i); // 堆顶元素与叶子元素交换  
  15.               
  16.             System.out.println("堆顶与第"+(lastIndex+1)+"个数交换后 :");  
  17.             print(arr);  
  18.             System.out.println("----------------建堆----------------");  
  19.         }  
  20.   
  21.         return arr;  
  22.     }  
  23.   
  24.     //大顶堆  
  25.     private static void createHeap(int[] arr, int lastIndex) {  
  26.       
  27.         int j = lastIndex;  
  28.         int i = 0;  
  29.         for (i = (lastIndex - 1) / 2; i >= 0; i--) {// 初始i等于lastIndex 的父节点  
  30.             // 保存当前正在判断的节点  
  31.             int k = i;  
  32.   
  33.             // 若当前节点的子节点存在  
  34.             if ((2 * k) + 1 <= j) {  
  35.                 // biggerIndex总是记录较大节点的值,先赋值为当前i节点的左子节点  
  36.                 int biggerIndex = 2 * k + 1;  
  37.   
  38.                 if (biggerIndex < j) {//如果有右节点  
  39.                     // 若右子节点存在,则判断左子节点与右子节点谁大,将大的放入biggerIndex  
  40.                     if (arr[biggerIndex] < arr[j]) {  
  41.                         biggerIndex++;  
  42.                     }  
  43.                 }  
  44.   
  45.                 if (arr[k] < arr[biggerIndex]) {  
  46.                     // 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k  
  47.                     swap(arr, k, biggerIndex);  
  48.                     j = (--k) * 2 + 2;  //j 一直为k的右子节点  
  49.                     // k = biggerIndex;  
  50.                 }else{  
  51.                     j = (--k) * 2 + 2;//j 一直为k的右子节点  
  52.                 }  
  53.                   
  54.             }  
  55.             print(arr);  
  56.         }  
  57.     }  
  58.   
  59.     // 检查堆是否都满足大顶堆得性质  
  60.     private static void isHeap(int[] arr, int lastIndex) {  
  61.   
  62.         int leftIndex = 0;  
  63.         int rightIndex = 0;  
  64.         int parent = 0;  
  65.   
  66.         for (int i = 0; i < (arr.length - 2) / 2; i++) {  
  67.   
  68.             leftIndex = 2 * i + 1;  
  69.             rightIndex = 2 * i + 2;  
  70.             parent = i;  
  71.   
  72.             if (arr[leftIndex] > arr[parent] || arr[rightIndex] > arr[parent]) {  
  73.                 createHeap(arr, lastIndex);  
  74.             }  
  75.         }  
  76.   
  77.     }  
  78.   
  79.     // 交换数组元素  
  80.     private static void swap(int[] arr, int i, int j) {  
  81.   
  82.         if (i == j) {  
  83.             return;  
  84.         }  
  85.         arr[i] = arr[i] + arr[j];  
  86.         arr[j] = arr[i] - arr[j];  
  87.         arr[i] = arr[i] - arr[j];  
  88.     }  
  89.   
  90.     // 打印数组  
  91.     public static void print(int[] arr) {  
  92.         for (int i = 0; i < arr.length; i++) {  
  93.             System.out.print(arr[i] + " ");  
  94.         }  
  95.         System.out.println();  
  96.     }  
  97.   
  98.     public static void main(String[] args) {  
  99.   
  100.         System.out.println("任意数组测试:");  
  101.         Random r = new Random();  
  102.         int[] testArr = new int[20];  
  103.         for (int i = 0; i < 20; i++) {  
  104.             testArr[i] = r.nextInt(100);  
  105.         }  
  106.   
  107.         int a[] = { 167320178 };  
  108.         System.out.println("排序前  : ");  
  109.         print(a);  
  110.   
  111.         System.out.println("排序  : ");  
  112.         print(heapSort(a));  //得到小顶堆  
  113.   
  114.         /* 
  115.          * System.out.println("排序前  : "); print(testArr); 
  116.          *  
  117.          * System.out.println("排序  : "); print(heapSort(testArr)); 
  118.          */  
  119.   
  120.     }  
  121.   
  122. }  


[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. </pre><pre>  
打印结果:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 任意数组测试:  
  2. 排序前  :   
  3. 16 7 3 20 17 8   
  4. 排序  :   
  5. ----------------建堆----------------  
  6. 16 7 8 20 17 3   
  7. 16 20 8 7 17 3   
  8. 20 16 8 7 17 3   
  9. 20 16 8 7 17 3   
  10. 20 17 8 7 16 3   
  11. 20 17 8 7 16 3   
  12. 堆顶与第6个数交换后 :  
  13. 3 17 8 7 16 20   
  14. ----------------建堆----------------  
  15. 3 17 8 7 16 20   
  16. 17 3 8 7 16 20   
  17. 17 16 8 7 3 20   
  18. 17 16 8 7 3 20   
  19. 堆顶与第5个数交换后 :  
  20. 3 16 8 7 17 20   
  21. ----------------建堆----------------  
  22. 3 16 8 7 17 20   
  23. 16 3 8 7 17 20   
  24. 16 7 8 3 17 20   
  25. 16 7 8 3 17 20   
  26. 堆顶与第4个数交换后 :  
  27. 3 7 8 16 17 20   
  28. ----------------建堆----------------  
  29. 8 7 3 16 17 20   
  30. 8 7 3 16 17 20   
  31. 堆顶与第3个数交换后 :  
  32. 3 7 8 16 17 20   
  33. ----------------建堆----------------  
  34. 7 3 8 16 17 20   
  35. 7 3 8 16 17 20   
  36. 7 3 8 16 17 20   
  37. 堆顶与第2个数交换后 :  
  38. 3 7 8 16 17 20   
  39. ----------------建堆----------------  
  40. 3 7 8 16 17 20   
  41. 3 7 8 16 17 20   
  42. 3 7 8 16 17 20   
  43. 堆顶与第1个数交换后 :  
  44. 3 7 8 16 17 20   
  45. ----------------建堆----------------  
  46. 3 7 8 16 17 20   


(本人写的代码输出结果意在看排列的过程,请去除重复后,再与下面结论进行比较验证)


分析:

设树深度为k,。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式: 

                                

而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值