目录
算法效率
算法效率通常涉及两个主要方面:时间效率和空间效率,它们分别对应于算法运行时间的长短和占用内存的多少。一个高效的算法能够在最小的资源消耗下完成任务。效率的提高往往依赖于算法设计中的创新,如使用更优的数据结构、减少不必要的计算、或通过并行处理来加速算法运行。
在评估算法效率时,我们关注几个关键指标:
-
最坏情况复杂度:确保算法在任何情况下都不会表现得太差。
-
平均情况复杂度:提供一般情况下算法的性能预期。
-
最佳情况复杂度:在理想情况下算法的性能。
通过这几个关键指标,能帮助我们更好改善我们的代码,使用更加优质的数据结构类型。
大O渐进表示法
大O渐进表示法规则:
- 所有常数都用1来表示
- 只保留最高阶项
- 如果最高阶项存在且不为1,去掉其系数,保留其最高阶项即可,如2logN,写成logN
三个法则:
- 添加法则:如果一个算法由两个步骤组成,其总复杂度为各个步骤复杂度的最大值。
- 乘法法则:如果一个算法步骤嵌套另一个,其总复杂度为各个步骤复杂度的乘积。
- 多项式和非多项式复杂度:识别算法是否具有多项式时间复杂度(如O(n), O(n^2))或非多项式时间复杂度(如O(2^n))对于理解算法的实用性至关重要。
接下来会用详细的例子来增加对大O渐进表示法的认识。
时间复杂度
时间复杂度是一种计算机科学术语,用于描述一个算法在执行过程中所需时间的量度。它是评价算法效率的关键指标之一,表示随着输入数据量的增加,算法执行时间的增长速度。时间复杂度提供了一种估计算法运行时间的方法,并不直接测量秒数,而是关注操作数量的增长率。
表达方式:
- O(1):常数时间复杂度,意味着算法的执行时间不随输入数据的大小变化而变化。
- O(n):线性时间复杂度,算法的执行时间与输入数据的大小成正比。
- O(n^2):二次时间复杂度,算法的执行时间与输入数据大小的平方成正比。
- O(log n):对数时间复杂度,算法的执行时间与输入数据大小的对数成正比,通常见于二分搜索等算法。
- O(n log n):线性对数时间复杂度,典型的如快速排序和归并排序等排序算法。
- O(2^n):指数时间复杂度,算法的执行时间随输入数据的大小呈指数方式增长,常见于某些递归算法。
例1
//计算Mat1函数的时间复杂度
void Mat1(int N)
{
int count = 0;
for (int i = 0; i < 100; i++)
{
++count;
}
printf("%d\n", count);
}
Mat1函数循环了100次,O(n)应为100,但是常数我们都用1表示
最终的时间复杂度为:O(1)。
例2
//计算Mat2函数的时间复杂度
void Mat2(int N)
{
int count = 0;
for (int i = 0; i < 2 * N; i++)
{
for (int j = 0; j < 2 * N; j++)
{
count++;
}
}
for (int m = 0; m < 2 * N; m++)
{
count++;
}
}
Mat函数用了一个嵌套for循环(O(n)=2*N*2*N=4*N^2)加上一个单独for循环(O(n)=2*N),总的O(n)=4*N^2+2*N
但是需要保留最高阶项,并去除系数,最终的时间复杂度为:O(N^2)。
例3
//计算二分查找函数的时间复杂度
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 (x > a[mid])
begin = mid + 1;
else if (x < a[mid])
end = mid - 1;
else
return mid;
}
return -1;
}
使用二分法查找,每次找不到要找的数字,都会将数据除以2,不断除以2筛选,直到所选数据被筛选剩了1,那我们的查找次数就while循环的次数
while循环了多少次呢?每次都除以2,直到剩了1,使X/2/2/2/2......=1,那不就是N=2^x,x=logN
所以最终的时间复杂度为:O(logN)
注意:logN都是以 2为底的log
例4
//计算斐波那契函数的时间复杂度
long long Fibonacci(int n)
{
if (n == 0 || n == 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}//递归
要知道第N个数的大小,就必须知道N-1和N-2的大小,不断递归,直到2=1+1;可以看出共有N层,最高层需要调用到两个函数,第二层要调用四个函数........直到最后一层才调用结束,加上使用该函数的一层,那不就是2的一个等比数列吗
得出来的结果就是2^N-1
那我们最终的时间复杂度为:O(2^N)。
空间复杂度
空间复杂度是在计算机科学中用来描述一个算法在执行过程中所需存储空间的量度。它包括算法程序本身占用的空间、输入输出数据占用的空间以及算法执行过程中临时占用的额外空间。空间复杂度是衡量算法资源消耗的一个重要指标,尤其在内存受限的应用场景中尤为重要。
表达方式:
- O(1):常数空间复杂度,意味着算法所需的额外空间不随输入数据的大小变化而变化,只需要固定的存储空间。
- O(n):线性空间复杂度,算法所需的额外空间随输入数据的大小线性增长。这种情况常见于需要存储等量于输入数据的辅助数据结构的情况。
- O(n^2):二次空间复杂度,如果算法需要一个二维数组大小与输入规模n相关,那么其空间复杂度就是O(n^2)。
例1
//计算冒泡排序的空间复杂度
void BubbleArray(int* a, int n)
{
for (int i = 0; i < n; i++)
{
for (int t = 0; t < n - i-1; t++)
{
if (a[t] > a[t + 1])
{
Swap(&a[t], &a[t + 1]);
}
}
}
}
冒泡排序函数中使用了常数个额外空间(即常数个变量),所以用大O的渐进表示法表示冒泡排序函数的空间复杂度为O(1) 。
例2
//计算阶乘递归函数的空间复杂度
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N - 1)*N;
}
递归函数每次都会开辟空间,这时的算法所需空间随着数据的大小线性增长,所以空间复杂度为O(N)。
主要是看递归的深度。