前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >三刷”数组中的第K个最大元素“,我终于学会了堆排序

三刷”数组中的第K个最大元素“,我终于学会了堆排序

作者头像
虎妞先生
发布2022-10-27 20:07:18
3900
发布2022-10-27 20:07:18
举报

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

灵魂拷问

身为前端的你,数据结构排序算法掌握得怎么样了,我想大家对冒泡排序,插入排序,快速排序已经掌握了,业务代码中 sort() 方法也用的不亦乐乎,但是提起堆排序肯定是马马虎虎,因为我也是,leetcode有这么一道题,我刷了3遍,终于弄明白了堆排序,今天和大家分享一下,如果能帮到你,那真是太好了!

题目

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2

输出: 5 示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4

输出: 4

第一次刷

不就是个简单的数组排序嘛,从小到大排序之后,取倒数第k个元素不就完了

代码语言:javascript
复制
var findKthLargest = function(nums, k) {
    nums.sort(function(a,b) {return a-b});
    return nums[nums.length-k];
};

js一行代码搞定,简单清晰,一看就懂。

但是看到评论区热评,让人顿觉羞愧,如果面试的时候,还在这里调API,这不是刷滑头嘛

image.png
image.png
image.png
image.png

第二次刷

既然不用sort()方法,那我自己写个快速排序吧,插入排序,冒泡泡序,面试官自己看吧,喜欢哪个我我给你写哪个。

代码语言:javascript
复制
function qsort(nums) { 
    if (nums.length <= 1) { 
        return nums; 
    } 
    const pivotIndex = Math.floor(nums.length / 2); 
    const b = nums.splice(pivotIndex, 1)[0]; 
    const left = [], right =[]; 
    nums.forEach(item => { 
        if (item > b) { 
            left.push(item); 
        } else { 
            right.push(item); 
        } 
    }); 
    return qsort(left).concat(b,qsort(right)); 
} 

var findKthLargest = function(nums, k) { 
    return qsort(nums)[k-1]; 
};

自信满满得写出来了,一般小厂算法面试基本拿下,面试官还会觉得你基础扎实,如果能说出来,时间复杂度是Θ(n\lgn),最坏情况是Θ(n2),那基本稳了。

但是直到,参加高德地图的面试,

  • 上来就是问的原题,返回数组中第K个最大元素,使用堆排序
  • 我一时语塞,半天没说话,这不按套路出牌啊
  • 面试官,继续发问,有听说堆排序吗
  • 我说听说,但没有写过
  • 面试官:好吧,那就不问了
  • ......结果当然是凉凉了

于是痛定思痛,决心一定要搞清楚堆排序

第三次刷

看到了leetcode的题解,大大得写着一句话

image.png
image.png

所以今天带着大家一起用堆排序重刷一遍

前置知识

  • 堆 heap
    • 完全二叉树
    • 父节点内容大于子节点内容 堆必须满足 以上两个条件,必须是完全二叉树,且父节点大于子节点
  • 完全二叉树 以下图中的树,都是完全二叉树

完全二叉树的特点就是,生成的时候,从上往下,从左到右,依次添加,下面的7个图,可以看成是生成完成二叉树的过程。

image.png
image.png
  • 父节点内容大于子节点内容

故名思义,每个父节点的内容,都大于它的子节点的值,就不展开解释了

怎样用代码表示一个堆

用数组可以表示一个堆 因为堆是从上至下,从左至右构建的,我们可以给每个节点加上标识

正好可以用一个数组来存储这些标识 数组的下标对应堆的顺序标识,数组每一项的值,表示堆的节点的值

代码语言:javascript
复制
var arr = [10,5,8,3,4,6,7,1,2]
image.png
image.png

从任一节点出发,都可以拿到这个节点的父节点,和子节点

如第4个节点,i= 3

那么他的父节点的在数组中的顺序为:parent = Math.floor((i-1)/2) = 1

他的子节点的在数组中顺序为:

c1 = 2i+1 = 7 c2 = 2i+2 = 8

如第4个节点是 arr[3] = 3 他的父节点 是arr[1] = 5 他的两个子节点是arr[7] = 1 arr[8] = 2

用数组方便我们找到他的父节点和子节点

完全二叉树转化为堆的过程

我们把完全二叉树转化为堆的过程称为heapify,需要从上到下,对每个节点进行heapify操作,保证这个完全二叉树的每个父节点都大于子节点

接下来我们着手实现一下代码heapify的过程

