数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度

文章目录

  • Day1 绪论
    • 1. 数据结构
    • 1.1 数据结构是什么
    • 1.2 逻辑结构
    • 1.3 物理结构
    • 2. 算法
      • 2.1 算法的五个基本特征
      • 2.2 算法设计的要求
  • Day2~3 时间复杂度&空间复杂度
    • 1. 学习前准备
    • 1.1 两种计算方法
      • 1.1.1 事后统计方法
      • 1.1.2 事前分析估算方法
    • 1.2 为什么只用高阶阶数
      • 1.2.1 示例
      • 1.2.2 说明
      • 1.2.3 函数的渐进增长
    • 2. 时间复杂度
    • 2.1 大O表示法
    • 2.2 万能公式
    • 3. 函数调用的时间复杂度分析
      • 3.1 Demo1
      • 3.2 Demo2
      • 3.3 Demo3
    • 4. 常见时间复杂度
    • 5. 最坏情况&平均情况
    • 6. 空间复杂度
      • 6.1 什么是空间复杂度
      • 6.2 附注

Day1 绪论

1. 数据结构

1.1 数据结构是什么

什么是数据结构?

  • 程序设计 = 数据结构 + 算法
  • 数据结构: 数据元素相互之间存在的一种或多种特定关系的集合

传统上:数据结构 = 逻辑结构 + 物理结构

  • 逻辑结构:数据对象中数据元素中的相互关系
  • 物理结构:数据的逻辑结构在计算机中的存储形式。

1.2 逻辑结构

  1. 集合结构: 除了属于一个集合外,没有其它任何关系
    数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第1张图片

  2. 线性结构:
    数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第2张图片

  3. 树型结构:
    数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第3张图片

  4. 图形关系:
    数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第4张图片

1.3 物理结构

物理结构,研究的是,如何把数据元素存储到计算机的存储器中。

  • 存储器主要是针对内存而言,外部存储器常用文件结构来描述。
  • 数据元素存储形式有两种:顺序存储链式存储

  • 顺序存储结构: 把元素存储在地址连续的存储单元里,数据间的逻辑关系和物理单元是一致的。(下图是物理结构,表现为紧挨着
    在这里插入图片描述

  • 链式存储结构: 数据元素存放在任意的存储单元里,然后在这一个数据元素上放一个指针,来记录下一个数据元素的地址。(因为存放了指针,所以更费空间)
    数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第5张图片

2. 算法

算法就是解决问题的技巧和方式。

  • 如何计算 1 + 2 + … + 99 + 100 的值?有的人可能会一个个加过去,有的人会一个个加过去,有的人会使用等差数列(这个算法是高斯在小学发明的)。
  • 一个问题可以由多个算法解决,一个算法不可能有通解所有问题的能力。

2.1 算法的五个基本特征

  1. 输入
  2. 输出
  3. 有穷性
  4. 确定性
    算法的每一条步骤都有确定的含义,不会出现二义性,不会有歧义。
    算法在一定条件下,只有一条执行路径。相同的输入只能有一个输出的结果。
  5. 可行性
    每一步都可以在当前环境下执行有限次数完成(算法可以注明自己需要的环境)

2.2 算法设计的要求

1. 正确性 (大致分为四层次)

  1. 算法程序没有语法错误
  2. 算法程序对于合法输入能够产生满足要求的输出
  3. 算法程序对于非法输入能够产生满足要求的说明
  4. 算法程序对于故意刁难的测试输入都有满足要求的输出结果

2. 可读性

  1. 算法设计的另一目的是为了便于阅读理解和交流
  2. 写代码的目的,一方面是为了让自己理解执行。另一方面是为了便于自己或他人阅读修改。

3. 时间效率高 & 存储量低

Day2~3 时间复杂度&空间复杂度

1. 学习前准备

如何计算算法效率呢?

1.1 两种计算方法

1.1.1 事后统计方法

需要事先编制好测试程序。利用计算机计时器对不同算法编制的运行时间做比较,从而确定算法效率的高低。

  • 缺点:
  1. 编制测试程序需要花费时间和精力。
  2. 不同测试环境的差别还非常之大。

1.1.2 事前分析估算方法

在计算机重新编写前,依据统计方法对算法进行估算。

  • 影响因素:
  1. 算法的策略,方案
  2. 编译参数的代码质量
  3. 问题的输入规模
  4. 机器执行指令的速度

由此可见: 除了软硬件之外,就是算法的好坏和问题的输入规模。

1.2 为什么只用高阶阶数

1.2.1 示例

计算1~100相加

  • 算法1:
for(int i = 1, n = 100, sum = 0; i <= n; i++) {  // 执行 1+n+1 次
    sum = sum + i;  // 执行 n 次
}
  1. 初始化方法执行 1 次
  2. 条件判断执行 n+1 次(1次是判断不成功时跳出循环)
  3. 函数体执行n次

总共 2n + 2 次


  • 算法2:
int sum = 0, n = 100;  // 执行1次
sum = (1 + n) * n / 2;  // 执行1次

总共 2 次

1.2.2 说明

  1. 不关心编写程序的语言是什么,也不关心程序跑在什么样的计算机上,只关心实现的算法。
  2. 最重要的是,把一系列的步骤抽象出来。
  3. 把基本操作的数量和输入模式关联起来。

所以上面的算法1和算法2的关系,是n和1的关系。

数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第6张图片

1.2.3 函数的渐进增长

给定两个函数f(n) 和 g(n),如果存在一个整数N,使得对于所有的n > N, f(n) 总是比g(n)大,那么,我们说f(n)的渐进增长快于g(n)。
数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第7张图片

  • f(n) = 3n + 1
  • g(n) = 2n + 3
  • N = 2
  • 存在n > N时,f(n) > g(n),所以f(n)的渐进增长快于g(n)

可以使用相关工具(如Excel或matlab),进行绘图。会发现,当n足够大时,似乎只与最高阶阶数相关,其它因素可以忽略不计。
判断一个算法效率时: 函数中的常数和次要项常常快于忽略,而更应该关注主项(最高项)的阶数。

2. 时间复杂度

2.1 大O表示法

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

  • O ( ) O() O()来体现算法时间复杂度的记法,被称为大O表示法
  • 随着输入规模n的增大,T(n)增长最慢的算法,一般为最优算法。
  • 1.2.2中,三条曲线算法的时间复杂度分别为 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)

