堆和优先级队列2:java实现堆和优先级队列

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆和优先级队列2:java实现堆和优先级队列相关的知识,希望对你有一定的参考价值。

1.什么是优先级队列

在C++和java等库中,都提供了优先级队列这个容器,java中的优先级队列是PriorityQueue。其实底层就是一个堆的结构,只不过将堆封装了一层而已。其实名字叫个优先级队列,但总觉得和队列是不沾边的。 不知道为啥这么叫。

接下来我们看一下java实现堆和使用优先级队列的一些代码,这些面试一般不用写,我们感受一下其实现原理就好。

PriorityQueue的实现直接参考jdk源码。我们这里用java自己实现一个。

2.堆的建立过程

给定一个初始化数组来建立一个最大堆,假定数组长度为N,则从N/2到1位置处,对于每一个节点元素进行向下调整,则最终数组为一个最大堆。

   public MaxPQ(Key[] keys){
		n = keys.length;
		pq = (Key[]) new Object[n+1];
		for(int i=0; i<n; i++){
			pq[i+1] = keys[i];
		}
		for(int k=n/2; k>=1; k--){
			sink(k);
		}
	}

插入操作

首先判断当前元素数量与堆容量大小关系,判断是否需要扩容;如果不需要,则将新插入元素放置到数组最后位置。将最后位置元素执行向上调整操作,可以实现堆的平衡。

   public void insert(Key x){
		if(n==pq.length-1) resize(2*pq.length);
		pq[++n] = x;
		swim(n);
	}
   private void resize(int capacity) {
		Key[] tmp = (Key[]) new Object[capacity];
		for(int i=1; i<=n; i++){
			tmp[i] = pq[i];
		}
		pq = tmp;
	}

删除最大值

最大堆可以在O(1)内返回数组元素的最大值,在O(logN)内删除最大值并且调整堆有序。

删除最大值,即删除索引位置为1的元素,在此首先记录堆顶元素值,然后将数组最后一个元素与堆顶元素交换,对于堆顶新元素执行向下调整操作,以此保证堆有序。

   public Key delMax(){
		if(isEmpty()) return null;
		Key max = pq[1];
		exch(1, n--);
		sink(1);
		pq[n+1] = null;
		if(n>0 && n==(pq.length-1)/4)
			resize(pq.length/2);
		return max;
	}

3.堆的调整算法

上一篇我们通过一个demo演示了调整完全二叉树为堆的方法,其实堆的调整有向上和向下两种方式。

3.1 向上调整

向上调整:以最大堆为例,如果某一个节点值小于其父节点,则需要交换该节点与父节点值,继续比较交换,直到根节点一直是有序的。

代码如下:

    private void swim(int k) {
		while(k>1 && less(k/2, k)){
			exch(k, k/2);
			k = k /2;
		}
	}

其中less是小于判断,exch为交换操作

    private void exch(int i, int j) {
		Key swap = pq[i];
		pq[i] = pq[j];
		pq[j] = swap;
	}
 
	private boolean less(int i, int j) {
		if(comparator==null){
			return ((Comparable<Key>)pq[i]).compareTo((Key)pq[j])<0;
		}else{
			return comparator.compare(pq[i], pq[j])<0;
		}
	}

3.2向下调整

最大堆,当某一个节点小于其左右孩子节点时,应该将该节点与左右孩子最大值节点交换,然后继续判断被交换孩子节点位置是否满足最大堆的定义。

代码如下:

    private void sink(int k) {
		while(2*k<=n){
			int j=2*k;
			if(j<n && less(j, j+1)) j++;
			if(!less(k, j)) break;
			exch(k, j);
			k = j;
		}	
	}

4.最终用最大堆实现的优先级队列

public class MaxPQ<Key> implements Iterable<Key>{
	
	private Key[] pq;
	private int n;
	private Comparator<Key> comparator;
	
	public MaxPQ(int initCapacity){
		pq = (Key[]) new Object[initCapacity+1];
		n = 0;
	}
	
	public MaxPQ(){
		this(1);
	}
	
	public MaxPQ(int initCapacity, Comparator<Key> comparator){
		this(initCapacity);
		this.comparator = comparator;
	}
	
	public MaxPQ(Comparator<Key> comparator){
		this(1, comparator);
	}
	
	public MaxPQ(Key[] keys){
		n = keys.length;
		pq = (Key[]) new Object[n+1];
		for(int i=0; i<n; i++){
			pq[i+1] = keys[i];
		}
		for(int k=n/2; k>=1; k--){
			sink(k);
		}
	}
	
