数据结构与算法 - 基础篇

前文 概念

  1. 明确两个基本概念,数据结构和算法的关系
数据结构所表达的含义是数据所存储的一种方式,简称数据结构。
 算法:对于操作一系列数据所需要的操作方法,简称算法。

 算法的执行操作是针对特定数据结构存储的数据,算法因数据结构而诞生,数据结构因算法而存在,两者紧密相连。
  1. 算法精髓 : 复杂度分析(时间、空间、最好、最坏、平均、均摊)
复杂度分析的由来,前辈们已经通过大量的验证,发表出大量的算法思想与算法实现,
 在适当的场景下选择合适的算法,有利于提高开发性能,代码性能维护,
 这一决策都来源于分析算法的时间复杂度、空间复杂度等复杂度,
 当然还得考量公司机器的性能等本身原因。

复杂度分析基础

1. 为什么需要考量一个算法的复杂度

事后统计法:
    算法的好坏,可以考量我们应用效率,有些公司通过大量的测试去考量一个算法,通过测试监控执行的时间复杂度、空间复杂度,这都必须通过数据测试来求得,这种结果相对来说还是比较准确的,毕竟是经过实践测试得到的,
    但是这种测试依赖集群环境,cpu处理器等影响,可以说是事后统计法。
存在的问题:
    a. 太过于依赖环境,机器硬件、cpu处理器等影响结果
    b. 相对麻烦,如果有100亿条数据,只有通过测试才能知道复杂度,需要时间消耗、时间等待、占用机器资源,从而引出通过数学知识分析时间复杂度、空间复杂度、最好、最坏、平均、均摊时间复杂度等复杂度,总体考量一个算法的执行效率,到底能做到节省多少的时间、空间。

2. 复杂度的表示法 (大 O 表示法 )

例 2.1:

public int sum_int(int n){
            int sum = 0;
            for(int i=1;i<=n;i++){
                sum +=i
            }
            return sum;
        }
解释:这段代码是从1+2+3+......+n求和运算,
    假设每行代码的执行时间都是相同的,都是 1 unit_time ,其中,
    第 2 行代码执行了 1 次,对应执行时间是 1 unit_time,
    第 3 行代码执行了 n 次,对应执行时间是 n unit_time,
    第 4 行代码执行了 n 次,对应执行时间是 n unit_time,

    总的执行时间是 1 unit_time + n unit_time + n unit_time = (2n + 1) unit_time,
    即 T(n) = (2n + 1) unit_time  ,T(n) 即为 时间复杂度,
    代码的执行时间与每行代码的执行时间成正比。

    可总结出,对于所有的时间复杂度分析,可用大O表示法来表达一个算法的时间复杂度 :
        T(n) = O(f(n));
    注释:  T(n) 表示代码的执行时间
            n表示数据i摩的大小
            f(n)代表每行代码的执行次数
            公式中的O代表执行时间T(n) 与 f(n)成正比
    总结:随着数据规模的不断增大,算法的执行时间与数据规模线性相关且成正比。

例 2.2:
稍复杂的例子

public int cal(int n){
        int sum = 0;
        int i = 1;
        int j = 1;
        for(int i = 1;i <= n;i++) {
            j=1;
            for(int j = 1;j<=n;j++) {
                sum = sum + i*j
            }
        }
        return sum;
    }
解释:
    其中第  2、3、4 行代码每行执行了 1 次, 运行时间 3 unit_time,
        第  5、6    行代码每行执行了 n 次,运行时间 2n unit_time,
        第  7、8    行代码每行执行了 n^2 次,运行时间 2n^2 unit_time,
    所以总的时间复杂度为( 2n^2 + 2n +3 ) unit_time ,即时间复杂度
        T(n) = 2n^2 + 2n + 3
    总结:随着数据规模的增大,执行时间也增长没所以该时间复杂度也称作 渐进时间复杂度。

3. 几种常见的时间复杂度分析方法

1). 只关注循环次数最多的那段代码 :

用大O表示法来表示一种算法的执行时间的一种趋势,通常情况下会忽略常量、低阶、系数,
    这样做只是因为这些对一个算法指向效率影响不大,我们在关注一个算法的时候,
    往往最受到关注的是循环次数最多的那段代码,它们将极大的影响算法的执行效率。

例 3.1:

public int (int n) {
        sum = 0;
        for(int i = 1;i<=n;i++){
            sum+=i;
        }
        return sum;
    }
时间复杂度 : T(n) = 2n + 1; 忽略掉常量、系数,即 时间复杂度为 O(n) ;

2). 加法法则计算时间复杂度 (总的时间复杂度等于量级最大的时间复杂度)

例 3.2

public int (int n) {
        int sum = 0;
        for(int i =1;i<=n;i++) {
            sum += i;
        }
        int sum1 = 1;
        int a = 1;
        int b = 1;
        for(;a<=n;i++){
            b=1;
            for(;b<=n;b++){
                sum1 += sum1;
            }
        }
        return sum1;
    }
解释:
    这段代码的时间复杂度可分为两段,O(n)1=O(n) , O(n)2=O(n^2),
    所以总的时间复杂度由最大量级的时间复杂度决定,
    则这段代码的时间复杂度是 O(n) = n^2

3). 乘法法则:

且套代码的时间复杂度等于内外且套代码时间复杂度的乘积.

总结:几种常见的时间复杂度分析

标题

常数阶

O(1)

线性阶

O(n)

对数阶

logn

线性对数阶

nlogn

指数阶

2^n

阶乘阶

n!

平方阶

n^2

立方阶

n^3

k次方阶

n^k