优先队列之堆的实现

[TOC]

1、优先队列

优先队列是一种高效实现插入和删除最大元素(或者是最小元素)的数据结构,堆是它的高效的实现,两种操作的时间复杂度均为O(log n)

2、典型的应用

  • 在N个数中找出最大的K个数(top k)
  • 堆排序

3、基于堆高效的实现优先队列

a、一些概念

  • 堆有序:一个二叉树的每个结点都大于它的两个子节点,则称这个二叉树堆有序 -->一个堆有序的二叉树的根节点是所有结点中最大的
  • 二叉堆:我的理解是一个堆有序的完全二叉树,简称堆。二叉堆的用数组存储(不使用数组的第一个元素),一个大小为N的完全二叉树的高度为log N

b、关键的步骤

  • 使得堆有序化的两个操作:上浮(swim)和下沉(sink)
  • 插入元素:将新的元素加到数组的末尾,并使这个元素上浮到合适的位置
  • 删除最大元素:将数组顶端的元素删去并将最后一个元素放到顶端,然后使之下沉到合适的位置

c、代码实现

/*
 * <p>
 * 优先队列的实现(基于堆)
 * <p>
 * 问题:  //items = new Item[5];    为什么这样写会报错
 */
public class MaxPQ<Item extends Comparable<Item>> {

    private int N;   //堆中元素的个数
       private int maxN;
    private Item[] items;  //存储完全二叉树的数组

    public MaxPQ(int maxN) {
        this.maxN = maxN;
        items = (Item[]) new Comparable[maxN + 1]; 
        //items = new Item[5];    
    }

    //删除最大元素
    public Item delMax() {

        if (isEmpty()) return null;
        Item max = items[1];
        items[1] = items[N--];
        items[N + 1] = null;
        sink(1);

        return max;
    }

    //插入元素
    public void insert(Item item) {
        if (item == null) throw new NullPointerException("插入元素为空!");
       /* if(N >= maxN){
            if (less(items[N],item)) items[N] = item;
        }else{
            items[++N] = item;
        }*/
        items[++N] = item;
        swim(N);
        //printAr();
    }

    private boolean less(Item item, Item item1) {
        return item.compareTo(item1) < 0;
    }

    //上浮
    private void swim(int k) {
        while (k > 1) {
            if (less(k / 2, k))
                swap(k, k / 2);
            k /= 2;
        }

        //其实这样写更好
        /*while(k > 1 && less(k / 2,k)){
            swap(k,k / 2);
            k /= 2;
        }*/
    }


    //下沉
    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;
                swap(k,j);
                k = j;
       }
    }

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

    public int size() {
        return N;
    }

    /**
     * 几个辅助函数
     */

    private void swap(int k, int i) {
        Item tmp = items[k];
        items[k] = items[i];
        items[i] = tmp;
    }

    private boolean less(int i, int k) {
        return items[i].compareTo(items[k]) < 0;
    }

    public void printAr(){
        System.out.println(Arrays.toString(items));
    }
}

4、在N个数字中找到最大的M个数大概的思路(用最小堆)

  • 将N个数字逐一插入到最小堆中; //插入
  • 如果堆中的元素的个数超过了M,则删除最小的元素,直到读完N个数据 //删除优先级最高的元素

剩下的M个元素就是N个数字中最大的M个

5、利用系统自带的PriorityQueue来实现在N个数字中找到最大的M个,这里要用最小堆

PriorityQueue represented as a balanced binary heap(平衡二叉堆),PriorityQueue默认是用最小堆的实现(java默认将最小值放到前面),在它的构造方法里面传一个Collections.reverseOrder()就是一个最大堆的实现

代码:

 /**
 *
 * 主要是为了在N个数字中找到最大的M个
 *
 * 要找到N个数字中最大的M个要用最小堆,相反要找到N个数字中最小的M个数字要用最大堆。
 *
 * 大概的思路(在N个数字中找到最大的M个):
 *
 *          将N个数字逐一插入到最小堆中;
 *          如果堆中的元素的个数超过了M,则删除最小的元素,直到读完N个数据
 *
 *    剩下的M个元素就是N个数字中最大的M个
 */
public class TestTopM {

    public static void main(String[] args) {
        TestTopM t = new TestTopM();

        //测试PriorityQueue
      // t.testPriorityQueue();

        //测试MaxPQ
        t.testMaxPQ();


        //测试排序
        // t.testOrder();


    }

    /**
     * java默认是给基本类型的数据从小到大排序
     * <p>
     * 要给基本类型的数据按从大到小的顺序来排,可以使用包装类并传Collections.reverseOrder()参数;
     * 但是这样涉及到数据的装箱与拆箱,所以建议先从小到大排序,然后再逆序
     */

    private void testOrder() {

        Integer[] ar = {1, 6, 3, 2, 10, 13, 23};
        System.out.println(Arrays.toString(ar));
        Arrays.sort(ar, Collections.reverseOrder());
        System.out.println(Arrays.toString(ar));
    }

    private void testMaxPQ() {

        int M = 5;
        MaxPQ<Integer> queue = new MaxPQ<>(M + 1);  //由于MaxPQ维护数组的第一个元素没有用到
        int[] ar = ArrayUtils.randomArray(10, 1, 15);
        ar = new int[]{1, 6, 9, 15, 5, 8, 13, 9, 11, 14};
        System.out.println("原数组 :" + Arrays.toString(ar));
        for (int i = 0; i < ar.length; i++) {

            queue.insert(ar[i]);
            if (queue.size() > M) queue.delMax();

        }

        Arrays.sort(ar);

        System.out.println("排序后的数组 :" + Arrays.toString(ar));
        System.out.println("队列的长度 :" + queue.size());

        //为了从大到小到小输出
        ArrayDeque<Integer> stack = new ArrayDeque<>();
        while(!queue.isEmpty()){
            stack.push(queue.delMax());
        }

        while (!stack.isEmpty()){
            System.out.print(stack.poll() + ",");
        }
    }

