《编程导论(Java)·11.2.2 堆排序*》

简单选择排序是蛮力算法,时间复杂度为O(n*(n-1)/2)=O(n2)。

选择排序做了大量重复性的无用功。如{5, 6,7,2}将5与6、7、2比较和换位得{2, 6,7,5},而第二趟时6与5将会再次比较,显然重复。

可以借鉴锦标赛的一些作法使每次比较都有意义。树形选择排序(tree selection sort)或锦标赛排序(tournament sort)核心是利用树形结构将已经进行过的比较信息保留,但是用数组保存比较信息,需要辅助空间。1964年J.willioms提出的堆(heap),(注:这个概念和日常英语中的heap和内存分配中的heap相差甚远)通过调整arr元素的顺序,让它包含比赛过程的信息。

堆是一种重要的数据结构,这里使用int[]构建最大堆(排序时通常使用最大堆)。堆(heap):树形为完全二叉树,若任一父节点保存的值大于等于孩子的值,称为最大(二叉)堆(max heap)

首先,将int[] arr的元素想象/构造成一个完全二叉树。arr[0]在完全二叉树第一层,arr[1]和arr[2]在第二层,arr[3 4 5 6]在第三层...


任何数组按照1、2、4、8个元素一层(最下一层可以不足),可以想象/构造成一个完全二叉树。构成完全二叉树后,父节点与它的孩子结点的索引之间,存在如下关系:

  • 树根的索引1;注意:为了方便下面的计算,完全二叉树采用基于1的索引。故编程时必须注意基于1的索引与数组的基于0的索引之间的转换。
  • 索引为i的结点,它的左孩子的索引为2i,右孩子的索引为2i+1;
  • 任一孩子结点(索引为i)的父节点的索引为i/2(Java整除)。(再次提醒:索引基于1)

堆排序Heap Sort就是将待排序的arr构建成一个最大堆,(最大堆意味着经过一系列的比赛产生了冠军——堆顶),将堆顶元素与最后元素交换,堆顶获得其最终的位置;剩下的元素重新建最大堆,将堆顶元素与本堆最后元素交换,重复上述步骤。

堆化Heapify

建成一个最大堆是堆排序的核心。

长度为n数组arr对应的完全二叉树, n/2是分界点,[0.. n/2]的元素是中间结点,后面的结点都是树叶(没有孩子结点)。我们采用自底而上(bottom-up)的方式构造最大堆。
树叶是由单一元素构成的堆,不需要进一步处理;因此,大堆化即从最后一个的中间结点开始直到根结点,逐一检查它们(作为父节点)是否满足最大堆的性质:父节点的值大于等于孩子的值。它调用方法void maxHeapify(int[] arr,int index) 检查index为根的子树并使该子树大堆化。
    private static void buildHeap(int[] array){
        for(int i = array.length/2;i>0;i--){//基于1的索引,所以i>0而非等于0。从最后一个的中间结点开始直到根结点
            maxHeapify(array, i);p(i+" ");pln(array);
        }
    } 

maxHeapify(int[] arr,int index)的工作,对index为根的子树,比较父节点与左右孩子节点的3个值,将最大的值交换到父节点的位置,并对受影响的孩子节点,递归检查是否满足最大堆的性质。
例如长度为10的数组[4, 1, 3, 2, 16, 9, 10, 14, 8, 7],maxHeapify(arr,5)对当前根节点16(基于1索引)和左右孩子节点(只有一个左孩子7,索引为2*5——基于1索引)相比较,将大的值放在树根的位置,并递归地对受影响的孩子节点大堆化。 请自己画图
    /**
     * 大堆化。
     * 当前根节点与左右孩子节点的key相比较,将大的值放在树根的位置;
     * 并递归地对受影响的孩子节点大堆化。
     * T (n) = O(lgn) ,因为T (n) ≤ T(2n/3) + Θ(1)
     * @ array 待大堆化的数组。
     * @ index 当前根节点的索引,基于1.
     */
    private static void maxHeapify(int[] array, int index){
        //int heapSize = array.length;//递归时,将被注释掉。
        int iL = index * 2;//index of left child
        int iR = index * 2 + 1;
        int iMax = index;//树根与左右孩子节点的key相比较,保存最大值的索引
        if( iL<=heapSize && array[iL-1]>array[index-1]){//基于1 Vs 0
            iMax = iL;
        }
        if( iR<=heapSize && array[iR-1]>array[iL-1]){
            iMax = iR;
        }
        //获得了iMax
        if (iMax != index) {
            IntSort.swap(array ,index-1,iMax-1 );
            maxHeapify(array, iMax);//只处理受影响的孩子节点
        } 
    }
可以测试:
    static void test(){
        int[] arr = new int[] { 4,1,3,2,16,9,10,14,8,7 };
        pln(arr);
        buildHeap(arr);
    }
打印:
[4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
5 [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
4 [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
3 [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
2 [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
1 [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]

package algorithm.sorting;
import static tips.Print.*;

public class HeapSort extends IntSort{
    static int heapSize; 
    /**
     * 时间复杂度:平均O(nlgn),最坏情况O(nlgn)。
     */
    @Override public int[] sort(int[] arr){
        heapSize = arr.length;
        buildHeap(arr);
        for(int i = arr.length;i>0;i--){//i to 1.基于1
            IntSort.swap(arr , 0, i-1  );//基于0
            heapSize--;
            maxHeapify(arr, 1);
        }
        return arr;
    }

   
    private static void buildHeap(int[] array){
        //
    }

    
    private static void maxHeapify(int[] array, int index){
        //
    }
   
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值