文章目录
数据结构
什么是置换选择排序?置换选择排序是用来干啥的?
我们都知道,归并排序是将若干个有序的子数组合并成一个更大的有序数组。那现在问题就来了,我一般手里拿到的往往是一大坨乱序的数据,我现在要对它进行归并,那归并需要的那些有序的子数组是怎么得到的呢?(初始归并段是如何获得的?)
在外部排序基本方法中,我们给出了构建初始归并段的基本方法,思路也很简单,就是从单个元素作为一个归并段开始,不断进行二路归并,不断地扩大基本归并段的容量
这种方法思路简单,但是存在着一些问题,问题就在于它归并段的数量实在是太多了,我们知道在上面的归并方法中,主要采用的是二路归并,你每增加两个归并段,在第一轮中就要增加一趟归并。在外部排序中,归并可是和IO密切相关的,你每多一趟归并,就要多若干次IO。而IO次数正是我们性能的最大阻碍,为了减少归并过程中的归并段数,我们才提出了置换选择排序的方法。这种方法以增加基本归并段中元素的个数为代价,减少了归并过程中的归并段数,有效地提高了外部排序的效率
那置换选择排序的归并段是怎么生成的呢?他咋就能减少IO次数呢?
看下面的例子

什么是最佳归并树?最佳归并树是用来干什么的?
看下面的例子,假如说一共有四个归并段,长度分别是2、3、5、100(每个归并段内部都是有序的)。我现在要把他们合并成一个大的有序数组,但是我的机器最多只能支持三路归并,也就是说我没办法把这四个归并段一口气合完(这种情况很常见,因为实际场景中你往往可以需要将上百个归并段合并成一个大的归并段,这时候你直接用百路归并显然是不现实的)
在这种情况下,我们只能通过多次三路归并来实现我们的目的,那问题就来了,既然是多次三路归并,就肯定要分组,确定哪三个在一组里面。那是不是怎么分都一样呢?显然不是
在具体举例子之前,我们需要先来复习一个简单的知识点:
在某次三路归并过程中,归并段中元素的个数分别是 2 3 5 ,请问这次归并过程中总共需要比较多少次(时间复杂度)?
3 路归并的总比较次数公式(针对 k 个段合并为 1 个段的场景):总比较次数 =(总元素数 - 1)×(k-1)
代入数据计算:
- 总元素数 = 10,k=3;
- 总比较次数 =(10-1)×(3-1)= 9×2 = 18 次
了解了这个之后,我们再来看下面的例子


可以看到,这三个例子中构建的归并树是不同的,总比较次数也是不同的。那对于计算机专业的同学来说,肯定是要求最优解,即我构建一棵怎样的归并树,能够使得归并的总比较次数最小呢?
答案就是,构建一棵哈夫曼树!假如说你计算机能支持k路归并,那你就构建一棵k阶的哈夫曼树。
这时候有的同学就会问,哈夫曼树是一棵在贪心策略下最优带权路径长度(WPL)最小的二叉树,但我想要的是让归并的比较次数最少呀,这俩一样吗?
在归并树中,元素的大小其实就是二叉树的权重,你再去看看比较次数的计算公式,发现比较次数与权重是成线性关系的,所以基本上就可以近似看做是一样的。
另外值得注意的是,为啥要补虚0节点呢?因为如果节点数目不足以构成一个严格的k叉树,那它的WPL不可能是最小的,为了让我们构建的二叉树WPL最小,我们才要补充若干个虚0节点,让节点的数量足以构成一棵严格的k叉树
什么是败者树?败者树是用来干什么的?
败者树是用来优化多路归并过程中每一轮的比较次数的。看下面的例子:
假设我们有 4个有序子序列(即4路归并),需要合并成一个整体有序的序列:
- 子序列1:[1, 5, 9]
- 子序列2:[2, 6, 10]
- 子序列3:[3, 7, 11]
- 子序列4:[4, 8, 12]
对于上面的4路归并而言,每个子序列都是有序的(每个子序列的第一个元素都是当前子序列中最小的元素),我们需要将这4个有序的子序列合成一个更大的有序序列,因此我们每次归并时都需要从4个子序列各自的第一个元素中选出一个最小的元素。
在引入败者树之前,我们正常的思路肯定是写一个for循环遍历去寻找最小元素,时间复杂度就是O(n)。引入败者树之后,我们的时间复杂度却可以达到O(log n),即我们只需要比较log n次,就可以从n个元素中选出一个最小的元素
什么是十字链表?什么是多重邻接表?二者有啥区别?
我们知道,图这种数据结构的基本存储方法有邻接矩阵和邻接表两种。十字链表和多重邻接表就可以看成是邻接表的变种,其中十字链表用来存储有向图,多重邻接表用来存储无向图
十字链表介绍
十字链表头结点由3个数据域组成:
data | firstIn | firstOut |
---|
① data 存储顶点相关的信息。
② firstIn 指向入边表中的第一个边结点。
③ firstOut 指向出边表中的第一个边结点。
十字链表边结点由4个或5个数据域组成:
tailVex | headVex | hLink | tLink | info(可选) |
---|
① headVex 指弧头(边终点)在顶点表中的下标。即邻接矩阵元素 edges[i][j] 的下标 j(列标)。
② tailVex 指弧尾(边起点)在顶点表中的下标。即邻接矩阵元素 edges[i][j] 的下标 i(行标)
③ hLink 指弧头(终点)为顶点 headVex 的下一条边。即邻接矩阵中所在列的下一个元素。
④ tLink 指弧尾(起点)为顶点 tailVex 的下一条边,即邻接矩阵中所在行的下一个元素。
⑤ info 数据域可有可无,存储边的相关信息。对于带权有向图,可以用 info 存放边的权值。

