前端算法面试之堆排序-每日一练

news2024/10/7 19:23:57

如果对前端八股文感兴趣,可以留意公重号:码农补给站,总有你要的干货。

今天分享一个非常热门的算法--堆排序。堆的运用非常的广泛,例如,Python中的heapq模块提供了堆排序算法,可以用于实现优先队列;Java中的PriorityQueue类实现了堆队列,可以用于实现优先级任务队列;C++中的优先队列容器适配器提供了基于堆的优先队列实现。

还有前端开发特别熟悉的React框架中也用到了,其中使用堆来管理组件的渲染优先级。在React中,组件的渲染优先级被抽象为一种堆结构,称为“Fiber堆”。Fiber堆中的每个节点代表一个组件,组件的优先级越高,在渲染时越优先。

什么是堆呢?

堆分为大根堆和小根堆,大根堆的每个结点的值都大于等于其子结点的值,即该结点是该子树中的最大值。小根堆的每个结点的值都小于等于其子结点的值,即该结点是该子树中的最小值。

他们都是一种特殊的完全二叉树,物理存储结构一般是一个连续的线性数组。并且节点的下标和左右子节点的下标之间存在一定的关系。假设节点的下标为 i,那么它的左子节点的下标为 2i,右子节点的下标为 2i + 1。相反地,如果一个节点的下标为 j,那么它的父节点的下标为 j/2(向下取整)。

那如何利用堆进行排序呢

以大根堆为例,就两步,建堆和堆化。

第一步先建堆,然后将堆顶和数组的最后一位更换位置,数组的最后一个位置就是最大值了。堆的大小减一。

第二步,再调整堆,使其再次满足大根堆的定义。

重复上面两步,直到堆的大小为1。

下面用代码实现这两个过程

建堆

 
class Heap {
	constructor(data) {
		this.data = data;
	}

	build() {
		for (let i = 2; i < this.data.length; i++) {
			this.heapfyTop(i);
		}
	}

	heapfyTop(n) {
		while (n > 1 && this.data[n] > this.data[Math.floor(n / 2)]) {
			this.swap(n, Math.floor(n / 2));
			n = Math.floor(n / 2);
		}
	}

	swap(index1, index2) {
		const temp = this.data[index1];
		this.data[index1] = this.data[index2];
		this.data[index2] = temp;
	}
}

建堆有两种方法,这里先讲第一种。

建堆的过程有点像插入排序,假设第一个元素已经是一个大根堆,从第二个节点开始往后遍历,每个元素都往前面的大根堆中插入。直到遍历完整个数组的元素。完整的大根堆就建好了。

假设往大根堆中插入元素a,先将元素a放到数组的最后一个位置,然后比较a元素和其父元素的大小,如果大于父元素,就将两个元素的位置更换。这样a元素就有了新的父元素。然后继续比较a 元素和其父元素的大小。直到a元素小于等于父元素,或者a元素变成了大根堆的堆顶。

这个比较的过程,就是大根堆堆化的过程

上面代码中,build函数作用是从数组的第二个元素开始往后遍历,每遍历一个元素,就调用一次heapfyTop 函数。heapfyTop 函数的作用是调整大根堆。遍历完整个数组,堆也就建好了。

数组元素从下标 1 开始

测试代码

 
const data = [-1, 21, 33, 5, 42, 123, 54, 65, 23, 33, 55];
const heap = new Heap(data);

heap.build();

console.log(heap.data);
// [
//   -1, 123, 55, 65, 33,
//   42,   5, 54, 21, 23,
//   33
// ]

新建一个 Heap 类,然后调用 build 方法,并且将堆的内容打印出来。打印数组确实满足大根堆定义,没有问题。

堆排序

 
class Heap {

  //省略其他代码
  sort() {
    this.build2(); // 构建大顶堆
    let len = this.data.length - 1; // 数组长度减1,因为堆排序是从下标1开始
    while (len > 1) { // 当堆长度大于1时,继续排序
      this.swap(1, len); // 交换堆顶元素与堆尾元素
      len--; // 减小堆长度
      this.heapfyBelow(1, len); // 对新的堆顶元素进行调整
    }
  }

