Loading

[2024-08-19]CSP-S初赛复习

CSP-S初赛复习

ASCII码

字符 ASCII码
'a' 97
'A' 65
'1' 49
空格:' ' 32

Linux相关

基础命令

命令 功能
cd 进入目录
ls 列出工作目录所含的文件和子目录
pwd 显示目前的目录
mkdir 创建文件夹
rmdir 删除空文件夹
touch 创建空白文件
cp 复制文件或目录
rm 删除文件或目录(包括目录内所有文件)
mv 移动文件或目录
file 查看文件类型
man 查看命令使用文档

Linux time 指令

  • 引用于 CSDN Linux time命令用法

Linux time命令是一个非常实用的工具,主要用于计算程序执行的时间。这个命令可以用来度量一段代码或者脚本的运行时间,有助于性能调优和基准测试。

  • 例子:time -p ls ,time -v ls
  • -p:以可移植输出格式打印实际时间、用户 CPU 时间和系统 CPU 时间。
  • -v:提供更详细的信息,包括进程使用的系统资源。

通常输出三种时间:

  • real(实际时间)
    从开始到结束的墙钟时间(wall-clock time)。也就是我们常说的实际时间。

  • user(用户CPU时间)
    在用户模式中执行该过程所花费的CPU时间。

  • sys(系统CPU时间)
    在内核模式中执行该过程所花费的CPU时间。

情景

[root@localhost home]# time -p ls
centos  docker  keen  mc  woakioi  woakioi1
real 0.00
user 0.00
sys 0.00
[root@localhost home]# \time -v ls
centos	docker	keen  mc  woakioi  woakioi1
	Command being timed: "ls"
	User time (seconds): 0.00
	System time (seconds): 0.00
	Percent of CPU this job got: 100%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.00
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 2576
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 0
	Minor (reclaiming a frame) page faults: 120
	Voluntary context switches: 1
	Involuntary context switches: 0
	Swaps: 0
	File system inputs: 0
	File system outputs: 0
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 0

  • 一般情况 \(real=user+sys\)

  • 多核处理器 \(real<user+sys\) 。因为多个CPU核心可以同时工作,或者说导致实际时间变少。

  • 有他进程占用时 \(real>user+sys\)

GCC/G++编译命令

  • 如果你编写的是 C 代码,使用 gcc

  • 如果你编写的是 C++ 代码,使用 g++

  • CSDN:GCC编译常用命令

选项 功能
-o 指定输出文件名。例如,-o myprogram 表示将输出文件命名为 myprogram
-c 只编译,不链接,生成目标文件(.o 文件)。
-Wall 全称"Warn All" ,启用所有警告信息,帮助发现潜在问题。
-Werror 全称 "Warnings as Errors",将所有警告视为错误,编译失败。
-g 生成调试信息,便于使用调试器(如 gdb)调试程序。
-std 指定 C++ 标准版本,例如 -std=c++11-std=c++14-std=c++17
-I 指定头文件搜索路径。例如,-I/path/to/include
-L 指定库文件搜索路径。例如,-L/path/to/lib
-l 链接指定的库。例如,-lm 链接数学库。
-static 进行静态链接,生成不依赖于共享库的可执行文件。
-fPIC 生成位置无关代码,通常用于创建共享库。
-O 启用优化,-O1-O2-O3 分别表示不同级别的优化。
-S 只编译源文件,生成汇编代码(.s 文件)。
-E 只进行预处理,输出预处理后的代码。

CCF考点编译环境:-O2 -std=C++14

进制转换

  • 二进制:C和C++都没有提供二进制数的表达方法。
  • 八进制:C/C++中为了区分,以0开头。
  • 十六进制:C/C++为了区分,以0x开头。

十进制转X进制

十进制转二进制

小数转化

十进制转X进制

十进制小数转换成二进制小数采用 “乘2取整,顺序输出” 法。

  • 例子:

    0.618D = 0.101B(保留三位小数)

X进制转十进制

把X进制数按权展开、相加即得十进制数。

  • 例子:

