常用内部排序算法之三:堆排序

前言

堆排序是以堆为原型的排序。堆首先是一棵二叉树,具有以下两个性质:每个节点的值大于或者等于其左右孩子结点的值,称为大顶堆;或者每个节点的值都小于或者等于其左右孩子结点的值,称为小顶堆。从这个定义中可以发现,堆得根节点要么是最大值要么是最小值。实现堆排序的基本思想是:将待排序的序列构造成一个大顶堆或者小顶堆。此时整个堆满足根节点是最大值或者最小值。将根节点移走,并与堆数组的最后一个值进行交换,这样的话最后一个值就是最大值或者最小值了。然后将剩余n-1(假设原来总共有n个元素)未排序的序列重新构造成一个最大堆或者最小堆,再与除原来最大值之外的最后一个元素进行交换,得到次小值。如此反复进行,就得到一个排序的序列。

堆排序算法的Java实现

package com.rhwayfun.algorithm.sort;

public class HeapSort2 {

    public void heapSort(int[] a){
        for(int i = a.length/2-1; i >= 0; i--){
            //建立一个最大堆
            heapAdjust(a,i,a.length - 1);
        }
        for (int i = a.length - 1; i > 0; i--) {
            //将堆顶元素与当前未经排序的最后一个记录交换
            swap(a,0,i);
            //将数组a中下标从0到i-1的子序列重新调整为最大堆
            heapAdjust(a, 0, i - 1);
        }

        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    private void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    private void heapAdjust(int[] a, int s, int m) {
        int temp = 0,j=0;
        //把堆顶元素保存在临时变量中
        temp = a[s];
        //由于s可能是0,所以需要+1。此外,如果当前结点的序号是s,那么其左孩子是2*s+1(+1是因为s可能是0)
        for(j = 2*s+1; j <= m; j = 2*j+1){
            //找出左右孩子较大的结点的下标
            if(j < m && a[j] < a[j+1]){
                ++j;
            }
            if(temp >= a[j]){
                break;
            }
            //把较大的孩子结点的赋给当前结点
            a[s] = a[j];
            //把更大孩子结点的下标赋给s
            s = j;
        }
        //把原来s下标位置的值赋给新的下标为s的值,这样就完成了当前结点与更大孩子结点值的交换
        a[s] = temp;
    }

    public static void main(String[] args) {
        new HeapSort2().heapSort(new int[]{90,70,80,60,10,40,50,30,20});
    }
}

可以发现在建立最大堆的时候,i是从a.length/2开始的,为什么要从这个位置开始呢?比如有一个数组{90,70,80,60,10,40,50,30,20},初始情况是这样的:

一共有9个元素,那么a.length/21的值是3,也就是第4个元素,执行for循环,i的值从3->2->1->0变化,可以发现这几个下标的结点都是有孩子结点的元素,所以第一个for循环就很好理解了:其实就是从下往上、从右到左,将每个非终端结点当做根节点,将其和子树调整成最大堆,所以下标3->2->1->0的变化,也就是60,80,70,90结点的调整过程。

堆排序算法的时间复杂度

可以发现堆排序的主要时间主要花在建堆和重建堆时的反复筛选上。在初始建堆的时候,由于只涉及两个节点的交换操作,所以建堆的时间复杂度是O(n)。在正式排序的时候,第i次取堆顶的元素并重建堆需要花去O(logi)的时间,需要n-1次取堆顶记录,所以排序的过程的时间复杂度是O(nlogn)。而且这是最好、最坏和平均的时间复杂度,但是由于记录的交换与比较是跳跃式的,所以与归并排序不同,堆排序是一种不稳定的排序算法。

由于初始建堆的比较次数比较多,所以堆排序不适合记录数较少的情况(使用简单排序算法是一种不错的选择,没必要大材小用了)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值