发布时间:2024-11-29 14:01
在学习具体的数据结构和算法之前,每一位初学者都要掌握一个技能,即善于运用时间复杂度和空间复杂度来衡量一个算法的运行效率。
通过算法所编写出的程序的运行效率。程序的运行效率具体可以从 2 个方面衡量,分别为:
数据结构中,用时间复杂度来衡量程序运行时间的多少;用空间复杂度来衡量程序运行所需内存空间的大小。
判断一个算法所编程序运行时间的多少,并不是将程序编写出来,通过在计算机上运行所消耗的时间来度量。原因很简单,一方面,解决一个问题的算法可能有很多种,一一实现的工作量无疑是巨大的,得不偿失;另一方面,不同计算机的软、硬件环境不同,即便使用同一台计算机,不同时间段其系统环境也不相同,程序的运行时间很可能会受影响,严重时甚至会导致误判。
实际场景中,我们更喜欢用一个估值来表示算法所编程序的运行时间。所谓估值,即估计的、并不准确的值。注意,虽然估值无法准确的表示算法所编程序的运行时间,但它的得来并非凭空揣测,需要经过缜密的计算后才能得出。
也就是说,表示一个算法所编程序运行时间的多少,用的并不是准确值(事实上也无法得出),而是根据合理方法得到的预估值。
那么,如何预估一个算法所编程序的运行时间呢?
很简单,先分别计算程序中每条语句的执行次数,然后用总的执行次数间接表示程序的运行时间。
大 O 记法的表示方法也很简单,格式如下:
O(频度)
其中,这里的频度为最简之后所得的频度。
例题1):
for(int i = 0 ; i < n ; i++) //<- 从 0 到 n-1,执行n次
{
a++;
}
看循环次数,循环n次,所以程序的时间复杂度为 O(n);
例题2):
for(int i = 0 ; i < n ; i++)
{
for(int j = 0 ; j < m ; j++)
{
num++;
}
}
外层循环n,内层循环m次,要知道,当 n、m 都无限大时,我们完全就可以认为 n==m。
程序的时间复杂度为 O(n^2)。
例题3):
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;
}
}
时间复杂度为N^2+2*N+M;
事实上,对于一个算法(或者一段程序)来说,其最简频度往往就是最深层次的循环结构中某一条语句的执行次数。例如 2n+1 最简为 n,实际上就是 a++ 语句的执行次数;同样 2n2+2n+1 简化为 n2,实际上就是最内层循环中 num++ 语句的执行次数。
简写程序时间复杂度为O(N^2);
例题5):
void Func(int N)
{
int count = 0;
for (int k = 0; k < 100; k++)
{
count++;
}
}
只要是常数次执行,时间复杂度统统都是O(1);
例题6):
//二分查找
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;
}
最糟糕的情况就是没找到目标,总数N个,每次查找数量减少一半
可以表示为 :N/2/2/2/……=1;
N=1*2*2*2*2*……;
2^x=N;
x=log2N
所以时间复杂度为O(log2N);
例题7):
//递归斐波那契数
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
时间复杂度为,2^0+2^1+2^2+2^3+……+2^N
简写程序的时间复杂的为 :O(2^N)
O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n2)平方阶 < O(n3)(立方阶) < O(2n) (指数阶)
和时间复杂度类似,一个算法的空间复杂度,也常用大 O 记法表示。
要知道每一个算法所编写的程序,运行过程中都需要占用大小不等的存储空间,例如:
首先,程序自身所占用的存储空间取决于其包含的代码量,如果要压缩这部分存储空间,就要求我们在实现功能的同时,尽可能编写足够短的代码。
程序运行过程中输入输出的数据,往往由要解决的问题而定,即便所用算法不同,程序输入输出所占用的存储空间也是相近的。
事实上,对算法的空间复杂度影响最大的,往往是程序运行过程中所申请的临时存储空间。不同的算法所编写出的程序,其运行时申请的临时存储空间通常会有较大不同。
例题:
void BubbleSort(int* a, int n)
{
assert(a);
for (int end = n; end > 0; end++)
{
int exchange = 0;
for (int i = 1; i < end; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
上面程序实在原来位置上交换数据,没有开辟新的空间,所以程序的空间复杂度为O(1);
注意:在多数场景中,一个好的算法往往更注重的是时间复杂度的比较,而空间复杂度只要在一个合理的范围内就可以。
【C++碎碎念】C++11新特性(声明、智能指针、右值引用、lambda表达式)
从零到一学习计算机视觉:朋友圈爆款背后的计算机视觉技术与应用 | 公开课笔记...
GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
python爬虫如何更换ip_Python爬虫被封IP,怎么换ip?
3.Flink-On-Yarn开发使用\原理\Session会话模式\Per-Job模式
上传android程序到git,Android 进阶之旅 | git 上传代码到远程仓库
面试突击49:说一下 JUC 中的 Exchange 交换器?