2.7 二叉堆及优先队列

一.二叉树

1.首先介绍一下二叉树

(1)二叉树:每个节点最多有两个子树的结构

(2)完全二叉树:叶节点只能出现在最下层和次下层,并且最下一层的节点集中在该层最左边的若干位置的二叉树

(3)满二叉树:叶节点只能出现在最下层

2.7 二叉堆及优先队列

二.二叉堆(堆)

1.堆有序:当一棵二叉树的每个结点都不小于它的两个子结点时,称为堆有序

2.二叉堆:一组能够用堆有序的完全二叉树排序的元素,并且能在数组中按照层级存储。

3.说明:

(1)二叉堆可以用数组来表示,从1-N,根节点位置在a[1]

(2)二叉堆可以不使用指针而是简单的使用数学关系,来表示结构

(3)位置k的结点的父节点是(int)k/2,两个子结点的位置是2k和2k+1

(4)一颗大小为N的完全二叉树的高度是|_lgN_|

2.7 二叉堆及优先队列

4.堆的维护

(1)上浮。当某个结点比父结点更大时,需要交换它和父结点的位置,并继续判断,直到比父结点小。

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

2.7 二叉堆及优先队列

(2)下沉。当某个结点比它的两个子结点或其中一个子结点小,需要将父结点与子结点中较大的一个进行交换,重复该过程,直到比子结点都大。

private void sink(int k) {
        while(2*k<=N) {
            int j=2*k;
            //考虑到只有左结点的情况
            //如果有2个结点,则取最大的结点
            if(j<N && less(j,j+1)) j++;
            //比较子结点和k的大小
            if(less(j,k)) break;
            exch(j,k);
            k=j;
            
        }
    }
View Code

2.7 二叉堆及优先队列

三.优先级队列

1.能够进行删除最大元素和插入元素的操作的数据结构

2.方法1:将元素进行排序,然后就可以用普通队列的方式,从队尾插元素,从队首取元素

   方法2:使用无序数组的方式,在a[N]出插入元素,删除最大元素时,需要遍历数组

   方法3:利用堆来实现优先队列

3.性能比较

2.7 二叉堆及优先队列

4.使用无序数组实现优先队列

package com.cx.sort;

//可变数组构建的优先级队列
public class UnorderedMaxPQ<T extends Comparable<T>> {

    private T[] pq;
    private int N;
    
    public UnorderedMaxPQ(int capacity) {
        pq=(T[])new Comparable[capacity];
    }
    public boolean isEmpty() {
        return N==0;
    }
    public int size() {
        return pq.length;
    }
    public void insert(T x) {
        //如果pq容量不够,将pq容量变为2倍
        if(N==pq.length) resize(2*N);
        pq[N++]=x;
    }
    public T delMax() {
        //初级优先队列,是遍历数组找到最大的元素,然后删除
        int max=0;
        for(int i=1;i<N;i++) {
            if(less(max,i)) max=i;
        }
        exch(max,N-1);
        T delVal=pq[--N];
        //避免对象游离
        pq[N]=null;
        //删除完后,如果数组长度过大(1/4满),将数组长度减半
        if(N>0 && N<pq.length/4) resize(pq.length/2);
        return delVal;
    }
    
    private boolean less(int i,int j) {
        return pq[i].compareTo(pq[j])<0;
    }
    private void exch(int i,int j) {
        T temp=pq[i];
        pq[i]=pq[j];
        pq[j]=temp;
    }
    //可变数组
    private void resize(int capacity) {
        T[] temp=(T[])new Comparable[capacity];
        for(int i=0;i<N;i++)
            temp[i]=pq[i];
        pq=temp;
    }

}
View Code

5.使用堆实现优先队列:充分利用上一段指出的堆的维护的特点

(1)当插入元素时,将元素放于数组的末尾,然后利用swim,即可继续维持堆的结构

(2)当删除元素时,优先级最高的元素时根的元素即a[1]。将a[1]与末尾元素交换,对刚变为根节点的元素使用sink,即可继续维持堆的结构。并且成功找到并删除了优先级最高的结点

(3)由于堆是从数组的1到N的,所以在调整数组大小的时候,有很多需要注意的地方,具体实现如下:

public class MaxPQ <T extends Comparable<T>>{
    private T[] pq;
    //元素存在pq[1..N]
    private int N=0;
    
    public MaxPQ(int capacity) {
        pq=(T[])new Comparable[capacity+1];
    }
    public boolean isEmpty() {
        return N==0;
    }
    //pq中有效元素的个数
    public int size() {
        return N;
    }
    //pq的真实容量
    public int length() {
        return pq.length;
    }
    public void insert(T v) {
        //如果容量不够,变为2倍
        if(N==pq.length-1) resize(2*N);
        pq[++N]=v;
        swim(N);
    }
    public T delMax() {
        T max=pq[1];
        exch(1,N);
        pq[N--]=null;
        sink(1);
        //如果容量过大,变为一半
        if(N>0 && N<pq.length/4) resize(pq.length/2); 
        return max;
    }
    //将对象上浮
    private void swim(int k) {
        while(k>1 && less(k/2,k)) {
             exch(k,k/2);
             k=k/2;
        }
    }
    //将对象下沉
    private void sink(int k) {
        while(2*k<=N) {
            int j=2*k;
            //考虑到只有左结点的情况
            //如果有2个结点,则取最大的结点
            if(j<N && less(j,j+1)) j++;
            //比较子结点和k的大小
            if(less(j,k)) break;
            exch(j,k);
            k=j;
            
        }
    }
    private boolean less(int i,int j) {
        return pq[i].compareTo(pq[j])<0;
    }
    private void exch(int i,int j) {
        T temp=pq[i]; pq[i]=pq[j]; pq[j]=temp;
    }
    //可变数组
    private void resize(int capacity) {
        T[] temp=(T[])new Comparable[capacity+1];
        //这一步注意是从1..N,容易把pq[N]变为空了
        for(int i=0;i<=N;i++)
            temp[i]=pq[i];
        pq=temp;
    }
    public void show() {
        for(T t:pq) {
            System.out.print(t+" ");
        }
        System.out.println();
    }
}
View Code

6.测试代码如下:

package com.cx.sort;

public class TestPQ {

    public static void main(String[] args) {
//        UnorderedMaxPQ<String> pq1=new UnorderedMaxPQ<>(5);
        MaxPQ<String> pq1=new MaxPQ<>(2);
        pq1.insert("c");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.insert("a");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.insert("d");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.insert("m");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.insert("r");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.show();
        pq1.insert("z");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.show();
        pq1.insert("f");
        System.out.println(pq1.size()+" "+pq1.length());
        pq1.show();
        while(pq1.size()>0) {
            System.out.println(pq1.size()+" "+pq1.length());
            pq1.delMax();
            pq1.show();
        }
    }

}
View Code

7.说明:

对于一个含有N个元素的基于堆的优先队列,插入元素操作只需要不超过(lgN+1)次比较,删除最大元素的操作需要不超过2lgN次比较

相关推荐