最大堆应用: 堆排序 --- Java版

堆定义

生活中需要使用优先队列, 比如cpu调度算法,线程调度算法都需要把优先级高的任务装入一个优先队列PriorityQueue。这个需求是很频繁的。优先级队列其实就是最大最小堆,本文的堆都是二叉堆。

堆定义: 当一棵完全二叉树的每一个节点都大于(小于)等于它的两个子节点,那么它就是最大(小)堆。

最大堆堆算法

我们以最大堆为例子,用N+1的数组pq[N+1]表示容量为N的堆。pq[0]作为哨兵不使用,填入数组元素数量或者其他范型值。元素存在pq[1]~pq[N]中。最大堆这种数据结构最主要的方法是:

  • 创建堆
  • 插入堆元素
  • 删除堆元素

最大堆需要实时维护最大堆性质,比如下图:


heap_insert.png

index=6的地方需要插入58, 但是插入后58比父节点31大也比爷爷节点44大,所以需要循环一次次维护新节点插入以后堆的性质。

最大堆插入/删除元素操作需要自下而上的堆有序化(上浮)和自上而下的堆有序化(下沉)函数来帮助完成:

// 上浮
private void swim(int k) {
    while(k > 1 && less(k/2, k))  { //index=k/2的元素小于index=k的元素
        swap(k/2, k);               //交换index=k/2和index=k的元素
        k = k/2;
    }
}

// 下沉
private void sink(int k) {
    while(2*k <= N) {
        int j = 2*k;
        if(j < N && less(j, j+1))  //取得k节点的两个子节点中大一点的那个节点的下标
          j++;
        swap(k, j);                //交换下沉节点和那个子节点的元素
        k = j;
    }
}

最大堆堆代码

public class MaxHeap<T extends Comparable<T>> {
    private T[] pq;
    private int N = 0;  //元素存在于PQ[1]~PQ[N]中,pq[0]存放堆中元素数量

    public MaxHeap(int capacity) {
        pq = (T[]) new Comparable[capacity + 1];
    }

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

    public int size() {
        return N;
    }

    public void insert(T e) {
        pq[++N] = e;
        swim(N);
    }

    public T deleteMax() {
        T max = pq[1];              //下标1的节点是最大值
        swap(1, N);                 //将第一个元素和最后一个元素交换
        pq[N] = null;               //GC
        sink(1);                 //恢复删除以后堆的有序
        --N;
        return max;
    }

    private void swim(int k) {
        while(k > 1 && less(k/2, k))  { //index=k/2的元素小于index=k的元素
            swap(k/2, k);               //交换index=k/2和index=k的元素
            k = k/2;
        }
    }

    private void sink(int k) {
        while(2*k <= N) {
            int j = 2*k;
            if(j < N && less(j, j+1))  //取得k节点的两个子节点中大一点的那个节点的下标
                j++;
            swap(k, j);                //交换下沉节点和那个子节点的元素
            k = j;
        }
    }

    // swap函数和less函数自己去实现,这里不展开。
}




最大(小)堆的实际应用: 堆排序

  由于每次出队的都是在剩下元素里面最大(小)的, 所以只要把数组的元素放到一个pq里, 然后依次poll出来, 得到的序列就是排序好了的。
  不管是插入还是删除操作, 每次调整的复杂度为log(h) (堆的高度), 所以算法复杂度为 O(NlgN). 实际使用的时候效率比快速排序/合并排序略差。
  heapsort里第一步是要建立一个最大堆,最直白的建堆操作就是和上文一样新建一个空的数组然后不断向里面加入元素,同时维护堆(空间复杂度N, 时间复杂度NlgN),但其实这个操作可以做的更好:我们先直接把数组a当作最大堆pq[]数组 , 现在显然它不满足最大堆性质, 只需要 多次使用sink()进行调整即可。

  假设堆数组维护的完全二叉树一共有h(=lgN)层, 由于最后一层的节点不必调用sink(), 我们只要从倒数第二层开始调用sink()即可, 结合前面提到的最大堆pq的性质(N/2以后的节点都在最后一层), 写法很简单(简单起见认为pq数组也是把第0个元素空出来好了):

public class HeapSort {

    public static void sort(Comparable[] pq) {
        int N = pq.length - 1;    //index=0的元素不使用,N是最后一个的index
        buildHeap(pq, N);
        while (N > 1) {
            swap(pq, 1, N);
            sink(pq, 1, --N);
        }
    }

    // 从原始数组里面建立最大堆
    // 这个操作的时间复杂度是O(N)的 ! 为什么呢?
    // 第k层节点有2^k个节点, 这一层的节点向下调整最多会进行h-k步, 所以计算量是一个求和表达式:
    // Sigma( 2^k * (h-k) ) for k=0,...,h-1 = h + 2(h-1) + 4(h-2) + 2^h(0) = 2^h+1 - h - 2 = N - (h - 1) <= N
    // 复杂度为O(N)
    private static buildHeap(Comparable[] pq, int N) {
        for (int i = N / 2; i >= 1; i--) {
            sink(pq, i, N);
        }
    }

    // logN复杂度
    private static void sink(Comparable[] pq, int k, int N) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && less(pq, j, j + 1))  //取得k节点的两个子节点中大一点的那个节点的下标
                j++;
            if (!less(pq, k, j))
                break;
            swap(pq, k, j);                //交换下沉节点和那个子节点的元素
            k = j;
        }
    }

    private static boolean less(Comparable[] pq, int i, int j) {
        if (pq[i].compareTo(pq[j]) < 0)
            return true;
        else
            return false;
    }

    private static void swap(Comparable[] pq, int i, int j) {
        Comparable t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    }
}

Heap construction uses ≤ 2 N compares and exchanges.

Heapsort uses ≤ 2 N lg N compares and exchanges.

Not Stable algorithm

堆排序算法过程(demo过程)

  第一步: 最大堆Heap的建立。
private static void buildHeap().

左上角图是原始数组.png

  第二步: 循环sortdown步骤:

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

推荐阅读更多精彩内容

  • 排序的基本概念 在计算机程序开发过程中,经常需要一组数据元素(或记录)按某个关键字进行排序,排序完成的序列可用于快...
    Jack921阅读 1,416评论 1 4
  • 四. 走向世界之巅——快速排序 你可能会以为归并排序是最强的算法了,其实不然。回想一下,归并的时间效率虽然高,但空...
    Leesper阅读 1,612评论 9 7
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,255评论 0 35
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,164评论 0 52
  • 大抵我是不适合做设计的。 最基本的专业课,我也学不好,也不感兴趣…… 我想,要是我出来当设计师,那肯定第一天就会被...
    简书客服人员177号阅读 389评论 3 3