《算法》2.4-优先队列

1. 基本概念

比如:输入N个字符串,我们需要找打最大的M个字符串。我们可以将N个字符串进行排序,然后取最大的M个。也可以将新的输入和已知的M个最大字符串进行比较。只要我们用优先队列能高效的实现insert()和delMin()就能解决该问题。


《算法》2.4-优先队列_第1张图片
API
public class TopM {   

    // This class should not be instantiated.
    private TopM() { }

    /**
     *  Reads a sequence of transactions from standard input; takes a
     *  command-line integer m; prints to standard output the m largest
     *  transactions in descending order.
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) {
        int m = Integer.parseInt(args[0]); 
        MinPQ pq = new MinPQ(m+1);

        while (StdIn.hasNextLine()) {
            // Create an entry from the next line and put on the PQ. 
            String line = StdIn.readLine();
            Transaction transaction = new Transaction(line);
            pq.insert(transaction); 

            // remove minimum if m+1 entries on the PQ
            if (pq.size() > m) 
                pq.delMin();
        }   // top m entries are on the PQ

        // print entries on PQ in reverse order
        Stack stack = new Stack();
        for (Transaction transaction : pq)
            stack.push(transaction);
        for (Transaction transaction : stack)
            StdOut.println(transaction);
    } 
} 

2. 堆的定义

定义:当一颗二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。根节点是堆有序的二叉树中的最大结点。
堆的表示:
①按照树的层级结构来存储
②不使用数组中的第一个位置。
③一颗大小为N的完全二叉树的高度为lgN向下取整。当N的数量达到2的整数次幂时树的高度加1
④位置k的结点的父节点是⎣ k/2⎦ ,子节点分别为2k或2k+1


《算法》2.4-优先队列_第2张图片
堆的表示

高度=⎣ lg 11 ⎦ 向下取整=3

3. 堆的算法

我们用长度为N+1的pq[]来表示一个大小为N的堆,不使用pq[0]。

由下至上的堆有序化(上浮)
某个结点比自己的父结点更大,交换它和它的父节点,直到满足堆有序。

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

《算法》2.4-优先队列_第3张图片
上浮

由上至下的堆有序化(下沉)
某个结点比它的两个子结点或是其中之一要小,将它与两个子节点中较大的值进行交换,直到保证堆有序。

 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;
        }
    }

《算法》2.4-优先队列_第4张图片
下沉

插入元素
将新的元素添加到数组末尾,增加堆的大小,不断上浮

《算法》2.4-优先队列_第5张图片
插入元素

删除最大的元素
从数组顶端删除最大的元素,并将数组的最后一个元素放到顶端,减小堆的大小,并让这个元素下沉到合适的位置。
《算法》2.4-优先队列_第6张图片
删除最大的元素

基于堆的优先队列

public class MaxPQ>
{
    private Key[] pq; // heap-ordered complete binary tree
    private int N = 0; // in pq[1..N] with pq[0] unused
    public MaxPQ(int maxN)
    { pq = (Key[]) new Comparable[maxN+1]; }
    public boolean isEmpty()
    { return N == 0; }
    public int size()
    { return N; }
    public void insert(Key v)
    {
        pq[++N] = v;
        swim(N);
    }
    public Key delMax()
    {
        Key max = pq[1]; // Retrieve max key from top.
        exch(1, N--); // Exchange with last item.
        pq[N+1] = null; // Avoid loitering.
        sink(1); // Restore heap property.
        return max;
    }
    // See pages 145-147 for implementations of these helper methods.
    private boolean less(int i, int j)
    private void exch(int i, int j)
    private void swim(int k)
    private void sink(int k)
}

⑥性能分析
长度为N的基于堆的优先队列,插入元素操作只需要不超过lgN+1次比较,删除最大元素操作,不超过2lgN次比较(确定是否上浮和确定子结点中的最大值)。

4. 索引优先队列

可以当做:能够快速访问其中最小元素的数组。you can think of an IndexMinPQ named pq as representing a subset of an array pq[0..N-1] of items. Think of the call pq.insert(k, item) as adding k to the subset and setting pq[k] = item and the call pq.change(k, item) as setting pq[k] = item。
问题:多个有序的输入流如何归并成一个有序的输出流?

public class Multiway { 

    // This class should not be instantiated.
    private Multiway() { }

    // merge together the sorted input streams and write the sorted result to standard output
    private static void merge(In[] streams) {
        int n = streams.length;
        IndexMinPQ pq = new IndexMinPQ(n);
        for (int i = 0; i < n; i++)
            if (!streams[i].isEmpty())
                pq.insert(i, streams[i].readString());

        // Extract and print min and read next from its stream. 
        while (!pq.isEmpty()) {
            StdOut.print(pq.minKey() + " ");
            int i = pq.delMin();
            if (!streams[i].isEmpty())
                pq.insert(i, streams[i].readString());
        }
        StdOut.println();
    }
public static void main(String[] args) {
        int n = args.length;
        In[] streams = new In[n];
        for (int i = 0; i < n; i++)
            streams[i] = new In(args[i]);
        merge(streams);
    }

命令行:java Multiway m1.txt m2.txt m3.txt
A A B B B C D E F F G H I I J N P Q Q Z
% more m1.txt
A B C F G I I Z
% more m2.txt
B D H P Q Q
% more m3.txt
A B E F J N

关键:将多路输入和索引相关连,每次都从删除最小的元素的输入流中继续添加元素。

5. 堆排序

堆的构造
方法1:构造N个元素的堆,从左至右依次将每个元素添加至数组的末尾,然后不断的上浮,直到将N个元素都添加至堆中。NlgN
方法2:从右至左不断下沉构造堆。
**利用下沉操作构建堆只需要2N比较和N交换。

《算法》2.4-优先队列_第7张图片
i堆排序

将堆中最大元素删除,然后放入堆缩小后数组中空出的位置

 public static void sort(Comparable[] pq) {
        int n = pq.length;
        for (int k = n/2; k >= 1; k--)
            sink(pq, k, n);
        while (n > 1) {
            exch(pq, 1, n--);
            sink(pq, 1, n);
        }
    }

分析
将N个元素排序,堆排序只需要少于2NlgN+2N次比较,以及一半的交换次数。2N来自于堆的构建,2NlgN来自于每次下沉操作最大可能次数。
堆排序是唯一可以同时利用空间和时间的方法,但是许多应用中很少使用它,原因是堆排序没有利用缓存,数组元素很少和相邻的其他元素进行比较,因此缓存未命中的次数远远高于其它的算法(快速、归并、甚至希尔)

你可能感兴趣的:(《算法》2.4-优先队列)