目录
前言
本文介绍数据结构的入门——时间复杂度。将会对时间复杂度进行剖析,通过例题讲解时间复杂度的应用范围和注意事项。
一、时间复杂度
概念:算法的时间复杂度是一个函数:(是数学含义上的函数)数据里面带未知数的函数式。
含义:算法在机器中运行所消耗的时间。
实际意义:算法中基本操作的执行次数。
作用和理解:降低占用内存和减少运行时间,提高效率。
好的算法相当于拿着筷子吃饭,差的算法相当于拿着竹竿吃饭,又长又麻烦。
二、大O表示法
大O符号(Big O notation):用于描述函数渐进行为的数学符号(描述时间复杂度)
推导大O阶方法:
1,用常数1取代运行次数中的所有加法常数;
2,在修改后的运行次数函数中,只保留最高阶项;
3,如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶;
含义:(保留最高阶的未知数,不保留常系数)。
三,实例介绍
例1:O(N^2)
找到某条基本语句与问题规模N之间的数学表达式,就是算出了算法的时间复杂度。
代码如下:
//请计算一下Func1中的++count语句总共执行了多少次? 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; } printf("%d\n",count);
Func1 执行的基本操作次数:F(N)= N^2+2*N+10 O(N^2)
- N =10 F(N) = 130
- N =100 F(N) =10210
- N = 1000 F(N) =1002010
- 随着N的变大,后两项的结果影响逐渐变小
- 当N无限大的时候,后两项对结果的影响可以忽略不计。
- 所以时间复杂度为O(N^2)。
例2:O(1)
代码如下:
void Func2(int N) { int count =0; for(int i =0;i<100;++i) { ++count; }
如果只是循环次数只是常数项的话者就直接是O(1)
例3:O(M +N)
代码如下:
void Func1(int N, int M) { int count =0; for(int i =0;i<N;++i) { ++count; } for(int k = 0; k < M; ++k) { ++count; } printf("%d\n",count);
虽然是不同的未知数,但是时间复杂度O只看阶数:所以为O(M)或O(N)
(重点)例4:O(N)
计算strchr的时间复杂度
const char* strchr(const char *str,int character);
whlie(*str)
{
if(*str==chaeacter)
return str;
else
++str;
}
return NULL;
此类时间复杂度是通过输入进去的数组的元素个数确定。
- 最好可以为1
- 最坏可以为N
- 但是时间复杂度一般取最坏的情况,所以此题的搜索数组数据的时间复杂度为O(N)
- 有些算法的时间复杂度存在最好,平均,最坏情况:
- 最坏情况:输入任意规模的最大运行次数(次数上限)
- 平均情况:输入任意期望的运行次数
- 最好情况:输入任意规模的最小运行次数(次数下限)
例5:冒泡排序( O(N^2) )
int Fib(int *a, int size_a) { for (int i =0;i<size_a -1;i++) { for (int j = 0; j< size_a -1 -i;j++) { int temp; if( a[j] < a[j+1] ) { temp = a[j]; a[j] = a[j+1] a[j+1] = temp; } } } }
例6:二分法查找(O(log2N))
我们要准确分析算法时间的复杂度,一定要去看思想,不能只去看程序是几层循环。
int BinarySearch(int*a, int n, int x) { assert(a); //排序 int begin =0; int end =n; 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(循环次数)X(查找次数)
通常运算级的大小在初始基数特别大的时候比较有优势。
例7:
(1)递归法(阶乘)O(N) | O(1)
时间复杂度计算·
1,每次函数调用为O(1),那么就看他的递归次数
2,每次函数调用不是O(1),那么就看他的递归调用中次数的累加
long long Fac (size_t N)
{
if(0 ==N)
return 1;
return Fac(N-1)*N;
}
(2)递归法(斐波那契数列)( O(2^N))
long long Fib(size_t N) { if(N < 3) return 1; return Fib(N-1) +Fib(N-2); }
递归法算斐波那契数列是比较占用内存的一种方法,运行次数随着设定值呈指数性增长。
总结
1.本次讲了时间复杂度的基本认识。
通过对7例不同的时间复杂度的分析,我们可以得出以下结论:
(1)时间复杂度不局限与循环有关,核心是与算法思想有关。
(2)时间复杂度主要取其最坏循环次数。
(3)通过对时间复杂度的认识,能更好的对我们的程序进行优化。