数据结构与算法( 2 ):时间空间复杂度

文章目录

  • 一、算法效率的度量规模(引入问题)
    • 1、程序耗费时间的影响因素
    • 2、函数的渐进增长
  • 二、时间复杂度
    • 1.定义
    • 2、推导大 O 阶方法
    • 3、常见算法时间的多项式关系:
    • 4、函数调用 的时间复杂度分析:
    • 5、最坏(好)情况 与 平均情况
  • 三、空间复杂度
    • 1、定义
    • 2、例子


一、算法效率的度量规模(引入问题)

1、程序耗费时间的影响因素

(1)、算法采用的策略、方案;
(2)、编译产生的代码质量(编译器的优劣、编译器采用的解决方案的优劣);
(3)、问题的输入规模 n;
(4)、机器执行指令的速度

故 抛开计算机硬件、软件有关的因素,一个程序运行时间依赖于 算法的好坏问题的输入规模

例如:等差求和

 算法(1):

int sum = 0, n = 1000000000;     // 1
for (int i = 1; i <= n; i++)     // 判断了 n+1  次
{
	sum += i;     // 执行了 n 次
}

 算法(2):

int sum = 0, n = 1000000000;     // 1次
sum = (1 + n) * n / 2;        // 1次,而不是O(n^2),时间复杂度看的是 执行次数

分析:算法(1)执行了 1+(n+1)+ n = 2n + 2 次,而算法(2)只执行了 2 次,忽略头尾判断,那么这两个算法就是 n 和 1 的差距

一方面,如果较真研究 精确执行的次数 是非常累的;另一方面,研究算法的复杂度,侧重研究算法随着输入规模的增长量的一个抽象,而不是精确定位次数。

因此只需要看抽象出来的次数,从而得出时间复杂度

分析一个算法的运行时间时,重要的是把基本操作的 数量 和 输入模式 关联起来

2、函数的渐进增长

(1)、函数渐进增长:给定两个函数 f(n)、g(n),如果存在一个正数 N ,使得所有 n>N 使得 f(n) > g(n),那么f(n) 增长渐进快于 g(n)

(2)、例1:算法A要做 2n+3 次(2个不嵌套循环,之后有3次运算),算法B要做 3n+1 次(同上),哪个更快呢?

规模 算法A1(2n+3) 算法A2(2n) 算法B1(3n+1) 算法B2(3n)
n=1 5 2 4 3
n=2 7 4 7 6
n=3 9 6 10 9
n=10 23 20 31 30
n=100 203 200 301 300

当 n=1 时,A1 不如 B1;
当 n=2 时,二者效率(耗费时间)相等;
n>2 时,A1 优于 B1,算法A 逐渐拉大与 B 的差距,总体上 A 优于 B
随着规模增大,算法A1、A2 和 B1、B2基本相互覆盖,可见加一个系数对时间影响不大

(3)、例2:算法C :4n+8,算法D:2n2+1 哪个更快呢?