多重邻接表介绍
邻接多重表头结点由2个数据域组成:
data | firstEdge |
---|
① data 存储顶点相关信息。
② firstEdge 指向第一条和该顶点相连的边。
邻接多重表边结点由4~6个数据域组成:
mark(可选) | iVer | iLink | jVer | jLink | info(可选) |
---|
① mark数据域可有可无,可以用来标记该边结点是否被访问。
② iVer和jVer:分别存储一条边的两个顶点在顶点表(数组)中的下标。
③ iLink指和顶点iVer相连的下一条边。
④ jLink指和顶点jVer相连的下一条边。
⑤ info数据域可有可无,存储边的相关信息。对于带权有向图,可以用info存放边的权值。

操作系统
文件系统的结构是怎样的?
文件系统结构的本质就是用分层抽象屏蔽硬件差异,用元数据+数据分离实现高效管理:
一、文件系统的核心分层结构
从底层硬件到上层用户接口,文件系统通常分为5层,每层负责特定功能,且上层依赖下层提供的服务。这种分层设计让文件系统的维护、扩展和跨硬件适配更灵活。
层级 | 核心功能 | 示例(以Linux Ext4为例) |
---|---|---|
1. 硬件层 | 存储文件的物理介质,提供"块级"数据读写能力(最小单位为扇区,通常512B) | 机械硬盘(HDD)、固态硬盘(SSD)、U盘的物理扇区 |
2. 驱动层 | 操作系统与硬件的接口,将硬件的"扇区"抽象为"逻辑块"(大小通常4KB/8KB) | 硬盘驱动程序(如sd 系列驱动),屏蔽硬件差异 |
3. 核心层 | 文件系统的核心逻辑,管理"逻辑块"的分配、回收,以及文件的元数据和数据存储 | Ext4的super block (超级块)、inode (索引节点) |
4. 接口层 | 提供用户/应用程序访问文件的"系统调用接口",屏蔽底层实现细节 | Linux的open() /read() /write() ,Windows的CreateFile() |
5. 应用层 | 用户直接交互的工具或程序,基于接口层实现文件的可视化管理或操作 | Windows资源管理器、Linux的ls /cp 命令、macOS访达 |
二、文件系统的核心数据结构(以Ext4为例)
文件系统的核心设计思路就是通过元数据(描述文件的信息) 和数据(文件的实际内容) 分离存储,实现高效的文件管理。下图是一个文件系统可能的结构布局
0. 引导块
C盘中引导块里面存了一段引导程序,运行这段引导程序,就能启动存在C盘中的操作系统
1. 超级块(Super Block):文件系统的"总目录"
超级块里面存的是啥呢?超级块里面记录的是一份文件系统的具体配置信息,具体来说:
- 超级块所属的这个文件系统的类型
- 这个文件系统有多少个逻辑块,每个逻辑块的大小,用了多少个逻辑块,还有多少个空闲的逻辑块
- 规定这个文件系统最多允许创建多少个innode,每个Innode的大小,现在创建了多少个innode,还能创建多少个innode
- 规定一个文件的文件名最多有多长,一个文件最多有多大等等
- 记录这个文件系统的创建时间、最近的挂载时间
2. 索引节点(inode):文件的"身份证"
在文件系统内,每个文件/目录都对应一个唯一的inode(索引节点),它存储文件的"元数据"(描述信息),但不包含文件名和实际数据。
3. 数据块(Data Block):文件的"内容仓库"
数据块用来存储文件的具体内容,是同时也是存储的最小单位(大小由文件系统预设,如4KB),每个数据块对应一个逻辑块号。
4. 空闲块管理:避免空间浪费
文件系统需要实时跟踪"空闲的逻辑块"和"空闲的inode",避免分配时重复或遗漏。常见的管理方式有3种:
- 位图(Bitmap):用1位(bit)表示1个块/inode的状态(0=空闲,1=已用),查询和修改效率高(Ext4、NTFS均采用);
- 链表(Linked List):将空闲块/inode用链表串联,分配时从表头取,回收时插入表尾(适合小文件系统,效率低);
- 组块(Group):将文件系统划分为多个"块组"(如Ext4的Block Group),每个块组内包含独立的超级块、inode表、数据块,分散负载(提升大文件系统的性能)。
主流的文件系统有哪些?
主要就是 Linux的Ext4,Windows的NTFS,还有macOS的APFS。
不同文件系统适配的硬件也不一样,比如SSD适合Ext4/APFS,机械硬盘适合NTFS
对比维度 | Linux(Ext4) | Windows(NTFS) | macOS(APFS) |
---|---|---|---|
根目录 | / (唯一根目录,所有分区挂载到子目录) |
多根目录(如C:\ 、D:\ ,对应不同分区) |
/ (唯一根目录,分区通过"宗卷"管理) |
特殊目录 | /home (用户目录)、/etc (配置文件) |
C:\Users (用户目录)、C:\Windows (系统文件) |
/Users (用户目录)、/System (系统文件) |
元数据扩展 | 支持xattr (扩展属性,如文件标签) |
支持"访问控制列表(ACL)""加密(EFS)" | 支持"快照""加密""文件克隆" |
inode特点 | inode号全局唯一 | 用"MFT(主文件表)"替代inode,MFT项存储元数据 | 用"inode",但支持动态调整inode大小 |
开机后操作系统的启动流程
- 通电后先找到BIOS:计算机开机后,首先会找到固化在主板芯片上的BIOS(Basic Input/Output System,基本输入输出系统),将它放到处理机上运行
- BIOS 完成硬件自检与硬件初始化: BIOS 首先检查硬件(内存、硬盘等),然后读取磁盘第 0 扇区的 MBR;
- MBR 定位C盘:MBR 中的引导程序扫描分区表,找到标记为 "活动" 的分区(通常是 C 盘);
- 加载分区引导块:MBR 将控制权交给活动分区的第一个扇区(即 C 盘的分区引导块),BIOS 读取该引导块;
- 分区引导块加载系统核心:C 盘的分区引导块执行代码,识别 NTFS 文件系统,找到并加载bootmgr;
- 启动操作系统:bootmgr加载BCD配置,调用winload.exe加载 Windows 内核,最终完成系统启动。
BIOS存在计算机的什么位置?负责什么工作?
BIOS的全称是 Basic Input/Output System(基本输入输出系统) ,本质是一段固化在主板ROM芯片(早期为只读ROM,现代为可擦写的Flash ROM)中的底层程序。
BIOS是计算机通电后运行的第一个程序,负责在操作系统(Windows/macOS/Linux)启动前,完成"硬件自检"和"硬件初始化",搭建起"硬件与操作系统"的沟通桥梁
BIOS的所有操作都发生在"操作系统启动前"(即我们常说的"开机自检阶段"),核心功能可归纳为3点:
1. 硬件自检(POST:Power-On Self-Test)------ 硬件"健康检查"
这是BIOS通电后的第一个任务:自动检测主板、CPU、内存、硬盘、显卡、键盘等核心硬件是否正常。
2. 硬件初始化------ 让硬件"进入工作状态"
自检通过后,BIOS会对硬件进行"初始化配置":比如设定CPU的基础频率、内存的时序、硬盘的识别模式(IDE/ACHI)、显卡的输出分辨率等,确保所有硬件都处于"可被调用"的状态。
3. 加载并执行引导程序------ 找到"操作系统入口"
执行MBR中的主引导程序(Boot Loader),启动操作系统
MBR里面装的是啥?
MBR(Master Boot Record,主引导记录)是硬盘的"第一个扇区"(位于硬盘0磁道0柱面1扇区,大小为固定是512字节),是BIOS启动时识别硬盘、引导操作系统的关键数据区域。
MBR的512字节并非"杂乱存储数据",而是按"引导程序+分区表+校验位"的结构划分,每个部分的功能不可替代
主引导程序的作用
BIOS硬件自检与初始化完成后,会将MBR中的这部分程序加载到内存),主引导程序执行时,首先会检查硬盘分区表的有效性。如果检查没问题,那就会紧接着从分区表中找到"活动分区"(基本上就是C盘),并将该分区的"分区引导记录(PBR)"加载到内存,最终由PBR启动操作系统。
硬件分区表的作用
记录硬盘的分区配置信息
- 你这个硬盘有几个分区
- 每个分区大小多少,起始位置在哪,每个分区采用的文件系统是什么,是不是活动分区等等
校验位
MBR的标识位,用来表示MBR的结束