目录
基本概念
数据
数据是信息的载体,是描述客观事物属性的数、字符以及所有能输入到计算机中并被计算机程序识别和处理的符号集合。数据是计算机程序加工的原料。
数据元素、数据项
数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。
一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。
数据对象、数据结构
数据对象是具有相同性质的数据元素的集合,是数据的一个子集。
数据结构是相互之间存在一种或多种特定关系的数据元素的的集合
数据结构三要素
逻辑结构
逻辑结构描述的是数据元素之间的逻辑关系。
数据的逻辑结构分为:
- 集合:各个元素同属一个集合,别无其他关系。
- 线性结构:数据元素之间是一对一的关系;除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继。
- 树形结构:数据元素之间是一对多的关系。
- 图状结构(网状结构):数据元素之间多对多的关系。

物理结构(存储结构)
物理结构描述的是数据元素之间的逻辑关系在计算机中的表示。
数据的物理结构分为:
- 顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
- 链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。(用指针表示下一个数据元素的存储地址)
- 索引存储:在存储元素信息的同时,还建立附加的索引表;索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)。
- 散列存储:根据元素的关键字直接计算出该元素的存储地址,又称哈希存储。(后面的章节再讲)

链式存储、索引存储、散列存储又称非顺序存储。
注意事项:
- 若采用顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的。
- 数据的存储结构会影响存储空间分配的方便程度。
- 数据的存储结构会影响对数据运算的速度。
数据的运算
数据的运算是施加在数据上的运算包括运算的定义和实现。运算的定义是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤。
数据类型、抽象数据类型
数据类型是一个值的集合和定义在此集合上的一组操作的总称
- 原子类型:其值不可再分的数据类型(基本数据类型)。
- 结构类型:其值可以再分解为若干成分(分量)的数据类型(结构体)。
抽象数据类型(Abstract Data Type,ADT)是抽象数据组织及与之相关的操作。
- ADT 用数学化的语言定义数据的逻辑结构、定义运算。与具体的实现无关。
算法的基本概念
算法
算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。
算法的特性:
-
有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。(算法必须是有穷的,程序可以是无穷的)
-
确定性:算法中每条指令必须有确切的含义,对于相同的输入只能得到相同的输出。
-
可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现。
-
输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。
-
输出:一个算法有一个或多个输出,这些输出是与输入有着某种特定关系的量。
算法设计的要求
设计一个"好"的算法要考虑达到以下要求:
- 正确性:算法应能够正确地解决求解问题。
- 可读性:算法应具有良好的可读性,以帮助人们理解。
- 健壮性:输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。
- 高效率与低存储量需求:花的时间少,时间复杂度低;不费内存,空间复杂度低。
算法效率的度量
时间复杂度
时间复杂度的本质
时间复杂度用大 O 符号表示算法运行时间随输入规模 n 增长的趋势,关注 "规模变大时的效率变化",而非实际运行时间。例如:
- O(1):操作次数固定,与 n 无关(如数组随机访问)。
- O(n):操作次数随 n 线性增长(如遍历数组)。
计算时间复杂度的三步法
找基本操作
定位算法中最深层循环内的操作。
- 例 1 :冒泡排序中,元素比较
if (arr[i] > arr[i+1])
是基本操作,位于最内层循环。 - 例 2:计算数组和的 C 代码:
c
#include <stdio.h>
int sum_array(int arr[], int n) {
int total = 0;
for (int i = 0; i < n; i++) { // 基本操作:total += arr[i]
total += arr[i];
}
return total;
}
分析执行次数与 n 的关系
确定基本操作执行次数 x = f(n)。
- 例 1 :单层循环遍历 n 个元素:
c
for (int i = 0; i < n; i++) { // 执行 n 次
printf("%d ", i);
}
执行次数 x = n。
- 例 2:双层循环:
c
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) { // 内层循环 n 次,外层 n 次
printf("%d %d\n", i, j);
}
}
执行次数 x = n × n = n 2 n^2 n2。
确定数量级(大 O 表示)
忽略常数和低阶项,只保留最高阶。
- 例 1:若 x = 3n + 5,结果为 O(n)。
- 例 2 :若 x = 2 n 2 2n^2 2n2 + 100n + 200,结果为 O( n 2 n^2 n2)。
常用技巧
加法规则:O(f(n)) + O(g(n)) = O(max(f(n), g(n)))
取多个操作中复杂度 "最大" 的部分。
- 例:
c
void example(int n) {
// 第一部分:O(n)
for (int i = 0; i < n; i++) {
printf("%d ", i);
}
// 第二部分:O(n^2)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("%d %d\n", i, j);
}
}
}
总复杂度为 O( n 2 n^2 n2)。
乘法规则:O(f(n)) × O(g(n)) = O(f(n) × g(n))
- 例:矩阵乘法(3 层循环):
c
void matrix_multiply(int a[][100], int b[][100], int result[][100], int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) { // O(n^3)
result[i][j] += a[i][k] * b[k][j];
}
}
}
}
杂度对比:"常对幂指阶"
效率从高到低:O(1) < O(logn) < O(n) < O(nlogn) < O( n 2 n^2 n2) < O( n 3 n^3 n3) < O( 2 n 2^n 2n) < O(n!) < O( n n n^n nn)。
- O(logn):二分查找
c
int binary_search(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
三种复杂度类型
最坏时间复杂度
- 例:顺序查找,最坏查 n 次,复杂度 O(n)。
c
int sequential_search(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) return i;
}
return -1;
}
平均时间复杂度
顺序查找中,目标在每个位置概率为 1 2 \frac{1}{2} 21,平均查找次数 n + 1 2 \frac{n + 1}{2} 2n+1,复杂度 O(n)。
最好时间复杂度
顺序查找中,目标在第一个位置,仅需 1 次操作,复杂度 O(1)。
空间复杂度
空间复杂度核心概念
空间复杂度用于衡量算法运行过程中额外占用存储空间的大小,关注存储空间随问题规模 n 的增长趋势,用大 O 符号表示。例如:
- O(1):额外空间固定,与 n 无关。
- O(n):额外空间随 n 线性增长。
空间复杂度计算方法
普通程序空间复杂度计算
步骤:
- 找到所占空间大小与问题规模相关的变量。
- 分析空间 x 与 n 的关系 x = f(n)。
- 用 O(x) 表示空间复杂度。
C 语言示例:
c
// 求数组前n个元素和,空间复杂度分析
int array_sum(int arr[], int n) {
int sum = 0; // 与n无关,固定空间
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
// 分析:仅用sum、i变量,空间固定,空间复杂度 O(1)
c
// 数组复制,新开辟n大小空间
void array_copy(int arr[], int n, int new_arr[]) {
for (int i = 0; i < n; i++) {
new_arr[i] = arr[i]; // 新数组占用O(n)空间
}
}
// 分析:new_arr数组空间随n变化,空间复杂度 O(n)
递归程序空间复杂度计算
步骤:
- 找到递归调用深度 x 与 n 的关系 x = f(n)。
- 用 O(x) 表示空间复杂度(注:若各层函数存储需求不同,分析需调整)。
C 语言示例:
c
// 递归求阶乘,分析空间复杂度
int factorial(int n) {
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1); // 递归深度n,每层存储函数调用信息
}
// 分析:递归深度最大为n,空间复杂度 O(n)
c
// 斐波那契数列递归(优化前)
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2); // 递归树深度接近n
}
// 分析:递归树深度最大为n,空间复杂度 O(n)(实际因重复计算,效率低,但空间看最大递归深度)
空间复杂度常用技巧
加法规则
O(f(n)) + O(g(n)) = O(max(f(n), g(n)))
示例:
c
void complex_operation(int n) {
int arr1[100]; // O(1),固定大小
int arr2[n]; // O(n)
// 其他操作
}
// 分析:O(1) + O(n) = O(n),取最大
乘法规则
O(f(n)) × O(g(n)) = O(f(n) × g(n))
示例:
c
// 二维数组初始化,m行n列
void init_2d_array(int m, int n) {
int array[m][n]; // 空间大小 m×n
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
array[i][j] = 0;
}
}
}
// 分析:空间复杂度 O(m×n)
复杂度对比 "常对幂指阶"
效率从高到低:O(1) < O(logn) < O(n) < O(nlogn) < O( n 2 n^2 n2) < O( n 3 n^3 n3) < O( 2 n 2^n 2n) < O(n!) < O( n n n^n nn)
应用场景:
- O(1):简单变量操作,如固定大小数组。
- O (n):一维数组存储。
- O ( n 2 n^2 n2):二维数组存储。