排序算法

  • 排序算法的稳定性
  • 8大排序算法的稳定和不稳定分析:CSDN

稳定性是指相等的元素经过排序之后相对顺序是否发生了改变。

  • 笔者总结的规律:时间复杂度带\(log_2n\)一般不稳定,归并排序除外。除此之外有交换操作且相邻两个交换的一般稳定,反之,交换的两元素距离太大一般不稳定。

  • 常见排序算法

  • \(n\) 是待排序元素的数量。
  • \(k\) 是元素值的范围。

插入排序

插入排序是一种简单的排序算法,适用于小规模数据的排序。它的基本思想是将待排序的元素逐个插入到已排序的序列中,直到所有元素都被插入为止。

  • 最坏情况\(O(n²)\)(当输入数组是逆序时)
  • 最好情况\(O(n)\)(当输入数组已经是有序时)
  • 平均情况\(O(n²)\)
  • bilibili 可视化插入排序。

希尔排序

希尔排序是一种基于插入排序的排序算法,通过将数据分成多个子序列来进行排序,从而提高插入排序的效率。它的基本思想是先对间隔为 h 的元素进行插入排序,然后逐步缩小间隔,最终对整个序列进行插入排序。

  • 最坏情况\(O(n²)\)(具体取决于增量序列的选择)
  • 最好情况\(O(n log n)\)(当输入数组接近有序时)
  • 平均情况\(O(n log n)\)(具体取决于增量序列的选择)
  • bilibili 可视化希尔排序。(建议观看)

选择排序

选择排序的基本思想是每次从未排序的部分中选择最小(或最大)元素,将其放到已排序部分的末尾。该过程重复进行,直到所有元素都被排序。

  • 最坏情况\(O(n²)\)
  • 最好情况\(O(n²)\)
  • 平均情况\(O(n²)\)
  • bilibili 可视化选择排序。

堆排序

堆排序利用堆这种数据结构来进行排序。它的基本思想是将待排序的数组构建成一个最大堆(或最小堆),然后反复将堆顶元素(最大或最小)移到数组的末尾,逐步缩小堆的范围。

  • 最坏情况\(O(n log n)\)
  • 最好情况\(O(n log n)\)
  • 平均情况\(O(n log n)\)
  • bilibili 可视化堆(数据结构)。
  • bilibili 可视化生成最大堆算法。
  • bilibili 可视化最大堆维护算法。
  • bilibili 可视化堆排序算法。

冒泡排序

冒泡排序是一种简单的排序算法,通过重复遍历待排序的数组,比较相邻元素并交换它们的顺序,直到没有需要交换的元素为止。该算法因其每次遍历都将最大(或最小)元素“冒泡”到数组的一端而得名。

  • 最坏情况\(O(n²)\)
  • 最好情况\(O(n)\)(当数组已经有序时)
  • 平均情况\(O(n²)\)
  • bilibili 可视化冒泡排序。

快速排序

快速排序是一种高效的排序算法,采用分治法(Divide and Conquer)策略。它的基本思想是通过一个“基准”元素将数组分为两部分,左边部分的元素都小于基准,右边部分的元素都大于基准,然后递归地对这两部分进行排序。

  • 最坏情况\(O(n²)\)(当数组已经有序或逆序时)
  • 最好情况\(O(n log n)\)
  • 平均情况:$O(n log n) $
  • bilibili 可视化快速排序(只展示了其中一种,快速排序有很多实现方法)。
  • 快速排序是不稳定排序。如果随机选择基准数则会导致排序不稳定,但如果每次都选定头部位置,那么这不会导致排序的不稳定。另一个原因是在快速排序过程中,在 \(i\) 指针左边的数都小于基准,右边的数都大于等于基准的时候,这个时候我们需要交换 \(a[i]\)\(a[t]\)(基准数),一旦 \(i\)\(t\) 之间有和 \(a[t]\) 相等的数,那么这个操作就破坏了排序的稳定性。

归并排序