    /**
     * 利用jdk自带的PriorityQueue来实现在N个数字中找到最大的M个,这里要用最小堆
     * <p>
     *  a、PriorityQueue represented as a balanced binary heap(平衡二叉堆)
     *  b、PriorityQueue默认是用最小堆的实现(java默认将最小值放到前面),在构造方法里面传一个
     * Collections.reverseOrder()就用一个最大堆的实现
     *  c、插入元素:offer()、add()  找出并删除优先级最高的元素:poll()  找出优先级最高的元素:peek()
     */
    void testPriorityQueue() {
        int M = 4;
        //PriorityQueue<Integer> queue = new PriorityQueue<>(Collections.reverseOrder());
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        int[] ar = ArrayUtils.randomArray(10, 1, 15);
        System.out.println("原数组 :" + Arrays.toString(ar));
        for (int i = 0; i < ar.length; i++) {
            queue.offer(ar[i]);
            if (queue.size() > M) queue.poll();
        }

        Arrays.sort(ar);
        ArrayUtils.inverseArray(ar);

        System.out.println("排序后的数组 :" + Arrays.toString(ar));
        System.out.println("队列的长度 :" + queue.size());

        //为了从大到小到小输出
        ArrayDeque<Integer> stack = new ArrayDeque<>();
        while(!queue.isEmpty()){
            stack.push(queue.poll());
        }

        while (!stack.isEmpty()){
            System.out.print(stack.poll() + ",");
        }

    }
}

6、基于堆的排序

  • 思路一:利用辅助空间进行:把数组的元素依次放到一个堆里面,然后把堆中的元素放到数组中去,这样的话,从小到大排序要用到的是最小堆。这样做牺牲了一点的空间复杂度为O(N),虽然简单,但是浪费空间
  • 思路二:原地堆排序
    分为两步:
    a.原地构造堆:从完全二叉树中最后一个叶子节点的的父节点开始(一个节点的树就是一个堆),先构造以这个节点为根节点的堆,重复次步骤直至根节点;
    这样每个被遍历到的结点的子树都是一个堆,所以每次构造堆就只要一次sink操作即可
    b.下沉排序: 依次交换第一个和最后一个元素,然后下沉第一个元素使得除了被换到后面的元素堆有序,直到所有元素都到被交换过 ,由于第一个是优先级别最大的,
    所以下沉排序后的数组的顺序和原来的优先级的顺序相反
    这样的实现比较复杂,但是空间复杂度为O(1)
    c.堆排序源代码:
      /**
 *
 * 利用优先队列的高效找出最大(或者最小)元素进行排序(要用到缓存数组,不知道这样叫不叫堆排序)
 *
 */
public class HeapSort {

    public static void main(String[] args) {

        int[] ar = ArrayUtils.randomArray(10,1,20);
        //ar = new int[]{0,1,2,3,4,5,6,7,8};
        System.out.println("原数组 :" + Arrays.toString(ar));
        //sort(ar);
        sortInPlace(ar);
        System.out.println("变化后的数组:" + Arrays.toString(ar));
    }

    //使用系统自带的优先队列和实现堆排序
    public static void sort(int[] ar){
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int i : ar)   queue.offer(i);
        for (int i = 0; i < ar.length; i++)  ar[i] = queue.poll();
    }

    /**
     *  原地实现堆排序(从小到大的排序和最大的的sink一致)
     *
     *  分为两步,第一步:堆的构造,第二步:下沉排序
     *
     *  注意:这里有用数组的第一个位置来存储元素
     */
    public static void sortInPlace(int[] ar){
        int N = ar.length;
        //堆的构造
        for (int k = (N - 2) / 2; k >= 0; k--)   sink(ar,k,N - 1);

        /*System.out.println("堆有序化的数组 :" +Arrays.toString(ar));
        System.out.println("-------------------------");*/
        //下沉排序
        while(N > 0){
            ArrayUtils.swap(ar,0,-- N);
            sink(ar,0,N - 1);
        }

    }

    private static void sink(int[] ar, int from, int to) {

       // System.out.println("from =" + from);
        while((2 * from + 1) <= to){
            //System.out.println(Arrays.toString(ar) + "  ,from =" +  from);
            int k = 2 * from + 1;
            if (k < to && less(ar[k],ar[k + 1])) k ++;
            if(!less(ar[from],ar[k]))  break;
            ArrayUtils.swap(ar,from,k);

            from = k;

        }

    }

    private static boolean less(int i, int j) {
        return i < j;
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 排序的基本概念 在计算机程序开发过程中,经常需要一组数据元素(或记录)按某个关键字进行排序,排序完成的序列可用于快...
    Jack921阅读 1,421评论 1 4
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,083评论 0 12
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,245评论 0 2
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,170评论 0 52
  • 如何创造良好的工作氛围? 1、绩效管理:鼓励员工勇于面对绩效,评价是为了改进,不是为了处罚,绩效辅导大于绩效评估 ...
    企业经营管理秘籍阅读 817评论 0 1