【排序】堆、完全二叉树、堆排序、PriorityQueue、TopK

可以利用堆、外排序的方法来做多个处理单元的结果合并

写在前面:

堆通常是一个可以被看做一棵`完全二叉树的数组对象`,
小根堆,最小值在`arr[0]`;    大根堆,最大值在`arr[0]`
插入节点的时间复杂度是`O(log(n))`,获取最值的时间复杂度是`O(1)`

堆,其实就是一个完全二叉树;
完全二叉树,要么是一个满二叉树,要么叶子节点层为从左到右;
堆,实际中是可以用数组实现的;

规律,把数组脑补成完全二叉树
节点 n 的左孩子为 2*n + 1,也就是 n<<1 +1
节点 n 的左孩子为 2*n + 2,也就是 n<<1 +2
节点 n 的父节点为 (n-1)/2,也就是 (n-1)>>2

堆分为小根堆、大根堆
小根堆:在完全二叉树中,任何一个子树的最小值都在头部,小根堆,数组最小值在arr[0]
大根堆:在完全二叉树中,任何一个子树的最大值都在头部,大根堆,数组最大值在arr[0]

堆排序

思想:1、根据大根堆的特点,先将一个数组构建成为一个大根堆,这样堆顶就是最大的
2、再将堆顶和最后一个数互换,将最大的数放到最后,随即调整维持大根堆
继续互换、调整。

    public void heapSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        // 1、将整个数组构建成为大根堆数组
        for (int i = 1; i < arr.length; i++) {
            maxHeapInsert(arr, i);
        }
        // 2、将大根堆对顶(index为0)的数换到最后去,再heapify调整大根堆
        for (int i = arr.length - 1; i >= 0; i--) {
            swap(arr, 0, i);
            heapify(arr, 0, i - 1);
        }
    }
    /**
     * 在arr尾部新增一个数,继续保持arr是大根堆
     * arr 在 p 之前已经是一个大根堆了,现在加入的 p 的数,需要维持 [a,p]是一个大根堆
     * 实现原理:联想大根堆的特点,插入的数如果比父节点大,则向上交换,一直到小于父节点或者堆顶
     * 下标小于0 的情况考虑到的
     *
     * @param arr 传入的数组
     * @param p   指针,表示即将加入的节点
     */
    private void maxHeapInsert(int[] arr, int p) {
        while (arr[p] > arr[(p - 1) / 2]) {
            swap(arr, p, (p - 1) / 2);
            p = (p - 1) / 2;
        }
    }

    /**
     * 大根堆位于index的数与最后一个数互换之后,调整arr继续保持大根堆
     * 在本例堆排序中,是一直将大根堆的最后一个和第一个互换,也就是将最大的值放到最后
     *
     * @param arr       传入的数组
     * @param index     被改变的数的下标,将一直为0
     * @param lastIndex 大根堆的最后一个index
     */
    private void heapify(int[] arr, int index, int lastIndex) {
        // index的左孩子
        int left = index * 2 + 1;
        while (left <= lastIndex) {
            // 判断是否有右孩子,如果有比较左右孩子的大小,选择较大的一个的indx
            int largerSonIndex = left + 1 <= lastIndex && arr[left + 1] > arr[left] ? left + 1 : left;
            // 如果父节点比孩子节点都大的话,就不用调整了,直接返回
            if (arr[index] >= arr[largerSonIndex]) {
                return;
            }
            // 父节点与较大的孩子交换
            swap(arr, index, largerSonIndex);
            index = largerSonIndex;
            left = index * 2 + 1;
        }
    }

应用:

1、优先级队列

java实现的堆结构、优先级队列,PriorityQueue()
PriorityQueue当size达到了初始的initialCapacity容量后会进行扩容,每次容量加1。

= fun 时间复杂度
插入到堆中 add() O(logn)
取出、删除堆顶 poll() O(logn)
get堆顶 peek() O(1)
堆的大小 size()
        // 默认小顶堆,默认容量为11
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        // 添加比较器,实现大顶堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11, (i1, i2) -> i2-i1);
        //添加数据
        int []arr={2,3,1,4,2,0,5,7};
        for (int each : arr) {
            minHeap.add(each);
            maxHeap.add(each);
        }
        while(minHeap.size()>0){
            System.out.print(minHeap.poll()+" ");
        }
        System.out.println("\n=====");
        while(maxHeap.size()>0){
            System.out.print(maxHeap.poll()+" ");
        }