归并排序是一种基于分治法的排序算法。它将数组分成两个子数组,分别对这两个子数组进行排序,然后将已排序的子数组合并成一个最终的排序数组。

  • 最坏情况\(O(n log n)\)

  • 最好情况\(O(n log n)\)

  • 平均情况\(O(n log n)\)

  • bilibili 可视化归并排序。

计数排序

计数排序是一种非比较的排序算法,适用于范围有限的整数排序。它的基本思想是通过统计每个元素出现的次数,然后根据这些计数来确定每个元素在排序后数组中的位置。

  • 时间复杂度\(O(n + k)\),其中 \(n\) 是待排序元素的数量,\(k\) 是元素值的范围。
  • 空间复杂度\(O(k)\)
  • bilibili 可视化计数排序。

桶排序

桶排序是一种分布式排序算法,适用于均匀分布的数据。它的基本思想是将数据分到有限数量的桶中,然后对每个桶内的数据进行排序,最后将所有桶中的数据合并。

  • 时间复杂度\(O(n + k)\),其中 \(n\) 是待排序元素的数量,\(k\) 是桶的数量(在理想情况下,桶内元素数量较少时)。
  • 空间复杂度\(O(n + k)\)
  • bilibili 可视化桶排序。

基数排序

基数排序是一种非比较的排序算法,适用于整数或字符串的排序。它通过将数据分成不同的“桶”来进行排序,通常是按位进行排序,从最低位到最高位。

  • 时间复杂度\(O(n * k)\),其中 \(n\) 是待排序元素的数量,\(k\) 是数字的位数。
  • 空间复杂度\(O(n + k)\)
  • bilibili 可视化基数排序。

二进制数的原码、反码、补码

根据冯·诺依曼提出的经典计算机体系结构框架,一台计算机由运算器、控制器、存储器、输入和输出设备组成。其中运算器只有加法运算器,没有减法运算器。
从硬件的角度上看,只有正数加负数才算减法,正数与正数相加,负数与负数相加,其实都可以通过加法器直接相加。
符号位在内存中存放的最左边一位,如果该位为0,则说明该数为正;若为1,则说明该数为负。
原码、反码、补码的产生过程就是为了解决计算机做减法和引入符号位的问题。

  • 正数的原码、反码、补码都一致,只有负数不同。

原码

原码:是最简单的机器数表示法,用最高位表示符号位,其他位存放该数的二进制的绝对值。

数值 二进制原码 数值 二进制原码
1 0001 -1 1001
2 0010 -2 1010
3 0011 -3 1011
4 0100 -4 1100
5 0101 -5 1101

反码

负数的反码就是它的原码除符号位外,按位取反,即对应正数取反。

为什么要有反码? 为了解决原码做减法的问题

数值 二进制原码 数值 二进制原码
1 0001 -1 1110
2 0010 -2 1101
3 0011 -3 1100
4 0100 -4 1011
5 0101 -5 1010

补码

负数的补码等于反码+1,即对应正数取反加一。

为什么要有补码? 为了解决正负0同一个编码的问题

数值 二进制原码 数值 二进制原码
1 0001 -1 1111
2 0010 -2 1110
3 0011 -3 1101
4 0100 -4 1100
5 0101 -5 1011

负数运算

存在争议,对于CSP-S初赛,纯数学题使用数学定义,程序题参考C++的计算结果。

C++ Java Python 百度 谷歌
7 mod 3 1 1 1 1 1
(-7) mod 3 -1 -1 2 2 2
7 mod (-3) 1 1 -2 -2 -2
(-7) mod (-3) -1 -1 -1 -1 -1

C++下,对于 $A, B \in \mathbb{Z}^+ $ :

  • $(-A)%(-B)=-(A%B) $
  • $(-A)%B = -(A%B) $
  • $A%(-B) = A%B $

即:结果的正负由 \(A\) 的正负决定,而结果的绝对值\(=|A|\%|B|\)

  • 数学定义\(x\%y=x-y\lfloor \dfrac{x}{y}\rfloor\)

位运算

按位与(&)

0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1