  heapfyBelow(n, end) { // 对下标为n的元素进行调整,使其满足大顶堆的性质,end为调整范围的上界
    // 是否是叶子节点
    while (n * 2 <= end) {
      let maxIndex = n; // 假设当前结点是最大值
      // 如果有左孩子,且左孩子的值比当前结点大,则将maxIndex更新为左孩子的下标
      if (n * 2 <= end && this.data[maxIndex] < this.data[n * 2]) maxIndex = n * 2;
      // 如果有右孩子,且右孩子的值比当前结点大,则将maxIndex更新为右孩子的下标
      if (n * 2 + 1 <= end && this.data[maxIndex] < this.data[n * 2 + 1]) maxIndex = n * 2 + 1;
      // 如果maxIndex没有发生变化,说明当前结点的值已经是最大值,调整结束
      if (maxIndex == n) break;
      // 否则,交换当前结点与maxIndex指向的结点
      this.swap(n, maxIndex);
      n = maxIndex; // 更新当前结点为新的maxIndex
    }
  }

}

将堆顶元素和最后一个元素更换位置之后,堆的大小减一,并且需要重新调整堆的大小,所以代码中 len--,并且调用了this.heapfyBelow(1, len)。这也是一个堆调整的代码,与之前不同的是,这个代码是从上往下调整堆。不断地比较当前元素和子元素,如果有子元素比当前元素还大的,就更换位置。直到遍历到叶子节点,或者没有比当前元素更大的子节点。

为了方便调用者,sort 函数中直接调用了 build 函数,完成建堆的步骤。

测试代码

 
const data = [-1, 21, 33, 5, 42, 123, 54, 65, 23, 33, 55];
const heap = new Heap(data);
heap.sort();
console.log(heap.data);
// [
//    -1,  5, 21, 23, 33,
//    33, 42, 54, 55, 65,
//   123
// ]

打印的数组有序,代码正确

完整代码

 
class Heap {
	constructor(data) {
		this.data = data;
	}

	build() {
		for (let i = 2; i < this.data.length; i++) {
			this.heapfyTop(i);
		}
	}

	sort() {
		this.build2();
		let len = this.data.length - 1;
		while (len > 1) {
			this.swap(1, len);
			len--;
			this.heapfyBelow(1, len);
		}
	}

	heapfyBelow(n, end) {
		// 是否是叶子节点
		while (n * 2 <= end) {
			let maxIndex = n;
			// 是否有左孩子
			if (n * 2 <= end && this.data[maxIndex] < this.data[n * 2]) maxIndex = n * 2;
			// 是否有右孩子
			if (n * 2 + 1 <= end && this.data[maxIndex] < this.data[n * 2 + 1]) maxIndex = n * 2 + 1;
			if (maxIndex == n) break;
			this.swap(n, maxIndex);
			n = maxIndex;
		}
	}
  
	heapfyTop(n) {
		while (n > 1 && this.data[n] > this.data[Math.floor(n / 2)]) {
			this.swap(n, Math.floor(n / 2));
			n = Math.floor(n / 2);
		}
	}
  
	swap(index1, index2) {
		const temp = this.data[index1];
		this.data[index1] = this.data[index2];
		this.data[index2] = temp;
	}
}

const data = [-1, 21, 33, 5, 42, 123, 54, 65, 23, 33, 55];
const heap = new Heap(data);

heap.sort();

console.log(heap.data);

这是堆排序的完整代码,大家可以直接 copy 下来在本地跑一跑

总结

这篇文章分享了堆排序的概念讲解以及 JS 代码实现。堆排序是一种高效的排序算法,利用堆的特性进行排序。它的时间复杂度为O(nlogn),通过建堆和堆化的过程,可以将一个无序的数组转化为有序的数组。堆排序在实际应用中有广泛的应用,特别是在需要维护优先级队列的场景中非常有用。

下篇文章来分享建堆的另一种方式,以及堆的元素如何删除,并且分析堆排序的时间复杂度