2.2 万能公式

关键是输入n与输出T(n)的关系

  1. 聚焦最高次项,其它全扔
  2. 最高次项常数变为1

  • 常数阶:最高阶为常数, O ( 1 ) O(1) O(1)
  • 线性阶:最高阶为一次, O ( n ) O(n) O(n)。一般涉及非嵌套循环。
  • 平方阶:最高阶为两次, O ( n 2 ) O(n^2) O(n2)。一般涉及一个嵌套的循环。
  • 对数阶: l o g 2 n log_2n log2n,这个写法的时间复杂度为 O ( l o g n ) O(logn) O(logn)

注: 推导大O并不难,关键是对数列的一些相关运算,更多地考虑数学知识与能力。

3. 函数调用的时间复杂度分析

3.1 Demo1

int i, j;  // 1
for(i = 0; i < n; i++) {  // n + 1
    fuction(i);  // 1
}
product static void function(int count) {
    System.out.println(count);  // 1
}

1 + n + 1 + n * 1 ⇒ 2n +2
所以,时间复杂度是: O ( n ) O(n) O(n)

3.2 Demo2

int i, j;  // 1
for(i = 0; i < n; i++) {  // n + 1
    fuction(i);  // n
}
product static void function(int count) {
    int j;  // 1
    for(j = count; j < n; j++) {  // O(n)
		System.out.println(j);
	}
}

里面那个循环,一眼瞄上去,执行次数就是一个等差数列。不管它是从n还是n+1还是n-1开始,也不管它是1或2或0结束。总归是等差数列,最高阶是 n 2 \frac n 2 2n,也就是 O ( n ) O(n) O(n)。(作为程序员,需要的是抽象的概括能力。比如这里,一眼瞄过去知道是等差,立马就是一个 O ( n ) O(n) O(n)甩过去。)
1 + n + 1 + n * O(n) ⇒ O ( n 2 ) O(n^2) O(n2)

3.3 Demo3

n++;  // 1
function(n);  // n * O(n)
for(i = 0; i < n; i++) {  // n+2
	function(i);  // O(n) * (n + 1)
}
for(i = 0; i < n; i++) {  // O(n^2)
	for(j = i; j < n; j++) {
		System.out.println(j)
	}
}
product static void function(int count) {
    int j;  // 1
    for(j = count; j < n; j++) {  // O(n)
		System.out.println(j);
	}
}

( 1 + O ( n 2 ) + O ( n 2 ) + O ( n 2 ) ) (1 + O(n^2) + O(n^2) + O(n^2)) (1+O(n2)+O(n2)+O(n2)) O ( n 2 ) O(n^2) O(n2)

4. 常见时间复杂度

时间复杂度(升序) 术语 举例
O ( 1 ) O(1) O(1) 常数阶 1
O ( l o g n ) O(logn) O(logn) 对数阶 3 l o g 2 n + 1 3log_2n +1 3log2n+1
O ( n ) O(n) O(n) 线性阶 2n + 1
O ( n l o g n ) O(nlogn) O(nlogn) nlogn阶 4 n + 3 n l o g 2 n + 1 4n +3nlog_2n +1 4n+3nlog2n+1
O ( n 2 ) O(n^2) O(n2) 平方阶 3 n 2 + 2 n + 1 3n^2 + 2n + 1 3n2+2n+1
O ( n 3 ) O(n^3) O(n3) 立方阶 4 n 3 + 3 n 2 + 2 n + 1 4n^3 + 3n^2 + 2n + 1 4n3+3n2+2n+1
O ( 2 n ) O(2^n) O(2n) 指数阶 2 n 2^n 2n
O ( n ! ) O(n!) O(n!) 阶乘 n!
O ( n n ) O(n^n) O(nn) - -

由与 O ( n 3 ) O(n^3) O(n3)太大了,我们没有讨论它们的意义。我们只探究前5个。
数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第8张图片
数据结构与算法 #Day1~3 #绪论 #时间复杂度 #空间复杂度_第9张图片

5. 最坏情况&平均情况

  • 最坏情况: 穷尽到最后一次才得到自己想要的结果
  • 平均情况: 期望的结果

  • 最坏运行时间: 是一种保证,在应用中,这是一种最重要的需求,如果没特别指定,我们提到的运行时间都是最坏情况的运行时间。
  • 平均运行时间: 期望的运行时间

6. 空间复杂度

6.1 什么是空间复杂度

写代码时,可以用空间时间

  • 判断一个年份是否是闰年时,可以采取两种方式:
  1. 写一个判断的函数,经过计算,返回结果(省空间,耗时间)
  2. 写一个大型的数组,输入年份,直接返回结果(省时间,耗空间)

6.2 附注

空间复杂度计算公式: S ( n ) = O ( f ( n ) ) S(n) = O(f(n)) S(n)=O(f(n))

n为问题规模
f(n)为语句关于n所占存储空间的函数

  • 我们用时间复杂度 来指运行时间的需求,空间复杂度 来指空间需求。(一般指的复杂度是指时间复杂度

你可能感兴趣的:(算法)