数据结构与算法(23)——优先队列和堆

来源:互联网 发布:怎么举报网络诈骗 编辑:程序博客网 时间:2024/05/01 03:23
优先队列(Priority Queue)是一种数据结构,它支持插入(Insert)操作和删除最小值(DeleteMin)或删除最大值(DeleteMax)并返回删除元素操作。优先队列的这些操作等价于队列的EnQueue和DeQueue操作。区别在于,对于优先队列,元素进入队列的顺序可能与其被操作的顺序不同。简单来说就是优先队列不一定满足先入先出的原则,而是根据元素的优先级来进行操作。如果最小键值元素拥有最高的优先级,那么这种优先队列叫作升序优先队列(即总是先删除最小的元素)如果最大键值元素拥有最高的优先级,那么这种优先队列叫作降序优先队列(即总是先删除最大的元素)
  • 优先队列的主要操作
  • 优先队列是元素的容器,每个元素有一个相关的键值。
  • Insert(key, data):插入键值为key的数据到优先队列,元素以其key进行排序。
  • DeleteMin/DeleteMax:删除并返回最小/最大键值的元素
  • GetMin/GetMax:返回最小/最大键值的元素,但不删除该元素
  • 第k最小/最大:返回优先队列中键值为第k个最小/最大的元素
  • 大小(Size):返回优先队列中的元素个数
  • 堆排序(Heap Sort):基于键值的优先级将优先队列中的元素进行排序

    优先队列的实现主要讨论二叉堆的实现方式

    堆(Heap)是一棵具有特定性质的二叉树。
    堆的基本要求是堆中所有节点的值必须大于等于(或小于等于)其孩子节点的值。
    堆还要求当h>0时,所有的叶子节点都处于第h或h-1层(其中h为树的高度),也就是说堆是一棵完全二叉树。

            7                           1       / \                         / \      3   6                       5   2     / \ /                       / \ / \    1  2 4                      6  1 1  3该树是堆,每个节点元素都        这棵树不是堆,因为5大于其右孩子1大于其孩子节点的元素

堆的类型:
最小堆:节点值必须小于等于其孩子节点的值
最大堆:节点值必须大于等于其孩子节点的值

            1                           17           / \                         / \          15  2                       13  6         / \ / \                     / \ / \        16 174  3                   1  4 2  3         最小堆                        最大堆

在二叉堆(Binary Heap)中,每个节点最多有两个孩子。一般在优先队列中二叉堆称作堆(Heap)。
堆的表示:一般使用数组来存储堆中的元素。例如,最大堆可以表示如下:

data    17  13  6   1   4   2   3        -------------------------index   0   1   2   3   4   5   6

堆相关的代码,如下:

