堆、堆排序以及TopK问题


堆的定义

堆是一种特殊的数据结构,可以形象化的看成一颗完全二叉树,一般内部的存储结构为数组;堆中的某个节点总是不大于或者(不小于)其左右节点,其中前者为成为小顶堆(最小堆,堆顶为最小值),后者成为大顶堆(最大堆,堆顶为最大值)

堆的一些操作

  • build: 建立一个堆
  • insert: 往堆中插入一个新节点
  • update: 更新某个节点的值, 根据节点的新值重新调整堆,使其符合堆的特性
  • get_top : 获取堆顶的节点
  • delete_top : 删除堆顶的节点, 然后重新调整堆,使其满足堆的特性

下面以最大堆为例进行堆操作的说明

  • 建堆
    • 自上到下构建堆

      • 自上到下构建堆的流程是:假定数组arr的前i个节点已经满足堆的特性,然后新增一个节点arr[i]到数组尾,调整堆使得数组arr[0~i]满足堆的特性;当i = n -1(n是数组长度)时,则建堆完毕;
      • 新增节点arr[i]到数组尾,调整的过程:可以形象的描述为,该节点沿着到根节点的二叉树路径,进行上浮;如果该节点比其父节点大,则将该节点和父节点进行交换,然后将当前节点设置为其父节点继续进行上浮比较;如果该节点比其父节点小,则停止上浮;直到该节点上浮到根节点,则本次上浮调整结束
      • 代码示例
        struct max_heap_t {
        int32_t* arr;
        int32_t  n;
        
        max_heap_t (int32_t* input_arr, int32_t arr_size){
            arr = new int32_t[arr_size];
            memcpy(arr, input_arr, sizeof(int32_t) * arr_size);
            n = arr_size;
        }
        
        ~max_heap_t () {
            if (arr) {
                delete[] arr;
                arr = nullptr;
            }
        }
        
        /** time complexity => O(nlogn) */
        void    build_heap_from_top_to_bottom() {
          
            for (int32_t i = 1; i < n; i++) {
               heap_ajust_from_bottom_to_top(i);
            }
        }
        
        /* O(logn) */
        void    heap_ajust_from_bottom_to_top(int32_t bottom_index) {
            int32_t tmp = arr[bottom_index];
            while (bottom_index > 0) {
                int32_t parent_index = (bottom_index - 1) / 2;
                if (arr[parent_index] < tmp ) {
                    arr[bottom_index] = arr[parent_index];
                    bottom_index = parent_index;
                }
                else {
                    break;
                }
            }
            arr[bottom_index] = tmp;
        }
        
      • 时间复杂度
        每次上浮调整的时间复杂度为O(logn), 总共要调整n-1次,所以建堆的时间复杂度为 O(nlogn)
    • 自下到上构建堆

      • 自下到上构建堆的流程是: 从最后一个节点的父节点开始一直到到根节点,逐步进行以选中节点为起点,进行下沉调整;到根节点下沉调整结束,则建堆完毕
      • 下沉调整的过程,可以描述为:从当前节点以及其左右子节点中选出最大的一个节点,如果最大节点是该节点本身,则下沉调整结束;如果是其左子节点(或者右子节点),则将该节点与其左子节点(或者其右子节点)交换,然后将当前节点设置为其左子节点(后后者右子节点)继续下浮比较;
      • 代码示例
         /* O(n) */
        void    build_heap_from_bottom_to_top() {
            int32_t max_index = n - 1;
            for (int32_t i = (max_index - 1) / 2; i >= 0; i--) {
                heap_adjust_from_top_to_bottom(i, max_index);
            }
        }
        
        /* O(logn) */
        void    heap_adjust_from_top_to_bottom(int32_t top_index, int32_t bottom_index) {
            int32_t tmp = arr[top_index];
            while (top_index <= (bottom_index - 1) / 2) {
                int32_t max_one = tmp;
                int32_t child_idx = 0;
                int32_t left_child_idx = top_index * 2 + 1;
                int32_t right_child_idx = top_index * 2 + 2;
                
                if (left_child_idx <= bottom_index && max_one < arr[left_child_idx] ) {
                    max_one = arr[left_child_idx];
                    child_idx = left_child_idx;
                }
                if (right_child_idx <= bottom_index && max_one < arr[right_child_idx] ) {
                    max_one = arr[right_child_idx];
                    child_idx = right_child_idx;
                }
              
                if (max_one != tmp) {
                    arr[top_index] = max_one;
                    top_index = child_idx;
                }
                else {
                    break;
                }
            }
            arr[top_index] = tmp;
        }
        
        
      • 时间复杂度
        自下到上建堆的时间复杂度是O(n)
        证明方法如下:

        假定节点个数为n, 叶子高度为h = log2(n)
        1. 假设根节点的高度为0,叶子节点高度为h,那么每层包含的元素个数为2^x,x从0到h。
        2. 构建堆的过程是自下而上,对于每层非叶子节点需要调整的次数为h-x,因此很明显根节点需要调整(h- 0)次,第一层节点需要调整(h-1)次,最下层非叶子节点需要调整1次。
        3. 因此可知,构造树高为h的二叉堆精确时间复杂度为:
        s = 12^(h-1) + 22(h-2)+……+h*20
        可以看出以上公式是等差数列和等比数列乘积之和,可以通过错位相减求:
        2s = 2^h + 22^(h-1)+32(h-2)+……+h*21
        因此可得:
        s = 2s -s = 2^h + 2^(h-1) + 2^(h-2) +…… + 2^1 - h
        最终可以通过等比数列公式进行计算得到s = 2*2^h - 2 -h
        将代入的s = 2n - 2 - log2(n),近似的时间复杂度就是O(n)
        转载自: https://blog.csdn.net/hupenghui1224/article/details/57427045

堆排序

堆排序的话,无需额外的空间,直接可以在原堆内数组上进行堆排序;

  • 堆排序的过程: 对于给定的数组arr以及长度n,首先根据该数组,构建堆;然后依次将当前数组尾元素arr[i]( i => n -> 0)与堆顶元素交换,然后重新调整堆(从 arr[0] ~ arr[i - 1]);一直到当前i = 0, 则排序完成;
  • 代码实例:
      void    sort() {
    
          // build  heap first
          build_heap_from_bottom_to_top();
    
          // sort
          int32_t tmp = 0;
          for (int32_t i = n - 1; i > 0;) {
              // move heap top to end
              tmp = arr[0];
              arr[0] = arr[i];
              arr[i] = tmp;
    
              // adjust the heap
              heap_adjust_from_top_to_bottom(0, --i);
          }
      }
    

TopK问题

TopK问题描述:在N个无序元素中,找到最大的K个(或最小的K)

按照一般排序算法来寻找的话,时间复杂度为O(nlogn) + O(k); 当N很大,而K很小的时候,这种方法很慢;
可以采用堆来解决这个问题;下面以找到最大的K个值为例

  • 算法流程:取该数组的前K个元素,构建一个最小堆;然后从该无序数组的第k个元素开始,一直到数组尾,遍历该数组,将当前元素与最小堆的堆顶元素比较,如果当前元素大于堆顶元素(最大的K个值里面的最小的那个),那么可以认为当前元素可能是要寻找的最大的K个元素之一,则将当前元素替换堆顶,然后重新进行堆调整;当遍历结束的时候,最小堆中的K个元素就是要寻找的最大的K个元素

  • 代码示例

    void top_k(int32_t* input_arr, int32_t n, int32_t k) {
        printf("input array: (%d)\n", n);
        for (int32_t i = 0; i < n; ++i) {
            printf(", %d", input_arr[i]);
        }
        printf("\n");
        
        // O(k)
        // we suppose the k element of the min heap if the default top k element
        min_heap_t min_heap(input_arr, k);
        min_heap.build_heap_from_bottom_to_top();
        
        for (int32_t i = k; i < n; ++i) {
            // compare each element with the min element of the min heap
            // if the element > the min element of the min heap
            // we think may be the element is one of what we wanna to find in the top k
            if (input_arr[i] > min_heap.arr[0]){
                // swap
                min_heap.arr[0] = input_arr[i];
                
                // heap adjust
                min_heap.heap_adjust_from_top_to_bottom(0, k - 1);
            }
        }
        
        printf("top k: (%d)\n", k);
        min_heap.print_arr();
    }
    
  • 算法复杂度
    创建初始堆的过程O(KlogK), 每次堆调整的时间复杂度O(logK), 寻找最大K个值的过程(N-K) * O(logK) = O((N-K)logK), 总得时间复杂度O(NlogK)

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

推荐阅读更多精彩内容

  • 算法思想贪心思想双指针排序快速选择堆排序桶排序荷兰国旗问题二分查找搜索BFSDFSBacktracking分治动态...
    第六象限阅读 2,983评论 0 0
  • 选择排序 每次在n个记录中选择一个最小的需要比较n-1次,但是这样的操作并没有把每一趟的比较结果保存下来,在后一趟...
    wlj1107阅读 1,688评论 0 2
  • 堆是一棵满足一定性质的二叉树,具体的讲堆具有如下性质:父节点的键值总是不大于它的孩子节点的键值(小顶堆), 堆可以...
    9527Roy阅读 609评论 0 0
  • 我的博客地址:https://rebornc.github.io/2018/11/15/%E5%A0%86%E6%...
    白夜叉小分队阅读 1,957评论 0 5
  • 王冬冬,刘有龙网络初七中七高一,持续记录248天(2018.8.9) 阅读打卡第28天:《焦点解决短期心里治疗的应...
    和佛陀去赏花阅读 486评论 0 2