Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
前言
每天至少一道算法题,死磕算法
我们讲一个知识点,要想想如何才能使你看过,认真学过这个知识点后,再过100天以后,你依然不忘呢,所以只有讲的通俗易懂,生动形象,才能更让现在屏幕前的你所接受,记忆才能更深刻,刻在你的脑子里
今天我们就讲一个排序算法,堆排序
首先先来几个概念,什么是完全二叉树,什么是堆
什么是完全二叉树
完全二叉树需要同时满足以下两个条件
- 从第一层到倒数第二层,每一层都是满的,也就是达到了当前层的最大值
- 最后一层的排列是从左到右排列的,中间不能跳跃
向以上这种就属于完全二叉树
规律这里是重点,下面我们写代码的时候用的到
- i 结点的父结点 par = floor((i-1)/2) 「向下取整」
- i 结点的左子结点 2 * i +1
- i 结点的右子结点 2 * i + 2
什么是堆
堆是完全二叉树的一种特例。堆分为以下两种
- 大顶堆
- 小顶堆
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
如何创建堆
大顶堆和小顶堆除了约束条件的规则相反之外,其他都是一样的,所以我们接下来以大顶堆为例
完成大顶堆需要分两步走
我们以数组
const arr = [4, 9, 2, 10, 7];
为例
他对应的数结构为这个样子
- 第一步:从顶部开始,以每三个数为一颗树,构成一个堆结构(递归)
/**
* @description: 向下堆化
* @param {*} nums 数组
* @param {*} n 数组个数
* @param {*} i 父节点
* @return {*}
* @author: ws
*/
const heapify = function (nums, n, i) {
// 递归出口
if (i >= n) {
return;
}
// 先求出子节点
let child1 = i * 2 + 1;
let child2 = i * 2 + 2;
// 设置哨兵,当前最大值序号
let max = i;
// 获取最大值序号,加一个判断条件,就是child1和child2是否存在
if (child1<n && nums[child1] > nums[max]) {
max = child1;
}
if (child2<n && nums[child2] > nums[max]) {
max = child2;
}
if (max !== i) {
// 做交换,此时这三个数就变成了一个堆结构
[nums[max], nums[i]] = [nums[i], nums[max]];
// 递归孩子
heapify(nums, n, max);
}
}
结果就会为这个样子
经过这个过程以后,我们发现除了最后三个数为一个大顶堆以后,其他的都不是,那怎么办呢?
我们可以从叶子节点的父节点开始,倒序遍历每一个节点,让他们也都具有大顶堆的结构
所以
- 第二步:从叶子节点的父节点开始,倒序遍历每一个节点,重复第一步的过程
const buildHeap = function (nums) {
// 求最后一个节点的父节点
let last_parent = Math.floor((nums.length - 1) / 2);
const length = arr.length;
// 倒序遍历
for (let i = last_parent; i >= 0; i--){
heapify(nums, length, i)
}
}
堆排序
如何排序,其实就是把堆顶的数拿出来,和最后一个值交换,然后在heapify一次,保持是大顶堆,重复以上过程
const heapSort = function (nums) {
let length = nums.length;
buildHeap(nums);
// 从最后一个数开始交换
for (let i = length - 1; i >= 0; i--){
//和大顶堆的第一个数开始交换
[nums[i], nums[0]] = [nums[0], nums[i]];
heapify(nums, i, 0);
}
}