二叉堆

 

 

最大堆_二叉堆:

最小堆_二叉堆

 堆的特征:

add:

 

 这个过程的时间复杂度是根树的高度有关

 是logn级别

 add代码:

  private void elementsNullCheck(E e){
        if(e==null){
            throw new IllegalArgumentException("element must not be null");
        }
    }
    @Override
    public void add(E element) {
        elementsNullCheck(element);
        //检查容量是否够再添加一个元素
        ensureCapacity(size+1);
        elements[size++]=element;
        siftUp(size-1);
    }
    /**
     *当添加之后 我们要上溢最后一个元素
     * 即是索引为size-1处的元素
     */
    private void siftUp(int index){
        E indexelement=elements[index];
        while (index>0){
            int parentindex=(index-1)/2;
            E parentelment=elements[parentindex];
            if(compare(parentelment,indexelement)>=0){
                return;
            }
            //交换
            E temp=elements[index];
            elements[index]=elements[parentindex];
            elements[parentindex]=temp;
            //重新赋值index
            index=parentindex;
        }
    }

 add优化:

也即是对上溢操作的优化:

 

 /**
     * 优化swift:
     * 到循环结束的时候再把最开始的时候元素加上 避免了大量的交换
     */
    private void siftUpWell(int index){
        E indexelememt=elements[index];
        while (index>0){
            int parentindex=(index-1)/2;
            E parentelement=elements[parentindex];
            if(compare(parentelement,indexelememt)>=0){
                break;
            }
            elements[index]=elements[parentindex];
            index=parentindex;
        }
        elements[index]=indexelememt;
    }

 

 

删除:每一次都是删除堆顶的元素

 

 

以下这两段代码等价:

第二段进行了优化。。。。。。。。。。。

 

 关键:

关键:

找到第一个叶子节点的索引  

这个值也就是非叶子节点的数量。。。。。并且我们默认是向下取整 直接带入公式即可

 

 remove代码:


    @Override
    public E remove() {
       emptyCheck();
       E root=elements[0];
        /**
         * 1、用最后一个节点元素覆盖顶点 之后删除最后一个节点元素
         */
       root=elements[size-1];
       elements[size-1]=null;
       size--;
       siftDown(0);
       return root;
    }

    /**
     *当我们进行交换之后 把最后一个元素值交换到堆顶
     * 很有可能已经违背了我们的大顶堆的原则:父节点的元素值大于子节点(们)的元素值
     * 另外给定几个公式:
     * 1.假设有n个节点  那么非叶子节点的个数为floor(n/2)
     * 2.假设一个节点索引为i  那么左子:2*i+1  右子:2*i+1+1   父节点:(i-1)/2
     */
    private void siftDown(int index) {
        E element=elements[0];
        int half=size>>1;//非叶子节点的数量
        while (index<half){
            //默认是左边的子节点与父节点比较 那么这里childindex默认为左子的索引下标
            int childindex=2*index+1;
            E childelement=elements[childindex];
            int childindexright=2*index+1+1;//即是childindexleft+1
            //右子节点与左进行比较一下
            //1.判断右子的索引范围
            // 2.如果相等那么就默认取左边即可
            if(childindexright<half&&compare(elements[childindexright],childelement)>0){
                childelement=elements[childindexright];
                childindex=childindexright;
            }
            if(compare(childelement,element)<=0){
                break;//退出循环
            }
            index=childindex;
        }
        elements[index]=element;
    }

 repalce:

删除一个堆顶元素并且再增加一个元素

操作:直接用这个增加的元素代替堆顶元素 

然后再把这个元素进行下滤操作

 

  /**
     *删除堆顶元素并且增加一个元素
     * 方法:把这个增加的元素直接代替堆顶元素
     * 然后把堆顶元素直接进行下溢操作
     */
    @Override
    public E replace(Object element){
        E root=null;
        if(size==0){
           elements[0]= (E) element;
            size++;
        } else {
            root=elements[0];
            elements[0]= (E) element;
            siftDown(0);
        }
        return root;
    }

 

1.

 这种操作每一次都把一部分的元素变成最大堆

如图画红圈的都是每一次变化之后对应的最大堆的形式。。。。

 总结:每完成一次上滤操作 就会让最大堆变得大一点 

最终让整个堆都变成最大堆。。。

这两种做法的效率对比:

 由图可见: 

自上而下的上滤操作  下面的基数比较大

因此:进行的操作比较多,,,,,,

因此后者更优。。。。。。。。。。。

 

总结:

前者时间复杂度为:O(nlogn)  自上而下的上滤类似于添加add

后者为O(n) 这种自下而上的下滤操作类似于remove删除操作

 代码:

private E[] elements;
    public BinaryHeap(Comparator<E> comparator,E[] elements){
        super(comparator);//调用父类的比较器即可_super表示调用父类的属性操作
        if(elements==null||elements.length==0){
            this.elements=(E[])new Object[DEFAULT_CAPACITY];
        } else {
            int capacity=DEFAULT_CAPACITY>elements.length?DEFAULT_CAPACITY:elements.length;
            this.elements=(E[])new Object[capacity];
            /**
             * 进行深拷贝。。。。。
             */
            for (int i = 0; i < elements.length; i++) {
                this.elements[i]=elements[i];
            }
            heapify();
        }
    }

    private void heapify() {
      /*  //自上而下的上滤
        for (int i = 0; i < elements.length; i++) {
            siftUp(i);
        }*/
        //自下而上的下滤效率较高
        for (int i =(size>>1-1); i>=0; i--) {
            siftDown(i);
        }
    }

 细节:

当我们外部new一个对象时  传参数传过去一个数组到我们的构造器那里

我们要进行深拷贝。。。。

因为有可能外部进行修改数组的内容会改变

深拷贝会在内存中申请另外一块独立的空间  避免被外部操作而修改

测试类:::::::::::::::::::::::::如下::—leomessi—

 

 如何搞出小顶堆?

重写这个比较器即可,,,,,,,匿名内部类:::

 Top K问题:

代码:

private E[] elements;
    public BinaryHeap(Comparator<E> comparator,E[] elements){
        super(comparator);//调用父类的比较器即可_super表示调用父类的属性操作
        if(elements==null||elements.length==0){
            this.elements=(E[])new Object[DEFAULT_CAPACITY];
        } else {
            int capacity=DEFAULT_CAPACITY>elements.length?DEFAULT_CAPACITY:elements.length;
            this.elements=(E[])new Object[capacity];
            /**
             * 进行深拷贝。。。。。
             */
            for (int i = 0; i < elements.length; i++) {
                this.elements[i]=elements[i];
            }
            heapify();
        }
    }

    private void heapify() {
      /*  //自上而下的上滤
        for (int i = 0; i < elements.length; i++) {
            siftUp(i);
        }*/
        //自下而上的下滤效率较高
        for (int i =(size>>1-1); i>=0; i--) {
            siftDown(i);
        }
    }

 思路:

1.首先先模拟出一个小顶堆  测试类中重写比较策略即可。。。。

 2.把前k个元素放入这个小顶堆中

3.运用heap.get() 每一次拿出堆顶的元素与从第k+1个元素开始进行比较

如果第k+1个元素开始  一旦这个新来的元素大于堆顶元素

那么进行replace操作

何为replace?

把堆顶元素替换删除并且下滤堆顶元素

4.每一次的操作都会使这个堆的元素进一步的整体范围增大。。。。

5.最终在堆中的k个元素就是我们要找的k个元素。。。。。

时间复杂度:

O(nlogk)相对于快速排序的O(nlogn)更优秀了多。。。。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值