一、堆

       堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
【数据结构】堆和优先队列

同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子 

【数据结构】堆和优先队列

 

该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

                    父亲节点:parent(i) = (i -1) / 2

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

                    小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  
代码实现最大堆:

                ①首先构造出堆的基本表示
                    
构建堆的底层采用动态数组ArrayList

public class MaxHeap<E extends Comparable<E>>{
    private ArrayList<E> data;
    public MaxHeap(){
        data = new ArrayList<>();
    }
    public MaxHeap(int capacity){
        data = new ArrayList(capacity);
    }
    //返回堆中的元素个数
    public int size(){
        return data.size();
    }
    public boolean isEmpty(){
        return data.isEmpty();
    }
    //返回index索引元素的父亲节点索引
    public int parent(int index){
        if(index == 0){
            throw  new IllegalArgumentException("root节点无父亲节点!");
        }
        return (index-1)/2;
    }
    public int leftChild(int index){
        return index*2+1;
    }
    public int rightChild(int index){
        return index*2+2;
    }
}

                ②构建堆的添加方法

                    首先将该元素添加到堆的最后一个节点(直接添加到数组末尾即可),然后通过与父节点相比较进行调整成大顶堆

 //向堆中添加元素
    public void add(E num){
        data.add(num);
        //将刚刚添加进来的元素进行调整
        adjustHeap(data.size()-1);
    }

    private void adjustHeap(int i) {
        //当i不为根节点且i对应的值大于父亲节点对应的值
        while (i>0 && data.get(i).compareTo(data.get(parent(i)))>0){
            //与父亲节点交换值
            swap(i,parent(i));
            //索引变为父亲节点的索引,向上升
            i = parent(i);
        }
    }
    //交换i,j索引处的值
    public void swap(int i,int j){
        E temp = data.get(i);
        data.set(i,data.get(j));
        data.set(j,temp);
    }

                  ③查看堆顶元素但不删除

    //查看堆顶元素但不删除
    public E findMax(){
        if(data.size() == 0)
            throw new IllegalArgumentException("Can not findMax when heap is empty.");
        return data.get(0);
    }

                  ④取出堆中最大元素 

                    步骤一:将堆顶元素与最后一个元素交换,也就是下图中的50和15交换位置,

                    步骤二:然后将堆中最后一个元素取出,在进行堆调整

【数据结构】堆和优先队列

 

    //取出堆顶元素并调整堆结构
    public E extractMax(){
        E max = findMax();
        swap(0,size()-1);
        data.remove(size()-1);
        siftDown(0);
        return max;
    }

    private void siftDown(int k) {
        //当左孩子存在时
        while(leftChild(k) < size()){
            int index = leftChild(k);
            //如果右孩子存在,且右孩子大于左孩子则用右孩子比较
            if(index+1 < size() && data.get(index+1).compareTo(data.get(index))>0){
                index++;
            }
            //如果k节点大于子孩子,则结束
            if(data.get(k).compareTo(data.get(index))>0)
                break;
            swap(k,index);
            k=index;
        }
    }

  到了这里,堆结构基本已经构建结束了,接下来进行测试以下

【数据结构】堆和优先队列

二、优先队列

普通队列:先进先出、后进后出

优先队列:出队顺序和入队顺序无关,和优先级相关

下面基于堆结构来构建一个优先队列

优先队列也是队列,首先定义一个接口,该接口有队列的常用方法

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    void enqueue(int e);
    E dequeue();
    E getFront();
}

实现上面的接口 

              下面的代码就是通过大顶堆实现的优先队列

               根据加入到优先队列中的值不同,则在优先队列中的顺序就不一样,出队顺序就会是优先级最大的。

public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxHeap<E> maxHeap;

    public PriorityQueue(){
        maxHeap = new MaxHeap<>();
    }

    @Override
    public int getSize(){
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty(){
        return maxHeap.isEmpty();
    }

    @Override
    public E getFront(){
        return maxHeap.findMax();
    }

    @Override
    public void enqueue(E e){
        maxHeap.add(e);
    }

    @Override
    public E dequeue(){
        return maxHeap.extractMax();
    }
}

注意:Java内置了优先队列,但是它的底层是最小堆实现的,使用时需注意!

相关文章: