-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
be686a6
commit 78b37d5
Showing
2 changed files
with
130 additions
and
8 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
--- | ||
title: 数据结构系列笔记-算法与复杂度分析 | ||
date: 2021-12-30 17:43:14 | ||
tags: | ||
- 数据结构 | ||
--- | ||
|
||
## 数据结构与算法的研究内容 | ||
1. 逻辑结构:研究对象的特征及其相互之间的关系。 | ||
2. 存储结构:研究有效组织计算机存储。 | ||
3. 算法:有效地实现对象之间的“运算”关系。 | ||
|
||
## 算法的定义 | ||
算法是对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。 | ||
|
||
<!-- more --> | ||
|
||
## 算法的描述 | ||
1. 自然语言:英语、中文。 | ||
2. 流程图:传统流程图、NS流程图。 | ||
3. 伪代码(类语言):类C语言。 | ||
4. 程序代码:C语言程序、JAVA语言程序。 | ||
|
||
## 算法与程序的关系 | ||
算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法。程序是用某种程序设计语言对算法的具体实现。程序=数据结构+算法;数据结构通过算法实现操作;算法根据数据结构设计程序。 | ||
|
||
## 算法特性 | ||
一个算法必须具备以下五个重要特征: | ||
1. 有穷性:一个算法必须总是在执行有穷步骤之后结束,且每一步都在有穷时间内完成。 | ||
2. 确定性:算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条直线路径,即对于相同的输入只能得到相同的输出。 | ||
3. 可行性:算法是可以执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。 | ||
4. 一个算法有零个或多个输入。 | ||
5. 一个算法有一个或多个输出。 | ||
|
||
## 算法设计的要求 | ||
具有四个要求: | ||
1. 正确性(Correctness) | ||
2. 可读性(Readability) | ||
3. 健壮性(Robustness) | ||
4. 高效性(Efficiency) | ||
|
||
### 正确性 | ||
正确性是满足问题要求,能正确解决问题。 | ||
算法转换为程序后要注意: | ||
1. 程序中不含语法错误; | ||
2. 程序对于几组输入数据能够得出满足要求的结果; | ||
3. 程序对于精心选择的、典型的、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果; | ||
4. 程序对于一切合法的输入数据都能得出满足要求的结果。 | ||
|
||
通常以第三层意义上的正确性作为衡量一个算法是否合格的标准。 | ||
|
||
### 可读性 | ||
可读性具有2层含义: | ||
1. 算法主要是为了人阅读和交流,其次才是为计算机执行,因此算法应该易于人的理解; | ||
2. 另一方面,晦涩难读的算法易于隐藏较多错误而难以调试。 | ||
|
||
### 健壮性 | ||
健壮性是指当输入非法数据时,算法恰当的做出反应或进行相应处理,而不是产生莫名奇妙的输出结果。处理出错的方法,不应是中断程序的执行,而应该是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。 | ||
|
||
### 高效性 | ||
高效性要求尽量少的时间和尽量低的存储需求。 | ||
|
||
## 算法分析 | ||
算法分析用来评价同一个问题的多个算法。算法分析的目的是看算法实际是否可行,并在同一问题存在多个算法时可进行性能上的比较,以便从中挑选除比较优的算法。 | ||
|
||
一个好算法首先具备正确性,然后健壮性、可读性;在这几个方面多满足的情况下,主要考虑算法的效率,通过算法的效率高低来评判不同算法的优劣程度。算法效率包括: | ||
1. 时间效率:指算法所消耗的时间; | ||
2. 空间效率:指算法执行过程中所消耗的存储空间。 | ||
|
||
**时间效率和空间效率有时候时矛盾的。** | ||
|
||
## 算法的时间效率 | ||
算法时间效率可以用依据该算法编制的程序在计算机上所消耗的时间来度量。有两种**度量**方法: | ||
1. 事后统计:将算法实现,测孙其时间和空间开销;缺点是编写程序实现算法将或非较多的时间和精力,所得实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣。 | ||
2. 事前分析:对算法所消耗资源的一种估计算法。 | ||
|
||
### 事前分析法 | ||
- 一个算法的运行时间是一个算法在计算机上运行所耗费的时间,大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的**时间**与算法中进行的简单操作**次数乘积**。 | ||
- 算法运行时间 = 一个简单操作所需要的时间×简单操作次数。 | ||
- 即算法中每条语句的执行时间之和。 | ||
- 算法运行时间 = Σ(每条语句执行次数)×该语句执行一次所需要的时间。 | ||
- 每条语句的执行次数又称为语句的频度。 | ||
- 算法运行时间 = Σ(每条语句频度)×该语句执行一次所需要的时间。 | ||
- 每条语句执行一次所需的时间,一般都是随机器而异的。每条语句执行一次所需的时间,一般都是随机器而异的。取决于机器的指令性能、速度以及编译的代码质量,是由机器本身软硬件环境决定的,它与算法无关。 | ||
- 我们可**以假设执行每条语句所需的时间均为单位时间**,此时对算法运行时间的讨论就可以转化为讨论该算法中所有语句的执行次数,即**频度之和**了。 | ||
- 这就可以独立于不同机器的软硬件环境来分析算法的时间性能了。 | ||
|
||
算法运行时间的例子:两个n×n矩阵相乘算法 | ||
``` C | ||
for (i=1; i<=n; i++) // n+1次,n次循环,1次判断 | ||
for (j=1; j<=n; j++) { // n*(n+1)次,外层循环n次,内层n+1次 | ||
c[i][j] = 0; // n*n次,2层循环 | ||
for (k=0; k<n; k++) // n*n*(n+1)次,外面2层循环,内层n+1次 | ||
c[i][j] = c[i][j] + a[i][k] * b[k][j]; // n*n*n次,三层循环 | ||
} | ||
``` | ||
我们把算法所耗费的时间定义为该算法中**每条语句的频度之和**,则该算法的时间消耗T(n)为:T(n) = 2×n3 + 3×n2 + 2×n + 1。 | ||
### 数量级 | ||
为了便于不同算法的时间效率,我们仅比较它们的数量级。例如,$T_1(n) = 10 \times n^2$ 与 $T_2(n)=5 \times n$这两个比较。若有某个辅助函数f(n)使得当n趋近于无穷大时,$T(n)/f(n)$的极限值为不等于零的常数,则称$f(n)$是$T(n)$的同数量级函数。记作$T(n)=O(f(n))$,称$O(f(n))$为算法的渐进时间复杂度(O是数量级的符号),简称时间复杂度。 | ||
|
||
例如,公式$T(n) = 2 \times n^3 + 3 \times n^2 + 2 \times n + 1$中,当$n\rightarrow \infin$时,$T(n)/n^2 \rightarrow 2$,这表示$n$充分大时,$T(n)$与$n^3$是同阶或同数量级,引入大“O”记号,则$T(n)$可记作:$T(n)=O(n^3)$ | ||
|
||
### 算法时间复杂度 | ||
- 定义:算法中基本语句重复执行的次数是问题规模$n$的某个函数$f(n)$,算法的时间量度记作:$T(n)=O(n)$。它表示随着n的增大,算法执行的时间的增长率和$f(n)$的增长率相同,称为渐进时间复杂度。数据符号“O”定义为若$T(n)$和$f(n)$是定义在正整数集合上的两个函数,则$T(n)=O(f(n))$表示存在正的常数$C$和$n_0$,使得当$n≥n_0$时都满足$0≤T(n)≤C*f(n)$。 | ||
- 基本语句是指算法中重复执行次数和算法的执行时间成正比的语句;对算法运行时间的贡献最大的语句;执行次数最多语句。 | ||
- 问题规模$n$越大,算法的执行时间越长。对于排序来说,$n$为记录数;对于矩阵来说,$n$为矩阵的阶数;对于多项式来说,$n$为多项式的项数;对于集合来说,$n$为元素个数;对于树来说,$n$为树的结点个数;对于图来说,$n$为图的顶点数或边数。 | ||
|
||
### 分析算法时间复杂度的基本方法 | ||
- 一般方法: | ||
- 找出语句频度最大的那条语句作为基本语句; | ||
- 计算基本语句的频度得到问题规模n的某个函数f(n); | ||
- 取其数量级用符号“O”表示。 | ||
- 技巧: | ||
- 忽略所有低次幂项和高次幂系数,体现增长率的含义; | ||
- 时间复杂度是由嵌套最深语句的频度决定的。 | ||
|
||
### 时间复杂度分析例题 | ||
计算下方语句的时间复杂度。 | ||
#### 例子1 | ||
```C | ||
for (i=1; i<=n; i++) | ||
for (j=1; j<=i; j++) | ||
for (k=1; j<=j; j++) | ||
x = x + 1; | ||
``` | ||
$$ | ||
语句的频度= \sum_{i=1}^n\sum_{j=1}^n\sum_{z=1}^n = \sum_{i=1}^n\sum_{j=1}^nj = \sum_{i=1}^n \frac{i(i+1)}{2}=\frac{1}{2} \left( \sum_{i=1}^ni^2+ \sum_{i=1}^ni\right) \\ | ||
=frac{1}{2} \left( \frac{n(n+1)(n+2)}{6} + \frac{n(n+1)}{2}\right) = \frac{n(n+1)(n+2)}{6}\\ | ||
$$ |