数据结构与算法分析之优先队列(堆)-Java语言描述(五)

本文内容基于《数据结构与算法分析 Java语言描述》第三版,冯舜玺等译。


参考以下场景:

  • 若在打印机有空时正好有多个单页的作业及一项100页的作业等待打印,则更合理的做法也许是最后处理长的作业,尽管它不是最后提交上来的。
  • 在多用户环境中,操作系统调度程序必须决定在若干进程中运行哪个进程,一般来说,短的作业要尽可能快地结束,应该拥有优先权。

这些特殊的应用需要一类特殊的队列,称之为优先队列。

1. 模型

优先队列是允许至少两种操作的数据结构:insert以及deleteMin。

2. 实现

可以使用一个简单链表在表头以O(1)执行插入操作,并遍历该链表以删除最小元,这需要O(N)时间。

或者反过来始终让链表保持排序状态,这样插入为O(N),删除为O(1)。

另一种实现方法是使用二叉查找树,两种操作的平均运行时间都是O(logN)。

3. 二叉堆

堆有两个性质:结构性和堆序性。恰似AVL树,对堆的一次操作可能破坏这两个性质中的一个,因此堆的操作必须到堆的所有性质都被满足时才能终止。

3.1 结构性质

堆是一颗被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入,这样的树被称为完全二叉树。

因为完全二叉树很有规律,所以可以用一个数组表示:

对于数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)中,它的父亲则在位置i/2上。

这种实现方法的唯一问题在于最大堆的达到需要事先估计(如有需要,可以重新调整)。

因此,一个堆结构将由一个Comparable对象的数组和一个代表当前堆的大小的整数组成。

3.2 堆序性质

最小元应该在根上,即在一个堆中,对于每一个节点X,X的父亲的关键字小于(或等于)X中的关键字,根节点除外(无父亲)。

3.3 堆操作

3.3.1 insert

为将一个元素X插入到堆中,先在下一个可用位置创建一个空穴。如果X可以放在该空穴中而并不破坏堆序,那么插入完成。否则把空穴的父节点上的元素移入到该空穴中。这个策略叫做上滤。

3.3.2 deleteMin

当删除一个最小元时,先在根节点建立一个空穴。由于现在堆少了一个元素,因此堆中最后一个元素X必须移动到该堆的某个地方。如果X可以被放到空穴中,那么deleteMin完成。否则将空穴的两个儿子中较小者移入空穴。这个策略叫做下滤。

4. 实现代码


    /**
     * Construct the binary heap.
     */
    public BinaryHeap() {
        this(DEFAULT_CAPACITY);
    }

    /**
     * Construct the binary heap.
     * @param capacity the capacity of the binary heap.
     */
    public BinaryHeap(int capacity) {
        currentSize = 0;
        array = (AnyType[]) new Comparable[capacity + 1];
    }

    /**
     * Construct the binary heap given an array of items.
     */
    public BinaryHeap(AnyType[] items) {
        currentSize = items.length;
        array = (AnyType[]) new Comparable[(currentSize + 2) * 11 / 10];

        int i = 1;
        for (AnyType item : items) {
            array[i++] = item;
        }
        buildHeap();
    }

    /**
     * Insert into the priority queue, maintaining heap order.
     * Duplicates are allowed.
     * @param x the item to insert.
     */
    public void insert(AnyType x) {
        if (currentSize == array.length - 1) {
            enlargeArray(array.length * 2 + 1);
        }

        // Percolate up
        int hole = ++currentSize;
        for (array[0] = x; x.compareTo(array[hole / 2]) < 0; hole /= 2) {
            array[hole] = array[hole / 2];
        }
        array[hole] = x;
    }


    private void enlargeArray(int newSize) {
        AnyType[] old = array;
        array = (AnyType[]) new Comparable[newSize];
        for (int i = 0; i < old.length; i++) {
            array[i] = old[i];
        }
    }

    /**
     * Find the smallest item in the priority queue.
     * @return the smallest item, or throw an UnderflowException if empty.
     */
    public AnyType findMin() {
        if (isEmpty()) {
            throw new UnderflowException();
        }
        return array[1];
    }

    /**
     * Remove the smallest item from the priority queue.
     * @return the smallest item, or throw an UnderflowException if empty.
     */
    public AnyType deleteMin() {
        if (isEmpty()) {
            throw new UnderflowException();
        }

        AnyType minItem = findMin();
        array[1] = array[currentSize--];
        percolateDown(1);

        return minItem;
    }

    /**
     * Establish heap order property from an arbitrary
     * arrangement of items. Runs in linear time.
     */
    private void buildHeap() {
        for (int i = currentSize / 2; i > 0; i--) {
            percolateDown(i);
        }
    }

    /**
     * Test if the priority queue is logically empty.
     * @return true if empty, false otherwise.
     */
    public boolean isEmpty() {
        return currentSize == 0;
    }

    /**
     * Make the priority queue logically empty.
     */
    public void makeEmpty() {
        currentSize = 0;
    }

    private static final int DEFAULT_CAPACITY = 10;

    private int currentSize;      // Number of elements in heap
    private AnyType[] array; // The heap array

    /**
     * Internal method to percolate down in the heap.
     * @param hole the index at which the percolate begins.
     */
    private void percolateDown(int hole) {
        int child;
        AnyType tmp = array[hole];

        for (; hole * 2 <= currentSize; hole = child) {
            child = hole * 2;
            if (child != currentSize && array[child + 1].compareTo(array[child]) < 0) {
                child++;
            }
            if (array[child].compareTo(tmp) < 0) {
                array[hole] = array[child];
            } else {
                break;
            }
        }
        array[hole] = tmp;
    }

    // Test program
    public static void main(String[] args) {
        int numItems = 10000;
        BinaryHeap<Integer> h = new BinaryHeap<>();
        int i = 37;

        for (i = 37; i != 0; i = (i + 37) % numItems) {
            h.insert(i);
        }
        for (i = 1; i < numItems; i++) {
            if (h.deleteMin() != i) {
                System.out.println("Oops! " + i);
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值