用途:

  1. 清零:如果想将一个单元清零,只要与一个各位都为零的数值相与,结果为零。
  2. 取一个数的指定位:例如,取数 X = 1010 1110 的低4位,只需另找一个数 Y = 0000 1111,然后 X & Y = 0000 1110 即可得到 X 的指定位。
  3. 判断奇偶:通过判断最未位是0还是1来决定奇偶,可以用 if ((a & 1) == 0) 代替 if (a % 2 == 0) 来判断 a 是否为偶数。

按位或(|)

0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1

用途:

  1. 设置某些位为1:例如,将数 X = 1010 1110 的低4位设置为1,只需另找一个数 Y = 0000 1111,然后 X | Y = 1010 1111 即可得到。

异或(^)

0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0

性质:

  1. 交换律
  2. 结合律(a ^ b) ^ c == a ^ (b ^ c)
  3. 对于任何数 x,都有 x ^ x = 0x ^ 0 = x
  4. 自反性a ^ b ^ b = a ^ 0 = a

用途:

  1. 翻转指定位:例如,将数 X = 1010 1110 的低4位翻转,只需另找一个数 Y = 0000 1111,然后 X ^ Y = 1010 0001 即可得到。
  2. 与0相异或值不变:例如 1010 1110 ^ 0000 0000 = 1010 1110
  3. 交换两个数
void Swap(int &a, int &b) 
{
	if (a != b) 
	{
    	a ^= b;
    	b ^= a;
    	a ^= b;
	}
}

取反(~)

~1 = 1111 1110
~0 = 1111 1111

用途

  1. 使一个数的最低位为零:例如,使 a 的最低位为0,可以表示为:a & ~1~1 的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0。

左移(<<)

a = 1010 1110a = a << 2a 的二进制位左移2位、右补0,即得 a = 1011 1000

右移(>>)

操作数每右移一位,相当于该数除以2。

复合赋值运算符

  • a &= b 相当于 a = a & b
  • a |= b 相当于 a = a | b
  • a >>= b 相当于 a = a >> b
  • a <<= b 相当于 a = a << b
  • a ^= b 相当于 a = a ^ b

大端和小端模式

大端(Big-endian)和小端(Little-endian)是两种不同的字节序(byte order)表示方式,主要用于多字节数据(如整数、浮点数等)的存储和传输。

大端模式(Big-endian)

  • 在大端模式中,数据的高位字节存储在低地址,低位字节存储在高地址。

  • 例如,32位整数 0x12345678 在内存中的存储顺序为:

    地址:   0x00  0x01  0x02  0x03
    数据:   0x12  0x34  0x56  0x78
    

小端模式(Little-endian)

  • 在小端模式中,数据的低位字节存储在低地址,高位字节存储在高地址。

  • 例如,32位整数 0x12345678 在内存中的存储顺序为:

    地址:   0x00  0x01  0x02  0x03
    数据:   0x78  0x56  0x34  0x12
    

应用场景

  • 大端模式:通常用于网络协议(如TCP/IP),因为它符合人类阅读的顺序。
  • 小端模式:常见于x86架构的计算机系统(如Intel处理器),因为它在某些情况下可以提高性能。

总结

选择大端或小端模式取决于具体的应用需求和硬件架构。在进行数据传输或存储时,了解字节序是非常重要的,以确保数据的正确解析和处理。

数据结构及相关算法

队列(Queue)

队列是一种数据结构,遵循先进先出(FIFO, First In First Out)的原则。即,最先插入队列的元素最先被移除。队列常用于需要按顺序处理数据的场景。

基本操作:

  1. 入队(Enqueue):将元素添加到队列的尾部。
  2. 出队(Dequeue):从队列的头部移除元素,并返回该元素。
  3. 查看队头(Front):返回队列头部的元素,但不移除它。
  4. 检查队列是否为空(IsEmpty):判断队列是否包含元素。
  5. 获取队列大小(Size):返回队列中元素的数量。

特点:

  • 顺序性:元素的处理顺序与插入顺序一致。
  • 动态性:队列可以根据需要动态扩展,通常实现为链表或数组。