入参数 arr 表示数组,n表示这数组的长度,也是树的节点个数,i表示对哪个节点进行heapify操作

c1 c2 分别为节点i的两个子节点,我们需要在i c1 c2 三个节点中找到最大值

当最大值为i 时,不需要交换

当最大值为c1 或者 c2,需要交换

交换之后,继续对下一个节点做 heapify 操作

直到每个节点都做完heapify 操作之后,跳出递归

测试代码为 [10, 5, 3, 4, 1, 2]

代码语言:javascript
复制
function swap(arr,i,j){
    let temp = arr[i]
    arr[i] = arr[j]
    arr[i] = temp
}
function heapify(arr,n,i){
    if(i>=n){
        return
    }
    let c1 = 2 * i + 1
    let c2 = 2 * i + 2
    let max = i
    if(c1<n &amp;&amp; arr[c1]>arr[max]){
        max = c1
    }
    if(c2<n &amp;&amp; arr[c2]>arr[max]){
        max = c2
    }
    if(max !== i){
        swap(arr,max,i)
        heapify(arr,n,max)
    }
}

funtion test(){
    let arr = [4,10,3,5,1,2]
    let n = arr.length
    heapify(arr,n,0)
    console.log(arr)
}
image.png
image.png

处理一个乱序的树

如果给定我们一个乱序的树,我们如何处理

image.png
image.png

只要对这个树所有非叶子节点,从下至上进行heapify 即可

比如上面这个节点,最后一个非叶子节点3,从下至上依次heapify是 3 -> 2 -> 1 -> 0

image.png
image.png

那么问题来了,如何找到最后一个非叶子节点3呢,最后一个非叶子节点,也就是树的最后一个节点的父节点

对应到图中就是 最后一个节点 8 的父节点 3

我们可以利用公式计算 parent = Math.floor((8-1)/2) = 3

代码处理

入参数 arr 表示数组,n表示这数组的长度,也是树的节点个数

对parnt 以上的所有节点进行heapify操作

代码语言:javascript
复制
function build_heap(arr,n){
    let last_node = n-1
    let parent = Math.floor((last_node -1)/2)
    for(let i = parent;i>=0;i--){
        heapify(tree,n,i)
    }
}

如何排序

当我们经过上面两个操作之后,拿到一个堆结构,那么如何排序呢

我们能确定的是,这个堆的根个节点肯定最大值

只要依次输出根节点,然后堆进行heapify操作,始终保持根节点是剩余值的最大值,就可以拿到一个降序的结果

如何输出根节点呢,将根节点和最后一个节点做交换,然后再砍断最后一个节点。

这样做的目的是为了保持结构始终一个完全二叉树,便于我们之后的heapify的操作

代码实现
代码语言:javascript
复制
function heap_sort(arr,n){
    build_heap(arr,n)
    for(let i= n-1;i>0;i--){
        swap(arr,i,0)
        heapify(arr,i,0)
    }
}

总结

总的过程包括 建堆 build_heap 调整 heapify 排序 heap_sort

堆排序找出最大的k值: 时间复杂度:O(k * logn) 空间复杂度:O(1),在原数组进行修改

image.png
image.png

完整代码如下

代码语言:javascript
复制
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
function swap(nums,i,j){
    let temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
} 
function heapify(tree,n,i){
    if(i>=n){
        return
    }
    let c1 =2*i+1
    let c2 = 2*i +2
    let max = i
    if(c1<n &amp;&amp; tree[c1]>tree[max]){
        max = c1
    }
    if(c2<n &amp;&amp; tree[c2]>tree[max]){
        max = c2
    }
    if(max !==i){
        swap(tree,max,i)
        heapify(tree,n,max)
    }
}

function heap_build(tree,n) {
    let last_node = n-1
    let parent = Math.floor((last_node-1)/2)
    for(let i = parent;i>=0;i--){
        heapify(tree, n, i);
    }
}

function heap_sort(tree,n){
    heap_build(tree,n)
    for(let i = n-1;i>=0;i--){
        swap(tree,i,0)
        heapify(tree,i,0)
    }
}
var findKthLargest = function(nums, k) {
    heap_sort(nums,nums.length)
    return nums.reverse()[k-1]
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 灵魂拷问
  • 题目
    • 215. 数组中的第K个最大元素
      • 第一次刷
        • 第二次刷
          • 第三次刷
            • 前置知识
            • 怎样用代码表示一个堆
            • 完全二叉树转化为堆的过程
            • 处理一个乱序的树
            • 如何排序
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档