꧁ 各位大佬们好!很荣幸能够得到您的访问,让我们一起在编程道路上任重道远!꧂
☙ 博客专栏:【数据结构初阶】❧
⛅ 本篇内容简介:数据结构初阶中的时间复杂度与空间复杂度的详解!
⭐ 了解作者:励志成为一名编程大牛的学子,目前正在升大二的编程小白。
✍励志术语:编程道路的乏味,让我们一起学习变得有趣!
✂ 正文开始
文章目录
☞ 什么是数据结构?
☞ 什么是算法?
☞ 数据结构与数据库的区别
☞ 如何学好数据结构和算法
▶ 算法效率
☞ 如何衡量一个算法的好坏
☞ 算法的复杂度
☞ 时间复杂度的概念
☞ 大O的渐进表示法
⭑ 推到大O阶的方法
☞ 常见时间复杂度计算举例
⭑ 实例 1
⭑ 实例 2
⭑ 实例 3
⭑ 实例 4
⭑ 实例 5
⭑ 实例 6
⭑ 实例 7
⭑ 实例 8
☞ 空间复杂度
☞ 常见空间复杂度计算举例
⭑ 实例 1
⭑ 实例 2
⭑ 实例 3
☞ 常见复杂度对比
☞ 结束语
官方术语:数据结构(Data Structure)是计算机的存储,组织数据的方式,指相互之间存在一种或者多种特定的关系的数据元素的集合。
通俗说法:就是数据在内存中的存储方式,以及如何在内存中管理数据。
算法(Algorithm):就是定义良好的计算过程,比如:他取一个或者一组值为输入,并由此产生一个或者一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入的数据转化成输出结果。
相同点:都是在管理数据的内容,都进行的是 增删查改 。
不同点:数据结构是在内存中进行数据的管理,而数据库是在磁盘中对数据进行管理。
① 死敲代码,把自己的代码量提升上去,对C语言中的 动态内存管理、指针、结构体等知识了如指掌就差不多了。那敲代码.....敲成什么样才算好呢,我想应该是这样(哈哈哈,玩笑话!):
② 多注意画图与思考,比如我们画一个链表的图:
③ 注意多刷题 比如:LeetCode和 牛客网 的题型都是不错的选择。
LeetCode链接:https://leetcode-cn.com/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china
牛客网链接:https://hr.nowcoder.com/?qhclickid=2abfa3675dda5302
如何衡量一个算法的好坏呢?比如我们之前学习过的斐波那契数列:
long long Fib(int n)
{
if (n < 3)
{
return 1;
}
else
return Fib(n - 1) + Fib(n - 2);
}
如果我们用递归的方式实现求斐波那契数列,它的方式非常简洁,但是我们还可以用循环相加的方式去实现斐波那契数列,比如:
int a = 0;
int b = 1;
int c = 0;
while (1)
{
c = a + b;
a = b;
b = c;
}
看,我们这样的方法去实现也是可以求出斐波那契数列的,但是怎么样去衡量哪个算法的好坏呢?我相信看完这篇博客,你就会知晓答案了!
算法在编写成可执行程序后,运行时需要耗费时间和空间(内存)资源,因此衡量一个算法的好坏,一般从时间和空间这两个维度去衡量,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法运行快慢,而空间复杂度主要衡量一个算法运行时所需要的而额外空间。在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是在乎。大家都知道摩尔定律吧,集成电路上可以容纳晶体管数目在大约每经过18个月便会翻一倍。经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度,所以我们如今已经不需要再特别关注一个算法的空间复杂度。
但是在校招的试题中,时间复杂度与空间复杂度依旧是作为限制的条件。
在计算机科学中,算法的时间复杂度是一个函数(这里的函数指的是在数学计算中的函数,不是代码过程中的函数),它定量描述该算法的运行时间。一个算法执行所消耗的时间,从理论上说,是算不出来的,只有你把程序放在机器上跑起来,才能知道。但是如果是这样,那每次的算法都要上机测试吗?太麻烦了,所以规定算法所花费的时间与其中的语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。
我们来看一个例子:
void Fun1(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);
}
问:在Fun1中count++语句共执行了多少次?
Fun1执行的基本操作次数: F(n)=n^2+2*n+10
● n=10 F(n)=130
● n=100 F(n)=10210
● n=1000 F(n)=1002010
实际中我们计算时间复杂度时,并不一定要计算到精确的执行次数,只需要大概的执行次数,这里我们使用大O的渐进表示法。
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
㊀ 用常数1取代运行时间中的所有加法常数。
㊁ 在修改后的运行次数函数中,只保留最高阶项。
㊂ 如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。
如上面的例子,在使用大O的渐进表示法后,Fun1的时间复杂度为:O(N^2)
● n=10 F(n)=100
● n=100 F(n)=10000
● n=1000 F(n)=1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示了执行次数。
另外有些算法的时间复杂度存在最好、平均、最坏的情况:
最坏情况:任何输入的规模的最大运行次数(上界)。
平均情况:任何输入的规模的期望运行次数。
最好情况:任意输入规模的最小运行次数(下界)。
例如:在一个长度为N的数组中搜索某一个数据x
最好情况:1次找到
最快情况:将数组遍历完了,从找到,即N次。
平均情况:N/2次找到。
在实际中一般情况关注的是算法最坏运行情况,所以数组中搜索数据的时间复杂度为O(N)。
void Fun2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; k++)
{
count++;
}
int M = 10;
while (M--)
{
count++;
}
printf("%d\n", count);
}
计算Fun2的时间复杂度?
基本操作执行了2*N+10次,通过推到大O阶方法可知道,时间复杂度为O(N)。
void Fun3(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);
}
计算Fun3的时间复杂度?
基本操作执行了M+N次,有两个未知数M和N,所以时间复杂度为O(N+M)。
void Fun4(int N)
{
int count = 0;
for (int k = 0; k < 100; k++)
{
count++;
}
printf("%d\n", count);
}
计算Fun4的时间复杂度?
基本操作执行了100次,通过推到大O阶方法,时间复杂度为O(1)。
const char* strchr(const char* str, int character);
计算strchr函数的时间复杂度?———— strchr功能是在字符串中查找某个字符
基本操作执行最好为1次,最坏为N次,取最坏的,时间复杂度为O(N)。
void BubbleSort(int* a, int n)
{
assert(a);
for (int end = n; end > 0; end--)
{
int flag = 0;
for (int i = 1; i < end; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
flag = 1;
}
}
if (flag == 0)
break;
}
}
计算冒泡排序的时间复杂度?
基本操作执行最好为N次,最坏遍历 N,N-1,N-2......2,1。为等差数列求和为(N*(N+1))/2次,取最坏的,即时间复杂度为O(N^2)。
void BinarySearch(int* a, int n, int x)
{
assert(a);
int left = 0;
int right = n - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (a[mid] < x)
{
left = mid + 1;
}
else if (a[mid] > x)
{
right = mid - 1;
}
else
return mid;
}
//找不到
return -1;
}
计算二分查找的时间复杂度?
我们来画个图:
基本操作执行最好1次,最坏为O(logN)次,时间复杂度为O(logN)。即(lgN)。
long long Fac(int N)
{
if (0 == N)
{
return 1;
}
return Fac(N - 1) * N;
}
计算阶乘递归Fac的时间复杂度?
基本操作递归了N次,时间复杂度为O(N)。
long long Fib(int N)
{
if (N < 3)
return 1;
else
return Fib(N - 1) + Fib(N - 2);
}
计算斐波那契递归Fib的时间复杂度?
画个递归的图:
可以看出,基本操作递归了2^N次,时间复杂度为O(2^N)。
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。
空间复杂度不是程序占用了多少bytes的空间,因为这个没有太大的意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则跟时间复杂度类型,也是用大O渐进表示法。
注:函数运行时所需要的栈空间(存储参数,局部变量,一些寄存器的信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定。
void BubbleSort(int* a, int n)
{
assert(a);
for (int end = n; end > 0; end--)
{
int flag = 0;
for (int i = 1; i < end; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
flag = 1;
}
}
if (flag == 0)
break;
}
}
计算冒泡排序的空间复杂度?
使用了常数个的额外空间,空间复杂度为O(1)。
long long* Fib(int n)
{
if (n == 0)
return NULL;
long long* fib = (long long*)malloc((n + 1) * sizeof(long long));
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i <= n; i++)
{
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib;//返回fib的前n项
}
计算Fib的空间复杂度?
动态开辟了N个空间,空间复杂度为O(N)。
long long Fac(int N)
{
if (N == 0)
return 1;
else
return Fac(N - 1) * N;
}
计算阶乘递归Fac的空间复杂度?
递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间,空间复杂度为O(N)。
一般算法常见的复杂度如下:
希望这篇接近5000字的博客能让你深刻了解时间复杂度于空间复杂度,再一次感谢各位大佬的阅读,创作不易,给个三联吧!!!