应用场景:

  • 任务调度:操作系统中的任务管理。
  • 数据缓冲:如打印队列、IO缓冲区。
  • 广度优先搜索:图算法中的节点访问顺序。

链表(Linked List)

链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表的大小可以动态变化,适合频繁插入和删除操作的场景。

基本结构:

  • 节点(Node):链表的基本单元,通常包含两个部分:
    • 数据部分(Data)
    • 指针部分(Next),指向下一个节点

类型:

  1. 单向链表(Singly Linked List):每个节点只指向下一个节点。
  2. 双向链表(Doubly Linked List):每个节点有两个指针,分别指向前一个节点和下一个节点。
  3. 循环链表(Circular Linked List):最后一个节点指向头节点,形成一个环。

基本操作:

  1. 插入(Insert):在链表的任意位置插入新节点。
  2. 删除(Delete):从链表中删除指定节点。
  3. 查找(Search):查找链表中是否存在某个值。
  4. 遍历(Traverse):访问链表中的每个节点。

栈(Stack)

栈是一种数据结构,遵循后进先出(LIFO, Last In First Out)的原则。即,最后插入栈的元素最先被移除。栈常用于需要按顺序处理数据的场景,如函数调用和表达式求值。

基本操作:

  1. 入栈(Push):将元素添加到栈的顶部。
  2. 出栈(Pop):移除并返回栈顶部的元素。
  3. 查看栈顶元素(Top/Peek):返回栈顶部的元素,但不移除它。
  4. 检查栈是否为空(IsEmpty):判断栈是否包含元素。
  5. 获取栈的大小(Size):返回栈中元素的数量。

特点:

  • 顺序性:元素的处理顺序与插入顺序相反。
  • 动态性:栈可以根据需要动态扩展,通常实现为链表或数组。

应用场景:

  • 函数调用管理:维护函数调用的返回地址和局部变量。
  • 表达式求值:用于解析和计算算术表达式。
  • 撤销操作:在应用程序中实现撤销功能,如文本编辑器。

图(Graph)

图是一种数据结构,由一组顶点(节点)和一组边(连接顶点的线)组成。图可以用来表示各种关系,如社交网络、城市之间的道路、网络拓扑等。图可以是有向的或无向的,边可以是加权的或非加权的。

基本术语:

  1. 顶点(Vertex):图中的基本单位,表示对象。
  2. 边(Edge):连接两个顶点的线,表示对象之间的关系。
  3. 有向图(Directed Graph):边有方向,从一个顶点指向另一个顶点。
  4. 无向图(Undirected Graph):边没有方向,表示两个顶点之间的双向关系。
  5. 加权图(Weighted Graph):边带有权重,表示连接的强度或成本。
  6. 邻接矩阵(Adjacency Matrix):用二维数组表示图的边关系。
  7. 邻接表(Adjacency List):用链表或数组表示每个顶点的邻接顶点。

基本操作:

  1. 添加顶点(Add Vertex):向图中添加一个新顶点。
  2. 添加边(Add Edge):在两个顶点之间添加一条边。
  3. 删除顶点(Remove Vertex):从图中删除一个顶点及其相关边。
  4. 删除边(Remove Edge):从图中删除一条边。
  5. 遍历图(Traverse):访问图中的所有顶点,常用的方法有深度优先搜索(DFS)和广度优先搜索(BFS)。

特点:

  • 灵活性:图可以表示复杂的关系和结构。
  • 多样性:图的类型多样,适用于不同的应用场景。

应用场景:

  • 社交网络:表示用户之间的关系。
  • 地图导航:表示城市和道路之间的连接。
  • 网络流:用于流量分析和优化。
  • 推荐系统:通过用户和物品之间的关系进行推荐。

树(Tree)

树是一种层次型数据结构,由节点组成,具有一个根节点和零个或多个子节点。树的每个节点可以有多个子节点,但每个节点只有一个父节点(根节点除外)。树常用于表示分层关系,如文件系统、组织结构等。

