时间复杂度
定义
一个函数,该函数计算的是执行基本操作的次数
表示方法 —- O(N)
一般用 O(N)来表示算法的时间复杂度,N为函数中增长速率最快的表达式
一般算法 O(N) 计算方法
忽略常数项,时间复杂度为O(1)
取最坏情况的时间复杂度和增长最快的表达式
递归算法的时间复杂度:递归总次数*每次递归次数
几个例子
例1:
int mul(int n)
{
if (n < 2)
return 1;
else
return n*mul(n - 1);
}
int main()
{
printf("%d\n", mul(1));
printf("%d\n", mul(10));
return 0;
}
分析:
此程序是用递归方法实现 n 的阶乘,根据前面说的计算方法,当 n<2 时,n 为常数,故时间复杂度为O(1),当 n>=2 时,开始进行递归,每次递归时须乘(n-1)项,时间复杂度为 O(1),总的递归次数就为 n , 故根据计算方法可知时间复杂度为O(n)
例2:
void Test(int n)
{
int count = 0;
for (int i = 0; i < 10; i++)
{
count++;
}
for (int i = 0; i < 2 * n; i++)
{
count++;
}
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
count++;
}
}
}
分析:
此函数分为 3 个代码块,代码块1的基本循环次数为1,代码块2的循环次数为2*n,代码块 3 的循环次数为 n^2,故此函数的基本操作次数为 f(n)=n^2+2 *n +1;根据计算方法的前两条可知,此函数的时间复杂度为 O(n^2)
空间复杂度
定义
函数中创建对象的个数关于问题的表达式
表示方法 ——- O(N)
一般用大O渐进表示法来表示算法的空间复杂度,N 为函数中的对象个数
计算方法
忽略常数,用 O(1)表示
递归算法的空间复杂度=递归深度*每次递归所要辅助的空间
几个例子:
例1:
int Sum(int N)
{
int count = 0;
for(int i = 1; i <= N; ++i)
{
count += i;
}
return count;
}
分析:由第一个计算方法可得此函数的空间复杂度为O(1)
例2:
int* Merge(int* array1, int size1, int* array2, int size
2)
{
int index1 = 0, index2 = 0, index = 0;
int* temp= (int*)malloc(sizeof(int)*(size1+size2));
if(NULL == temp)
return NULL;
while(index1 < size1 && index2 < size2)
{
if(array1[index1] <= array2[index2])
temp[index++] = array1[index1];
else
temp[index++] = array2[index2];
}
while(index1<size1)
temp[index++] = array1[index1++];
while(index2 < size2)
temp[index++] = array2[index++];
return temp;
}
分析:
这个函数代码非常多,求此空间复杂度时,抓住计算方法,可以看到 int* temp= (int*)malloc(sizeof(int)*(size1+size2)); 这句代码明显告诉我们开辟了(size1+size2)的空间,即可储存的对象个数,即空间复杂度为O(size1+size2)
实例分析:
斐波那契数列的时间复杂度和空间复杂度
两段代码
代码 1:
long long fib(long n)
{
if (n < 3)
return 1;
return fib(n - 1) + fib(n - 2);
}
代码 2:
long long fib(long long n)
{
long long a = 1;
long long b= 1;
long long tmp = 1;
while (n > 2)
{
tmp = a + b;
a = b;
b = tmp;
n--;
}
return tmp;
}
时间复杂度:
代码 1 O(2^n)
代码 2 O(n)
分析:代码 1 是斐波那契数列的递归算法,根据计算规则,每次递归的次数为2,总的递归次数为2^n , 忽略常数项,此递归的时间复杂度为O(2^n)(如下图)
代码 2 是非递归算法,此算法的循环次数为n,即操作次数为n,故而时间复杂度为O(n)
空间复杂度:
代码1 O(n)
代码2 O(n)
分析:代码 1 递归的最大深度为n,故而创建的个数为n,则空间复杂度为O(n);代码 2 递归,n 为常数,故而空间复杂度为O(1)
折半查找的时间复杂度和空间复杂度
两段代码:
代码1:
int binary_search(int arr[], int len, int key)
{
int left = 0;
int right = len - 1;
int mid;
while (left <= right)
{
mid = left + ((right - left) >> 1);
if (arr[mid] == key)
{
return mid;
}
else if (arr[mid]>key)
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return -1;
}
代码2:
int binary_search(int arr[], int left,int right, int key)
{
while (left <= right)
{
int mid = left + ((right - left) >> 1);
if (arr[mid] == key)
return mid;
else if (key > arr[mid])
return binary_search(arr, mid + 1, right, key);
else
return binary_search(arr, left, mid - 1, key);
}
return -1;
}
时间复杂度:
代码 1 O(log 2 n)
代码2 O(log 2 n)
分析:折半查找的过程如下图所示,由此我们可知,代码 1 的循环次数为log 2 n ,故时间复杂度为O(log 2 n); 代码 2递归实现,递归的次数为 log 2 n ,故时间复杂度为 O(log 2 n)
空间复杂度:
代码1 O(1)
代码2 O(log2 n)
分析:代码 1 每次循环时对象个数都是常数,故而空间复杂度为O(1);代码2 递归的最大深度为log2 n ,故创建的对象个数也是log2 n,即空间复杂度为O(log2 n)