基础算法与数据结构——二叉堆

一、引言

    堆是优先队列的一种实现,用来解决每次从队列中取出来的元素都为队列中最“优”的元素。

在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。----维基百科

    我们一般叫做堆的一般是二叉堆,除了明确指明为内存堆以外,堆一般都默认为二叉堆,在Java中Priorityqueue就是采用了堆来实现的。

二、堆的定义

    根节点大于等于(或者小于等于)其子节点的完全二叉树叫做堆。大于等于子节点的堆叫做大顶堆,反之则叫做小顶堆。如图:
在这里插入图片描述
    这两个完全二叉树都是大顶堆,即便第二个图第三层有节点的值比第二层还大。

三、堆的操作

3.1 堆的存贮

    对于堆来说,存贮一般为顺序存贮,采用数组来存贮堆中的数据,因为其完全二叉树的性质,通过根节点的索引就能得到其左右孩子节点的索引:

leftChildIndex=(parentIndex >> 1)+1;
rightChildIndex=(parentIndex >> 1)+2;

3.2 堆的插入

    出于对堆的 定义,假如为大顶堆,那么我们插入是将新的元素置于数组末尾,然后调整堆的结构,使其满足堆的定义。这一操作称作上浮,之所以被称作上浮,是因为新的元素就像泡泡一样漂浮了上去,直到属于它的位置。以图示来展示新元素的上浮:
在这里插入图片描述
    所以对于大顶堆而言,新元素插入后,只需要让其与它的根节点比较,如果大于根节点那么就交换位置。而换成小顶堆原理也是一样的。对于大顶堆的上浮操作代码如下:

public static void upHead(int index,int []array,int length){
        int parentIndex=caculateParentIndex(index);
        if(parentIndex >= 0 && array[parentIndex] < array[index]){
            swap(array,parentIndex,index);
            index=parentIndex;
            upHead(index, array, length);
        }
    }

3.3 堆的删除

    堆的删除刚好与堆的插入相反,堆的删除是删除堆顶的元素,然后将堆尾的元素置于堆顶,然后让堆顶元素下沉,直到满足堆的定义,如图:
在这里插入图片描述
    在下沉的时候,如果是大顶堆,那么选择与大的孩子节点交换,这样就能保证满足堆的定义,而且堆的删除与插入的时间复杂度都是O(logn),取决于完全二叉树的深度,其下沉代码如下:

public static void downHead(int index,int []array,int length){
        int leftChildIndex=caculateLeftChildIndex(index);
        int rightChildIndex=caculateRightChildIndex(index);
        int largestIndex=index;
        if(leftChildIndex < length && array[largestIndex] < array[leftChildIndex]){
            largestIndex=leftChildIndex;
        }
        if(rightChildIndex < length && array[largestIndex] < array[rightChildIndex]){
            largestIndex=rightChildIndex;
        }
        if(largestIndex != index){
            swap(array, largestIndex, index);
            downHead(largestIndex, array,length);
        }
    }

3.4 构建堆

    构建堆的思路是基于堆下沉的思路来的,在构建堆的时候,我们从倒数第一个非叶子节点开始进行堆的下沉,就能保证所有根节点的子节点是满足堆的定义的,根据完全二叉树的性质,可以得到第一个非叶子节点的索引为:(array.length-1)/2。代码如下:

public static void buildHead(int []array){
        for(int i=(array.length-1)/2 ;i>=0;i--){
            downHead(i, array, array.length);
        }
    }

3.5 堆排序

    堆排序是基于堆的删除的,我们知道对于大顶堆而言,堆顶是最大值,如果删除堆顶,那么很快就能在O(logn)的时间复杂度里面选择出第二个堆顶,也就是第二大的值,继续迭代下去就能得到有序的序列了。只不过再删除的时候,我们将堆顶的元素和堆尾交换一下,再进行下沉操作,那么每次就能在数组末尾得到倒数第n大的值了,代码如下:

public static void headSort(int []array){
        for(int i=array.length-1 ;i>0;i--){
            swap(array, 0, i);
            downHead(0, array, i);
        }
    }

    所以堆排序的时间复杂度为n*log2n=nlog2n,简化为O(nlogn)

    全部代码如下:


public class Head{

    private static int caculateLeftChildIndex(int index){
        return (index <<1 )+1;
    }

    private static int caculateRightChildIndex(int index){
        return (index <<1 )+2;
    }

    private static int caculateParentIndex(int index){
        return (index -1 )>>1;
    }

    public static void headSort(int []array){
        for(int i=array.length-1 ;i>0;i--){
            swap(array, 0, i);
            downHead(0, array, i);
        }
    }

    public static void buildHead(int []array){
        for(int i=(array.length-1)/2 ;i>=0;i--){
            downHead(i, array, array.length);
        }
    }

    public static void upHead(int index,int []array,int length){
        int parentIndex=caculateParentIndex(index);
        if(parentIndex >= 0 && array[parentIndex] < array[index]){
            swap(array,parentIndex,index);
            index=parentIndex;
            upHead(index, array, length);
        }
    }

    public static void downHead(int index,int []array,int length){
        int leftChildIndex=caculateLeftChildIndex(index);
        int rightChildIndex=caculateRightChildIndex(index);
        int largestIndex=index;
        if(leftChildIndex < length && array[largestIndex] < array[leftChildIndex]){
            largestIndex=leftChildIndex;
        }
        if(rightChildIndex < length && array[largestIndex] < array[rightChildIndex]){
            largestIndex=rightChildIndex;
        }
        if(largestIndex != index){
            swap(array, largestIndex, index);
            downHead(largestIndex, array,length);
        }
    }

    private  static void swap(int []array,int i,int j){
        if(i == j){
            return;
        }
        array[i]=array[i]^array[j];
        array[j]=array[i]^array[j];
        array[i]=array[i]^array[j];
    }

    public String toString(int []array){
        return Arrays.toString(array);
    }


    public static void main(String []args){
        int []array={9,7,6,3,5,2,1,8};
        //upHead(7, array, array.length);
        buildHead(array);
        headSort(array);
        System.out.println(Arrays.toString(array));
    }



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值