特征

  1. 节点(Node):树由多个节点组成,每个节点可以存储数据。
  2. 根节点(Root):树的顶层节点,唯一且没有父节点。
  3. 叶子节点(Leaf)
    • 无根树的叶节点:度数不超过 1 的节点。
    • 有根树的叶节点:没有子节点的节点。
  4. 子节点(Child):某个节点的下级节点。
  5. 父节点(Parent):某个节点的上级节点。
  6. 深度(Depth):节点到根节点的路径长度。
  7. 高度(Height):节点到其最远叶子节点的路径长度。
  8. 子树(Subtree):某个节点及其所有后代节点组成的树。

数学定义

  • 定义:一个树是一个无向图 ( T ),满足以下条件:
    • ( T ) 是连通的。
    • ( T ) 不包含环。
    • 如果 ( T ) 有 ( n ) 个节点,则 ( T ) 有 ( n-1 ) 条边。

特殊类型的树

  1. 二叉树(Binary Tree):每个节点最多有两个子节点(左子节点和右子节点)。
  2. 平衡树(Balanced Tree):树的高度尽可能小,以保证操作的效率。
  3. 搜索树(Search Tree):节点的值满足特定的顺序关系,便于查找。

二叉树的类型

  1. 满二叉树(Full Binary Tree):每个节点要么是叶子节点,要么有两个子节点。
  2. 完全二叉树(Complete Binary Tree):除了最后一层外,其他层的节点都被填满,且最后一层的节点从左到右排列。
  3. 平衡二叉树(Balanced Binary Tree):任意节点的左右子树高度差不超过 1。
  4. 二叉搜索树(Binary Search Tree, BST):对于每个节点,左子树的所有节点值小于该节点值,右子树的所有节点值大于该节点值。

森林与生成树

  • 森林(Forest):由多个连通分量(连通块)组成的图,每个连通分量都是树。根据定义,一棵树也是森林。
  • 生成树(Spanning Tree):一个连通无向图的生成子图,同时要求是树。即在图的边集中选择 ( n-1 ) 条边,将所有顶点连通。

基本操作

  1. 插入(Insert):在树中添加新节点。
  2. 删除(Delete):从树中移除节点。
  3. 查找(Search):查找树中是否存在某个值。
  4. 遍历(Traverse):访问树中的所有节点,常用的方法有前序遍历、中序遍历和后序遍历。

树的遍历

树的遍历是访问树中所有节点的过程,常用的遍历方法有三种主要类型:前序遍历、中序遍历和后序遍历。每种遍历方法都有其特定的访问顺序。

1. 前序遍历(Pre-order Traversal)

访问顺序:根节点 -> 左子树 -> 右子树

算法步骤

  1. 访问根节点。
  2. 递归前序遍历左子树。
  3. 递归前序遍历右子树。
2. 中序遍历(In-order Traversal)

访问顺序:左子树 -> 根节点 -> 右子树

算法步骤

  1. 递归中序遍历左子树。
  2. 访问根节点。
  3. 递归中序遍历右子树。
3. 后序遍历(Post-order Traversal)

访问顺序:左子树 -> 右子树 -> 根节点

算法步骤

  1. 递归后序遍历左子树。
  2. 递归后序遍历右子树。
  3. 访问根节点。
4. 层次遍历(Level-order Traversal)

访问顺序:按层次从上到下、从左到右访问每一层的节点。

算法步骤

  1. 使用队列(Queue)来存储节点。
  2. 访问队列中的节点,依次将其子节点加入队列。

特点

  • 层次性:树的结构反映了数据的层次关系。
  • 递归性:树的定义和操作通常使用递归方法。

应用场景

  • 文件系统:表示文件和目录的层次结构。
  • 数据库索引:如 B 树和红黑树,用于快速查找。
  • 表达式树:用于表示和计算数学表达式。
  • 决策树:用于分类和回归分析。

堆(Heap)

堆是一种特殊的树形数据结构,通常用于实现优先队列。堆具有以下特征:

  1. 完全二叉树:堆是一种完全二叉树,除了最后一层外,其他层的节点都被填满,最后一层的节点从左到右排列。

  2. 堆性质

    • 最大堆(Max Heap):每个节点的值都大于或等于其子节点的值。根节点是最大值。
    • 最小堆(Min Heap):每个节点的值都小于或等于其子节点的值。根节点是最小值。

