算法的复杂度详解编程语言

如何衡量一个算法的好与坏?
答案是:用时间复杂度和空间复杂度来衡量算法的好与坏。

一、时间复杂度
时间复杂度是指当前问题的规模以某种单位从1增加到n时,解决这个问题的算法在执行时所耗费的时间也以某种单位由1增加到T(n),就称此算法的时间复杂度为T(n)。

1、分析方法:
(1)时间复杂度就是函数中基本操作所执行的次数。
(2)一般默认的是最坏时间复杂度,即分析最坏情况下所能执行的次数。
(3)忽略掉常数项及系数,保留对增长因子影响最大的一部分。
(4)关注运行时间的增长趋势,关注函数式中增长最快的表达式,忽略系数。
(5)计算时间复杂度是估算随着n的增长函数执行次数的增长趋势。
(6)递归算法的时间复杂度为:递归总次数 * 每次递归中基本操作所执行的次数。

注意:时间复杂度实际就是一个函数,该函数计算的是执行基本操作的次数。

2、算法的情况分类:
(1)最坏情况:任意输入规模的最大运行次数。(上界)
(2)平均情况:任意输入规模的期望运行次数。
(3)最好情况:任意输入规模的最小运行次数,通常最好情况不会出现。(下界)

注意:在实际中,通常考量的是最坏情况。理由如下:
1)一个算法的最坏情况的运行时间是在任意输入下运行时间的上界。
2. 对于某些算法,最坏的情况出现的较为频繁。
3. 大体上看,平均情况与最坏情况一样差。

二、空间复杂度
空间复杂度是指当前问题的规模以某种单位从1增加到n时,解决这个问题的算法在执行时所占用的存储空间也以某种单位由1增加到S(n),就称此算法的空间复杂度为S(n)。
也就是说:算法的空间复杂度并不是计算实际占用的空间,而是计算整个算法的辅助空间单元的个数,与问题的规模没有关系。算法的空间复杂度S(n)定义为该算法所耗费空间的数量级。

(1)若算法执行时所需要的辅助空间相对于输入数据量n而言是一个常数(S(n)=O(f(n))),则称这个算法的辅助空间为O(1)。
(2)递归算法的空间复杂度计算:递归深度*每次递归所创建的对象个数(辅助空间)。如果每次递归所需的辅助空间是常数,则递归的空间复杂度是 O(N)。

三、算法复杂度的计算
1、递归算法的时间复杂度计算:
递归算法的时间复杂度=递归总次数*每次递归次数。
2、递归算法的空间复杂度计算:
递归算法的空间复杂度=递归深度*每次递归所创建的对象个数。
【例一】
(1)计算折半查找的复杂度(非递归)

# include<stdio.h> 
# include<stdlib.h> 
 
int BinerySearch(int *arr, int key, int len) 
{ 
    int left = 0; 
    int right = len - 1; 
    while (left <= right) 
    { 
        int mid = left + (right - left) >> 1; 
        if (key < arr[mid]) 
        { 
            return right = mid - 1; 
        } 
        else if (key > arr[mid]) 
        { 
            return left = mid + 1; 
        } 
        else 
        { 
            return mid; 
        } 
    } 
    return -1; 
} 
 
int main() 
{ 
    int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; 
    int len = sizeof(arr) / sizeof(arr[0]); 
    int ret = BinerySearch(arr, 6, len); 
    if (ret) 
    { 
        printf("找到了!/n"); 
    } 
    else 
    { 
        printf("没找到!/n"); 
    } 
    system("pause"); 
    return 0; 
}

查找过程如下:
这里写图片描述
由上图分析可得出规律:
如果程序要执行M次,那么N/(2^M)=1,则M=log2N(2为底数,N为对数)。因为在最坏的情况下,二分查找需要查找的次数为log2N(2为底数,N为对数)次,即执行的次数,所以二分查找的时间复杂度为O(log2N)。因为在整个程序中,新创建变量个数为len是常数级的,所以二分查找的空间复杂度即就是O(1)。

(2)计算折半查找的复杂度(递归)

# include<stdio.h> 
# include<stdlib.h> 
 
