时间复杂度例题
前言
本来我是想写一些概念性的东西的,但是想了半天无从下手。
于是我就想了想我之前是怎么学这块的,才决定还是出些例题好。
个人判断时间复杂度的方法
在我看来时间复杂度的话,就两种题型,一种是循环体里的,另一种是递归的。
对于循环体的
就只要盯着最内部循环体里的一条语句,看这条语句执行了多少次就形。为什么是最内部的循环体呢?因为有的题里循环是嵌套的,这样的话只能是看最里面那层循环体里的语句。
为什么是一条语句呢?因为最内层循环体语句执行的次数都是一样的,盯着一条语句就够了。
然后O()里的数是计最高阶次的。
下面的这些阶由上到下阶次由低到高。
前四个例题没啥好讲的,就是普通的循环。就讲一下后三个。
例题
例题1:O(N^2)
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{
++count;//N^2
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
++count;//2*N
}
int M = 10;
while (M--)
{
++count;//10
}
printf("%d\n", count);
}
上面的最高阶的是N^2,所以复杂度是O(N^2)的。
例题2:O(N)
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
例题3:O(M+N)
void Func3(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);
}
上面的两个参数没法判断谁大谁小,所以是M+N
例题4:O(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;
}
}
例题5:O(logN)
//这是二分查找
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;
}
二分查找是经典的logN。
这个算法每次都能去掉一半的数据,直到找到为止。假设我们现在要找一个数,找到的时候找了T次,那么假设整个数组的元素个数是N,第一次找,没找到,剩余的元素个数就是N/2,再找没找到,剩余的元素个数就是(N/2)/2,再继续找…这样一共除了T次2,就找到了那个数,找到的时候剩余的元素个数就是1,那么就是N/(2^T) = 1,这样算一下就是T = logN,所以这个时间复杂度就是logN的。
对于递归的
盯着看返回值
如果返回值的类型是参数/常数乘以/加函数的,那就是O(N)。例如递归实现阶乘
如果返回值是函数*函数或者函数加函数这种,那么就是O(2^N)。例如递归实现斐波那契数列,是类似于二叉树的。
例题6:O(N)
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
这个函数每次调用,只要参数不等于零,就会调用Fac(N-1),一直往下走,直到N等于0了就停止。
简单看一下图解:
这个调用是呈线性的,一直是1+1+1+1…+1+1,所以就是O(N)。
例题7:O(N^2)
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
这个也是看下图解:
空间复杂度的话是类似的,会了时间就会空间了。
结束。