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

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

时间复杂度

首先咱们看下官方是怎么定义时间复杂度的:算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于表明算法输入值的字符串的长度的函数。时间复杂度经常使用大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渐进表示法表示时间复杂度,它的具体操做方法是:svg

  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渐进表示法加深下理解:spa

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

咱们能够看到这个算法基本操做时iCount++,循环中执行了10次,因此它基本操做执行了10次,根据大O渐进表示法,将常数用1取代,因此该算法的时间复杂度为1,即O(1)。3d

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)。code

空间复杂度

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

咱们下面再看几个例子:blog

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);
}

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