算法-10-优先队列

久未见 提交于 2020-01-26 17:29:00

目录

1、背景

2、优先队列

3、二叉堆

4、插入函数-insert()

5、删除最大元素- delMax()

6、完整代码

7、三叉树


1、背景

考虑以下问题:输入 N 个int值,从中找出最大的(或是最小的)M 个int值。这中情景可以出现在:比如每个月的账单有很多记录,我只需要消费最大的5笔记录。那么我们的实现方法是什么呢?

方法一:将这N个int值排序,然后依次输出最大的M个。(如果数据量庞大,你得每次等到排序完之后才能拿到你想要的值,无法立即得到)

方法二:每次有新的消费记录插入的时候,都和那M(5)笔最大的记录进行比较。(除非M很小,否则代价很大)

方法三:优先队列(插入的时候就最大元素已经放在了合适的位置,等待获取)

2、优先队列

优先队列是一种抽象数据类型,和栈和队列类似,只是职能不一样。

它的主要职能是:1、删除(获取)最大的元素;

                             2、插入元素

插入和删除元素实现方式有三种:

第一种是插入的时候就排序好,这样取的时候直接拿就好了。(有序数组)

第二种是插入的时候不管,取的时候遍历拿到最大的元素。(无序数组)

第三种就是二叉堆。下面是这三种的时间复杂度。

3、二叉堆

二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。二叉堆有两种:最大堆最小堆。最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值。

二叉堆一般用数组来表示。如果根节点在数组中的位置是1,第n个位置的子节点分别在2n和 2n+1。因此,第1个位置的子节点在2和3,第2个位置的子节点在4和5。以此类推。这种基于1的数组存储方式便于寻找父节点和子节点。

下图中a[1]的子结点就是a[2]和a[3].

                                                     

4、插入函数-insert()

插入一个元素,我们会先把它放在数组的尾端,然后根据k/2找到它的父结点的下标,并和其比较,如果比a[k/2]大,那么就把父节点替换下来。这样我们就可以保证根结点a[1] (a[0]没有使用)永远是最大的元素。

                               

5、删除最大元素- delMax()

我们从数组顶端删去最大 的元素并将数组的最后一个元素放到顶端,减 小堆的大小并让这个元素下沉到合适的位置。

              

6、完整代码

public class MaxPQ {

    private static double[] a;
    private static int N = 0;

    private MaxPQ(int lenght) {
        a = new double[lenght + 1];
    }
    public void insert(double item) {
        a[++N] = item;
        swim(N);
    }
    /**
     * 删除堆中最大的元素
     */
    public double delMax() {
        double maxItem = a[1];
        a[1] = a[N--];// 先拿到a[N]元素之后,N才会减一
        a[N+1] = 0; // 如果这里是对象的话,可以用来置空释放对象的引用,防止对象游离
        sink(1);// 将新的根结点,下沉到合适位置,堆的重新排序

        return maxItem;
    }
    /**
     * 上浮
     */
    private void swim(int k) {

        while (k > 1) {
            if (a[k] > a[k / 2]) {
                exch(k, k / 2);// k/2获取到它的父结点
                k = k / 2;
            } else {
                break;
            }
        }
    }
    /**
     * 下沉
     */
    private void sink(int k) {

        while (2 * k <= N) {
            int j=2*k;
            if (j<N&&a[j]<a[j+1]) j++; //保证每次和a[k]比较的是它的两个子结点中较大的那个

            if (a[k]>a[j])break;

            exch(k,j);
            k=j;
        }
    }
    private void exch(int i, int j) {
        double temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    public static double[] getA() {
        return a;
    }
    private static void show(double[] a) {
        System.out.println("\n");
        for (double item : a) {
            System.out.print((int) item + ",");
        }
    }
    public static void main(String[] args) {
        double[] a = { 55, 43, 23, 12, 13, 11, 7, 8, 88, 6, 3, 2, 4, 1, 9, 8, 7, 11, 56, 45, 22, 23,
                45, 66 };

        MaxPQ maxPQ = new MaxPQ(a.length);
        for (double item : a) {
            maxPQ.insert(item);
        }
        
        show(getA());
        for (int i=0;i<a.length;i++){
            maxPQ.delMax();
            show(getA());
        }
    }
}

7、三叉树

我们上面的优先队列代码是利用完全二叉树的形式实现的,那么我们能不能用三叉树或者更多树来实现优先队列呢?

三叉树的父节点位于(k+1)/3 的位置,子结点位于 3k-1、3k、3k+1的位置。我们主要改动的代码就是上浮和下沉的代码。改后的代码如下:(根据下面代码的实现,我们可以实现多叉树的形式,不够性能的优劣得自己去实验)

    /**
     * 上浮
     */
    private void swim(int k) {
        while (k > 1) {
            if (a[k] > a[(k+1) / 3]) {
                exch(k, (k+1) / 3);// k/2获取到它的父结点
                k = (k+1) / 3;
            } else {
                break;
            }
        }
    }
    /**
     * 下沉
     */
    private void sink(int k) {
        int max;
        while ( (3*k-1) <= N) {
            int j=3*k-1;
            max=j;
            while (j<3*k+1&&j<N) {//保证每次和a[k]比较的是它的两个子结点中较大的那个
                if (a[j] < a[j+1]) max=j+1;
                j++;
            }
            if (a[k]>a[max])break;

            exch(k,max);
            k=max;
        }
    }

 

 

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!