	public void insert(Key x){
		if(n==pq.length-1) resize(2*pq.length);
		pq[++n] = x;
		swim(n);
	}
	
	public Key delMax(){
		if(isEmpty()) return null;
		Key max = pq[1];
		exch(1, n--);
		sink(1);
		pq[n+1] = null;
		if(n>0 && n==(pq.length-1)/4)
			resize(pq.length/2);
		return max;
	}
	
	 // is pq[1..N] a max heap?
    private boolean isMaxHeap() {
        return isMaxHeap(1);
    }
 
    // is subtree of pq[1..n] rooted at k a max heap?
    private boolean isMaxHeap(int k) {
        if (k > n) return true;
        int left = 2*k;
        int right = 2*k + 1;
        if (left  <= n && less(k, left))  return false;
        if (right <= n && less(k, right)) return false;
        return isMaxHeap(left) && isMaxHeap(right);
    }
 
	private boolean isEmpty() {
		return n == 0;
	}
 
	private void swim(int k) {
		while(k>1 && less(k/2, k)){
			exch(k, k/2);
			k = k /2;
		}
	}
 
	private void resize(int capacity) {
		Key[] tmp = (Key[]) new Object[capacity];
		for(int i=1; i<=n; i++){
			tmp[i] = pq[i];
		}
		pq = tmp;
	}
 
	private void sink(int k) {
		while(2*k<=n){
			int j=2*k;
			if(j<n && less(j, j+1)) j++;
			if(!less(k, j)) break;
			exch(k, j);
			k = j;
		}	
	}
 
	private void exch(int i, int j) {
		Key swap = pq[i];
		pq[i] = pq[j];
		pq[j] = swap;
	}
 
	private boolean less(int i, int j) {
		if(comparator==null){
			return ((Comparable<Key>)pq[i]).compareTo((Key)pq[j])<0;
		}else{
			return comparator.compare(pq[i], pq[j])<0;
		}
	}
	
	public int size() {
        return n;
    }
	
	@Override
	public Iterator<Key> iterator() {
		return new HeapIterator();
	}
	
	//迭代访问首先将原堆数据复制一份,然后执行迭代访问
	private class HeapIterator implements Iterator<Key> {
 
        // create a new pq
        private MaxPQ<Key> copy;
 
        // add all items to copy of heap
        // takes linear time since already in heap order so no keys move
        public HeapIterator() {
            if (comparator == null) copy = new MaxPQ<Key>(size());
            else                    copy = new MaxPQ<Key>(size(), comparator);
            for (int i = 1; i <= n; i++)
                copy.insert(pq[i]);
        }
 
        public boolean hasNext()  { return !copy.isEmpty();                     }
        public void remove()      { throw new UnsupportedOperationException();  }
 
        public Key next() {
            if (!hasNext()) throw new NoSuchElementException();
            return copy.delMax();
        }
    }
 
}

以上内容摘自https://blog.csdn.net/u014106644/article/details/92764446

5.java中的PriorityQueue

在Java中也实现了自己的优先队列java.util.PriorityQueue,与我们自己写的不同之处在于,Java中内置的为最小堆,然后就是一些函数名不一样,底层还是维护了一个Object类型的数组,大家可以戳戳看有什么不同,另外如果想要把最小堆变成最大堆可以给PriorityQueue传入自己的比较器,例如:

// 默认为最小堆
PriorityQueue&lt;Integer&gt; pq = new PriorityQueue&lt;&gt;();

pq.add(5);
pq.add(2);
pq.add(1);
pq.add(10);
pq.add(3);

while (!pq.isEmpty()) {
	System.out.println(pq.poll() + ", ");
}
System.out.println();
System.out.println("————————————————————————");

// 使用Lambda表达式传入自己的比较器转换成最大堆
PriorityQueue&lt;Integer&gt; pq2 = new PriorityQueue&lt;&gt;((a, b) -&gt; b - a);
pq2.add(5);
pq2.add(2);
pq2.add(1);
pq2.add(10);
pq2.add(3);

while (!pq2.isEmpty()) {
	System.out.println(pq2.poll() + ", ");
}

以上是关于堆和优先级队列2:java实现堆和优先级队列的主要内容,如果未能解决你的问题,请参考以下文章

数据结构 Java 版堆和优先级队列(超详解)

0038数据结构之堆和优先队列

如何用 Python 实现堆和优先队列?

手动实现最小堆和最大堆(优先队列)

堆和堆的应用:堆排序和优先队列

堆和优先队列