什么是数据结构?
数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
什么是算法?
算法就是定义良好的计算过程,他取一个或取一组的值为输入,并产生一个或一组作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
1.算法效率
1.1如何衡量一个算法的好坏
如何衡量一个算法的好坏呢?比如对于一下斐波那契数列
long long Fib(int n) { if(n<3) return 1; return Fib(n-1)+Fib(n-2); }斐波那契数列的递归实现方式非常简洁,但简介一定好吗?如何衡量好坏呢?
1.2算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。
2.时间复杂度
在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法所花费的时间与其中语句的执行次数成正比,算法中基本操作的执行次数,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出来该算法的时间复杂度。
T(n)=O(f(n))
for (int i = 0; i < n; i++) //频率为 n+1
{
for (int j = 0; j < n; j++) //频率为 n*(n+1)
{
c[i][j]=0; //频率为 n*n
for(int k=0;k<n;k++) //频率为 n*n*(n+1)
{
c[i][j]=c[i][j]+b[k][j]; //频率为 n*n*n
}
}
2.1时间复杂度的计算
步骤 1:确定「基本操作」
基本操作是算法中执行次数最多、最核心的原子操作(通常是循环体内部的赋值、比较、运算等,执行 1 次耗时固定)。
例:循环中的
a[i] = i、if (x > y)、sum++均为基本操作。步骤 2:统计基本操作的「执行次数」
根据输入规模 n,分析循环、递归等结构对执行次数的影响,写出次数表达式 T(n)。
- 循环结构:重点看循环的「迭代次数」(而非循环变量的初始值 / 步长,除非步长随 n 变化)。
- 递归结构:写出递归方程,通过递推或主定理求解(下文单独讲解)。
步骤 3:简化为大 O 记法
遵循 3 条规则:
- 忽略常数项:T(n)=n+5 → O(n);
- 忽略低次项:T*(n)=n^2^+3n* → O(n^2^);
- 忽略最高次项的系数:T(n)=5n^3^ → O(n^3^)。
2.2常见时间复杂度
| 复杂度 | 名称 | 适用场景 | 示例 |
|---|---|---|---|
| O(1) | 常数时间 | 无循环 / 递归,固定操作数 | 数组访问、简单运算 |
| O(logn) | 对数时间 | 每次操作将问题规模减半 | 二分查找、平衡二叉树操作 |
| O(n) | 线性时间 | 单循环遍历所有元素 | 数组遍历、线性查找 |
| O(nlogn) | 线性对数时间 | 分治思想(循环 + 二分) | 归并排序、快速排序 |
| O(n^2^) | 平方时间 | 双重嵌套循环 | 冒泡排序、插入排序 |
| O(n^k^) | 多项式时间 | k 重嵌套循环 | 多维数组遍历 |
| O(2^n^) | 指数时间 | 暴力枚举(子集、排列) | 斐波那契递归(无记忆化) |
| O(n!) | 阶乘时间 | 全排列生成 | 旅行商问题暴力解法 |
场景1:常数时间(O(1))
特征:无循环、无递归,操作数与 n 无关,固定次数。
x++; //频率为1
s=0; //频率为1
for(int i=0;i<1000;i++)
{
x++;
s=0;
}
场景2:线性时间(O(n))
特征:单循环,循环次数与 n 成正比(遍历所有元素 1 次)。
for(int i=0;i<n;i++)
{
x++;
s=0;
}
场景 3:平方时间 O(n^2^)
特征:双重嵌套循环,外层循环 n 次,内层循环每次也 n次(总次数 n×n=n^2^)。
x=0;
y=0;
for(int K=0;K<n;K++)
{
x++;
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
j++;
}
}
场景 4:对数时间 O(logn)
特征:每次循环将问题规模「减半」(循环次数为 log2n,底数可忽略,记为 logn)。
for(int i=0;i<n;i=i*2)
{
x++;
s=0;
}
| 次数 | 1 | 2 | 3 | 4 | … | t |
|---|---|---|---|---|---|---|
| i | 1 | 2 | 4 | 8 | … | ? |
| i | 2^0^ | 2^1^ | 2^2^ | 2^3^ | … | 2^t-1^ |
场景五:线性对数时间 O(nlogn)
特征:外层循环 n 次,内层循环 logn 次(总次数 n×logn),常见于高效排序算法。
int sum=0;
for(int i=0;i<n;i++)
{
for(int j=1;j<n;j=j*2)
{
sum++;
}
}
计算时间复杂度
1.设n是描述问题规模的非负整数,下面程序片段的时间复杂度是()。
x=2;
while(x<n/2)
x=2*x;
==A.O (log2n)== B.O (n) C.O(nlog2n) D.O (n^2^)
| 次数 | 1 | 2 | 3 | 4 | t |
|---|---|---|---|---|---|
| x | 2^2^ | 2^3^ | 2^4^ | 2^5^ | 2^t+1^ |
2^t+1^=n/2 2^t+2^=n log
22^t+2^=log2n t+2=log2n t=log2n-2T(n)=log
2n-2 =O(log2n)
2.下列函数的时间复杂度是()。
int fun(int n)
{
int i=0,sum=0;
while(sum<n) sum += ++i;
return i;
}
A.O (logn) ==B.O (n^1/2^)== C.O (n) D.O(nlog2n)
| 次数 | 1 | 2 | 3 | 4 | t |
|---|---|---|---|---|---|
| i | 1 | 2 | 3 | 4 | t |
| sum | 1 | 2 | 3 | 4 | t(t+1)/2 |
t(t+1)/2=n
t(t=1)=2n
t^2^+t=2n
t^2^约等于2n
T(n)=O (n^1/2^)
3.下列程序的时间复杂度是()。
int sum=0;
for(int i=1;i<n;i*=2)
for(int j=0;j<i;j++)
sum++;
A.O (logn) ==B.O (n)== C.O (nlogn) D.O(n^2^)
| 次数 | 1 | 2 | 3 | 4 | t |
|---|---|---|---|---|---|
| i | 2^0^ | 2^1^ | 2^2^ | 2^3^ | 2^t-1^ |
| 内层次数 | 2^0^ | 2^1^ | 2^2^ | 2^3^ | 2^t-1^ |
- 外层循环: i 从1开始,每次乘以2,直到 i < n ,执行次数是 log₂n 次
- 内层循环:每次外层循环时, j 从0到 i-1 ,执行次数等于当前的 i 值。
- 总执行次数: 1 + 2 + 4 + ... + 2^(log₂n - 1) ,这是等比数列求和,结果为 n - 1 ,时间复杂度是 O(n)。