算法的复杂度

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值