堆排序

概述:堆排序是一种树形选择排序,是对直接选择排序的有效改进。
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

在这里插入图片描述

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子 在这里插入图片描述
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

满足上述定义时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)或最大项(大顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点(有子女的结点)的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。
(a)大顶堆序列:(96, 83, 27, 38, 11, 09)
(b)小顶堆序列:(12, 36, 24, 85, 47, 30, 53, 91)
201668103036365.jpg (300×137)
初始时把要排序的n 个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素。然后对剩下的n-1个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到最后得到有n个节点的有序序列。称这个过程为堆排序。

步骤&实例
实现堆排序需解决两个问题:
(1)如何将n 个待排序的数建成堆;
(2)输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。
建堆方法(小顶堆):
对初始序列建堆的过程,就是一个反复进行筛选的过程。
n 个结点的完全二叉树,则最后一个结点是第n/2个结点的子树。
筛选从第n/2个结点为根的子树开始(n/2是最后一个有子树的结点),使该子树成为堆。
之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
如图建堆初始过程
无序序列:(49, 38, 65, 97, 76, 13, 27, 49)
201668103056618.jpg (450×268)
(a) 无序序列,初始二叉树,97(第8/2=4个结点)为最后一个结点(49)的父结点。
(b) 97>=49,替换位置,接下来对n/2的上一个结点65进行筛选。
© 13<=27且65>=13,替换65和13的位置,接下来对38进行替换(都大于它,不需操作),对49进行筛选。
(d) 13<=38且49>=13,替换49和13的位置,49>=27,替换49和27的位置。
(e) 最终得到一个堆,13是我们得到的最小数。
调整堆的方法(小顶堆):
设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶,堆被破坏,其原因仅是根结点不满足堆的性质。
将根结点与左、右子树中较小元素的进行交换。
若与左子树交换:如果左子树堆被破坏,则重复方法(2).
若与右子树交换,如果右子树堆被破坏,则重复方法(2).
继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
调整堆只需考虑被破坏的结点,其他的结点不需调整。
201668103119337.jpg (555×138)
代码实现:

/**
 * 类名:HeapSort<br>
 * 功能:堆排序<br>
 * 作者:java战士<br>
 * 日期:2019/8/20<br>
 * 版本:v1.0.0
 * 历史修订:
 */
public class HeapSort {
    //堆排序
    public static void heapSort(int[] arr){
        if (arr==null|| arr.length<2){
            return;
        }
        //生成大顶堆
        for (int i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }
        int size=arr.length;
        //树顶最大的数和树最后的数进行交换
        swap(arr,0,--size);
        while (size>0){
            //将交换后的树结构重新恢复成大顶堆
            heapify(arr,0,size);
            //然后继续顶部元素和最后元素进行交换,以此类推
            swap(arr,0,--size);
        }
    }

    //生成大顶堆
    public static void heapInsert(int[] arr,int index){
        while (arr[index]>arr[(index-1)/2]){
            //如果新加入的比父节点树大,则和父节点树进行交换
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }
    //将一个变小的值,与其子节点进行比较往下沉
    public static void heapify(int[] arr,int index,int size){
        //左子树
        int left=index*2+1;
        while (left<size){
            //将左子树和右子树进行比较,将较大值的下标赋给largest
            int largest=(left+1<size && arr[left+1]>arr[left])?left+1:left;
            //将子节点较大的元素和父节点元素进行比较,将较大元素的下标赋给largest
            largest=arr[largest]>arr[index]?largest:index;
            if (largest==index){
                break;
            }
            //比父节点大的元素与父节点进行交换
            swap(arr,largest,index);
            index=largest;
            left=index*2+1;
        }
    }
    //数组元素交换
    public static void swap(int[] arr,int i,int j){
        int temp;
        temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值