1 C语言
1.1 从源码到可执行文件的过程
- 预处理:去掉空格、注释,处理预处理指令(如
#include
、#define
),生成处理后的源代码文件。 - 编译:将预处理后的源代码翻译成汇编代码,生成汇编文件。
- 汇编:将汇编代码翻译成机器码,生成一个或多个目标文件(二进制文件)。
- 链接:合并目标文件、C标准库文件及其他库文件,生成最终可执行的二进制程序。
1.2 标识符及命名规范
- 定义:C语言中变量、函数、数组名、结构体等要素命名时使用的字符序列。
- 强制规范:
- 由小写/大写英文字母、0-9或
_
组成。 - 不能以数字开头。
- 不可以是关键字(如
int
、if
)。
- 建议规范:见名知意,如
sum
、userName
;可采用下划线拼接(max_score
)或小驼峰命名(maxScore
)。
1.3 不同位数系统的地址字节数
- 32位系统:地址用4个字节表示。
- 64位系统:地址用8个字节表示。
2 变量
2.1 变量的声明和定义区别
类型 | 核心区别 | 示例 |
---|
声明(Declaration) | 告知编译器变量存在及类型,不分配内存 | extern int a; |
定义(Definition) | 为变量分配内存并初始化,形成完整实体 | int a = 10; |
2.2 变量和常量的区别
- 值可改性:变量的值可修改,常量的值不可修改。
- 内存分配时机:变量通常在运行时分配内存,常量通常在编译时分配内存。
2.3 定义常量的方式及区别
方式 | 特点 | 示例 |
---|
const 关键字 | C99新增,需指定数据类型,编译时类型检查,有作用域,更安全 | const int MAX = 100; |
#define 宏定义 | 预处理阶段文本替换,无类型检查,无作用域,更灵活 | #define MAX 100 |
2.4 全局变量和局部变量的区别
对比维度 | 全局变量 | 局部变量 |
---|
作用域 | 整个程序 | 定义它的函数或代码块内 |
生命周期 | 程序运行期间始终存在 | 函数调用/代码块执行期间 |
存储位置 | 全局静态区 | 栈区 |
默认初始化 | 未初始化时默认值为0/空 | 未初始化时值未定义(随机) |
2.5 num++
和++num
的区别
num++
:先取值,再运算(值为num
累加前的结果),如int b = num++;
(先赋值b=num
,再num=num+1
)。++num
:先运算,再取值(值为num
累加后的结果),如int b = ++num;
(先num=num+1
,再赋值b=num
)。
3 宏
3.1 常用预处理命令
#include
(引入头文件)、#define
(宏定义)、#if
/#else
/#elif
/#endif
(条件编译)、#undef
(取消宏定义)、#ifdef
/#ifndef
(判断宏是否定义)。
3.2 取消宏定义
使用#undef
命令,如#undef MAX
(取消MAX
的宏定义)。
3.3 条件编译及应用场景
- 定义:通过预处理指令,根据条件编译或排除代码,实现不同版本代码生成。
- 应用场景:
- 跨平台编程(为Windows/Linux编写不同代码)。
- 调试代码(开发时启用调试逻辑,发布时屏蔽)。
- 特性选择(根据宏定义启用/禁用功能模块)。
- 功能开关(通过编译选项控制代码是否生效)。
4 数据类型
4.1 C语言数据类型分类
- 整数类型:
char
、short
、int
、long
、long long
。 - 浮点类型:
float
(单精度)、double
(双精度)、long double
(长双精度)。
4.2 整数和浮点数的存储区别
- 整数:以二进制补码形式存储。
- 浮点数:按IEEE 754标准存储,包含符号位、指数位和尾数位(如
float
占4字节,double
占8字节)。
4.3 char
类型的本质
长度为1字节(8位)的整数,实际存储字符对应的ASCII码值(如'a'
的ASCII码为97,存储为0x61
)。
4.4 数据类型自动转换原则
- 整数类型间:窄字节→宽字节(如
char
→int
),有符号→无符号。 - 浮点类型间:低精度→高精度(如
float
→double
)。 - 整数与浮点:整数→浮点数(如
int
→float
)。
4.5 枚举的应用场景
- 替代宏定义,提高代码可读性(如
enum Color {RED, GREEN, BLUE};
)。 - 表示相关状态/选项(如设备状态
enum State {IDLE, RUNNING, ERROR};
)。 - 表示错误代码(如
enum ErrorCode {SUCCESS=0, FILE_NOT_FOUND=1};
)。 - 限制变量取值范围,增强程序健壮性。
4.6 typedef
和#define
的区别
对比维度 | typedef | #define |
---|
本质 | C语言关键字,创建类型别名 | 宏定义,预处理阶段文本替换 |
类型检查 | 编译时进行类型安全检查 | 无类型检查 |
作用域 | 有作用域(如局部typedef 仅在函数内有效) | 无作用域,从定义到文件结束有效 |
4.7 switch
的参数类型要求
参数必须是整数类型(如int
、char
、enum
,不能是float
、double
或字符串)。
4.8 break
和continue
的区别
break
:结束当前循环或switch
语句,跳转到语句外继续执行。continue
:跳过当前循环的剩余部分,直接开始下一次循环(不结束循环)。
5 数组
5.1 数组特点及长度计算
- 特点:
- 元素在内存中连续、有序排列。
- 初始化后长度固定,不可修改。
- 通过索引(下标)快速访问元素,时间复杂度O(1)。
- 长度计算:
数组长度 = sizeof(数组名) / sizeof(单个元素)
,如int arr[5];
的长度为sizeof(arr)/sizeof(int) = 5
。
5.2 字符串的本质
是字符数组,在内存中以空字符\0
(ASCII值为0)作为结束标志(如"abc"
实际存储为'a'
、'b'
、'c'
、'\0'
)。
5.3 sizeof()
和strlen()
的区别
函数 | 计算范围 | 示例(char str[] = "abc"; ) |
---|
sizeof() | 字符数组总字节数(含\0 ) | sizeof(str) = 4 (3个字符+1个\0 ) |
strlen() | 实际字符个数(不含\0 ) | strlen(str) = 3 |
5.4 多维数组及内存存储
- 定义:元素为数组的数组(如
int arr[2][3];
表示2个元素,每个元素是含3个int
的数组)。 - 内存存储:连续存储,按行优先顺序(如
arr[0][0]
→arr[0][1]
→arr[0][2]
→arr[1][0]
…)。
6 指针
6.1 同类型指针相减的结果
得到两个指针之间元素的个数(非地址差值),如int arr[5], *p1=arr, *p2=arr+2;
,则p2-p1=2
(表示中间有2个int
元素)。
注意:指针仅支持减法,不支持加法。
6.2 指针加减整数的结果
指针指向的内存地址移动,移动字节数=指针类型字节数×整数,如:
int *p
:p+1
向后移动4字节(int
占4字节)。char *p
:p-2
向前移动2字节(char
占1字节)。
6.3 指针能否比较大小
可以比较(如==
、<
、>
),比较的是指针指向的内存地址大小(如p1 < p2
表示p1
指向的地址值小于p2
)。
6.4 数组名与指向首元素指针的区别
对比维度 | 数组名 | 指向首元素的指针 |
---|
类型 | 元素类型[](如int[5] ) | 元素类型*(如int* ) |
sizeof 结果 | 整个数组大小(如int arr[5] 的sizeof(arr)=20 ) | 指针本身大小(32位系统4字节,64位系统8字节) |
& 取地址 | 首元素地址(&arr 与arr 值相同,但类型不同) | 指针变量自身的地址 |
+1 结果 | 下一个元素地址(如arr+1 指向arr[1] ) | 下一个元素地址(与数组名+1 效果相同) |
* 取值 | 首元素的值(*arr = arr[0] ) | 首元素的值(与数组名* 效果相同) |
可变性 | 不可重新赋值(如arr = arr+1 错误) | 可重新赋值(如p = p+1 正确) |
6.5 指针数组和数组指针的区别
类型 | 定义 | 本质 | 示例 |
---|
指针数组 | 元素类型 *数组名[长度] | 数组,每个元素是指针 | int *p[3]; (3个int* 类型的指针组成的数组) |
数组指针 | 元素类型 (*指针名)[长度] | 指针,指向一个数组 | int (*p)[3]; (指向含3个int 的数组的指针) |
6.6 指针函数和函数指针的区别
类型 | 定义 | 本质 | 示例 |
---|
指针函数 | 返回类型 *函数名(参数列表) | 函数,返回值为指针 | int *add(int a, int b); (返回int* 类型的函数) |
函数指针 | 返回类型 (*指针名)(参数列表) | 指针,指向一个函数 | int (*p)(int, int); (指向参数为2个int 、返回int 的函数) |
6.7 常量指针和指针常量的区别
类型 | 定义 | 核心限制 | 示例 |
---|
常量指针 | const 类型 *指针名 或 类型 const *指针名 | 指向的常量值不可修改,指针地址可修改 | const int *p = &a; *p=10; (错误);p=&b; (正确) |
指针常量 | 类型 *const 指针名 | 指针地址不可修改,指向的值可修改 | int *const p = &a; p=&b; (错误);*p=10; (正确) |
6.8 字符数组和字符指针的区别
对比维度 | 字符数组 | 字符指针 |
---|
内存分配 | 局部数组存栈区,全局数组存全局区;连续内存 | 指针存栈区,字符串字面量存全局常量区 |
大小 | 定义时需指定大小(如char arr[10]; ) | 指针本身占4/8字节,无固定存储大小 |
修改字符 | 可直接修改元素(如arr[0] = 'A'; ) | 不可修改字符串字面量(如char *p="abc"; p[0]='A'; 错误) |
整体赋值 | 不可重新赋值(如arr = "def"; 错误) | 可重新指向新字符串(如p = "def"; 正确) |
6.9 空指针
- 定义:不指向任何有效内存地址的指针,用
NULL
表示(本质是(void*)0
)。 - 特点:可赋值给任何指针类型;声明指针时无确切地址,建议赋
NULL
(如int *p = NULL;
)。
6.10 悬空指针
- 定义:指向已释放内存地址的指针(如
free(p);
后未置NULL
,p
成为悬空指针)。 - 风险:访问悬空指针可能导致程序崩溃或数据错误。
6.11 野指针及避免方法
- 定义:指向不可知地址的指针(未初始化、悬空、越界访问均可能产生)。
- 避免方法:
- 指针声明时初始化(如
int *p = NULL;
或int *p = &a;
)。 - 释放内存后立即置
NULL
(如free(p); p = NULL;
)。 - 避免指针越界访问(如数组遍历不超过长度)。
6.12 指针和引用的区别
- 引用是C++特性,C语言无引用;引用本质是变量的别名,必须初始化且不可重新绑定,而指针可独立存在并修改指向。
7 结构体
7.1 结构体变量与结构体指针的访问区别
- 结构体变量:用**
.
(点运算符)** 访问成员,如struct Student s; s.age = 20;
。 - 结构体指针:用**
->
(箭头运算符)** 访问成员,如struct Student *p = &s; p->age = 20;
。
7.2 结构体长度计算(内存对齐)
- 核心规则(以8字节对齐为例):
- 第一个成员地址偏移量为0。
- 成员对齐:
- 成员类型<8字节:首地址为自身类型大小的整数倍(如
int
成员首地址需是4的整数倍)。 - 成员类型≥8字节:首地址为8的整数倍。
- 内嵌结构体:首地址为内嵌结构体最大成员类型大小的整数倍。
- 整体补齐:结构体总大小为最大成员类型大小(或8,若最大成员≥8)的整数倍。
- 示例:
struct A {char c; int i;};
,char
占1字节,int
需对齐到4字节,总大小为8字节(1+3(填充)+4=8)。
7.3 结构体和共用体的区别
对比维度 | 结构体(struct ) | 共用体(union ) |
---|
内存使用 | 各成员拥有独立内存,总大小=成员大小之和+填充(内存对齐) | 所有成员共用一块内存,总大小=最大成员大小(需内存补齐) |
数据存储 | 各成员可同时存储有效数据,互不干扰 | 同一时间仅一个成员可使用内存,修改一个成员会覆盖其他成员数据 |
用途 | 存储不同类型的关联数据(如学生信息:姓名、年龄、成绩) | 节省内存,存储互斥的不同类型数据(如设备状态:整数码/字符串描述) |
8 函数
8.1 形参和实参的区别
类型 | 定义 | 特点 |
---|
形参 | 函数定义中声明的参数(如void add(int a, int b) 中的a 、b ) | 本质是局部变量,仅在函数内有效;函数调用时接收实参的值 |
实参 | 函数调用时传递的具体值/变量(如add(3, 5) 中的3 、5 ) | 需与形参类型匹配;传递时为“值传递”(普通参数)或“地址传递”(指针参数) |
8.2 主函数(main
)的参数和返回值规则
- 参数:
- 无参数(最常用):
int main(void)
。 - 带命令行参数:
int main(int argc, char *argv[])
,其中argc
是参数个数(至少为1,含程序名),argv
是参数数组(argv[0]
为程序名,argv[1]
~argv[argc-1]
为命令行参数)。
- 返回值:
int
类型,return 0
表示程序运行成功,非零值表示运行失败(操作系统可捕获该值)。
8.3 函数原型
即函数声明,包含函数名称、返回类型、参数列表(无函数体),用于告知编译器函数的接口信息,如int add(int a, int b);
。通常放在头文件中,避免“未定义函数”错误。
8.4 常用C语言系统函数
- 输入输出:
printf()
(格式化输出到控制台)、scanf()
(格式化输入)。 - 字符串处理:
strlen()
(计算字符串长度)、strcat()
(拼接字符串)、strcmp()
(比较字符串)。 - 数学计算:
ceil()
(向上取整)、floor()
(向下取整)、round()
(四舍五入)、sqrt()
(平方根)。 - 内存操作:
malloc()
(动态分配内存)、free()
(释放内存)、memcpy()
(内存拷贝)。
8.5 传递指针与传递值的区别
传递方式 | 核心逻辑 | 适用场景 |
---|
传递值 | 实参的值复制到形参,函数内修改形参不影响实参 | 传递基本数据类型(int 、float 等),无需修改实参 |
传递指针 | 实参的地址传递到形参,函数可通过指针修改实参指向的数据 | 传递数组、结构体等大型数据(避免拷贝开销),或需修改实参值 |
8.6 回调函数
- 定义:作为参数传递给其他函数的函数,当满足特定条件时被调用。
- 实现方式:通过函数指针传递,如
void process(int (*callback)(int), int data)
,其中callback
是函数指针,指向需回调的函数。 - 用途:实现事件驱动(如中断回调)、框架扩展(如排序函数中的比较回调)。
8.7 带参宏定义和函数的区别
对比维度 | 带参宏定义(如#define ADD(a,b) ((a)+(b)) ) | 函数(如int add(int a, int b) {return a+b;} ) |
---|
执行时机 | 预处理阶段文本替换,无函数调用开销 | 运行时执行,需保存现场、传递参数、跳转执行,有调用开销 |
类型检查 | 无类型检查,可能因参数类型不匹配导致错误(如ADD(3.5, 2) 无问题,add(3.5, 2) 编译报错) | 编译器进行类型检查,参数类型不匹配时编译报错 |
代码体积 | 多次调用会导致代码膨胀(每次替换生成新代码) | 多次调用仅执行同一段函数代码,代码体积小 |
调试 | 无法单步调试宏替换后的代码 | 可单步调试函数内部逻辑 |
8.8 printf()
、sprintf()
、fprintf()
的区别
函数 | 功能 | 输出目标 | 示例 |
---|
printf() | 格式化输出数据 | 标准输出设备(控制台) | printf("age: %d\n", 20); |
sprintf() | 格式化输出数据到字符串 | 字符数组(内存) | char buf[50]; sprintf(buf, "name: %s", "Tom"); |
fprintf() | 格式化输出数据到文件 | 指定文件流(如FILE * ) | FILE *fp = fopen("log.txt", "w"); fprintf(fp, "error: %d\n", 1); |
9 关键字
9.1 static
关键字的作用
- 静态局部变量:
- 存储在全局静态区,函数调用结束后不销毁,生命周期贯穿程序运行期间。
- 仅在首次函数调用时初始化,后续调用使用上次的值(如
void count() {static int num=0; num++;}
,每次调用num
自增1)。
- 静态全局变量:
- 作用域仅限于声明它的源文件,其他源文件无法通过
extern
引用。 - 避免全局变量重名冲突(如
static int g_val;
仅在当前.c
文件中有效)。
- 静态函数:
- 作用域仅限于声明它的源文件,其他源文件无法调用。
- 减少函数名污染(如
static void func() {}
仅在当前.c
文件中可调用)。
9.2 extern
关键字的作用
- 用于声明外部链接的变量或函数,告知编译器该变量/函数在其他源文件中定义,当前文件可直接引用(无需重新定义)。
- 示例:在
a.c
中定义int g_val = 10;
,在b.c
中通过extern int g_val;
声明后,即可使用g_val
。
9.3 volatile
关键字的作用
- 核心作用:防止编译器优化,强制编译器每次使用变量时都从内存重新读取(而非使用寄存器中的备份)。
- 适用场景:
- 并行设备的硬件寄存器(如状态寄存器,值可能被硬件修改)。
- 中断服务子程序中访问的非自动变量(如
volatile int flag;
,中断中修改flag
,主程序需实时读取)。 - 多线程共享变量(避免线程间因编译器优化导致的数据不一致)。
10 内存
10.1 C语言内存模型
内存区域 | 存储内容 | 特点 |
---|
栈区(stack) | 函数参数、局部变量、函数返回地址 | 由操作系统自动分配/释放,生长方向为低地址→高地址,大小固定(栈溢出会导致程序崩溃) |
堆区(heap) | 动态分配的内存(malloc /calloc /realloc 申请) | 由程序员手动分配/释放(free ),生长方向为高地址→低地址,大小灵活(需避免内存泄漏) |
全局(静态)区(static) | 全局变量、静态变量(static 修饰)、字符串常量 | 程序启动时分配,结束时释放;全局变量和静态变量未初始化时默认值为0 |
程序代码区 | 函数体的二进制代码、只读数据(如const 修饰的全局变量) | 只读属性,防止程序意外修改代码 |
10.2 1G内存计算机能否malloc(1.2G)
有可能。因为malloc
申请的是虚拟地址空间的内存,与物理内存大小无直接关联;操作系统通过虚拟内存技术(如页交换),可将部分硬盘空间模拟为内存,满足大内存申请需求(实际使用时若物理内存不足,会触发页交换)。
10.3 内存泄漏
- 定义:程序中动态分配的堆内存(
malloc
/calloc
等申请)未释放或无法释放,导致系统内存浪费,长期运行可能使程序变慢甚至崩溃。 - 分类:
- 常发性内存泄漏:泄漏代码被多次执行(如循环中未释放内存)。
- 偶发性内存泄漏:仅在特定环境/操作下发生(如异常分支中未释放内存)。
- 一次性内存泄漏:泄漏代码仅执行一次(如程序启动时申请内存未释放)。
- 隐式内存泄漏:程序运行中持续分配内存,仅在结束时释放(如服务器长期运行会耗尽内存)。
10.4 内存溢出
- 定义:程序使用的内存超过系统可提供的最大内存(或进程允许的内存上限),导致程序无法运行(如提示“Out Of Memory”并崩溃)。
- 原因:
- 内存泄漏堆积:长期未释放的内存累积,最终耗尽可用内存。
- 超大对象加载:单个对象(如大数组、大结构体)大小超过剩余可用内存。
10.5 堆和栈的区别
对比维度 | 栈 | 堆 |
---|
申请/释放方式 | 操作系统自动分配/释放(函数调用时分配,返回时释放) | 程序员手动申请(malloc )/释放(free ) |
申请大小限制 | 栈大小固定(通常几MB),超过会触发栈溢出 | 堆大小灵活(受虚拟内存限制),可申请较大内存 |
生长方向 | 低地址→高地址 | 高地址→低地址 |
效率 | 效率高(使用一级缓存,无需复杂分配算法) | 效率低(使用二级缓存,需遍历空闲内存链表) |
内存碎片 | 无碎片(连续分配、释放) | 频繁申请/释放会产生碎片(小块空闲内存无法利用) |
10.6 堆栈溢出的原因
- 栈溢出:
- 栈大小设置过小(如局部数组过大:
int arr[100000];
)。 - 递归层次过深(如无终止条件的递归函数)。
- 函数调用层次过深(如嵌套调用过多函数)。
- 堆溢出:
- 堆大小设置过小(嵌入式系统中常见)。
- 动态内存未释放(内存泄漏堆积)。
- 单次申请内存超过堆剩余空间(如
malloc(100MB)
时堆仅剩余50MB)。
10.7 动态内存分配函数及注意事项
- 常用函数:
malloc(size_t size)
:分配size
字节的未初始化内存,返回指向内存的指针(失败返回NULL
)。calloc(size_t nmemb, size_t size)
:分配nmemb
个大小为size
的内存块,初始化为0,返回指针(失败返回NULL
)。realloc(void *ptr, size_t size)
:调整ptr
指向的内存大小为size
字节,返回新内存指针(失败返回NULL
,原内存不变)。free(void *ptr)
:释放ptr
指向的堆内存(ptr
必须是malloc
/calloc
/realloc
返回的指针,不可重复释放或释放NULL
)。
- 注意事项:
- 避免分配大量小内存块(增加系统开销,易产生碎片)。
- 内存使用后及时释放(避免内存泄漏),释放后将指针置
NULL
(避免悬空指针)。 - 检查分配结果(如
if ((p = malloc(100)) == NULL) { /* 处理分配失败 */ }
)。 - 不越界访问动态内存(如
int *p = malloc(5*sizeof(int)); p[5] = 10;
会破坏相邻内存)。
11 C语言库的使用
常用标准库及用途:
stdio.h
:输入输出函数(printf
、scanf
、fopen
等)。math.h
:数学计算函数(sqrt
、sin
、ceil
等)。stdlib.h
:内存分配、随机数、类型转换等函数(malloc
、free
、rand
等)。string.h
:字符串处理函数(strlen
、strcat
、memcpy
等)。time.h
:时间相关函数(time
、localtime
等)。
2 数据结构和算法
2.1 数据结构
2.1.1 数组和链表的区别
对比维度 | 数组 | 链表 |
---|
内存存储 | 连续内存空间 | 非连续内存空间(节点含数据域和指针域) |
访问效率 | 索引访问(O(1)),效率高 | 遍历访问(O(n)),效率低 |
插入/删除效率 | 中间插入/删除需移动元素(O(n)),效率低 | 中间插入/删除仅需修改指针(O(1)),效率高 |
内存开销 | 无额外开销(仅存储数据) | 有额外开销(每个节点需存储指针) |
长度灵活性 | 初始化后长度固定,不可动态扩展 | 长度灵活,可动态添加/删除节点 |
2.1.2 栈和队列的区别
对比维度 | 栈(Stack) | 队列(Queue) |
---|
核心原则 | 先进后出(LIFO,Last In First Out) | 先进先出(FIFO,First In First Out) |
操作位置 | 仅在栈顶(Top)进行插入(Push)和删除(Pop) | 队尾(Rear)插入(Enqueue),队头(Front)删除(Dequeue) |
适用场景 | 函数调用栈、表达式求值、括号匹配检查 | 任务调度(如进程队列)、消息队列、缓冲区处理 |
实现方式 | 数组栈(连续内存)、链表栈(非连续内存) | 数组队列(需循环队列避免假溢出)、链表队列 |
2.1.3 快慢指针在链表中的应用

- 判断链表是否有环:
- 快慢指针同时从表头出发,慢指针每次走1步,快指针每次走2步;若快指针追上慢指针,链表有环;若快指针走到
NULL
,链表无环。
- 找出链表的中间节点:
- 快慢指针同时从表头出发,慢指针每次走1步,快指针每次走2步;当快指针走到表尾时,慢指针指向中间节点(偶数长度时指向中间偏左节点)。
- 删除链表倒数第n个节点:
- 快指针先出发走n步,随后快慢指针同时走1步;当快指针走到表尾时,慢指针指向倒数第n个节点的前驱节点,此时可删除目标节点。
- 删除排序链表中的重复项:
- 慢指针从表头出发,快指针从表头.next出发;若快慢指针值相同,删除快指针节点并后移快指针;若值不同,快慢指针同时后移,直至快指针走到
NULL
。
2.1.4 满二叉树、完全二叉树、平衡二叉树的定义
- 满二叉树:每一层的结点个数都达到最大时的树,即第k层(从1开始计数)有2^(k-1)个结点,所有叶子结点均位于同一层。
- 完全二叉树:除最后一层外,其他每一层的结点数都达到最大,且最后一层的叶子结点均靠左排列的树;满二叉树是完全二叉树的特殊形式。
- 平衡二叉树:树上任意一个结点的两个子树的高度差不超过1,且该结点的两个子树也均为平衡二叉树的树。
2.2 算法
2.2.1 时间换空间与空间换时间的例子
策略 | 例子 | 时间复杂度 | 空间复杂度 |
---|
时间换空间 | 冒泡排序 | O(n²) | O(1) |
空间换时间 | 快速排序 | O(nlogn) | O(nlogn) |
2.2.2 常见查找算法
- 顺序查找(线性查找)
- 原理:逐个遍历数据集合,对比每个元素与目标元素,直到找到目标或遍历结束。
- 平均时间复杂度:O(n),适用于无序或小规模数据集合。
- 二分查找
- 原理:仅适用于有序数据集合,从集合中间元素开始对比;若中间元素为目标,则查找结束;若目标小于中间元素,在左半部分继续查找,反之在右半部分查找,重复此过程直至找到目标或集合为空。
- 平均时间复杂度:O(logn),效率高于顺序查找。
- 分块查找
- 原理:结合顺序查找与二分查找,先将数据“按块有序”划分为m块(块内元素无序,块间元素有序),用二分查找确定目标所在块,再用顺序查找遍历块内元素。
2.2.3 常见排序算法
- 冒泡排序
- 原理:通过相邻元素两两对比,若顺序错误则交换位置,每一轮遍历将最大(或最小)元素“冒泡”到集合末尾,重复此过程直至集合有序。
- 平均时间复杂度:O(n²),属于稳定排序(相等元素相对位置不变)。
- 快速排序
- 原理:选择一个基准元素,将集合划分为两部分,左部分元素均小于基准,右部分元素均大于基准;再递归对左右两部分执行相同操作,直至所有子集合有序。
- 平均时间复杂度:O(nlogn),属于不稳定排序。
- 选择排序
- 原理:每次从待排序部分选择最小(或最大)元素,将其放到已排序部分的末尾,重复此过程直至集合有序。
- 平均时间复杂度:O(n²),属于不稳定排序。
- 插入排序
- 原理:将待排序部分的元素逐个插入到已排序部分的合适位置,使已排序部分始终有序,重复此过程直至集合有序。
- 平均时间复杂度:O(n²),属于稳定排序。