当前位置: 首页 > news >正文

二叉树,堆排序及TopK问题

要讲二叉树的概念,就要先讲树的概念。
树是什么呢?
树其实是一种储存数据的结构,因为他的结构倒过来和生活中的树很相似所以才被称之为树。
在这里插入图片描述

这是一颗多叉树,从最顶端的节点可以找到下边的几个节点,下边的节点又可以找到他的下一级节点,注意观察,如下边的g h i节点,他们返回上一级只有一条路径,从最上边找到他也只有唯一的路径。
树的结构之间不可以交叉,一个节点只能有一个爸爸节点,但是一个爸爸可以有多个孩子。
联想一下这个结构,是不是和我们windows下的文件夹很相似,一个文件夹打开后可以找到很多文件夹,从某个文件夹内找到另一个文件夹只有一条路径。
讲几个比较重要的概念。
节点的度:一个节点含有的子树的个数,即一个爸爸有几个孩子。
在这里插入图片描述
A的度为4,B的度为2。

树的度:一棵树里面所有节点中,最大的节点的度为树的度
在这里插入图片描述
A的节点为2,B的节点为3,该树的度为3。
树的高度:就是树的深度
父节点:有孩子结点的节点,上图ABC都为父节点
子节点:上图除了A别的都可以称作子节点,他们都有父节点
节点的祖先:所有节点的祖宗,即A
兄弟节点:有相同的父节点的节点。F和G就不是相同的父节点,就不是兄弟节点。但是他们的爸爸在同一层,所以称为堂兄弟节点。、


多叉树的实现比较难搞,每个节点要保存父节点,还要保存他的兄弟节点,
所以我们先来实现一些简单的树形结构:二叉树。
二叉树
概念:这棵树的每个节点的子节点最多只能有两个,左孩子或者右孩子。
任意二叉树都由以下几种情况复合而成
在这里插入图片描述
特殊的二叉树

  1. 满二叉树:一个二叉树,如果他的每一层的节点数都达到最大值,这棵树就是满二叉树,如果该二叉树的层数为K,总结点个数为2k -1。
    例如:
    在这里插入图片描述
    每个父亲节点都有两个孩子。

现实中的二叉树如图:

在这里插入图片描述在这里插入图片描述
是不是超级标准。
2,完全二叉树
完全二叉树由满二叉树发展而来(满二叉树是完全二叉树的一种),如果一棵树有K个节点,这些节点从左往右依次数都是连着的,假设这里有一棵完全二叉树,节点个数为9。

在这里插入图片描述
只能4有左孩子后才能有右孩子,同一层节点,如果左边的节点没有两个儿子,后边的节点都不能有孩子。
下边两棵树都不是完全二叉树。
二叉树的存储结构
1,顺序结构
顺序结构用数组来存储,但一般只适用于完全二叉树,完全二叉树使用数组存储的话不会因为有些地方是空的会造成空间的浪费,所以现实中只有用堆才会使用数组存储。
二叉树顺序存储在物理上是一个数组,在逻辑上是一棵完全二叉树。

在这里插入图片描述
第二种是链式储存,用链表表示一棵二叉树,这种结构在数据结构高阶中才会用到,我们不做仔细讲解。

普通二叉树是不适合用数组建堆的,会造成大量的空间浪费,但是完全二叉树不一样,他的节点是挨个建立的,我们接下来建堆就是用这种结构。
堆的概念
有一堆元素,以顺序表按照完全二叉树的顺序将其储存起来,堆有两种,大堆和小堆。
大堆
all父节点大于子节点,根节点的值最大,这样的堆叫做大堆
如果根节点最小,所有父节点的值小于子节点,那这个堆被称为小堆。
堆的创建
给一个数组,

a[5,6,2,8,9,4,7]

这个数组在逻辑上是一棵完全二叉树,但还不是一个堆。
想要将它变成一个大堆,就要调整他的顺序。
首先我们会想到让根节点小于其子节点,然而其子节点也必须小于自己的子节点,所以想要让一棵树变为一个大堆,就要让自己的子树也变成一个大堆。

建堆过程如图
在这里插入图片描述
用数组的方式存储,但其逻辑上可以看作是一棵二叉树,完全二叉树
在这里插入图片描述

这里的1,2,3,4,5,6,7是数组的下标,
可以发现,若知道一个子节点的下标为child,其父节点的下标为(child-1)/2。
知道一个父节点parent,其左孩子节点的下标为parent2+1。右孩子的下标为parent2+2。

建堆的时间复杂度
向下调整法
向下调整法通过父节点的下标找左右孩子,不断判断值的大小,交换建堆。
代码如下

