数据结构:时间复杂度和空间复杂度

我们知道,同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。那么一个算法的好坏又由很多因素决定和影响,那么我们怎么衡量一个算法的好坏呢?这里我们引出来算法复杂度的概念。算法复杂度又分为时间复杂度空间复杂度。下面我们就一一来详细看下时间复杂度和空间复杂度。

时间复杂度

首先我们看下官方是怎么定义时间复杂度的:算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况。那我们自己通俗一点的解释就是时间复杂度其实就是一个函数,这个函数的功能是什么呢?是计算执行基本操作的次数。也就是程序中哪些操作或语句在大量执行,将这些操作认为是整个程序中占用的时间的大小。并且要知道时间复杂度是一个趋势,不是精确的数值,只是粗略数量级
还是用例子能更好的说明问题,我们看下面这个例子:

void Test(int n)
{
    int iCount = 0;
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < 0; ++j)
        {
            iCount++;
        }
    }

    for (int k = 0; k < 2 * n; ++k)
    {
        iCount++;
    }

    int count = 10;
    while (count--)
    {
        iCount++;
    }
}

这个例子中我们可以看到,有三个循环,第一个循环是一个双重循环,基本操作是icount++,循环了n^2次。第二个循环基本操作是iCount++,循环了2*n次。第三个循环基本操作是iCount++,循环了10次。所以这个程序算法基本操作执行的次数一共是 n^2+2*n+10。那么这是这个算法最终的正确的时间复杂度吗?我们说不是的,下面我们来看看时间复杂度具体是怎么计算的。

我们一般使用大O渐进表示法表示时间复杂度,它的具体操作方法是:

  1. 用常数1代替运行时间中的所有常数。例如上面例子中最后的常数10,就可以用常数1来的取代。
  2. 在第一步修改后的运行次数函数中,只保留最高阶项。例如上面例子,第一步修改之后,变为n^2+2*n+10被改为n^2+2*n+1,只保留最高阶项,就被修改成为了n^2。
  3. 去掉最高阶项的系数。例如上面例子中,去掉最高项系数,依然是n^2,所以上面例子最终的时间复杂度就是n^2,即O(n^2)。

在此我们需要注意:算法通常存在最好、平均、 最坏情况。我们通常关注的时间复杂度是算法的最坏运行情况

我们再来看几个例子对大O渐进表示法加深下理解:

void Test0(int n)
{
    int iCount = 0;
    for (int iIdx = 0; iIdx < 10; ++iIdx)
    {
        iCount++;
    }
}

我们可以看到这个算法基本操作时iCount++,循环中执行了10次,所以它基本操作执行了10次,根据大O渐进表示法,将常数用1取代,所以该算法的时间复杂度为1,即O(1)。

void Test1(int n)
{
    int iCount = 0;
    for (int iIdx = 0; iIdx < 10; ++iIdx)
    {
        iCount++;
    }

    for (int iIdx = 0; iIdx < 2 * n; ++iIdx)
    {
        iCount++;
    }
}

可以看到这个算法中有两个循环,两个循环中的基本操作都是iCount++,第一个循环循环了10次,第二个循环循环了2*n次,所以基本操作执行的次数是2*n+10次,根据大O渐进表示法,用1取代常数,只保留最高阶,并去掉最高阶系数,这个程序的时间复杂度为n,即O(n)。

空间复杂度

同样的,我们先看一下官方是怎么定义空间复杂度的:空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。我们用自己的话通俗一点解释空间复杂度就是:代码对于存储空间的占用情况,也就是创建变量的个数空间复杂度同样适用大O渐进表示法。

我们下面再看几个例子:

void Test1(int n)
{
    int iCount = 0;
    for (int iIdx = 0; iIdx < 10; ++iIdx)
    {
        iCount++;
    }
}   

根据我们自己的理解,这个程序创建了两个变量:count和i,即2,根据大O渐进表示法,用常数1代替常数项,所以它的空间复杂度为1,即O(1)。

int *Merge(int *array1, int size1, int *array2, int size2)
{
    int index1 = 0;
    int index2 = 0;
    int index = 0;
    int *temp = (int*)malloc(sizeof(int)*(size1 + size2));
    if (temp == NULL)
    {
        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[index2];
    }
    return temp;
}

我们可以看到,上面这个程序一共定义了三个一般的局部变量index1,index2,index。在下面又动态开辟了(size1+size2)个int类型的空间,也就是创建了(size1+size2)个变量,所以一共创建的变量个数为size1+size2+3,根据大O渐进表示法,空间复杂度为size1+size2,即O(size1+size2),即O(m+n)。

至此,我们的时间复杂度和空间复杂度介绍的差不多了,下面我们来看下比较复杂的两个例子:二分查找和斐波那契数列。

二分查找

首先我们要知道二分查找的基本思想就是折半。先取数组最中间的元素,与所要查找的元素进行比较,如果所要查找的元素比最中间的元素大,则取右边一半的数继续进行上一步操作。否则取左边一半的数继续进行上一步的操作。直到找到所要查找的数,或者找完所有的数,依然没有找到要查找的数。
那么我们要求它的时间复杂度和空间复杂度,怎么求呢?我们看下面这幅图:
这里写图片描述

斐波那契数列

long long Fib(int n)
{
    if(n<3)
        return 1;
    return Fib(n-1)+(n-2);
}

依旧是用图片来说明问题:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值