作者:慢功夫
链接:https://juejin.cn/post/7300779513910132747
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1208452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务nacos实战入门

注册中心 在微服务架构中&#xff0c;注册中心是最核心的基础服务之一 主要涉及到三大角色&#xff1a; 服务提供者 ---生产者 服务消费者 服务发现与注册 它们之间的关系大致如下&#xff1a; 1.各个微服务在启动时&#xff0c;将自己的网络地址等信息注册到注册中心&#x…

四、Ribbon负载均衡

目录 一、负载均衡流程 1、我通过浏览器直接访问userservice/user/1&#xff0c;无法访问&#xff0c;说明是负载均衡做了相应的处理 2、我们来看一下代码中负载均衡的流程是怎样的 3、图像流程 二、负载均衡策略 1、修改负载均衡策略 &#xff08;方式一&#xff09; &a…

D. Jumping on Walls bfs

Problem - 199D - Codeforces 题目大意&#xff1a;有一个两个垂直的平行墙壁组成的一个峡谷。一个人初始是在左边墙壁第一层。在每个墙壁上有些障碍点&#xff0c;用X表示&#xff0c;这些障碍点不能被到达。&#xff0c;他可以执行以下三个操作&#xff1a; 向当前墙壁往上…

【ASP.NET】Hello World

文章目录 1. 几个概念2. 搭建开发环境2.1 .NET SDK2.2 IDE & Editor 3 First Project3.1 步骤3.2 模板3.3 项目结构3.4 请求的处理流程 Reference Link 1. 几个概念 .NET 是一个平台&#xff0c;包括 .NET Framework、.NET Core、ASP.NET、C#等&#xff0c;可以构建桌面、W…

文心一言 VS 讯飞星火 VS chatgpt (135)-- 算法导论11.3 1题

一、用go语言&#xff0c;假设我们希望查找一个长度为 n 的链表&#xff0c;其中每一个元素都包含一个关键字k并具有散列值h(k)。每一个关键字都是长字符串。那么在表中查找具有给定关键字的元素时&#xff0c;如何利用各元素的散列值呢? 文心一言&#xff1a; 在Go语言中&a…

若依前后分离版框架下Springboot java引入Mqtt接受发送消息

**这只是其中一种而且是粗浅的接、发消息。 同步机制还要跟搞物联网的同事沟通确认去看看能不能实现 或者是设备比较多的情况下 不会去使用同步机制 首先pom文件 引入依赖 ** <dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse…

spring cloud微服务中多线程下,子线程通过feign调用其它服务,请求头token等丢失

在线程池中&#xff0c;子线程调用其他服务&#xff0c;请求头丢失&#xff0c;token为空的情况 看了很多篇文章的处理方法和在自己亲测的情况下做出说明&#xff1a; 第一种&#xff1a; 这种方式只支持在主线程情况下&#xff0c;能够处理&#xff0c;在多线程情况下&#…

基于Python实现汽车销售数据可视化【500010086】