堆的基本操作

  1. 插入(Insert)

    • 将新元素添加到堆的末尾。
    • 通过“上浮”操作(Bubble Up)调整堆的结构,确保堆性质得以保持。
  2. 删除(Delete)

    • 通常删除堆顶元素(最大值或最小值)。
    • 将堆顶元素替换为最后一个元素,然后通过“下沉”操作(Bubble Down)调整堆的结构。
  3. 堆化(Heapify)

    • 将一个无序数组转换为堆结构。可以通过从最后一个非叶子节点开始,依次进行下沉操作来实现。

应用场景

  • 优先队列:堆常用于实现优先队列,支持高效的插入和删除操作。
  • 排序算法:堆排序(Heap Sort)是一种基于堆的排序算法,时间复杂度为 (\(O(n \log n)\))。
  • 图算法:在 Dijkstra 和 Prim 算法中,堆用于高效地获取最小或最大权重的边。

总结

堆是一种高效的树形数据结构,适用于需要频繁插入和删除最大或最小元素的场景。其结构的完全性和堆性质使得许多操作能够在对数时间内完成。

二分图(Bipartite Graph)

二分图是一种特殊的图,其顶点可以分为两个不相交的集合,使得图中的每条边都连接来自不同集合的顶点。换句话说,二分图的顶点集可以被划分为两个部分,且图中没有边连接同一部分的顶点。

基本特性:

  1. 顶点划分:存在两个集合 ( U ) 和 ( V ),使得每条边连接的两个顶点分别来自 ( U ) 和 ( V )。
  2. 无奇环:二分图中不存在长度为奇数的环。

基本操作:

  1. 检查二分性(IsBipartite):通过图的遍历(如 BFS 或 DFS)来判断图是否为二分图。
  2. 构建二分图:根据给定的边集,将顶点分配到两个集合中。

特点:

  • 颜色标记:可以用两种颜色标记顶点,确保相邻顶点颜色不同。
  • 应用广泛:在匹配问题、网络流、社交网络分析等领域有重要应用。

应用场景:

  • 匹配问题:如婚姻匹配、工作分配等。
  • 社交网络:分析用户与兴趣之间的关系。
  • 图着色:解决图的着色问题,确保相邻顶点不同色。

欧拉图(Eulerian Graph)

欧拉图是一种特殊的图,其中存在一条经过每条边恰好一次的闭合路径,称为欧拉回路。如果图中存在一条经过每条边恰好一次但不一定回到起点的路径,则称为欧拉路径。

基本特性:

  1. 欧拉回路:图中存在一条回路,经过每条边一次且仅一次。
  2. 欧拉路径:图中存在一条路径,经过每条边一次且仅一次,但不一定回到起点。

欧拉图的条件:

  • 欧拉回路:图是连通的,且所有顶点的度数都是偶数。
  • 欧拉路径:图是连通的,且恰好有两个顶点的度数为奇数,其余顶点的度数为偶数。

基本操作:

  1. 检查欧拉性(IsEulerian):判断图是否为欧拉图,检查顶点的度数和连通性。
  2. 构建欧拉回路/路径:使用 Fleury 算法或 Hierholzer 算法找到欧拉回路或路径。

特点:

  • 连通性:欧拉图必须是连通的,确保可以从一个顶点到达另一个顶点。
  • 边的使用:每条边只能使用一次,适用于需要遍历所有边的场景。

应用场景:

  • 邮递员问题:寻找最短路径以遍历所有街道。
  • 网络设计:优化网络连接,确保每条连接都被使用。
  • 游戏设计:设计关卡或路径,确保玩家体验完整的游戏过程。

有向无环图(Directed Acyclic Graph, DAG)

有向无环图是一种特殊的图,其中所有边都有方向,并且不存在从某个顶点出发经过边回到自身的路径。

