目录

1.什么是数据结构?
数据结构 (Data Structure) 是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的
数据元素的集合。
**2.**什么是算法?
算法 (Algorithm): 就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为 输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
**3.**算法效率
衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即空间复杂度和时间复杂度
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需的额外空间,在计算机发展的早期,计算机的储存容量很小。所以对空间复杂度很在乎,但经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度,所以外面如今已经不需要再特别关注一个算法的空间复杂度。
4.时间复杂度
时间复杂度的定义 :在计算机科学中,算法的时间复杂度是一个函数 ,它定量描述了该算法的运行时间。一个算法执行所消耗的时间,从理论上来说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要算法都上机测试嘛?是可以都上机测试,但是这很麻烦,并且同一个程序在不同的机器上运行时间可能差异很大,所以我们有了时间复杂度的分析方法。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作执行的次数,为算法的时间复杂度。
cpp
// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{
++count;
}
}
//for (int k = 0; k < 2 * N ; ++ k)
//{
// ++count;
//}
//int M = 10;
//while (M--)
//{
// ++count;
//}
printf("%d\n", count);
}
4.1大O的渐近表示法
实际中我们计算时间复杂度的时候其实并不需要计算精确的执行次数,而只需要知道大概执行次数,所以这里我们引入了大O的渐进表示法。
其中大O符号 (Big O notation)是用于描述函数渐进行为的数学符号。
推导大O阶的方法:
1.用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数函数中,只保留最高阶项
3.如果最高阶项存在且系数是不唯1的常系数,则去除最高项的系数,得到的结果就是大O阶
4.如果最终结果是O(1),则表示常数次 ,并不是代表一次
---最坏情况:
任意输入规模的最大运行次数(上界)
---平均情况:
任意输入规模的期望运行次数
---最好情况
任意输入规模的最小运行次数(下界)
** 一般我们比较关心的是一个算法的最坏情况。**
例1
cpp
// 计算Func2的时间复杂度?
void Func2(int N)
{
int count = 0;//次数是1,忽略不计
//次数是2N
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
//次数是10
while (M--)
{
++count;
}
printf("%d\n", count);
}
//那么这个题我们觉得的时间复杂度是O(2N)
//但是实际确实O(N),因为这里的倍数对我们的时间复杂度影响不是很大
例2
cpp
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
int count = 0;
//执行了M次
for (int k = 0; k < M; ++k)
{
++count;
}
//执行了N次
for (int k = 0; k < N; ++k)
{
++count;
}
printf("%d\n", count);
}
//那么我们就得到了时间复杂度的函数式:T(N)=M+N
//M和N都是执行次数,都是变量,都会影响到时间复杂度的结果
//那么这个题的时间复杂度就是O(M+N)
//如果说M和N中有一个较大的项,那我们就保留高阶项
//如果M>>N的话,那么这里的时间复杂度就是O(M)
//如果M<<N的话,那么这里的时间复杂度就是O(N)
//如果M大约等于N的话,那么在时间复杂度里面就都保留
例3
cpp
// 计算Func4的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++k)
{
++count;
}
printf("%d\n", count);
}
//执行的次数是100,那么得出的函数式是T(N)=100
//那么我们的时间复杂度就是O(1)
//这里的1不是运行一次,而是代表所有的常数,可以说这个是一个表示法
例4
cpp
//计算strchr的时间复杂度? 字符串的查找
const char* strchr(const char* str, int character)
{
const char* p_begin = s;
while (*p_begin != character)
{
if(*p_begin == '\0')
return NULL;
p_begin++;
}
return p_begin;
}
/*
T(N)取决于字符的长度N以及查找的位置
如果我们找了几次就找到了,那么这个时间复杂度就是O(1)
但是我们如果找到最后字符的时候才找到或者没有找到,那么我们这个时间复杂度就是O(N)
如果查找位置在中间的话,那么时间复杂度就是O(N/2)
所以这个题的时间复杂度取决于这个字符串的长度和查找的位置
但是对于这种有多个选项的时间复杂度呢,我们应该选择哪个作为时间复杂度呢?
O(1)是最好的情况
O(N)是最坏的情况
O(N/2)是平均的情况
*/
通过上⾯我们会发现,有些算法的时间复杂度存在最好、平均和最坏情况。
最坏情况:任意输⼊规模的最⼤运⾏次数(上界)
平均情况:任意输⼊规模的期望运⾏次数
最好情况:任意输⼊规模的最⼩运⾏次数(下界)
⼤O的渐进表⽰法在实际中⼀般情况关注的是算法的上界,也就是最坏运⾏情况
那么对于这个题来说,我们应该关注最差的情况,那么这个题的时间复杂度就是O(N)
cpp
// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i - 1] > a[i])//升序
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
if(exchange == 0)//说明数组是有序的,我们不用继续进行冒泡。直接跳出循环
break;
}
}
//当数组有序的情况下,外层循环执行一次,内层循环执行N次,因为数组有序,就只执行N次,那么时间复杂度就是O(N)
//
//外层循环执行第一次的时候,内层循环执行N次,因为不是有序的,所以我们外层循要执行N次
/*
外1 2 3 ........n
内n-1 n-2 0
那么次数就是n-1+n-2+n-3+...+1
就是等差数列求和(n-1+1)*(n-1)/2
在这个结果中对结果影响最大的是N^2,所以这个代码的时间复杂度就是O(N^2)
*/
冒泡排序O(N^2)
cpp
void func5(int n)
{
int cnt = 1;
while (cnt < n)
{
cnt *= 2;
}
}
/*
* 当N为10的时候,我们循环4次就停止了
* 假设执行的次数为x,那么2^x=n
* 那么x就等于log2 n ,
*
* 所以这里的时间复杂度是O(log2 n)
*/
cpp
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if (0 == N)
return 1;
return Fac(N - 1) * N;
}
/*
递归的时间复杂度是多少呢
每次递归的时间复杂度是O(1)
总共有n个O(1),那么时间复杂度就是O(N)->将每次递归的时间复杂度进行相加
*/
5.空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中 临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少 bytes 的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。
空间复杂度计算规则基本跟实践复杂度类似,也使用 大 O 渐进表示法 。
注意: 函数运行时所需要的栈空间 ( 存储参数、局部变量、一些寄存器信息等 ) 在编译期间已经确定好了,因 此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
实例 1 :
cpp
// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
if(exchange == 0)
break;
}
}
/*
外面的for循环内局部变量end,内部还有变量exchange
内循环的变量i,总共三个变量
所以这里的空间复杂度是O(1)
*/
有三个变量,那么这个空间复杂度就是O(3)
cpp
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
/*
F(N)->F(N-1)->....->F(1)->F(0)
总共的空间复杂度就是O(N)
*/
cpp
//通过动态申请内容也会涉及到空间复杂度的计算的
int func(int n)
{
int arr[n] = malloc(sizeof(int) * n);
}
//这里的空间复杂度也是O(N)
6.常见复杂度
一般算法常见的复杂度如下:


复杂度的 oj 练习
面试题 17.04. 消失的数字 - 力扣(LeetCode)
cpp
int missingNumber(int* nums, int numsSize) {
int sum=(1+numsSize)*numsSize/2;
for(int i=0;i<numsSize;i++)
{
sum-=nums[i];
}
return sum;
}
cpp
void Swap(int*nums,int src,int dst)
{
while(src<dst)
{
int tmp=nums[src];
nums[src]=nums[dst];
nums[dst]=tmp;
src++;
dst--;
}
}
void rotate(int* nums, int numsSize, int k) {
k%=numsSize;
Swap(nums,0,numsSize-k-1);//前驱你这
Swap(nums,numsSize-k,numsSize-1);//后驱逆置
Swap(nums,0,numsSize-1);//整体逆置
}
