堆排序的分析与Java实现

堆排序的分析与Java实现

基本原理

是一颗完全二叉树(非整二叉树),可以用数组来实现。一般分为最大堆最小堆

最大堆的性质是堆中每一个节点的值都小于其父亲节点的值

最小堆的性质是堆中每一个节点的值都大于其父亲节点的值

具体关于堆的介绍,可以看我之前写过的二叉堆的数据结构。
在这里插入图片描述
无序数组建立堆的直接方法是从左到右遍历数组进行上浮操作。用途最广泛的操作是从右至左使用下沉操作,称为Heapify。

明白了堆的性质后,便会觉得用堆来实现排序是个很简单的工作。但是这里存在一个陷阱

如果要实现一个从小到大排序的数组,我的第一印象是使用最小堆,从右到左遍历一遍。但是最小堆除了可以保证父亲节点大于左右子节点,并不能保证左儿子的值要小于右儿子的值。

因此直接拿一个无序数组改造成最小堆,并不能达到排序的目的。

事实上,要实现排序的功能,只需要实现一个最大堆就可以了。每次将最大堆的顶点(即索引0)与最后一位交换,再对剩下的数组中的顶点做下沉操作就可以了。这样每次都可以取到剩余数组中最大的值放到最后一位,从而得到一个从尾到头的递减序列,即从头到尾的递增序列。

代码实现

import java.util.Arrays;
import java.util.Random;

/**
 * 堆排序
 * 不同于二叉堆的数据结构
 * 主要目的是将无序数组建立成最小堆
 * 本次排序使用数组第0个位置的元素
 *
 * @Author Nino 2019/10/5
 */
public class HeapSort<E extends Comparable<E>> {

    public void heapSort(E[] arr) {
        int N = arr.length - 1;
        // Heapify操作
        // 对原先的数组进行最大堆化
        // 需要从最后一个非叶节点开始就好了(因为叶节点没有子节点,不需要下沉)
        for (int i = parent(N); i >= 0; i--) {
            siftDown(arr, i, N);
        }

        //当需要最大堆的容量大于1的时候才进行取出最大值
        while (N > 0) {
            extractMax(arr, N--);
        }
    }

    /**
     * 对数组[0, N]的部分做下沉操作
     * 这里的下沉是构建最大堆的下沉
     * 把最大元素放在堆顶
     *
     * @param arr   数组
     * @param index 需要下沉的位置
     * @param N 下沉的终点
     */
    private void siftDown(E[] arr, int index, int N) {
        //当左儿子满足在N范围内才做下沉
        while (leftChild(index) <= N) {
            int j = leftChild(index);
            //如果存在右儿子,且右儿子元素比左儿子大,则交换右儿子
            if (j + 1 <= N && arr[j + 1].compareTo(arr[j]) > 0) {
                j++;
            }
            if (arr[j].compareTo(arr[index]) <= 0) {
                break;
            }
            swap(arr, j, index);
            index = j;
        }
    }

    /**
     * 取出堆顶元素并放到索引N处
     * 对[0, N-1]的堆进行下沉整理
     * @return
     */
    private void extractMax(E[] arr, int N) {
        E e = arr[0];
        swap(arr, 0, N);
        siftDown(arr, 0, N - 1);
    }

    /**
     * 获取左儿子节点
     * @param index
     * @return
     */
    private int leftChild(int index) {
        return index * 2 + 1;
    }

    /**
     * 获取右儿子节点
     * @param index
     * @return
     */
    private int rightChild(int index) {
        return (index + 1) * 2;
    }

    /**
     * 获取父亲节点
     *
     * @param index
     * @return
     */
    private int parent(int index) {
        if (index == 0) {
            return 0;
        }
        return (index - 1) / 2;
    }

    /**
     * 交换数组元素
     * @param arr
     * @param i
     * @param j
     */
    private void swap(E[] arr, int i, int j) {
        E e = arr[i];
        arr[i] = arr[j];
        arr[j] = e;
    }


    /**
     * 测试用例
     *
     * @param args
     */
    public static void main(String[] args) {
        Random random = new Random();
        Integer[] arr = new Integer[20];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = Integer.valueOf(random.nextInt(30));
        }
        System.out.println(Arrays.asList(arr).toString());
        new HeapSort<Integer>().heapSort(arr);
        System.out.println(Arrays.asList(arr).toString());
    }
}

性能分析

使用Heapify创建堆的复杂度为 O ( N ) O(N) O(N)

堆的高度为 l o g N logN logN,因此插入元素和删除元素的复杂度都为 l o g N logN logN

堆排序中要对N个节点进行下沉操作,复杂度为 N l o g N NlogN NlogN

综合来看,堆排序的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

堆排序是一种原地排序,没有利用额外的空间。

现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较和交换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
牙科就诊管理系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的Mysql数据库进行程序开发。实现了用户在线查看数据。管理员管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等功能。牙科就诊管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 管理员在后台主要管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等。 牙医列表页面,此页面提供给管理员的功能有:查看牙医、新增牙医、修改牙医、删除牙医等。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。药品管理页面,此页面提供给管理员的功能有:新增药品,修改药品,删除药品。药品类型管理页面,此页面提供给管理员的功能有:新增药品类型,修改药品类型,删除药品类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值