0 1 2 2 3 4 5 7 
=====
7 5 4 3 2 2 1 0

2、统计流中的中位数:

数据结构 插入的时间复杂度 得到中位数的时间复杂度 细节
没有排序的数组 O(1) O(n) 使用分治的方法
排序的数组 O(n) O(1) ddd
排序的链表 O(n) O(1) 定义两指针指向中间节点
二叉搜索树 平均O(logn) 平均O(logn) 最差O(n)
AVL树 O(logn) O(1) 中位数在root
大根堆小根堆 O(logn) O(1) 如下

1->1
1,2->1.5
1,2,3->2
...
准备一个大根堆,一个小根堆
把小于等于大根堆对顶的树插入大根堆,
把大于大根堆对顶的树插入小根堆;
同时,要调整大根堆和小根堆的数量,数量差值不要超过1;
如果大根堆的size大了,就把大根堆 的堆顶取出插入小根堆;
如果小根堆的size大了,就把小根堆 的堆顶取出插入大根堆;
这样就能达到,大根堆,小根堆数量保持平衡,同时大根堆中的小的n/2个数,小根堆中是大的n/2个数,
(大根堆堆顶+小根堆堆顶)/2 就是流的中位数

3、合并有序小文件

假设我们有 100 个小文件,每个文件的大小是 100MB,每个文件中存储的都是有序的字符串。我们希望将这些 100 个小文件合并成一个有序的大文件。
思路:利用一个小根堆,把每个文件的第一条记录取出来,放入小根堆中,那么小根堆的堆顶就是这100条记录中顺序最小的记,它为记录A,也是这100个文件中顺序最小的,将这个堆顶追加到准备的大文件。从记录A原来所在的文件中再取出一条记录,放入小根堆,取出堆顶,如此重复...

4、高性能定时器

5、利用堆求 Top K

思路:利用一个size为k的堆,求最大的Top K 用小根堆 ,求最小的Top k用大根堆。
我们可以维护一个大小为 K 的小顶堆,顺序遍历数组,从数组中取出取数据与堆顶元素比较。如果比堆顶元素大,我们就把堆顶元素删除,并且将这个元素插入到堆中;如果比堆顶元素小,则不做处理,继续遍历数组。这样等数组中的数据都遍历完之后,堆中的数据就是前 K 大数据了。

遍历数组需要 O(n) 的时间复杂度,一次堆化操作需要 O(logK) 的时间复杂度,所以最坏情况下,n 个元素都入堆一次,所以时间复杂度就是 O(nlogK)。

    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        if(input == null || input.length < k || k <=0 || input.length == 0){
            return new ArrayList<>();
        }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k,(x,y)->y-x);
        for(int i = 0;i<k;i++){
            maxHeap.add(input[i]);
        }
        for(int i = k ;i<input.length;i++){
            if(input[i] < maxHeap.peek()){
                maxHeap.poll();
                maxHeap.add(input[i]);
            }
        }
        return new ArrayList<>(maxHeap);
    }

6、假设现在我们有一个包含 10 亿个搜索关键词的日志文件,如何能快速获取到热门榜 Top 10 的搜索关键词呢,单机,可以使用的内存为 1GB?

使用map<key,count>计数,count放入小根堆中,多个小根堆合并求top10.
分析:假设一条搜索50个字节,10亿条那么就占用了50GB的大小,所以一次对所有数据进行统计是不可行的。
0、准备10个空文件
1、采用hash 算法将日志进行计算之后得到hash值,模10,存放入对应的文件中。那么每个文件大概会有1亿条记录,假设平均每条关键词重复10次,那么就是1000万条关键词,大概500MB,1G的内存是可以存下的
2、使用hashmap对每个小文件中的关键词进行数量统计,对每个小文件的统计的结果存入一个Size为10 的小根堆。
3、对这10个小根堆再进行合并得到最终的Top 10 的搜索关键词

7、赫夫曼编码、图的最短路径、最小生成树算法

推荐阅读:
数据结构和算法|堆的应用

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

推荐阅读更多精彩内容