绪论与复杂度分析
1. 课程定位
数据结构与算法(B) 是计算机基础课程之一,先修课程为编程语言 / 计算概论。课程强调:
- 具备一定编程能力,能熟练运用 C 语言。
- 培养抽象思维:把现实问题抽象成已有的数据模型和算法框架。
- 只要抽象得好,理论上都可以转化为现有算法去解决。
Niklaus Wirth(Pascal 之父)的名言:
数据结构 + 算法 = 程序
虽然当今软件开发中这句话被质疑,但其核心思想依然成立:
- 数据结构:研究信息在计算机中的表示问题。
- 算法:研究信息在计算机中的处理问题。
2. 计算机问题求解过程
- 分析:弄清问题,用语言清楚表述。
- 设计:建立程序系统结构,重点是算法设计和数据结构设计。
- 编码:采用适当的程序设计语言编写可执行程序。
- 测试和维护:发现并排除前面阶段产生的错误,测试通过后投入运行。
3. 数据结构三要素(背诵重点)
| 要素 | 含义 |
|---|---|
| 逻辑结构 | 定义结构中基本元素之间的相互关系。用二元组 |
| 存储结构 | 给出基本元素的存储方式,包括元素的表示和关系的表示。 |
| 数据的运算 | 结构的行为特征,体现为在存储结构上的具体实现算法。 |
数据的存储结构
- 顺序存储结构
- 链式存储结构
- 索引存储结构
- 散列存储结构
4. 指针回顾
指针类型指向某一内存单元的地址。64 位机器上,一个指针占 8 个字节。
常见操作:
- 分配地址、赋值(另一个指针的地址值、NULL)
- 比较两个指针地址
- 指针增减一个整数量(+1 跳过多少字节取决于指向对象大小)
关键符号:
&:取地址*:声明指针 / 解引用->:访问结构体/对象成员
int a;
int *pa;
pa = &a; // 指针 pa 指向 a
指针需要声明数据类型,因为编译器要知道一次取多少个字节。
5. 顺序检索算法示例
int SeqSearch(int element[], int n, int key, int *pos){
int i;
for (i = 0; i < n; i++){
if (element[i] == key){
*pos = i;
return 1;
}
}
*pos = i; // i == n,表示未找到
return 0;
}
数据结构的思路是:把共性的东西抽离出来,不应该因为 int 变成 float 就重写一遍。
6. 案例:多叉路口交通信号灯管理
问题:对车辆可能行驶方向分组,使同组内方向可安全同时行驶。
抽象过程:
- 每个可能行进方向是一个元素。
- 不能同时行驶的方向之间连一条边。
- 同组内不能连边 → 本质上是图着色问题。
这就是数学建模:把实际问题抽象为已有问题,再去找对应算法。
7. 算法复杂性分析
数据结构需要:
- 一定空间存储每个数据项。
- 一定时间执行单个基本操作。
评价算法好坏的两个维度:
- 空间代价:不包括输入本身占用的空间,指算法额外需要的空间。
- 时间代价:基本操作的执行次数
。
大 O 表示法
若存在正常数
大 O 只关注复杂性的量级,不关注系数。
常见量级关系(从大到小):
运算规则:
最好、最坏、平均情况
算法基本操作执行次数往往与输入数据有关:
- 最坏情况:输入数据集最坏时基本操作最多执行次数。
- 平均情况:基本操作的平均执行次数。
- 最好情况:最理想输入下的执行次数。
注意:"平均"需要明确概率测度。例如顺序查找若等概率分布,平均比较次数为
8. 示例:冒泡排序
void Bubble_Sort(int a[], int n){
int i, j, change = 1;
for (i = n - 1; i > 0 && change; --i){
change = 0;
for (j = 0; j < i; j++){
if (a[j] > a[j + 1]){
swap(&a[j], &a[j + 1]);
change = 1;
}
}
}
}
- 额外空间:主要是
swap中的临时变量。 - 时间复杂度:最坏
,最好 (已有序时)。
9. 小结
- 数据结构 = 逻辑结构 + 存储结构 + 运算。
- 评价算法重点看时间复杂度和空间复杂度。
- 大 O 表示法描述的是增长趋势,忽略低阶项和常数系数。