规模 算法C1(4n+8) 算法C2(n) 算法D1(2n2+1) 算法D2(n2
n=1 12 1 3 1
n=2 16 2 9 4
n=3 20 3 19 9
n=10 48 10 201 100
n=100 408 100 20001 10000
n=1000 4008 1000 2000001 1000000

观察发现,哪怕去掉与 n 相乘的系数,二者最终结果没有改变:算法C2 次数随着n的增长,还是远小于D2,也就是说, 与最高次项相乘的系数并不重要,可以忽略

(4)、结论:判断算法的效率,应主要考虑主项(最高次项)的阶数,而最高项的系数也不用关注

二、时间复杂度

1.定义

(1)语句总的执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 变化情况并确定 T(n) 的数量级;
算法时间复杂度就是算法的时间量度,记作 T(n) = O( f(n) )
它表示随问题规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中 f(n)是时间规模 n 的某个函数

(2)、用大写的 O() 来体现算法时间复杂度的记法,称为 大O记法
(3)、一般情况下,随着输入规模 n 的增大, T(n) 增长最 慢 的算法为最优算法

2、推导大 O 阶方法

(1)、用 常数1 取代所有 相加的常数
(2)、取代后的运行次数,只保留最高阶项
(3)、如果最高阶存在,且不为1,则去除与这个项相乘的系数
(4)、分类:
              a、常数阶:

int sum, n;                    // O(1)
printf("Hello World\n");    // O(1)
printf("Hello World\n");
printf("Hello World\n");
printf("Hello World\n");
printf("Hello World\n");
printf("Hello World\n");
sum += (1 + n) * n / 2;     // O(1)

O(8)吗 ?分析一下概念“T(n)是关于问题规模 n 的函数”,再根据推导方法(1),所有 常数和的规模是O(1)
              b、线性阶: 随问题规模n的增加,计算次数线性增长

int sum, n;
for (int i = 0; i < n;i++)
{
    sum += i;
}

时间复杂度:O(n)
              c、m方阶: m 层嵌套

int sum, n;
for (int i = 0; i < n;i++)
{
    for (int j = 0; j < n;j++)
    {
        sum += i;
    }
}

时间复杂度:O(nm
特殊情况:

int sum, n;
for (int i = 0; i < n;i++)
{
    for (int j = i; j < n;j++)	// j = i,与 i有关,通过执行次数判断
    {
        sum += i;
    }
}

执行次数:n + (n-1) + (n-2) + … + 1 = (n+1)*n/2
复杂度:O(n2)

              d、对数阶:

int i=1, x;
while (i < x)
{
    i *= 2;
}

循环次数:每次 I * 2后,离 x 更进一步,假设有 n 个2相乘后大于 x,结束循环,即 2^n = x,次数 n = log x

复杂度:O(logn)

3、常见算法时间的多项式关系:

1    <   log    <   n   <    n*log    <   n2    <   n3    <   2n   <   n!   <   nn

4、函数调用 的时间复杂度分析:

1、例子:

void function(int n)    // O(n^2)
{
    int j = 0;
    for (j = 0; j < n;j++)
    {
        printf("%d\n", j);
    }
}   

int main()
{
    int n = 10, i = 0, j = 0;
    n++;    // O(1)

    function(n);    //O(n^2)

    for (i = 0; i < n;i++)  // O(n^2)
    {
        function(i);
    }

    for (i = 0; i < n;i++)      // O(n^2)
    {
        for (j = 0; j < n;j++)
        {
            printf("%d\n", j);
        }
    }

        return 0;
}

注:对于for和function的结合,尽管function本身是O(n2 ) 可能会认为应该是O(n3),但将function带入后,与下一个for嵌套等效

5、最坏(好)情况 与 平均情况

(1)、平均运行时间是期望得到的运行时间
(2)、最坏运行时间是一种保证,在应用中,这是一种最重要的需求,除非特别指定,否则提到的时间都是最坏情况的时间



三、空间复杂度

1、定义

 (1)、算法的空间复杂度通过计算算法所需的存储空间实现
 (2)、算法的空间复杂度的计算公式:S(n) = O( f(n) ),n为问题规模,f(n) 为语句关于 n 所占存储空间的函数
 (3)、通常,用“时间复杂度”来指运行时间的需求,用“空间复杂度”指空间需求
 (4)、当直接要求求算法的“复杂度”时,通常求的是“时间复杂度”

2、例子

(1)、判断某年是否为闰年:
            算法1:想一个算法,通过输入的年份的特征,算出是否为闰年
            算法2:设立一个2050个元素的数组,所有年份输入,并用0、1标记是否为闰年
         分析:
                算法1节省空间,仅需算法计算,但消耗时间
                算法2节省时间,仅需引用查找,但消耗空间
         结论:可以通过时间换取空间,也可空间换取时间

你可能感兴趣的:(数据结构,与,算法)