时间复杂度和空间复杂度的理解

来源:互联网 发布:ds数据精灵注册机 编辑:程序博客网 时间:2024/05/21 12:44

一个算法的时间复杂度其实就是这个算法跑过的次数
eg:

void test(){     int i = 0;    int j = 0;    int k = 0;    int count = 0;    int count1 = 0;    for(i;i<n;i++)     {           for(j;j<n;j++)        {                count++;        }      }    for(k;j<2*n;k++)    {        count1++;    }} 

这个算法执行的总次数f(n)=n^2 + 2*n;
简单来说时间复杂度就是这个函数执行的基本操作次数
你是不是会想时间复杂度为什么不用时间来衡量,而是用基本操作次数,那是因为计算机处理的速率太快,有的函数你可能用时间很难衡量。

我们一般衡量一个算法的好坏是看它的最坏情况即:任意输入N时算法运行的最大次数,因为这是它的运行时间上界

时间复杂度一般用大O渐进表示法来计算
O(f(n))为时间复杂度的O渐进表示法,它的计算方法是:
1.O(1)表示运行时间中的所有的加法常数
2. 在的运行次数函数中,只保留最高阶项 eg :f(n)=n*n+2* n+1
则保留n*n项,即函数增长速率最快的
3.最高阶项系数不保留直接置1
4.递归算法的时间复杂度:递归次数*每次递归进行的基本操作次数
eg:

int fun(int n){     if(n <= 1 )    {        return 1;    }    else    {        return n*fun(n-1);    }}时间复杂度为:f(n)=n*1=n次;递归次数*每次递归进行的基本操作次数

按数量级递增排列:
常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n^2),立方阶O(n^3),…, k次方阶O(n^k),指数阶O(2^n)
就是函数递增的越快消耗的时间越多

一个算法的空间复杂度定义为该算法所耗费的存储空间
储存空间的消耗是由函数中创建对象的个数决定
即空间复杂度是看函数中创建对象的个数


空间复杂度计算规则:
1.个数为常数用O(1)表示
2.递归空间复杂度:递归的次数*每次递归所需要的辅助空间
空间复杂度一般用大O渐进法来表示
eg:

    int Sum(int N)    {        int count = 0;        for(int i = 1; i <= N; ++i)        count += i;        return count;    }    空间复杂度为:O(1)
int fun(int n){     int num = 1;    if(n <= num )    {        return 1;    }    else    {        return n*fun(n-1);    }}递归空间复杂度:O(n)=n*1=n;递归次数为n次,每次进入创建1个对象

实例解析
二分查找算法递归

int recursion_binary_search(int *arr,int num,int left,int right){    int left = 0;    int right = length - 1;    int mid = 0;    if(left <= right)    {        mid = left +(right - left)>>1;        if(arr[mid] > num)        {            binary_search(*arr,num,left,mid - 1);        }        else if(arr[mid] < num)        {            binary_search(*arr,num,mid + 1,right);        }        else        {            return mid;        }    }    return -1;}

这里写图片描述
因此时间复杂度为O(n)=log(2^n)*常数=log(2^n);
空间复杂度为O(n)=log(2^n)*常数=log(2^n);


二分查找非算法递归

int binary_search(int *arr,int num,int length){    int left = 0;    int right = length - 1;    int mid = 0;    while(left<=right)    {        mid = left +(right - left)>>1;        if(arr[mid] > num)        {            right = mid - 1 ;        }        else if(arr[mid] < num)        {            left = mid + 1;        }        else        {            return mid;        }    }    return -1;}时间复杂度为O(n)=log(2^n)*常数=log(2^n);空间复杂度为O(1),函数中创建的对象为常数项;

斐波那契数列非递归

int FiboSequ(int n)//斐波那契数列从1开始1 1 2 3 5 ...{    int First = 1;    int Second = 1;    int ret = 0;    if(n<3)    {        return 1;    }    while(n > 2)    {         ret = First + Second;         First = Second;         Second = ret;         n--;    }    return ret;}时间复杂度:O(n)=n,运行次数为n次;空间复杂的;O(n)=O(1),创建对象都为常数项

斐波那契数列递归

int FiboSequRecu(int n)//斐波那契数列从1开始1 1 2 3 5 ...{    int First = 1;    int Second = 1;    int ret = 0;    if(n<3)    {        return 1;    }    else    {        return FiboSequRecu(n - 1) + FiboSequRecu(n - 2);    }}时间复杂度:O(n)=递归次数(n-1)*基本操作1=n;空间复杂度:O(n)=递归次数(n-1)*创建对象个数为常数 = n

上面递归方式的斐波那契数列效率非常低,那么我们就应该想办法优化,要么用非递归方式,要么用下面尾递归方式实现。

long long Fib(long long first, long long second, long N){    if(N < 3)    {        return 1;    }    if(3 == N)    {        return first+second;        return Fib(second, first+second, N-1);    }}这里的long long 类型是在当你递归很大值时防止内存不够而溢出,尾递归就是在递归函数中,递归调用返回的结果被直接返回递归方式总是以二叉树一样在栈上以指数形式增长,耗费时间大,尾递归方式以类似于循环方式以线性增长,在尾递归时,调用自身的同时直接计算参数。

总结:尾递归的本质是:将每次计算的结果缓存起来,传递给下次调用,相当于自动累积,节省了计算时间
并且在有的编译器中通常都会对尾递归进行优化。编译器会发现根本没有必要存储栈信息了,因而会在函数尾直接清空相关的栈。