导入模块 import numpy as np import pandas as pd import plotly.graph_objects as go import plotly.express as px获取数据 df1 pd.read_excel(r"./data/中国汽车总体销量.xlsx") print(df1.head(5))df1.info()df1[年份] df1[时间].dt.year df1[月份] df1[时…

【论文阅读】GAIN: Missing Data Imputation using Generative Adversarial Nets

论文地址&#xff1a;[1806.02920] GAIN: Missing Data Imputation using Generative Adversarial Nets (arxiv.org)

【ML】欠拟合和过拟合的一些判别和优化方法(吴恩达机器学习笔记)

吴恩达老师的机器学习教程笔记 减少误差的一些方法 获得更多的训练实例——解决高方差尝试减少特征的数量——解决高方差尝试获得更多的特征——解决高偏差尝试增加多项式特征——解决高偏差尝试减少正则化程度 λ——解决高偏差尝试增加正则化程度 λ——解决高方差 什么是…

【Linux】Ubuntu16.04配置repo

Ubuntu16.04配置repo失败 在学习韦东山Linux嵌入式开发过程中&#xff0c;使用repo获取内核及工具链: git clone https://e.coding.net/codebug8/repo.gitmkdir -p 100ask_imx6ull-sdk && cd 100ask_imx6ull-sdk../repo/repo init -u https://gitee.com/weidongshan/m…

【Linux】gitee仓库的注册使用以及在Linux上远程把代码上传到gitee上的方法

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;今天为大家介绍一个在实际工作以及项目开发过程中非常实用的网站gitee&#xff0c;并教如何正确的使用这个网站以及常见问题的解决方案&#xf…

流量分析(信息安全铁人三项赛分区赛2-5.18)

题目描述 目录 题目描述 黑客的IP是多少 服务器1.99的web服务器使用的CMS及其版本号(请直接复制) 服务器拿到的webshell的网址(请输入url解码后的网址) 服务器1.99的主机名 网站根目录的绝对路径(注意最后加斜杠) 黑客上传的第一个文件名称是什么 黑客进行内网扫描&am…

实体门店创新神器曝光,拓世法宝AI智能直播一体机助力商家快速惊艳逆袭

在这个飞速变革的时代&#xff0c;传统实体门店面临着多重挑战。为了迎接市场的巨大变化&#xff0c;许多实体门店迫切寻求创新的方法来吸引顾客的眼球。数字化手段和新技术的引入成为实体门店应对市场需求的重要选择之一&#xff0c;是应对激烈竞争和不断变化的消费者行为的有…

《008.SpringBoot之教务系统》【界面简洁功能简单】

《008.SpringBoot之教务系统》【界面简洁功能简单】 项目简介 [1]本系统涉及到的技术主要如下&#xff1a; 推荐环境配置&#xff1a;DEA jdk1.8 Maven MySQL 前后端分离; 后台&#xff1a;SpringBootMybatis; 前台&#xff1a;JSPBootStrap; [2]功能模块展示&#xff1a; 管…

【云原生进阶之PaaS中间件】第三章Kafka-1-综述

1 Kafka简介 Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff09;、多副本的&#xff08;replica&#xff09;&#xff0c;基于zookeeper协调的分布式消息系统&#xff0c;它的最大的特性就是可以实时的处理大量数据以满足各…

适合孩子写作业的台灯?精选专业的读写台灯

要说现在孩子学习必不可少的一件物品&#xff0c;那一定是台灯。因为台灯可以在夜晚的时候给孩子提供充足、舒适的光源环境&#xff0c;避免光线不足导致伤眼。不过随着孩子年龄的增长&#xff0c;作业的增加用眼需求会越来越大&#xff0c;导致了很多孩子早早就出现的视力问题…

μC/OS-II---消息邮箱管理1(os_flag.c)

目录 消息邮箱创建消息邮箱删除等待邮箱中的消息向邮箱发送一则消息 消息邮箱创建 OS_EVENT *OSMboxCreate (void *pmsg) {OS_EVENT *pevent; #if OS_CRITICAL_METHOD 3u /* Allocate storage for CPU status register */OS_CPU_SR cpu_sr …

探讨计算机内存管理:分页与分段的地址空间维度差异(为什么分页机制中逻辑地址空间是一维的,而分段机制中逻辑地址空间是二维的?)

在计算机系统中&#xff0c;内存管理是一个至关重要的组成部分&#xff0c;而分页机制和分段机制是两种常见的内存管理方式。一个引人疑惑的问题是&#xff1a;为什么分页机制中逻辑地址空间是一维的&#xff0c;而分段机制中逻辑地址空间是二维的呢&#xff1f;在本文中&#…

Nacos漏洞复现合集

本文主要复现nacos的一些经典漏洞&#xff0c;既是分享也是为了记录自己的成长&#xff0c;近期会持续更新。 1. QVD-2023-6271 Nacos身份绕过漏洞 1.1 漏洞级别 &#xff1a;高危 1.2 漏洞描述&#xff1a;低版本的Nacos存在默认的scertkey在未更换的情况下可以生成任意的可…