重学数据结构(序:概览)
@
转眼大学毕业已经一年多,计算机专业四大基础课——《数据结构》、《计算机网络》、《计算机组成原理》、《操作系统》,当时学的实在马虎,到现在已经快要还完了。“基础不牢,地动山摇”,曾经偷过的懒,现在都得给它补回去。
1、数据结构
1.1、数据结构的起源
1968 年, 美国的高德纳( Donald E.Knuth) 教授在其所写的《计算机程序设计艺术》 第一卷《 基本算法》 中, 较系统地阐述了数据的逻辑结构和存储结构及其操作,开创了数据结构的课程体系。
70 年代初, 出现了大型程序, 软件也开始相对独立, 结构程序设计成为程序设计方法学的主要内容, 人们越来越重视 “数据结构” , 认为程序设计的实质是对确定的问题选择一种好的结构, 加上设计一种好的算法。
1.2、基本概念和术语
- 数据:是描述客观事物的符号, 是计算机中可以操作的对象, 是能被计算机识别, 并输入给计算机处理的符号集合。
- 数据元素: 是组成数据的、 有一定意义的基本单位, 在计算机中通常作为整体处理。 也被称为记录。
- 数据项: 一个数据元素可以由若干个数据项组成。
- 数据对象: 是性质相同的数据元素的集合, 是数据的子集
来重点看看什么是数据结构:
结构, 简单的理解就是关系, 比如分子结构, 就是说组成分子的原子之间的排列方式。 严格点说, 结构是指各个组成部分相互搭配和排列的方式。 在现实世界中, 不同数据元素之间不是独立的, 而是存在特定的关系, 我们将这些关系称为结构。
那数据结构是什么?
数据结构: 是相互之间存在一种或多种特定关系的数据元素的集合。
在计算机中, 数据元素并不是孤立、 杂乱无序的, 而是具有内在联系的数据集合。 数据元素之间存在的一种或多种特定关系, 也就是数据的组织形式。
1.3、逻辑结构和物理结构
数据元素之间存在一种或多种特定关系,接下来就看看这个关系指的是什么关系。
按照视点的不同, 我们把数据结构分为逻辑结构和物理结构。
1.3.1、逻辑结构
逻辑结构体现的是数据元素之间的逻辑关系, 换句话说就是从操作对象中抽象出来的数学模型, 因此又称为抽象结构, 通常我们习惯说的数据结构一般就是指的逻辑结构。
下面是几种逻辑结构:
1.3.1、物理结构
物理结构结构是数据在计算机内的存储形式, 又称存储结构。 它包括数据元素的表示和关系的表示。
数据元素的存储结构形式有两种: 顺序存储和链式存储。
- 顺序存储
顺序存储结构是把数据元素存放在地址连续的存储单元里, 其数据间的逻辑关
系和物理关系是一致的。
- 链式存储
链式存储结构是把数据元素存放在任意的存储单元里, 这组存储单元可以是连续的, 也可以是不连续的。 数据元素的存储关系并不能反映其逻辑关系。
2、算法
2.1、什么是算法?
先来看看算法的定义:
算法是解决特定问题求解步驟的描述, 在计算机中表现为指令的有限序列, 并且每条指令表示一个或多个操作。
通俗来说,算法是指解决问题的一种方法或者一个过程。 一个问题可以用多种算法来解决, 一个给定的算法解决一个特定的问题。
那么数据结构和算法有什么关系呢?算法和数据结构是焦不离孟的关系。
不了解施加于数据上的算法需求就无法决定数据结构; 反之算法的结构设计和选择又依赖于作为其基础的数据结构。
即数据结构为算法提供了工具。 算法利用这些工具来实施解决问题的最优方案。
在计算机领域内, 一个算法实质上是根据处理问题的需要, 在数据的逻辑结构和存储结构的基础上施加的一种运算。
因为数据的逻辑结构和存储结构不是惟一的, 所以算法的描述可能不惟一。 即使逻辑结构和存储结构相同, 算法可能也不惟一。 通过学习数据结构,可以使得程序设计者选择一种比较好的算法。
2.1、算法的特性
算法具有五个基本特性: 输入、 输出、 有穷性、 确定性和可行性。
- 输入/输出
输入和输出特性比较容易理解, 算法具有零个或多个输入。 尽管对于绝大多数算法来说, 输入参数都是必要的, 但对于个别情况, 如打印 “hello world!”这样的代码, 不需要任何输入参数, 因此算法的输入可以是零个。 算法至少有一个或多个输出, 算法是一定需要输出的, 不需要输出, 那这个算法就没有意义了。
-
有穷性
算法在执行有限的步骤之后, 自动结束而不会出现无限循环, 并且每一个步骤在可接受的时间内完成。 现实中经常会写出死循环的代码, 这就是不满足有穷性。 -
确定性
算法的每一步骤都具有确定的含义, 不会出现二义性。 算法在一定条件
下, 只有一条执行路径, 相同的输入只能有唯一的输出结果。 算法的每个步骤被精确
定义而无歧义。
- 可行性
算法的每一步都必须是可行的, 也就是说, 每一步都能够通过执行有限次数完成。 可行性意味着算法可以转换为程序上机运行, 并得到正确的结果。
2.2、算法设计要求
设计一个好的算法通常应考虑达到以下目标:正确性、可读性、健壮性、效率与低存储量需求。
2.3、算法分析
算法是解决计算问题的工具。 算法的好与坏直接影响解决问题的效率, 为提高解决问题的效率, 针对一个具体问题的解决方法, 除了需要考虑对算法的具体描述外, 还应具有衡量该算法好坏的方法。
算法的分析主要是指判断算法的优劣, 判断一个算法的好坏一般从两个方面考虑, 即从时间角度和从空间角度上衡量算法。 一般算法分析从时间角度考虑的比较多。 当然判断一个算法的好与坏, 也不能只以时间或空间衡量简单化, 而应该根据实际情况综合考虑。
算法度量主要有这两个方法:
- 事后统计方法
这种方法的缺点是, 必须先运行依据算法编制的程序; 所花时间的统计量依赖于计算机的软件、 硬件等环境因素, 容易掩盖算法本身的优劣。 因此人们常用第 2 种方法。
- 事前分析估算方法
一个用高级语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
• 算法选用何种策略
• 问题规模
• 书写程序的语言
• 编译产生的机器代码质量
• 机器执行指令的速度
当然,抛开硬件方向来讲,算法效率依赖于问题的规模, 或者说, 它是问题规模的函数。
但在实践中, 我们可以把两种方法结合起来使用。一般地, 我们将算法的求解问题的输入称为问题的规模, 并用一个整数 n 表示。 例如, 矩阵乘积问题的规模是矩阵的阶数, 而一个图论问题的规模则是图中的顶点个数或
边的条数。
在算法的每个步骤中, 可能有若干条语句, 而频度就是指每条语句的执行次数。 一个算法的时间复杂度是指算法的时间耗费。
简单地说, 就是以一条基本语句的执行时间为基本单位, 该算法所有语句中总的基本语句的执行次数就是该算法的时间耗费, 它是该算法所求解的问题规模n的函数。 当问题的规模n趋向无穷大时, 我们把时间复杂度的数量阶称为算法的渐进时间复杂度。
2.3.1、时间复杂度
算法时间复杂度定义:
在进行算法分析时, 语句总的执行次数 T ( n ) 是关于问题规模 n 的 函 数 。 进 而 分 析 T ( n ) 随 n 的变化情况并确定 T ( n ) 的 数 量级。 算法的时间复杂度,也就是算法的时间量度, 记作: T ( n )= O(f(n))。它表示随问题规模 n 的增大, 算法执行时间的增长率和f ( n ) 的增长率相同, 称作算法的渐近时间复杂度, 简称为时间复杂度。 其中 f ( n ) 是问题规模 n 的某个函数。
用大写的O()来表示时间复杂度,称之为大O表示法。
2.3.1.1、常数阶
顺序结构的时间复杂度。
例如下面这个算法:
int sum=0,n=100; //执行1次
sum=sum+n/2; //执行1次
System.out.println(sum); //执行1次
这个算法的运行次数函数是 f (n) =3。 执行次数不会随着n的变化而增大,所以这个算法的时间复杂度为 O(1)。
除了顺序结构,对于分支结构而言, 无论是真, 还是假, 执行的次数都是恒定的, 不会随着 n 的变大而发生变化 , 所以单纯的分支结构 ( 不包含在循环结构中) , 其时间复杂度也是O(1)。
2.3.1.2、线性阶
线性阶的循环结构会复杂很多。 要确定某个算法的阶次, 我们常常需要确定某个特定语句或某个语句集运行的次数。 因此, 我们要分析算法的复杂度, 关键就是要分析循环结构的运行情况。
for(int i=0;i<n;i++){
//时间复杂度为O(1)的运算
System.out.println(n);
}
上面这个算法的时间复杂度为O(n),因为算法要执行n次。
2.3.1.3、对数阶
看一下下面这个算法,乍一看时间复杂度是O(n)。
int n=10;
int count=1;
while (count<n){
count=count*2;
//时间复杂度为O(1)的运算
System.out.println(count);
}
每次 count 乘以 2 之后, 就距离n更近了一分。 也就是说, 经过log2^n次之后退出循环,所以这个循环的时间复杂度为O(logn)。
2.3.1.4、平方阶
看一下下面这个双循环,把 O(n) 的代码再嵌套循环一遍。它的执行次数是m*n,时间复杂度是O(n²)。
for (int i=0;i<n;i++){
for (int j=0;i<m;j++){
//时间复杂度为O(1)的操作
System.out.println(i*j);
}
}
在平方阶上扩展,立方阶O(n³)、K次方阶O(n^k),也都很好理解,3层循环,k层循环。
2.3.2、空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。
一般情况下, 一个程序在机器上执行时, 除了需要存储程序本身的指令、 常数、变量和输入数据外, 还需要存储对数据操作的存储单元。 若输入数据所占空间只取决于问题本身, 和算法无关, 这样只需要分析该算法在实现时所需的辅助单元即可。 若算法执行时所需的辅助空间相对于输入数据S而言是个常数, 则称此算法为原地工作, 空间复杂度为 O(1)。
通常, 我们都使用 “时间复杂度” 来指运行时间的需求, 使用 “空间复杂度” 指空间需求。 当不用限定词地使用 “复杂度” 时, 通常都是指时间复杂度。
本文为学习笔记类博客,主要资料来源如下!
参考:
【1】:邓俊辉 编著. 《数据结构与算法》
【2】:王世民 等编著 . 《数据结构与算法分析》
【3】: Michael T. Goodrich 等编著.《Data-Structures-and-Algorithms-in-Java-6th-Edition》
【4】:严蔚敏、吴伟民 编著 . 《数据结构》
【5】:程杰 编著 . 《大话数据结构》
【6】:VisuAlgo
【7】:看动画轻松理解时间复杂度(一)