int count = 0; 
int BinarySearch(int arr[], int left, int right, int key) 
{ 
    int mid = left + ((right - left) >> 1); 
    if (left > right)//递归出口 
    { 
        return -1; 
    } 
 
    if (key < arr[mid]) 
    { 
        right = mid - 1; 
        BinarySearch(arr, left, right, key); 
    } 
    else if (key > arr[mid]) 
    { 
        left = mid + 1; 
        BinarySearch(arr, left, right, key); 
    } 
    else 
    { 
        return mid; 
    } 
    count++; 
} 
 
int main() 
{ 
    int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; 
    int ret = BinarySearch(arr, 0, 8, 1); 
    if (ret == -1) 
    { 
        printf("没有找到!/n"); 
    } 
    else 
    { 
        printf("找到了!/n"); 
    } 
    printf("%d/n", ret); 
    system("pause"); 
    return 0; 
}

查找过程示意如下:
这里写图片描述
对上图分析可得:
1)假设递归的总次数为M,那么递归的深度也是M,则时间复杂度为O(log2 N)//2为底数,N为对数。
2)因为递归的空间复杂度=递归深度每次创建对象的个数,又因为每次创建对象所需要的辅助空间都是常数级别的,所以递归的空间复杂度=M常数=log2 N*常数,
则空间复杂度为O(log2 N )。

【例2】斐波那契数列的时间复杂度和空间复杂度
斐波那契数列的定义如下:
这里写图片描述

方法一:时间复杂度为:O(2^N);空间复杂度为:O(N)。

#include<stdio.h> 
#include<stdlib.h> 
 
long long Fibonacci1(int n) 
{ 
return n < 2 ? n : Fibonacci1(n - 1) + Fibonacci1(n - 2); 
} 
 
int main() 
{ 
long long ret = Fibonacci1(4); 
printf("%ld/n", ret); 
system("pause"); 
return 0;  
}

运行结果为:
这里写图片描述
其图示如下:
这里写图片描述
1)从图中可看出,这是非满二叉树。因为时间复杂度的计算是在最坏情况下考量的。所以以满二叉树来计算时间复杂度,即递归的总次数为总节点的个数:2^h-1=2^(N-1)-1,其中,h为递归深度,N为层数(在本例中N为4),则时间复杂度为O(2^N)。
2)因为递归算法的空间复杂度=递归深度*每次递归所创建的对象个数,其中递归深度为h=N-1,每次递归所创建的对象个数为2,则递归算法的空间复杂度=(N-1)*2=O(N)。

方法二(非递归-升级):时间复杂度为:O(n);空间复杂度为:O(n)。

long long *Fibonacci2(int n) 
{ 
long long *array = new long long [n + 1]; 
array[0] = 0;//注意 
array[1] = 1; 
for (int i = 2; i < n + 1; i++) 
{ 
array[i] = array[i - 1] + array[i - 2]; 
} 
return array; 
}

释:因为循环的基本操作次数是:n+1-2=n+1,辅助空间是:n+1,所以:
1)非递归算法的时间复杂度为:O(n);
2)非递归算法的空间复杂度为:O(n)。

方法三(非递归-再升级):时间复杂度为:O(n);空间复杂度为:O(l)。

#include<stdio.h> 
#include<stdlib.h> 
long long Fibonacci2(int n) 
{ 
    if (n == 1 || n == 2) 
    { 
        return n; 
    } 
 
    int first = 1; 
    int second = 1; 
    long long ret = 0; 
    for (int i = 2; i < n; i++) 
    { 
        ret = first + second; 
        first = second; 
        second = ret; 
    } 
    return ret; 
} 
 
int main() 
{ 
    long long ret = Fibonacci2(16); 
    printf("%ld/n", ret); 
    system("pause"); 
    return 0;  
}

释:因为循环的基本次数是:n-2+1=n-1,所用的辅助空间是:常数级别的,所以:
1)非递归算法的时间复杂度为:O(n);
2)非递归算法的空间复杂度为:O(l)。

四、时间复杂度的种类
1、常用的时间复杂度有以下七种,如下图所示:
这里写图片描述

2、复杂度依次增加:
O(1)常数型 < O(log2 n)对数型 < O(n)线性型 < O(nlog2n)二维型 < O(n^2)平方型 < O(n^3)立方型 < O(2^n)指数型。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/18199.html

(0)
上一篇 2021年7月19日 20:56
下一篇 2021年7月19日 20:56

相关推荐

发表回复

登录后才能评论