Heap和Heapify

最近复习数据结构,又回去再看塞神的课件,看到PriorityQueue的实现。自己也根据塞神的代码写一写。

下面使用Binary Heap实现了一个简单的 Max-oriented PriorityQueue。

  1. 这里Binary Heap我们使用的是array represetation,数组形式。

    1. 第0个元素我们留空,从第一个元素开始存储, 第一个元素也将是PQ里最大的元素。
    2. 特点是假如父节点位置是 k, 那么两个子节点的位置就是 2 * k 和 2 * k + 1。这样很方便计算,知道父节点很容易计算出子节点,知道子节点的位置也能立刻知道父节点的位置。
    3. Max Heap服从Max heap order, 即父节点的值永远大于等于子节点的值。           (但父节点的sibling可以不大于当前父节点的子节点值)
    4. 元素最好是implements Comparable[],否则我们还要另外写Comparator<>()
  2. 一开始的构造方法里,我们为简便使用了一个定长的数组。正常来说应该使用一个resizing array,以及一个load factor。
    1. 当load factor,也就是元素数 N / 数组长度 len = 0.75的时候,我们把数组扩容一倍,然后把之前的元素拷贝进去
    2. 当load factor < 0.25的时候,我们把数组减半,也要拷贝之前的元素。
  3. 主要的一些方法有insert(),peek(), delMax()以及isEmpty(),为了测试我也放入了一些其他方法,比如shuffle(), heapify(), 和heapSort(),下面一点点来分析各个方法。
    1. insert():  每次添加元素的时候,我们都先增加PQ中元素的个数,即++N,然后把新元素x放在数组中新的这个位置上。接下来我们调用swim()方法来使PQ依然保持有序。  总复杂度是O(logn)
    2. swim():   向上维护heap order。当我们发现,或者不确定数组中一个位置的元素是否符合Max heap order时,我们需要对这个位置的值进行一个swim()操作。只用考虑子节点和父节点,不用考虑sibling.
      1. 主要操作就是将这个子节点和其父节点进行比较,假如这个位置为k的子节点的值大于其父节点,我们交换这两个节点
      2. 继续比较交换后的子节点和其新的父节点, 这个可以通过 k /= 2来完成。
      3. 遍历在 k > 1的条件下进行。因为 k > 1的时候,  k / 2最大就是1,也就是我们的最大节点
      4. 每次insert的时候我们可以使用swim()来保持heap order
    3. peek():  我们可以直接返回最大节点elements[1],注意一些边界条件,或者这个节点不存在的时候抛出Exception
    4. delMax():  删除最大节点是Max-heap的特色。             总复杂度 O(logn)
      1. 我们先交换最大节点和最后一个位置的节点,用N-- 将元素数N减少1, 并且将最大节点所在位置置为空 -  elements[N + 1] = null。 这样可以避免loitering,避免垃圾回收机制收不到这个数组。
      2. 这时我们处在elements[1]位置上的元素有可能不满足Max heap order,我们执行 sink() 方法来进行处理。
    5. sink():  向下维护heap order。 这时我们知道这个元素有可能和其两个子节点间都不满足Max heap order。我们在判断的时候要同时比较父节点和两个子节点间的大小。
      1. 假设当前父节点位置为k,那么两个可能的子节点位置为 2 * k 和 2 * k + 1。我们要先判断左子节点是否存在,也就是 2 * k 是否 <= N
      2. 在左子节点存在的条件下,我们设置 j = 2 * k,接下来我们判断右子节点是否存在,即  j是否 < N, 假如右子节点存在,我们比较左右子节点的大小,并且尝试更新j 为较大子节点的index值
      3. 接下来我们判断是否较大的子节点大于父节点的值,  假如为否,elements[j] < elements[k], 那么我们直接break
      4. 否则,我们交换 k 和 j -  swap(k, j), 并且更新k  = j, 继续下一个level的sink
    6. isEmpty():  pq是否为空,这是我们直接判断是否  N == 0
    7. swap():  交换两个节点
    8. shuffle():  这里使用了knuth shuffle。就是先用seed建立一个Random, 然后遍历数组的时候生成伪随机数,与当前index进行交换。   O(n)
    9. heapify():  这里是指最大heapify。 我们只需要从  k = N / 2开始,  在k >= 1的条件下对 k 进行sink(), 然后k--就可以了。
    10. heapSort(): 堆排序, 这里我们先对数组进行heapify(), 然后在k > 1的条件下每次把最大元素交换到数组尾部,再对位置1的元素进行sink就可以了。   in-place  O(nlogn)。
public class MaxPQ {
    public Integer[] elements;
    public int N;

    public MaxPQ(int size) {
        elements = new Integer[size + 1];
        N = 0;            // index starts with 1
    }

    public void insert(Integer x) {
        elements[++N] = x;
        swim(N);
    }

    private void swim(int k) {
        while (k > 1 && elements[k] > elements[k / 2]) {
            swap(k, k / 2);
            k /= 2;
        }
    }

    public Integer delMax() {
        Integer max = elements[1];
        swap(1, N--);
        elements[N + 1] = null;
        sink(1);
        return max;
    }

    private void sink(int k) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && elements[j] < elements[j + 1]) {
                j++;
            }
            if (elements[j] < elements[k]) {
                break;
            }
            swap(k, j);
            k = j;
        }
    }

    public Integer peek() {
        return elements[1];
    }

    public boolean isEmpty() {
        return N == 0;
    }

    private void swap(int i, int j) {
        Integer tmp = elements[i];
        elements[i] = elements[j];
        elements[j] = tmp;
    }

    public void shuffle() {            // for testing
        java.util.Random rand = new java.util.Random(System.currentTimeMillis());
        for (int i = 1; i <= N; i++) {
            int r = 1 + rand.nextInt(i);
            swap(i, r);
        }
    }

    public void heapify() {            // for testing
        for (int k = N / 2; k >= 1; k--) {
            sink(k);
        }
    }

    public void heapSort() {
        heapify();
        int n = N;
        while (n > 1) {
            swap(1, n--);
            sink(1);
        }
    }
}

