系列文章目录
速通数据结构与算法系列
速通数据结构与算法第一站 复杂度
感谢佬们支持!
目录
前言
终于,我们在上一篇博客结束了C语言的学习(当然后续还会再深入学习),我们可以进行数据结构的学习。数据结构是一名很重要的课,不仅是对于我们算法的提升,作为408四大件之一
学好数据结构对于理解操作系统的底层等很重要。这一节我们先来看最简单的复杂度问题
衡量一个算法的好坏,一般会从花了多少时间和用了多少额外空间来看,以此诞生了两大标准:时间复杂度和空间复杂度
一、时间复杂度
我们先来给出时间复杂度的定义
在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。
直接计算运行时间非常的麻烦,不仅需要多次的上机测试,而且不同配置的电脑和网络的波动都可能影响时间的计算。由此引入了时间复杂度这种分析方式,一个算法花费的时间与其执行语句的次数成正比,所以我们称算法中基本语句的执行次数为一个算法的时间复杂度
在实际运算中,我们并不需要算出精确的次数,而是大概的次数就行,这称为大O渐进法
实际计算中,有如下规则
- 保留精确次数中影响最大的
- 以常数1取代时间中的所有常数
- 如果最高阶存在且不为1,则去除这个项目中相乘的常数,得到的结果即为大O阶
多的不说,我们先给一波例子
下面的代码中,test1函数中,++count执行了多少次呢
例1
void test(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;
}
}
由题易得
F(N)=N^2 + 2*N + 10
套用上面的规则,我们知道,最后时间复杂度是O(n^2)
再来看下一个例子
例2
void test2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
}
此时就是O(n)了
再例 有的时候复杂度的计算涉及不止一个变量
例3
void test3(int N,int M)
{
int count = 0;
for (int k = 0; k < N; ++k)
{
++count;
}
for (int k = 0; k < M; ++k)
{
++count;
}
}
此时为O(M+N)
再例
例4
void test4()
{
int count = 0;
int M = 100;
while (M--)
{
count++;
}
}
此时由于只做了常数次运算,所有是O(1)
所以之后如果让你讲算法优化至O(1),不是让你做1次运算,而是做常数次运算
大多数的时候算法的计算并非如此简单,而是根据不同的情况而不同
最坏场景:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
复杂度是个悲观的算法,我们一般更关注算法的最坏情况
例5
介绍一个函数strchr
定位字符串中要查字符的首个出现位置,并返回一个指向它的指针
它的实现大致如下
const char* strchr(const char* str, int character)
{
while (*str)
{
if (*str == character)
return str;
else
++str;
}
return str;
}
我们可以简单计算一下他的时间复杂度
比如我们给到一个字符串hello world,分3种情况:
1 查h:显然我们第一次就能查到,是最好的情况,也就是O(1)
2 查w:由于w在字符串的中间,所以遍历至字符串的中间时能查到,是平均的情况O(N/2)也就是O(N)
3 查d :我们最后一次才能查到,是最坏的情况,O(N)。
由于我们关注最坏的情况,所以最终是O(N)
例6
计算冒泡排序的复杂度?
void bubbleSort(int* arr, int size)
{
assert(arr);
for (int i = 0; i < size; ++i)
{
for (int j = i + 1; j < size; ++j)
{
if (arr[i] > arr[j])
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
数一下循环,第一次是size次,第二次是size-1,……最后是1,这是一个等差数列,最后循环
n*(n-1)/2次,也就是O(N方)
熟练以后我们看到双for循环,一般是典型的O(n方)
虽然但是,我们的计算结果来自于对冒泡排序思想的理解,所以复杂度的计算不能浮于表面,要关注到代码的思想
例7
计算二分查找的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n - 1;
while (begin < end)
{
int mid = begin + ((end - begin) >> 1);
if (a[mid] < x)
{
begin = mid + 1;
}
else if (a[mid] > x)
{
end = mid;
}
else
{
return mid;
}
}
return -1;
}
由于二分查找的逻辑是找一次减一半,即除2,设a的size为N的话,N=2^x,所以x=logN(以二为底但是可以忽略不记)
例8
计算阶乘递归的时间复杂度?
long long Fac(size_t N)
{
if (0 == N)
{
return 1;
}
return Fac(N - 1) * N;
}
众所周知递归是一个不断建立栈帧的过程,上面的例子中是这样
每层建立一个栈帧,每个栈帧中比较为一次,计算为一次,仍然为常数次运算
所以时间复杂度给到的是O(n)
例9
计算斐波那契递归的时间复杂度?
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
还是和上面一样,我们先画递归展开图
显然,右边有几个分支结束的早,不过我们仍然可以将上面看作一颗完全二叉树,所以他的时间复杂度是O(2^n)
由此可见,用递归的方式求斐波那契是非常慢的,还有可能出现栈溢出的问题。
二、空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度
,同理于时间复杂度,空间复杂度的计算也用大O渐近法。
另外需要注意的是,函数运行时所需的栈空间(存取参数,局部变量,一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数运行时显示申请的额外空间来确定
例1
计算一下冒泡排序的空间复杂度?
void bubbleSort(int* arr, int size)
{
assert(arr);
for (int i = 0; i < size; ++i)
{
for (int j = i + 1; j < size; ++j)
{
if (arr[i] > arr[j])
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
显然,在这段代码中我们只定义了i,j两个变量,
循环结束时,i,j销毁,然后再重新建立,用的还是那俩块空间
所以空间复杂度为O(1)
例2
计算返回斐波那契数列的前n项算法的空间复杂度?
long long* Fibnacci(size_t n)
{
if (n == 0)
return NULL;
long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
使用malloc开辟了n+1块空间并定义了一个变量,所以总体为O(n)
例3
计算阶乘递归的空间复杂度?
long long Fac(size_t N)
{
if (0 == N)
{
return 1;
}
return Fac(N - 1) * N;
}
我们还是看一下刚才画的递归展开图
每层建立一个栈帧而栈帧内部不定义临时变量,所以最终的空间复杂度是O(n)
例4
计算斐波那契递归的空间复杂度?
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
我们在计算之前首先要明确他的递归顺序:从Fib(n)这个节点由左一路向下递归直到Fib(n),此时递归结束,开辟了n个空间,空间销毁;再递归右边的时,依然会用刚才的空间
所以最终还是O(n)
至此我们可以做个总结,就是时间是一去不复返的,但是空间是可以反复利用的。
在上面的例子中无论是递归还是循环,变量,函数调用完后销毁,其占的空间也会又还回来,
下次接着用,而时间则不行。
总结
做总结,作为速通数据结构与算法的第一篇,主要先给大家铺垫一下复杂度的相关知识,下一篇
将给大家带来第一个数据结构---顺序表。
水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。