Java数据结构 -- 堆排序

1.什么是堆

堆是一种数据结构,需要满足以下两个特点

a. 是一个完全二叉树

b. 对于堆上的每一个结点, 都必须满足: 父结点的值大于子结点的值

2.什么是完全二叉树

下面七棵树都是完全二叉树

 

 完全二叉树最大的特点是: 生成节点顺序是 从上到下,从左到右 依次生成

从树的第h-1层(倒数第二层)开始进行向下调整

建大堆 进行堆排序之后 是升序

建小堆 进行堆排序之后 是降序

向下调整有两种方式: 一种是"父节点大于所有的子节点",另一种是"父节点小于所有的子节点"

 

针对经典topK问题, 也就是找出很多个数中最大的前K个数, 是建大堆还是建小堆?

答: 建小堆. 举例:要挑选出武林中前五名最厉害武林高手, 现在随机给出五个武林高手, 让这五个武林高手内部做一次向下调整, 挑出最菜的那个人跟武林中其他人进行比武. 如果没有人打过这个最菜的武林高手, 那么这五位武林高手就是武林中最厉害的五位; 而如果来比武的那个人打赢了最菜的, 那么最菜的那个人就出去, 这个来比武的人就是这五分之一了, 然后这五个人内部再进行一次向下调整, 再挑选出最菜的那个人, 跟武林中其他人进行比武....以此类推, 就可以找出武林中最厉害的前五名高手

 

记住一个公式:

对于一个下标为i的结点:

     它的父结点是: parent = (i - 1) / 2

     它的左孩子是: 2i + 1

     它的右孩子是: 2i + 2

3.代码实现(建大堆)

 

public class heapSort {
    public static void heapSort(int[] array){//进行堆排序
        buildHeap(array); //先建堆
        for(int i = 0; i < array.length - 1; i++){
            // 交换前: 无序区间: [0, array.length - i) ; 有序区间: [array.length - i, array.length)
            //因为每当交换完根结点和最后一个结点后, 就将最后一个结点砍断, 然后剩下的结点继续进行堆排序
            //然后再调用这一过程, 那么就会再将最大的那个结点放到最后,然后砍断, 反复调用后, 砍断的这些结点就形成了有序区间
            //剩下还没有进行向下调整的结点就是无序区间
            swap(array, 0, array.length - i - 1); //交换根结点(即下标为0的结点)和最后一个结点(下标为array.length-i-1)
            // 交换后: 无序区间: [0, array.length - i - 1);有序区间: [array.length - i - 1, array.length)
            // 无序区间长度: array.length - i - 1
            shiftDown(array, array.length - i - 1, 0); // -i是代表每交换完之后,就会砍断一个结点,
        }                                   //砍断一个结点这一步不用写出来,只用在heapify这一步时默认减少了一个结点即可
    }
    private static void swap(int[] array, int a, int b){
        int temp = array[a];
        array[a] = array[b];
        array[b] = temp;
    }
    private static void buildHeap(int[] array){ //建堆
        for(int i = (array.length - 1)/2; i>= 0; i--){ //从array.length的父结点i到根结点开始向下调整.
            shiftDown(array, array.length, i);//表示调整array.length的父节点i那一块小三角区域
            //每次只调整一个小三角区域的内容(即只调整父结点以及它的左右子树)
        }
    }
    public static void shiftDown(int[] array, int size, int index){
        //size表示这棵树一共有size个结点
        //index表示对下标为某某的结点做向下调整的操作
        //如果要对一个结点进行向下调整操作, 这个结点必须是父结点
        int left = 2 * index + 1;
        while(left < size) { //表示"如果左子树存在"
            // 是为了保证是在树节点总个数范围内,因为是从倒数第二层开始向下调整,可能倒数第二层有的结点没有子节点
            int max = left;//先让最大值等于左子树
            int right = 2 * index + 2;
            if(right < size){ //表示"如果右子树存在"
                if(array[right] > array[left]){
                    max = right;
                }
            }
            if(array[index] >= array[max]){
                break;//若要调整的下标是最大的, 则break, 表示树的这一块区域已变成堆,停止对这一块区域的调整
            }
            //否则交换
            swap(array, index, max);

            index = max;//index的下标是max
            left = 2 * index + 1; //更新一下新index的左子树
        }
    }
    public static void main(String[] args){
        int[] array = {4,2,1,9,3,7,2,55};
        heapSort(array);
        for(int i = 0; i < array.length; i++){
            System.out.print(array[i] + " ");
        }
    }
}

4.性能分析

时间复杂度: O(nlog(n))

空间复杂度: O(1)

不稳定

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值