请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{ 这里是N^2
++count;
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
++count; 这里是2*N
}
int M = 10;
while (M--)
{ 这里是10次
++count;
}
经过计算我们可以得出:是F(N)=N^2+2*N+10
其实,对于求复杂度,我们一般使用大O的渐进表示法,,现在来介绍下:
大O的渐进表示法
1.大O符号:是用于描述函数渐进行为的数学符号
2.规则方法:
1)用常数1取代运行时间中的所有加法常数。
2)、在修改后的运行次数函数中,只保留最高阶项。
3)、如果最高阶项存在且不是1 ,则去除与这个项目相乘的常数。
最后得到的结果就是大O阶
eg:N(10000000000000)这应该写成什么呢?
答案:仍然是O(1).
有人会问为啥它这个数那么大了,还是用O(1)表示呢?
现在我们来用形象的例子来解释下:
假如那里有座泰山,我们要铲平,
不同视角:会不同
我们人的视角觉得它非常大,
但是放到太阳系里面,就显得非常小了。
同样,对于编程也是一样的道理。
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到最坏情况:N次找到平均情况:N/2次找到
所以,我们实际中一般关注的是最坏的情况。
原因:同样是以一个形象的例子来解释
约会的预期管理
情人节约会可能到时的时间
最好:17:00
平均:18:00
最坏:19:00
那么因为只有30%的机会准时到
所以是不是选择说19:00时最好,把预期拉到最低。
常见时间复杂度计算举例
cs复制代码
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{ 这里是2*N次
++count;
}
int M = 10;
while (M--) 这里是10次
{
++count;
}
printf("%d\n", count);
}
所以,函数是不是就是:F(N)=2*N+10
根据大O法:得时间复杂度就是O(N)
例子2:
cs复制代码
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count; 这里是M次
}
for (int k = 0; k < N ; ++ k)
{ 这里是N次
++count;
}
printf("%d\n", count);
}
函数式:F(N)=M+N
注意:这里的M,N都是未知数,并不是具体数
所以根据大O法:时间复杂度O(M+N)
例子3:
cs复制代码
// 计算Func4的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{ 这里是100次
++count;
}
printf("%d\n", count);
}
函数式:F(N)=100
根据大O法:由于它是常数嘛,所以用1代替
所以时间复杂度:O(1).
例子4:
cs复制代码
const char * strchr ( const char * str, int character );
对于指针,如果没有具体说,一般默认都是O(N)
例子5:
cs复制代码
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{ 一般没说的话,默认N次
int exchange = 0;
for (size_t i = 1; i < end; ++i) 因为两个for循环,所以是N^2次
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0) 因为这里有了判断,它可以改变最好的情况
break; 如果没有这句,
} 最好也是O(N^2)
因为它无法知道它已经排序了
}
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是数组个数,x是最坏查找次数
N/2/2/2/2.../2=1
折半查找多少次就除多少个2
假设是x次
2^x=N
x=log N
这里我们先来说明一下:
接着,我们来看一下二分查找的显著优势:
对比:
N 1000 100W 10亿 2^10=1024
O(N) 1000 100W 10亿 2^20=1024*1024
O(log N) 10 20 30 2^30=1024*1024*1024
我们可以看到这二分查找极大改变它的时间效率。
但是一个很不友好的一点是,它仅仅局限于排序好的数,所以我们以后也不太实用
例子6:
cs复制代码
计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
这是不是相当于有N+1次,O(N+1)
根据大O法:时间复杂度:O(N)
Fac(N)
Fac(N-1)
Fac(N-2)
....
Fac(1)
Fac(0)
例子七:
cs复制代码
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
我们上面说了空间复杂度算的是变量的个数
我们看到了上面用了三个变量:end,exchange,i。 -->O(3)
根据大O法:O(1).
例子2:绝大多数空间复杂度都是O(1)或O(N)
cs复制代码
计算Fibonacci的空间复杂度?
返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{
if(n==0)
return NULL;
long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
fibArray[0] = 0; 为什么要多加1,因为数组从0开始,
fibArray[1] = 1;
for (int i = 2; i <= n ; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
上面创建了n+1个空间
所以根据大O法:O(N)
例子3:
cs复制代码
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}