上面是用Binary heap设计一个 Max-oriented Priority Queue, 数组是1-based。 假如遇到面试官问怎么heapify怎么办?  下面我们就对上面代码进行少许改动,变为0-based,可以直接对数组进行max - heapify。

  1. heapify()方法: 可以看出我们的heapify方法基本没有变化,除了把N / 2变成了数组的长度 nums.length / 2
  2. sink()方法 : 这里我们要注意一下边界条件。 先设置len = nums.length,这里len就相当于之前的N, 然后再进行比较的时候,我们要把每次的 j 都减1,从1-based改变为 0-based,其他代码都不需要改变
    public static void heapify(int[] nums) {
        if (nums == null) {
            return;
        }
        for (int k = nums.length / 2; k >= 1; k--) {
            sink(nums, k);
        }
    }

    private static void sink(int[] nums, int k) {
        int len = nums.length;
        while (2 * k <= len) {
            int j = 2 * k;
            if (j < len && nums[j - 1] < nums[j]) {
                j++;
            }
            if (nums[k - 1] > nums[j - 1]) {
                break;
            }
            swap(nums, k - 1, j - 1);
            k = j;
        }
    }

    private static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

Test Client:

public static void main(String[] args) {

        int len = 10;
        int[] nums = new int[len];
        for (int i = 0; i < len; i++) {
            nums[i] = i + 1;
        }

        shuffle(nums);

        for (int i : nums) {
            System.out.print(i + " ");
        }

        heapify(nums);
        System.out.println();

        for (int i : nums) {
            System.out.print(i + " ");
        }
    }
    

Reference:

http://algs4.cs.princeton.edu/24pq/

时间: 2024-10-20 12:37:05

Heap和Heapify的相关文章

自己实现的一个最大堆

做online judge的时候用到了最大堆,自己实现了一个,不过只适用于unsigned int类型,因为代码量比较小就都写在一起了.看有时间重写一下改成模版类吧. class MaxHeap { private: int heap[MAX]; int len; public: MaxHeap() { len = 0; memset(heap, 0, MAX); } MaxHeap(uint *arr, int n){ len = n; memset(heap, 0, MAX); build(

python[数据]--队列,堆,优先级队列

队列:from collections import deque:实现保存最后几条历史记录,list = deque(maxlen=6),那么超过六条记录会删除之前的记录. 堆:import heapq;最大特点是第一弹出的元素总是堆中最小的元素:list=[1,2,3] heap=heapq.heapify(list) ,nlargest(3,数据,key=lambda) nsmallest() 优先级队列:堆中的元素(-优先级,序号,item)这样即可实现优先级,优先级越高最先pop出堆,优

codevs 2830 蓬莱山辉夜

2830 蓬莱山辉夜 http://codevs.cn/problem/2830/ 题目描述 Description 在幻想乡中,蓬莱山辉夜是月球公主,居住在永远亭上,二次设定说她成天宅在家里玩电脑,亦称NEET姬一天,她要她帮忙升级月球的网络服务器,应为注册用户过多(月兔和地球上的巫女都注册了--),所以作为代理管理员(俗称网管)的她,非常蛋疼.注册用户格式:TouhouMaiden 2004 200其中前面的Touhoumaiden是预设,不做更改,第一个数是标识,第二个数是每次接受信息访问

【上海交大oj】合并果子(堆数据结构)

4012. 合并果子 Description 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和.可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了.多多在合并果子时总共消耗的体力等于每次合并所耗体力之和. 因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力.假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计

模板总复习

啊还有十天不到就要noip提高组了,还是觉得好慌张,周围一大堆大佬,唔菜鸡还是背背模板吧. 每天一个部分好啦. 第一部分:数论+线段树+树状数组+rmq+最短路+最小生成树(是不是觉得非常的繁杂哈哈哈我就喜欢先上一大堆最主要的) 快速幂 用途:用来计算a^b mod n的值,且复杂度为log级 假设我们要求a^b,那么其实b是可以拆成二进制的,该二进制数第i位的权为2^(i-1),例如当b==11时a^11=a^(2^0+2^1+2^3) 11的二进制是1011,11 = 23×1 + 22×0

Lintcode: Heapify &amp;&amp; Summary: Heap

Given an integer array, heapify it into a min-heap array. For a heap array A, A[0] is the root of heap, and for each A[i], A[i * 2 + 1] is the left child of A[i] and A[i * 2 + 2] is the right child of A[i]. Example Given [3,2,1,4,5], return [1,2,3,4,

二叉堆(binary heap)

堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权.堆即为解决此类问题设计的一种数据结构. 本文地址:http://www.cnblogs.com/archimedes/p/binary-heap.html,转载请注明源地址. 逻辑定义 n个

[Python] Heap Sort in Python

代码: #! /usr/bin/env python #coding=utf-8 import random,copy def heap_sort_helper(lst,left,right): # max heapify current_value = lst[left] child = 2 * left + 1 while (child <= right): if (child < right and lst[child] < lst[child+1]): child = child

[leetcode]Merge k Sorted Lists @ Python [基础知识: heap]

原题地址:https://oj.leetcode.com/problems/merge-k-sorted-lists/ 题意:Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 解题思路: 归并k个已经排好序的链表, 使用堆这一数据结构. 堆,也叫做:priority queue 首先将每条链表的头节点进入堆中. 然后将最小的弹出,并将最小的节点这条链