- 持续更新~
- 欢迎评论区留言指正
- 如果你是Obsidian用户,可以导入自己的笔记库中,效果最佳
- ⚠️如需转载,请标明出处!
DS
口诀:
1、插冒归基,五稳四不
算法题
一、快速排序
1、快排代码
①递归—>乱序数组
②关键思想:划分—>参数(数组指针、范围)
// Partitation 划分
int huafen(int A[], int L, int R) {
int mid = A[L]; // 刚开始 枢轴元素的值 为 数组最左边的值
while (L < R) {
while (A[R] >= mid)
R --;
A[L] = A[R];
while (A[L] <= mid)
L ++;
A[R] = A[L];
}
A[L] = mid;
return L; // 返回 L 以便于 递归地进行划分
}
// 快速排序
void QuickSort(int A[], int L, int R) {
if (L >= R)
return; // 递归结束
int M = huafen(A, L, R); // 定义一个 M 记录 上一层递归的枢轴元素
QuickSort(A, L, M - 1); // 左半部分 划分
QuickSort(A, M + 1, R); // 右边部分 划分
}
2、快排实战
2011-42、2013-41、2018-41、2016-43
3、运用快排的"划分"思想
例:使用"划分"函数找到数组中第k小的元素
int func(int A[], int n, int k) {
int L = 0, R = n - 1, M = 0;
while (1) {
M = huafen (A, L, R);
if (M = k - 1)
break;
else if (M > k - 1)
R = M - 1; // 下标为 k - 1 的元素只能出现在左半部分
else if (M < k - 1)
L = M + 1; // 下标为 k - 1 的元素只能出现在右半部分
}
return A[k - 1]; // 返回下标 k - 1 对应元素的值
}
⚠️复杂度:
每一轮划分的时间复杂度:
n
+
n
2
+
n
4
+
⋅
⋅
⋅
+
n
n
令
n
=
2
x
,
⇒
2
x
+
2
x
−
1
+
2
x
−
2
+
⋅
⋅
⋅
+
1
=
1
−
2
x
+
1
1
−
2
=
2
x
+
1
−
1
=
2
n
−
1
⇒
总体时间复杂度
O
(
2
n
+
1
)
=
O
(
n
)
空间复杂度
O
(
1
)
\\ 每一轮划分的时间复杂度:n + \frac{n}{2} + \frac{n}{4}+···+ \frac{n}{n} \\ 令\ n=2^x, \\ \Rightarrow 2^x+2^{x-1}+2^{x-2}+···+1 \\ =\frac{1-2^{x+1}}{1-2}=2^{x+1}-1=2n-1 \\ \Rightarrow 总体时间复杂度O(2n+1)=O(n) \\ 空间复杂度O(1)
每一轮划分的时间复杂度:n+2n+4n+⋅⋅⋅+nn令 n=2x,⇒2x+2x−1+2x−2+⋅⋅⋅+1=1−21−2x+1=2x+1−1=2n−1⇒总体时间复杂度O(2n+1)=O(n)空间复杂度O(1)
实战:8.3.3 大题 2
2016-43
二、二路归并排序
0、回顾
1、二路归并 VS 快速排序
2、二归实战
①基本功
②2011-42
三、链表(2.3课后习题)
0、大观
1、基本功训练
①按位序查找
(1)代码
int Length = 0;
while (p != nullptr) {
Length ++;
p = p->next;
}
(2)实战
②按关键字条件查找+删除
(1)代码
ListNode* pre = L;
ListNode* p = pre->next; // 定义两个链表节点
if (p->val == x) { // 如果 p节点的值为 x
ListNode* q = p; // 定义辅助节点 q
p = p->next;
pre->next = p;
free(q);
}
(2)实战
③按关键字条件查找+插入
④头插法(原地逆置)
⑤尾插法(保持原序)
四、二叉树
0、大观
处理二叉树时常用的代码思路
1、基本功训练
①前/中/后序遍历(递归方法)
// 链式存储的二叉树节点定义
typedef struct BiTNode {
int data; // 数据域
struct BiTNode *lchild, *rchild; // 左右孩子指针
}BiTNode, *BiTree
// 前序遍历
void PreOrder(BiTree root) {
if (root == null)
return; // 根节点为空 递归结束
visit(root);
PreOrder(root->left);
PreOrder(root->right);
}
// 中序遍历
void InOrder(BiTree root) {
if (root == null)
return;
InOrder(root->left);
visit(root);
InOrder(root->right);
}
// 后序遍历
void PostOrder(BiTree root) {
if (root == null)
return;
PostOrder(root->left);
PostOrder(root->right);
visit(root);
}
②层序遍历
层序遍历考应用题的概率更大
⚠️考试时可自行定义队列Queue
// 初始化队列
void InitQueue(Queue &Q);
// 判空
bool IsEmpty(Queue &Q);
// 入队
void EnQueue(Queue &Q, BiTree T);
// 出队
void DeQueue(Queue &Q, BiTree T);
// 层序遍历
void LevelOrder(BiTree T) {
Queue Q;
InitQueue(Q); // 初始化队列
BiTree p;
EnQueue(Q, T); // 根节点入队
while(!IsEmpty(Q)) { // 队列非空
DeQueue(Q, p); // 队头节点出队
visit(p); // 先出队再访问
if (p->lchild != null)
EnQueue(Q, p->lchild); // 左孩子入队
if (p->rchild != null)
EnQueue(Q, p->rchild); // 右孩子入队
}
}
二叉树属性
③求二叉树的高度
④求二叉树的宽度
⑤求二叉树的的WPL
树形判定
⑥是否为二叉排序树
⑦二叉树是否平衡
⑧是否为完全二叉树
2、实战
2014-41、2017-41
五、图
0、大观
1、代码
不要死背代码,尝试理解着记忆
①图的数据结构定义——邻接矩阵
# define MAXV 6 // 顶点数目的最大值
// 图的邻接矩阵
typedef struct {
int numVertices, numEdges; // 图的实际顶点数和边数
char VerticesList[MAXV]; // 顶点表
int Edge[MAXV][MAXV]; // 边表
}MGraph;
MGraph * G = (MGraph *) malloc (sizeof(MGraph));
for (int i = 0; i < MAXV; i ++)
for (int j = 0; j < MAXV; j ++)
G->Edge[i][j] = 0; // 408中,权值0表顶点之间没有边
G->numVertices = 4;
G->numEdges = 5;
// 初始化各顶点数据
G->VerticesList[0] = 'a';
G->VerticesList[1] = 'b';
G->VerticesList[2] = 'c';
G->VerticesList[3] = 'd';
// 初始化各边数据
G->Edge[0][1] = 1;
G->Edge[0][3] = 1;
G->Edge[1][2] = 1;
G->Edge[1][3] = 1;
G->Edge[2][3] = 1;
②图的数据结构定义——邻接表
// 定义邻接表
# define MAXV 6 // 顶点数目的最大值
typedef struct EdgeNode { // 边表节点
int index; // 这条边所指向的顶点的位置
struct EdgeNode *next; // 指向下一条边的指针
int weight; // 权值(若是无权图,可删)
}EdgeNode;
typedef struct VertexNode { // 顶点表节点
char data; // 节点信息
EdgeNode * first; // 指向第一条依附该顶点的边的指针
}VertexNode, VertexList[MAXV];
typedef struct {
VertexList list; // 邻接表
int num Vertex, numEdge; // 图的顶点数和边数
}ALGraph; // ALGraph 是以邻接表存储的图
// 初始化无权有向图
ALGraph * G = (ALGraph *) malloc(sizeof(ALGraph));
for (int i = 0; i < MAXV; i ++)
G->List[i].first == NULL;
G->numVertex = 4;
G->numEdge = 5;
G->list[0].data = 'a';
G->list[1].data = 'b';
G->list[2].data = 'c';
G->list[3].data = 'd';
AddEdge(G, 0, 1, 1);
AddEdge(G, 0, 3, 1);
AddEdge(G, 1, 2, 1);
AddEdge(G, 1, 3, 1);
AddEdge(G, 2, 3, 1);
// 在节点<i, j> 之间增加一条边; 边的权值为weight
void AddEdge(ALGraph * G, int i, int j, int weight) {
EdgeNode * p = (EdgeNode *) malloc (sizeof(EdgeNode));
p->weight = weight;
p->index = j; // 指向顶点j
p->next = G.list[i].first; // 单链表的头插法
G->list[i].first = p;
}
2、实战
2021-41、2023-41
2021改、2023改
应用题
OS(读者-写者问题—今年很可能要考)
建议复习顺序:
第二章 进程与线程
零、大观
复习顺序:进程的同步与互斥(第二章)——>虚拟分页存储管理(第三章)——>文件目录、文件的物理结构(第四章)
一、PV操作
生产者-消费者问题
六步骤(草稿纸上的分析步骤)
1、有几类进程?
2、每个进程分别对应了哪些动作?
a. 只做一次——不加while
b. 不断重复——加while(1)
3、分析每一动作之前,是否需要P什么(⚠️有P必有V)
⚠️注意可能存在隐含的互斥
如:对缓冲区访问需加P(mutex),保证访问的互斥性
4、所有PV操作写完后,再定义信号量
5、检查多个P连续出现的地方是否产生死锁;调整(交换)P顺序
6、检查是否符合题意
单纯的同步问题(前v后p)
哲学家进餐问题
模板题
Semaphore Lock = 1; //互斥信号量
int a = 9, b = 8, c = 5; // abc三类资源,定义3个int类型信号量
Process() {
while(1) {
P(Lock);
if(所有资源都够) {
所有资源 int --;
取xxx资源;
V(Lock);
break;
}
V(Lock);
}
做进程该做的事(如:哲学家就餐)
P(Lock);
归还所有资源,所有资源int ++;
V(Lock);
} // END
理发师问题
代码框架(较复杂情况——没有顾客,服务员睡眠休息💤)
// 初始化
int waiting = 0;
semaphore mutex = 1; // 保证各进程对 waiting 的互斥访问
semaphore customer = 0; // 这里 考虑 服务员 会休息
semaphore service = 0; // 保证"被服务"与"提供服务"同步 // 先取号——确认有顾客——叫号——提供服务
// 顾客
customer_i() {
while(1) {
P(mutex); // 我们要使用 waiting 变量——需要互斥访问
取号;
waiting ++;
V(mutex); // 使用(修改)完 释放 mutex
V(customer); // 唤醒 服务员
等待被叫号;
P(service); // 上锁之后再被提供服务——1对1——避免产生互斥
被服务;
}
}
// 服务员
server_j() {
while(1) {
P(mutex); // 需使用 waiting ——上锁
if (waiting > 0) { // 有顾客
叫号;
waiting --;
V(mutex);
V(service);
提供服务;
} else {
V(mutex); // 保证不存在顾客也能解锁
P(customer); // 没有顾客,服务员休息——上锁
} // else_end
} // while_end
}
读者-写者问题(OS结课考试会讲)
⚠️近几年从未考察,今年考察的概率很大
第三章 内存管理
一、内存管理大题
1、内存管理章节大题概念考察的很细,多听几遍OS骚图pro
2、CPU执行指令MOV[VA], 3
3、一般都是考察——虚拟分页的存储管理
4、TLB命中,不可能发生缺页
只有查主存中的慢表没命中,才可能发生缺页
今年有可能考察选择题
第四章 文件管理
文件目录、文件的物理结构(王道课后大题全做
补充知识:
1、局部淘汰策略——只从分配给进程的页框中国淘汰页面
全局淘汰策略——还可能从其他进程中淘汰出去
2、进程间通信
共享内存方式:
两个进程共享OS的内核区:只需将两个进程P1、P2的VA的高地址部分映射到同一片物理页框中
信箱通信方法
CO
大致有五章(共七章)可能出现大题
建议复习顺序:三(存储系统)—>二(数据的表示和运算)—>四(指令系统)—>五(中央处理器—5.6指令流水线)—>七(输入/输出系统)
第三章 存储系统
一、Cache大题
近16年真题共32题中占9题
Cache第一类大题——Cache地址结构
Cache第二类大题——Cache行完整构成
参考真题:
2020、2010 44题
2018(1)(3)
2023 43题(3)(4)
王道大题:
第三章的3.5、3.6
二、TLB大题
参考真题:
2021(TLB经典题)
2018(2)
2016
第二章 数据的表示和运算
近16年真题共32题中占3+n题
真题对应25版王道书位置

零、大观
重点需要掌握内容(复习强化)
①减法运算
(i)无符号减法
A
、
B
均能表示
8
b
i
t
整数
A
−
B
→
转化为加法
A
+
(
B
反
末位
+
1
)
\\ A、B均能表示8bit整数 \\ A - B \xrightarrow{转化为加法}A +(B_{反}末位+1)
A、B均能表示8bit整数A−B转化为加法A+(B反末位+1)
(ii)有符号减法(补码减法)
同理
②浮点数的补码表示
(i)只需关注IEEE754标准
结构:符号位+阶码+尾数
float: 1+8+23=32
double: 1+11+52=64
long double: 1+15+64=80
③标识位的生成
O
F
=
C
n
⊕
C
n
−
1
C
F
=
S
u
b
⊕
C
o
u
t
(
或
C
i
n
⊕
C
o
u
t
)
Z
F
S
F
\\ OF = C_{n} \oplus C_{n-1} \\ CF = Sub \oplus C_{out}(或C_{in} \oplus C_{out}) \\ ZF \\ SF
OF=Cn⊕Cn−1CF=Sub⊕Cout(或Cin⊕Cout)ZFSF
分别表示:溢出标识位、进位标识位、零标识位、符号标识位
OF=符号位的进位和最高位的进位相异或
CF=控制信号Sub和最高位的进位相异或
⚠️补充:
⊕ \oplus ⊕:相同取0,相异取1
Sub:Substration
用来判断有符号数是否溢出;OF = 1 表溢出
CF用来判断无符号数是否溢出; CF = 1 表溢出
④常见数据类型
short | 16bit | 2B |
---|---|---|
int | 32bit | 4B |
float | 32bit | 4B |
double | 64bit | 8B |
unsigned | 32bit | 4B |
⑤ 2 16 = 65536 2 15 = 32768 2^{16} = 65536 \\ 2^{15} = 32768 216=65536215=32768
带符号short:-32768~32765
无符号unsigned short:0~65535
⑥类型转换
无符号整数和带符号整数机器数相同,但解释方式不同
带符号整数要将机器数转换为补码形式,再翻译为真值
⑦
⑧
第四章 指令系统
建议重看
一、一堆指令的执行(指令序列的工作过程)
二、MIPS/RISC-V 一堆指令的执行
在录播里
一般会结合指令流水线
第五章 指令流水线(5.6)
一、结构冒险、数据冒险和控制冒险的处理
1、结构冒险及其处理(又称结构冲突,资源冲突)
- 处理方法一:通过硬件阻塞来处理
- 处理方法二:指令Cache和数据Cache分离
2、数据冒险的分析及其处理
①分析
从某条指令开始,分析是否与其后相邻的3条指令发生写后读
②处理
- 硬件阻塞
- 转发技术(旁路)
3、控制冒险的分析及其处理
①分析
只有==转移类指令(尤指 条件转移)==的执行(阶段)才会引发控制冒险
②处理
-
硬件阻塞
将转移类指令的后一条指令的IF段硬件阻塞3个时钟(访存阶段M会修改PC值)
转移: IF ID EX M WB
后一条: 阻 阻 阻 IF ID EX M WB
-
转发技术
二、五段式指令流水线
IF | ID | EX | M | WB |
---|---|---|---|---|
取指 | 译码/取数 | 执行/计算地址 | 访存 | 写回 |
指令按序发送,按序完成
三、一条指令的执行
1、指令执行过程
(I)取指(译码)阶段
所有指令在取指阶段的操作都相同
①根据PC从主存取指令到IR——数据流向:PC->MAR->M(MAR)->MDR->IR
②PC+"1"
- 由ALU、加法器、带"自增功能"的寄存器实现
- "1"与指令字长和编制方式(按字节编制/按字编制)有关
(II)执行阶段
学会根据指令类别思考
①数据传送类指令(如:mov、load、store)——关注数据从哪到哪?
数据只会来源于 寄存器、主存、立即数
寄—>寄
寄—>主
主—>寄
立—>寄
立—>主
寄—>暂存寄存器(某条指令的子步骤)
⚠️若有主存,则关注是 读/写主存
// 读
地址—>MAR;
M(MAR)—>MDR; // CU 给 主存发出读信号
MDR—>目的地;
// 写
地址—>MAR;
数据—>MDR;
MDR—>M(MAR); // CU 给 主存发出写信号
⚠️关注总线占用(总线是临界资源),安排控制信号
②运算类指令
加、减
- ALU、加法器
- 带"自增自减"的寄存器(实现
++/--
)
乘
- ALU、乘法器
- 带"移位功能"的寄存器(实现 2 n 2^n 2n 这种特殊乘法,亦可用ALU的移位功能平替)
除
- ALU、除法器
- 带"移位功能"的寄存器(实现 2 n 2^n 2n 这种特殊除法,亦可用ALU的移位功能平替)
移位
- ALU
- 带"移位功能"的寄存器
与、或、异或等双操作数逻辑运算
- ALU
非
- ALU——只需一端有输入
- 带"取反功能"的寄存器
短数—>长数
- 带"符号扩展功能"的寄存器
- 带"零扩展功能"的寄存器
③转移类指令(条件转移)——改变PC值
-
一般前置会有cmp指令(其本质是减法A-B,生成符号位CF、ZF、OF、SF)
-
原理:根据标志位判断是否转移(指令中会给出地址码)
⚠️若要转移
①注意指令寻址方式
- 相对寻址——PC+偏移量——其中偏移量可能以字/字节为单位
- 直接寻址(少见)
②注意PC的值
- 以字节为单位——CISC、RISC
- 以指令字为单位——RISC(精简指令集系统的计算机指令长度相同)
第七章 输入/输出系统
重听
零、大观
考察年份
2010、2011、2012
2017、2018
2023(大题)、2024
一、深入理解I/O系统
PIC—可编程的中断控制器
1、从中断控制器的角度理解整个中断处理过程
2、printf—从系统调用到I/O控制方式的具体实现
3、scanf—中断方式下输入一个字符串的具体过程
二、Printf—从系统调用开始
printf函数是C语言中的库函数,属于用户层I/O软件
1、中断隐指令与中断处理程序
中断隐指令(由硬件实现)
①保存断点和程序状态字
②将CPU模式从用户态切换为内核态
中断处理程序(由OS实现)
①保存现场(保存通用寄存器中的内容)
②执行系统调用服务例程(一种特殊的中断处理程序)
sys_write可用三种I/O方式实现:程序查询、中断驱动和DMA
1、轮询(程序查询)方式下输出一个字符串
①为什么不直接使用用户缓冲区buffer呢?
答:系统调用函数write位于内核态,需要把用户缓冲区的内容复制到内核空间(以确保数据的一致性)
②上述过程又是如何实现的呢?
答:直接访问即可,因为此时并没有发生进程切换
在内核态下,完全可以访问内核空间
2、中断驱动方式下输出一个字符串
①中断服务程序(中断总控程序)与设备驱动程序的关系==——重听==
若同时出现中断服务程序和设备驱动程序
则将中断服务程序理解为中断总控程序并与设备驱动程序区分开
3、DMA方式下输出一个字符串
三、Scanf的完整故事
sys_read用中断方式实现
1、中断驱动方式下输入一个字符串
计网
二轮复习大观
1、详细描述访问WWW.xxx.com服务器资源(HTTP资源)所发生的过程
①DNS查询得到IP地址
②ARP查询得到MAC地址
发送ARP请求的是一个广播;收到ARP响应的是一个单播(先发送广播再返回单播)
③主机与服务器建立TCP连接
④主机发送HTTP协议请求服务器响应请求