//向下调整
void AdjustDown(HeapStyle* a, int n, int parent)
{int child = parent * 2 + 1;while (child<n){//找出小的孩子if (child + 1 < n && a[child]> a[child + 1]){++child;}if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

为了容易计算,且满二叉树是完全二叉树的一种,为了化简过程就利用满二叉树来证明。
假设树的高度为n
第一层一个节点最多需要向下调整n-1层
第二层21个节点,最多每个节点要调整h-2层
第三层有22个节点,最多每个节点要调整h-3层

第h-1层2h-2个节点,最多向下调整一层
在这里插入图片描述
建堆的时间复杂度为O(N)。
向上调整法建堆
第一层不需要调整
第二层21个节点,每个节点最多需要向上调整1次。
第三层22个节点,每个节点最多向上调整2次

第h-1层2h-2个节点,最多每个节点向上调整h-2次。
第h层2h-1个节点,每个节点最多向上调整h-1次。
不用算就可以得出向上调整的时间复杂度为O(N2)。
向上调整法通过孩子找父亲判断交换建堆
代码如下

//向上调整
void AdjustUp(HeapStyle* a, int child)//向上调整
{int  parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

建堆过程
通过上边的分析,我们已经知道向下调整法相对于向上调整优势巨大,所以在建堆的过程中,使用向下调整法,遍历数组的每个节点,使用向下调整法建大堆,上边的流程图可知如果从根节点开始建堆,不能确保左子树和右子树是否为堆,最后一层不需要调整,从倒数第二层开始调整,使其变为一个堆。

//找出小的孩子if (child + 1 < n && a[child]> a[child + 1]){++child;}

找出该节点左右孩子中大的那个。

if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}

如果小于,就互换,越小越往上,所以建的是小堆,将判断条件更改为大于号,即建大堆。这里的大于号和小于号是建大堆还是建小堆的关键。
利用循环,交换后将parent改为child,child也更新一次,是因为更换过来的父节点有可能比该子树的子节点更小。
如图所示
在这里插入图片描述
给定一个数组a[k]

	for (int i = (k - 2) / 2; i >= 0; --i)//这里要思考一下{AdjustDown(minheap, k, i);}

从倒数第二层开始建堆即可,k-1是数组最后一个函数,要想知道其父节点下标,要减一再除以2,所以就变成了(k-2)/2。
堆排序
假设我们要排升序
建立一个大堆,堆顶元素一定是最大的那个,如果直接取出根节点,那么我们建的堆将被破坏,左子树和右子树可能就不再是大堆。
就像上图数组,建堆后下标为0,1,2,3,4的数字为4 6 5 7 7,如果取出4,剩余的6,5,7,7明显不再是一个堆,我们又要重新建堆,这样堆排序有什么优势可言,时间复杂度就变为O(N2)。如何取出根节点又不破坏左子树和右子树的大堆状态呢?
将堆顶元素与最后一个交换,最大的数就到了数组最后边,然后只需要对换过去的根节点向下调整即可。
代码如下

//向下调整
void AdjustDown(HeapStyle* a, int n, int parent)
{int child = parent * 2 + 1;while (child<n){//找出小的孩子if (child + 1 < n && a[child]< a[child + 1])//找出左右子树大的那个{++child;}if (a[child] > a[parent])//建大堆,大于就交换{swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void Heapsort(int* a, int n)//建大堆
{for (int i = (n-2)/2; i >=0; i--){AdjustDown(a,n, i);}//int end = n - 1;while (end > 0){swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

int end=n-1。a[end]为最后一个节点,交换后传参为n-1,向下调整就不再带最后一个节点玩了,直到调整至第一个节点,这样这个数组就有序了。
TOPK问题
TOPK问题在生活中常常出现,就比如年级前几名,饭店味道排名等等等等,如何在一大地数据中找到前几名呢?
如果我们建一个小堆,那么堆顶节点即为整棵树最小的那个,假设有10000个数据,我们要找出其中的前10个,我们就可以使用前十个元素建一个小堆,然后再让剩下的元素与堆顶元素进行比较,如果大于对顶元素,就与对顶元素进行交换,然后向下调整重新建出一个小堆,这时堆顶元素会发生变化,会变成一个新的堆中最小的数,遍历完成之后队中剩下的元素即为这10000个数里面最小的那个。
创建一个数组向里面写入10000个随机数进行测试。
在这里插入图片描述
随机数的范围是32767,如果我们创建100000个数据,只会出现很多重复的数据,所以我们就使用一万个数据测试一下数据。
代码如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//int main()
//{
//	printf("%d ", RAND_MAX);
//
//	return 0;
//}void  swap(int* a, int* b)
{int swp = 0;swp = *a;*a = *b;*b = swp;
}
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){//找出小的孩子if (child + 1 < n && a[child] > a[child + 1]){++child;}if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
void TestTopk()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * (n));srand(time(0));for (int i = 0; i < n; i++){a[i] = rand() % 10000;}a[5] = 100000 + 1;a[6100] = 100002;a[1007] = 100003;a[8678] = 888888;a[3459] = 777777;int k = 10;for (int i = (k - 2) / 2; i >= 0; i--)//倒数第二层最后一个节点{AdjustDown(a, k, i);//传入下标}//以前k个数建堆完毕for (int i = k + 1; i < n; i++){if (a[i] > a[0]){swap(&a[i], &a[0]);}AdjustDown(a, k, 0);}for (int i = 0; i < k; i++){printf("%d ", a[i]);}
}int main()
{TestTopk();return 0;
}

为了测试方便,我们修改了数组中的几个数值。
运行后如下
在这里插入图片描述
是不是小堆呢?
在这里插入图片描述
答案是,他就是个小堆,这样一万个数据里的前10个最大的数据就找出来了。
然而10000个数据是不是有点太少了。
我们普通创建的数组存储在栈上,malloc出的数组存放在堆里,如果有很多很多数据,就会占据很多时间,我们可以考虑将这些数字存放在一个文件里,然后利用相关的文件操作找出这些数字里最大得前几个。
向文件里写入数据

//创造数据
void CreatNData()
{int n = 1000000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen,error");return;}for (int i = 0; i < n; ++i){int x = rand() % 100000;//然而最大不过32767,有很多重复数据得了fprintf(fin, "%d\n", x);}int k = 10;//PrintTopk(file, k);fclose(fin);
}

创建完成之后可以打开修改修改数据,然后在main函数里将创建数据的函数注释,防止数据覆盖。
在这里插入图片描述
打开后直接就是一顿修改
在这里插入图片描述
那几个后边数字相同且贼大的就是修改的数据。

void PrintTopk(const char*file, int k)
{//1,建堆--用a中的前k个元素建堆FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen fail");return;}int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc fail");return;}for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);//依次先读K个数}for (int i = (k - 2) / 2; i >= 0; --i)//这里要思考一下{AdjustDown(minheap, k, i);}//2.将剩余的n-k个元素依次与堆顶元素int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > minheap[0]){//替换进堆minheap[0] = x;AdjustDown(minheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}fclose(fout);
}

逻辑和上边malloc出的数组找最大得前十个一样。建小堆,找到小于根节点的数据就进行交换,交换后再次进行向下调整。
要注意,rand出的数据最大32767,因为我们创建了一百万个数据,所以如果不修改数据的话,查找出来的都会是32767。
运行结果如图
在这里插入图片描述
是一个小堆
在这里插入图片描述
对顶元素在预料之中。这样Topk问题就解决啦。
这篇文章就讲解到这里,如果有什么问题欢迎大家提出指正。

相关文章:

二叉树,堆排序及TopK问题

要讲二叉树的概念&#xff0c;就要先讲树的概念。 树是什么呢&#xff1f; 树其实是一种储存数据的结构&#xff0c;因为他的结构倒过来和生活中的树很相似所以才被称之为树。 这是一颗多叉树&#xff0c;从最顶端的节点可以找到下边的几个节点&#xff0c;下边的节点又可以找…...

iphone xr密码错误太多次 连接itunes

itunes下载的固件在电脑在电脑的“C&#xff1a;\Users\用户名\AppData\Roaming\Apple Computer\iTunes\iPhone Software Updates”文件夹之中。 如果你忘记了 iPhone 密码 - 官方 Apple 支持 (中国) 下载和使用 Windows 10 版 iTunes - 官方 Apple 支持 (中国) 查找手机 iClo…...

设置RabbitMQ超时时间

RabbitMQ默认的超时时间是30分钟&#xff0c;在消息消费超过30分钟后&#xff0c;rabbitMQ会发生错误&#xff0c;导致整个channel被销毁&#xff0c;无法继续消费 在RabbitMQ安装的终端执行 rabbitmqctl eval application:set_env(rabbit,consumer_timeout,180000000). 命令…...

QT计时器

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> //计时器类 #include <QTime> //时间类 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widg…...

3-k8s-镜像仓库harbor搭建

文章目录 一、概念二、安装harbor三、使用harbor仓库 一、概念 官方概念&#xff1a;Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器。 我们平时拉去镜像都是从线上仓库拉去&#xff0c;但是企业内部的镜像一般都不会随意传到网上&#xff0c;而是保存在自己公…...

0基础学习PyFlink——模拟Hadoop流程

学习大数据还是绕不开始祖级别的技术hadoop。我们不用了解其太多&#xff0c;只要理解其大体流程&#xff0c;然后用python代码模拟主要流程来熟悉其思想。 还是以单词统计为例&#xff0c;如果使用hadoop流程实现&#xff0c;则如下图。 为什么要搞这么复杂呢&#xff1f; 顾…...

【无人机】太阳能伪卫星VoLTE无人机设计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

2023.10.20 LED驱动

驱动程序 #include<linux/init.h> #include<linux/module.h> #include<linux/fs.h> #include<linux/uaccess.h> #include<linux/io.h> #include"head.h"unsigned int major; unsigned int *vir_moder; unsigned int *vir_odr; unsign…...

【力扣刷题】回文链表、环形链表、合并两个有序链表

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 刷题篇 一、回文链表1.1 题目描述1.2 思路分…...

linux错误处理函数

linux c之perror、exit_perror与exit-CSDN博客 linux网络编程&#xff08;三&#xff09; TCP通信时序与多进程/线程并发服务器的编写-阿里云开发者社区 函数简介篇——错误处理函数&#xff1a;errno值、perror()、streeor()、streeor_r()_惺忪牛犊子的博客-CSDN博客...

vue2技能树(5)-条件渲染和列表渲染

目录 Vue 2 条件渲染详解v-if 和 v-else 指令项目示例 v-show 指令项目示例 v-if 和 v-show 的区别v-if 和 v-else-if 指令项目示例 Vue 2 列表渲染详解v-for 指令项目示例 计算属性和方法项目示例 v-bind:key项目示例 使用对象的v-for项目示例 v-for 的索引项目示例 &#x1f…...

MySQL基本操作之创建数据库

1、大小写敏感 一般大家都默认使用:小写 在MySQL中,大小写敏感性有两个方面的规定:lower_case_file_system和lower_case_table_names。 lower_case_file_system:指定操作系统文件系统是否对大小写敏感。 当设置为ON时,表示文件系统对大小写不敏感;当设置为OFF时,表示…...

8.对象贴地

愿你出走半生,归来仍是少年&#xff01; 在场景中&#xff0c;有时候需要对地物&#xff08;房屋、楼宇&#xff09;进行贴地处理&#xff0c;或者说相对地面高度&#xff08;井盖、井室&#xff09;进行设置。 通过自定义的Terrain切片以及影像瓦片构建的三维场景应该如何获取…...

AWS Lambda – 函数版本,别名,API网关,CodeDeploy协同

Hello大家好&#xff0c;我们今天继续讨论AWS Lambda的内容。 Lambda函数的版本 Lambda函数的版本和别名是辅助资源&#xff0c;我们可以通过创建这些资源管理函数的部署和调用。 首先&#xff0c;让我们来看一下Lambda 函数版本的概念。您可以使用版本来管理函数的部署。例…...

flutter doctor检测环境,出现CocoaPods installed but not working

1. 安装flutter, 地址: 安装和环境配置 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 2. 安装成功后&#xff0c;通过flutter doctor检测环境。以mac为例&#xff0c;出现了CocoaPods installed but not working 错误提示时&#xff0c;以下为解决方案: 2.1 rvm i…...

Python 条件和 if 语句

Python支持来自数学的通常逻辑条件&#xff1a; 等于&#xff1a;a b不等于&#xff1a;a ! b小于&#xff1a;a < b小于或等于&#xff1a;a < b大于&#xff1a;a > b大于或等于&#xff1a;a > b 这些条件可以以多种方式使用&#xff0c;最常见的是在"i…...

行业领先的三个企业正在利用聊天机器人变得更强

聊天机器人已成为客户服务领域的革命者&#xff0c;深刻地改变了企业与客户互动的方式。这些虚拟助手简化了交互&#xff0c;提供了24/7全天候高效和个性化的支持。凭借先进的技术和自然语言处理能力&#xff0c;聊天机器人擅长快速处理查询。 效率是聊天机器人的关键优势。它…...

「Git|场景案例」从项目中删除之前commit过的文件并且让git不追踪删除操作

本文主要介绍如何在git中删除文件但是让git不追踪这些删除操作 文章目录 场景说明解决方案删除一个文件删除一个文件夹以及子文件夹 场景说明 自己在使用react开发一个包含大量媒体文件的网站时&#xff0c;项目初期临时将这些媒体文件都放在项目中&#xff0c;直接使用访问本…...

一款.NET Core开源的基于Vue+ElementUI开发的博客系统 - StarBlog

前言 今天给大家推荐一款.NET Core开源的基于VueElementUI开发的博客系统 - StarBlog。该项目配套详细的文章教程&#xff0c;可以作为 .Net Core 入门项目学习。 官方项目介绍 StarBlog支持Markdown导入的博客。后端基于最新的.Net6和Asp.Net Core框架&#xff0c;遵循REST…...

用git stash暂存修改

git stash命令用于保存当前工作目录的临时状态&#xff0c;包括暂存区和已修改但未暂存的文件。它会将这些修改保存在一个临时区域&#xff08;即“堆栈”&#xff09;中&#xff0c;让你能够回到一个干净的工作目录&#xff0c;可以进行其他操作。等到你完成其他任务后&#x…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...