算法的复杂度

来源:互联网 发布:广通软件怎么样 编辑:程序博客网 时间:2024/06/05 19:17

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

一、时间复杂度
时间复杂度是指当前问题的规模以某种单位从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)指数型。

原创粉丝点击