堆与PriortyQueue

堆与PriortyQueue

  • 堆是完全二叉树
  • 大顶堆(大根堆)——每个结点的值都大于等于其左右孩子结点的值
  • 小顶堆(小根堆)——每个结点的值都小于等于其左右孩子节点的值

逻辑实现:
大根堆与小根堆

物理实现:
堆的物理实现

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

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

PriortyQueue

基本结构

PriorityQueue逻辑上是堆结构(平衡二叉树),物理实现(实际储存)是数组

the two children of queue[n] are queue[2n+1] and queue[2(n+1)]

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    //默认用于数组初始化大小
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    /**
     * Priority queue represented as a balanced binary heap: the two
     * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
     * priority queue is ordered by comparator, or by the elements'
     * natural ordering, if comparator is null: For each node n in the
     * heap and each descendant d of n, n <= d.  The element with the
     * lowest value is in queue[0], assuming the queue is nonempty.
     */
    //存储结点信息的数组
    transient Object[] queue; // non-private to simplify nested class access

    /**
     * The number of elements in the priority queue.
     */
    //数组中实际存放元素的个数
    private int size = 0;

    /**
     * The comparator, or null if priority queue uses elements'
     * natural ordering.
     */
    //比较器
    private final Comparator<? super E> comparator;

    /**
     * The number of times this priority queue has been
     * <i>structurally modified</i>.  See AbstractList for gory details.
     */
    //记录修改次数的变量
    transient int modCount = 0; // non-private to simplify nested class access

基本操作(增+删+出队)

添加

添加步骤

  • 将新元素添加到堆结构的末尾
  • 不断调整直至满足堆结构

调用add()或者offer(),实际上add()也是调用的offer(),二者效果相同

    public boolean add(E e) {
        return offer(e);
    }

    /**
     * Inserts the specified element into this priority queue.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;   //获取queue实际容量
        if (i >= queue.length)
            grow(i + 1);    //扩容
        size = i + 1;
        if (i == 0)    //如果queue为空,直接插入新元素
            queue[0] = e;
        else          //如果不为空,插入新元素并调整
            siftUp(i, e);
        return true;
    }
    // 根据情况(当前size)扩容    
    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }
    //根据比较器调用相应的函数
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1; //获得第k结点的父节点索引
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)//满足堆结构
                break;
            //当不满足堆结构时
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }
删除

1、删除的是头结点

  • 用最后一个元素替换头部元素
  • 用头元素和两个孩子中值较小的节点相比较,如果小于该节点的值则满足堆结构,不做任何调整,否则交换之后做同样的判断

2、删除的是中间结点

  • 用最后一个元素替换将要被删除的元素并删除最后元素
  • 判断该节点的值与其子节点中最小的值比较,如果小于最小值则维持堆结构,否则向下调整
  • 判断该节点的值是否小于父节点的值,如果小于则向上调整,否则维持堆结构

实现删除结点的主要方法是removeAt(),还有remove()方法也是调用的removeAt()

    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        if (s == i) // 如果要删除的结点是最后一个结点,直接删除
            queue[i] = null;
        else { //要删除的结点不是最后一个结点,用最后一个结点的来覆盖要删除的结点
            E moved = (E) queue[s];//最后一个节点
            queue[s] = null;
            siftDown(i, moved); //向下调整  
            /*如果向下调整后,移动过结点,就不用再向上调整了*/
            if (queue[i] == moved) { //向下都满足堆,未调整,则需要向上调整
                siftUp(i, moved); //向上调整
                if (queue[i] != moved)//说明向上调整移动过结点
                    return moved;
            }
        }
        return null;
    }
出队

peek()获得对根节点,poll()弹出堆顶元素

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);//向下调整
        return result;
    }

运用例子

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平值。

package 数据流中的中位数;

import java.util.Comparator;
import java.util.PriorityQueue;

/**
 * 用一个大根堆+一个小根堆解决
 */
public class Solution {

    static int count = 0;
    static PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    static PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(
            new Comparator<Integer>() {
                public int compare(Integer o1, Integer o2) {
                    return o2 - o1;
                }
            });

    public void Insert(Integer num) {
        count++;
        if(count % 2 == 1) {//奇数,插入大根堆
            minHeap.offer(num);
            Integer temp = minHeap.poll();
            maxHeap.offer(temp);
        } else {//偶数,插入小根堆
            maxHeap.offer(num);
            Integer temp = maxHeap.poll();
            minHeap.offer(temp);
        }
    }

    public Double GetMedian() {
        if(count % 2 == 1) {//奇数
            return new Double(maxHeap.peek());
        } else {//偶数
            return new Double((minHeap.peek() + maxHeap.peek())) / 2;
        }
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        //[5,2,3,4,1,6,7,0,8]
        s.Insert(5);
        System.out.print(s.GetMedian()+", ");

        s.Insert(2);
        System.out.print(s.GetMedian()+", ");

        s.Insert(3);
        System.out.print(s.GetMedian()+", ");
    }
}

堆排序

堆的优秀实现类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值