基本特性:

  1. 有向性:图中的边是有方向的,表示从一个顶点指向另一个顶点。
  2. 无环性:图中不存在环,即不可能从某个顶点出发经过边回到自身。

有向无环图的条件:

  • 无环性:图中不包含任何环。
  • 有向性:每条边都有明确的方向。

基本操作:

  1. 拓扑排序(Topological Sorting):对有向无环图进行排序,使得对于每一条边 (\(u \to v\)),顶点 (u) 在顶点 (v) 之前。
  2. 检测环(Cycle Detection):判断图是否为有向无环图,通常使用深度优先搜索(DFS)或 Kahn 算法。

特点:

  • 层次结构:有向无环图常用于表示层次结构,如任务调度、依赖关系等。
  • 唯一性:拓扑排序可能不唯一,存在多种有效的排序方式。

应用场景:

  • 任务调度:表示任务之间的依赖关系,确保任务按顺序执行。
  • 编译顺序:在编译过程中,处理源文件的依赖关系。
  • 数据流分析:在计算机科学中,表示数据处理的顺序和依赖关系。

拓扑排序(Topological Sorting)

拓扑排序是对有向无环图(DAG)的一种线性排序,使得对于图中的每一条有向边 ( \(u \to v\) ),顶点 ( \(u\) ) 在排序中出现在顶点 ( \(v\) ) 之前。

基本特性:

  1. 唯一性:对于一个有向无环图,拓扑排序不一定是唯一的,可能存在多个有效的排序。
  2. 存在性:只有在图是有向无环图的情况下,才能进行拓扑排序。

拓扑排序的条件:

  • 图必须是有向无环图(DAG)。
  • 图中不能存在环路。

基本操作:

  1. Kahn 算法

    • 计算每个顶点的入度。
    • 将入度为 0 的顶点加入队列。
    • 逐步移除队列中的顶点,并更新其邻接顶点的入度,直到所有顶点都被处理。
  2. 深度优先搜索(DFS)

    • 对每个未访问的顶点进行 DFS。
    • 在回溯时,将顶点加入栈中。
    • 最终从栈中弹出顶点,得到拓扑排序。

特点:

  • 线性时间复杂度:拓扑排序的时间复杂度为 ( \(O(V + E)\) ),其中 ( \(V\) ) 是顶点数,( \(E\) ) 是边数。
  • 适用性:广泛应用于任务调度、编译顺序、依赖关系管理等场景。

应用场景:

  • 任务调度:在有依赖关系的任务中,确定执行顺序。
  • 编译顺序:在编译多个源文件时,确保依赖关系得到满足。
  • 课程安排:在学术课程中,确保 prerequisite 课程在后续课程之前完成。

哈希表(Hash Table)

哈希表是一种数据结构,通过哈希函数将键映射到数组的索引,以实现快速的数据存取。它支持高效的插入、删除和查找操作。

基本特性:

  1. 键值对存储:哈希表以键值对的形式存储数据,每个键唯一对应一个值。
  2. 快速访问:通过哈希函数,能够在平均常数时间复杂度 (O(1)) 内访问元素。

哈希函数:

  • 定义:将输入(键)转换为数组索引的函数。
  • 要求:应尽量均匀分布,以减少冲突。

冲突处理:

  1. 链式法:在同一索引处使用链表存储多个元素。
  2. 开放地址法:在发生冲突时,寻找下一个可用的索引。

基本操作:

  1. 插入(Insert):计算键的哈希值,将键值对存入相应索引。
  2. 查找(Search):计算键的哈希值,查找对应的值。
  3. 删除(Delete):计算键的哈希值,移除对应的键值对。

特点:

  • 动态扩展:当负载因子(已存储元素与数组大小的比率)过高时,可以扩展数组大小并重新哈希。
  • 空间效率:相较于其他数据结构,哈希表在存储和访问上通常更高效。

应用场景:

  • 数据库索引:快速查找记录。

  • 缓存实现:存储临时数据以提高访问速度。

  • 唯一性检查:快速判断元素是否存在于集合中。

posted @ 2024-09-15 10:29  TommyJin  阅读(112)  评论(0)    收藏  举报