数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算
Posted 爱敲代码的三毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算相关的知识,希望对你有一定的参考价值。
文章目录
时间复杂度&空间复杂度
1. 算法效率
算法效率分析一般分为两种,一种是时间效率,另外一种是空间效率。时间效率被称为时间复杂度,空间效率则被称为空间复杂度。时间复杂度是用来衡量一个算法的运行速度,而空间复杂度主要是用来衡量一个算法的所需要的额外空间,早期的计算机存储容量很小,所以比对空间复杂度很是在乎。但是随着计算机的叙述发展,计算机的存储已经到了一个很高的程度,比如现在的一台笔记本至少都是16G内存+512G磁盘,服务甚至是几百个G的内存,几百T的磁盘。所以现在并不那么关心空间复杂度,也经常出现空间换时间的做法。
2. 时间复杂度
1) 时间复杂度的概念
时间复杂度的一个定义:在计算机科学中,算法的时间复杂度是一个函数(一个数学函数),它定量描述了一个算法执行所消耗的时间,从理论上来说,是不能计算出来的,只有当你把代码放到机器上跑才能知道运行时间,但是通过机器测试这种方式明显不现实,因为每台计算机的配置都不一定相同,所以才有了时间复杂度的分析方式。一个算法所花费的时间与其语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度。
2) 大O的渐近表示法
来看一段代码
void func1(int N)
int count = 0;
int i = 0;
for (i = 0; i < N ; i++) //执行N次
for (int j = 0; j < N ; j++) //执行N次
count++;
int k = 0;
for (k = 0; k < 2 * N ; k++) //执行2*N次
count++;
int M = 10;
while (M--) //执行10次
count++;
printf("%d\\n",count);
那么我们来计算一下这个代码的运行次数就是
f ( N ) = N 2 + 2 ∗ N + 10 f(N) = N^2+2*N+10 f(N)=N2+2∗N+10
但实际我们计算时间复杂度时,并不一定要计算的那么精确,而是只计算大概的执行次数.
我看到func1的执行次数,如果当我们的N非常大时,假设N = 1000,那么这里的+10是可以忽略了,因为 100 0 2 = 1000000 1000^2=1000000 10002=1000000,在一百万面前+10可以说是微乎其微了,所以+1和+10没什么区别。同理 2 ∗ N 2*N 2∗N也是一样的,当N足够大趋近于无穷时, 2 ∗ N 2*N 2∗N也时微乎其微了。
那么就可以使用大O的渐近表示法
大 O O O符号:是用于描述函数渐进行为的数学符号
推到大 O O O阶方法
- 用常数1取代运行时间汇总的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶存在且不是1,则去除与这个项数相乘的常数,得到的结果就是大 O O O阶
通过上面的方法来推导一下
用常数1取代运行时间汇总的所有加法常数
f ( N ) = N 2 + 2 ∗ N + 1 f(N) = N^2+2*N+1 f(N)=N2+2∗N+1
在修改后的运行次数函数中,只保留最高阶项
f ( N ) = N 2 f(N) = N^2 f(N)=N2
这里的最高阶项不是1,所以func1函数的时间复杂度就是** O ( N 2 ) O(N^2) O(N2)**
大 O O O渐进表示法去掉了那些对结果影响不大的项数,只保留了最影响结果的那一项。
另外有些算法存在着,最好、平均和最坏情况
- 最坏情况:任意输入规模的最大运行次数(上界)
- 平均情况:任意输入规模的期望运行次数
- 最好情况:任意输入规模的最小运行次数(下界)
举个例子:
假设在一个数组中查找一个数字。
- 最好情况:1次找到,数组第一个数子就是我们要找的, O ( 1 ) O(1) O(1)
- 最坏情况:最后一个是我们要找的, O ( n ) O(n) O(n)
- 平均情况:注意:这里的平均情况并不是最好和最坏情况相加的平均值,而是我们期望运行的次数,有时候平均情况可能和最好或者是最坏情况一样。
我们平常嘴上所说的时间复杂度就是最坏情况的时间复杂度
3) 时间复杂度案例举例
实例1:
void Func2(int N)
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
++count;
int M = 10;
while (M--)
++count;
printf("%d\\n", count);
粗略计算就是 f ( N ) = 2 ∗ N + 10 f(N) = 2*N+10 f(N)=2∗N+10
在修改后的运行次数函数中,只保留最高阶项,如果最高阶存在且不是1,则去除与这个项数相乘的常数
那么这个代码的时间复杂度就是** O ( N ) O(N) O(N)**
实例2:
void Func3(int N, int M)
int count = 0;
for (int k = 0; k < M; ++ k)
++count;
for (int k = 0; k < N ; ++ k)
++count;
printf("%d\\n", count);
这种情况有一点特殊,因为不知到N和M谁大,所以对于这种不确定谁对结果的影响大,就都需要保留下来。所以这个代码的时间复杂度就是 O ( N + M ) O(N+M) O(N+M)
如果可以确定N远远大于M那么时间复杂度就是 O ( N ) O(N) O(N)
实例3:
void Func4(int N)
int count = 0;
for (int k = 0; k < 100; ++ k)
++count;
printf("%d\\n", count);
对于这种场数次数的时间复杂度就是 O ( 1 ) O(1) O(1)
实例4:
int find(int* arr, int N int key)
assert(arr);
int i = 0;
for (i = 0; i < N; i++)
if (arr[i] == key)
return i;
return -1;
这个代码是在一个数组中查找一个数字,对于这种情况就是直接取它的最坏情况的时间复杂度,就是 O ( N ) O(N) O(N)
实例5:
void BubbleSort(int* arr, int n)
assert(arr);
int i = 0;
for (i = 0; i < n-1; i++)//排序趟数
int flag = 0;
int j = 0;
for (j = 0; j < n - 1 - i; j++)//比较次数
if (arr[j] > arr[j + 1])
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 1;
if (flag == 0)
break;
这是冒泡排序的代码,冒泡排序的时间复杂度也是比较特殊的。
冒泡排序的外层循环是排序的趟数,每一趟冒泡排序都会确定一个数字的位置。所以每一趟排序后比较的次数都要减去1。
那么计算的次数就是 ( N − 1 ) + ( N − 2 ) + ( N − 3 ) . . . + 2 + 1 (N-1) + (N-2) + (N-3)... + 2 + 1 (N−1)+(N−2)+(N−3)...+2+1
这是要给等比数列,通过等比数列的前N项和公式得出$\\fracN*(N-1)2 $
通过大 O O O渐进法得出冒泡排序的时间负责度就是 O ( N 2 ) O(N^2) O(N2)
实例6:
void BinarySearch(int* arr, int size, int key)
assert(arr);
int left = 0;
int right = size - 1;
int mid = 0;
while (left < right)
mid = (right - left) / 2 + left;
if (arr[mid] < key)
left = mid + 1;
else if (arr[mid] > key)
right = mid - 1;
else
return mid;
return -1;
这是一个二分查找的代码,二分查找一次砍掉数组的一半元素。那么查找的次数就是 N / 2 / 2 / 2 / 2... / 2 = 1 N/2/2/2/2.../2 = 1 N/2/2/2/2.../2=1,找了 x x x次,则 / 2 /2 /2了 x x x次,那么长度为$N 的 数 组 最 坏 则 要 查 找 次 数 就 是 , 的数组最坏则要查找次数就是, 的数组最坏则要查找次数就是,N = 2^x , 转 为 对 数 的 形 式 就 是 ,转为对数的形式就是 ,转为对数的形式就是x = \\log_2N $
所以二分查找的时间复杂度就是 O ( log 2 N ) O(\\log_2N) O(log2N)
实例7:
long long Factorial(size_t N)
return N < 2 ? N : Factorial(N-1)*N;
这是一个递归计算N的阶层的代码,这个代码的时间负责度又是多少呢?
递归算法的时间复杂度 = = = 递归次数 * 每次递归函数中的执行的次数
这里的递归次数是 N − 2 N-2 N−2,每次递归函数中的执行次数就是1,那么这个代码的时间复杂度就是 O ( N ) O(N) O(N)
实例8:
long long Fibonacci(size_t N)
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
这是递归计算斐波那契数列的函数
递归计算斐波那契数列,类似于一颗二叉树。假设这一棵二叉树是满二叉树。
那么第一层计算次数就是 2 0 2^0 20,第二层就是 2 1 2^1 21,第三层就是 2 2 2^2 22,以此类推…,那么函数每一次的执行次数就是 f ( N ) = 2 N − 1 f(N) = 2^N-1 f(N)=2N−1,根据等比数列前N项和公式$S_n\\fraca_1(1-q^n ) 1-q $,它的准确的时间复杂度就是 2 N − 1 − 空 缺 2^N-1-空缺 2N−1−空缺
通过大
O
O
O渐进法推导后,这个代码的时间复杂度就是
O
(
2
N
)
O(2^N)
O(2< 以上是关于数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算的主要内容,如果未能解决你的问题,请参考以下文章