/** * 最大堆 */public class Heap {    // 堆元素数组    private int[] array;            // 堆中元素的个数    private int size;    // 堆的大小    private int capacity;    // 堆的类型     private String heap_type;    /**     * 构造方法:根据大小和类型创建堆     *      * @param capacity 堆的大小     * @param heap_type 堆的类型     */    public Heap(int capacity, String heap_type) {        this.size = 0;        this.capacity = capacity;        this.heap_type = heap_type;        this.array = new int[capacity];    }    /**     * 根据节点的位置获取双亲结点的的位置     *      * 对于第i个位置上的节点,则其双亲节点在(i-1)/2位置上     * @param index 节点的位置     * @return 双亲结点的位置     */    public int getParentByIndex(int index) {        if ((index < 0) || (index >= this.size)) {            return -1;        }        return (index - 1)/2;    }    /**     * 根据节点位置获取左孩子节点的位置     *      * 对于第i个位置上的节点,则其左孩子节点在2*i+1位置上     * @param index 节点位置     * @return 左孩子节点位置     */    public int leftChild(int index) {        int left = 2 * index + 1;        if (left >= this.size) {            return -1;        }        return left;    }    /**     * 根据节点位置获取右孩子节点的位置     *      * 对于第i个位置上的节点,则其右孩子节点在2*i+2位置上     * @param index 节点位置     * @return 右孩子节点位置     */    public int rightChild(int index) {        int right = 2 * index + 2;        if (right >= this.size) {            return -1;        }        return right;    }    /**     * 获取最大元素     *      * 最大堆中的最大元素就是根节点,所以其存储在数组0号位置     * @return 最大元素     */    public int getMax() {        if (this.size == 0) {            return -1;        }        return this.array[0];    }    /**     * 堆化当前元素     *      * 当插入或删除一个堆元素时,它可能不满足堆的性质。     * 在这种情况下就需要调整堆中的元素使之重新变成堆。这一过程叫作堆化(heapifying)     * 在最大堆中,要堆化一个元素,需要找到其孩子节点中的最大值,然后交换元素,重复该过程直到每个节点都满足堆的性质。     * 这一过程是自顶向下,所以也叫作向下渗透。     * @param index 当前元素的位置     */    public void percolateDown(int index) {        int max = 0;        /* 先获取当前节点的孩子节点 */        int left = leftChild(index);        int right = rightChild(index);        /* 比较当前节点与其孩子节点的值 */        if ((left != -1) && (this.array[left] > this.array[index])) {            max = left;        } else {            max = index;        }        if ((right != -1) && (this.array[right] > this.array[max])) {            max = right;        }         /* 如果当前节点不是最大值则交换元素 */        if (max != index) {            int temp = this.array[index];            this.array[index] = this.array[max];            this.array[max] = temp;            /* 递归堆化 */            percolateDown(max);        }     }    /**     * 删除堆元素     *      * 删除堆元素,只需要从根节点删除元素。     * 删除根节点后,将堆的最后一个元素复制到根节点位置,然后删除最后一个元素。     * 当用最后一个元素替换根节点后,可能导致二叉树不满足堆的性质,所以需要堆化根节点元素。     * @return 删除的元素     */     public int deleteMax() {        /* 先判断堆是否存在 */        if (this.size == 0) {            return -1;        }        // 获取根节点元素        int data = this.array[0];        // 将最后一个元素复制到第一个元素位置        this.array[0] = this.array[this.size - 1];        // 堆的大小减小        this.size--;        /* 堆化根节点元素,即数组第一个位置的元素 */        percolateDown(0);        // 返回删除的元素        return data;    }    /**     * 插入元素     *      * 堆的大小增加一     * 将新元素放到堆的尾部     * 从下至上的堆化该元素,也称作向上渗透     * @param data 插入的新元素     */    public void insert(int data) {        /* 如果数组容量不够则扩容 */        if (this.size == this.capacity) {            resizeHeap();        }        // 数组元素个数加一        this.size++;        // 新元素的位置        int index = this.size - 1;        /* 堆化元素,向上渗透 */        while ((index >= 0) && (data > this.array[(index - 1) / 2])) {            // 当新元素大于其双亲结点,复制双亲结点到新元素位置            this.array[index] = this.array[(index - 1) / 2];            // 更新新元素位置            index = (index - 1) / 2;        }        this.array[index] = data;    }    /**     * 清空堆     */    public void destroyHeap() {        this.size = 0;        this.array = null;    }    /**     * 数组建堆     *      * 先将数组元素插入空堆中,不考虑堆的性质。     * 因为叶子节点总是满足堆的性质,无需关注它们。     * 叶子节点元素总是在最后面,因此要对给定的数组建堆,只需要堆化叶子节点即可。     *      * 堆的最后一个元素所处的位置为size-1,通过最后一个元素的双亲节点就能找到第一个非叶子节点((size-1)-1)/2。     * @param heap 堆     * @param array 数组     */    public void buildHeap(Heap heap, int[] array) {        // 获取数组的大小        int n = array.length;        /* 如果堆不存在则返回 */        if (heap == null) {            return;        }        /* 如果堆的容量不够则扩容 */        while (n > this.capacity) {            heap.resizeHeap();        }        /* 先将数组元素存入堆中 */        for (int i = 0; i < n; i++) {            this.array[i] = array[i];        }        // 设置堆的大小        this.size = n;        // 堆化非叶子节点元素        for (int i = (n-2)/2; i >= 0; i--) {            heap.percolateDown(i);        }    }    /**     * 堆排序     *      * 堆排序算法首先将所有元素插入堆中,然后从堆中依次删除根节点元素,直到堆为空     * @param array 无序数组     * @return 排序后的数组     */    public int[] heapSort(int[] array) {        // 获取数组大小        int n = array.length;        // 创建堆        Heap heap = new Heap(n, "big");        // 数组建堆        heap.buildHeap(heap, array);        int temp = 0;        for (int i = 0; i < n; i++) {             // 获取根节点元素            temp = heap.array[0];            // 将最后一个元素复制到第一个元素位置            heap.array[0] = heap.array[heap.size - 1];            // 堆的大小减小            heap.size--;            /* 堆化根节点元素,即数组第一个位置的元素 */            heap.percolateDown(0);            // 存储删除的元素            array[i] = temp;        }        return array;    }    /**     * 内部方法:堆扩容     */    private void resizeHeap() {        // 创建新的数组,大小为原来数组的两倍        int[] newArray = new int[2 * capacity];        // 复制原数组到新数组        System.arraycopy(this.array, 0, newArray, 0, this.size);        // 重置数组容量        capacity = capacity * 2;        // 复制给原数组,也就是使array指向新生成内存        array = newArray;    }    getter/setter......}
阅读全文
0 0