学前准备——数据结构基本概念
0. 学习目标
数据结构 + 算法 = 程序设计——瑞士 Niklaus Wirth
1968年,美国的高德纳教授在其缩写的《计算机程序设计艺术》第一卷《基本算法》中,较系统地阐述了数据的逻辑结构和存储结构及其操作,开创了数据结构课程体系。
感谢全文背诵的周树人,感谢数据结构的高德纳
参考资料
- 《大话数据结构》——程杰
- 《数据结构Java版》(“十二五”国家级规划教材)
- 电子科技大学Mooc数据结构与算法
注,本文中出现的图片来源于参考资料,侵删
1. 基本概念
数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。
简单来说,数据结构主要研究数据在计算机内(特别是内存)如何存放、如何排序以及如何查找等一系列操作如何定义与实现。
这一小节主要介绍一些基本概念,注意,以下概念会略显枯燥
1.1 数据的概念
数据(Data):描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。
数据项(Data Item): 是数据元素中由独立含义的,不可分割的最小标识单位。
数据元素(Data Element): 数据元素是数据的基本单位。一个数据元素可以是一个不可分割的原子项,也可以由多个数据项组成。
关键字(Keyword): 能够识别该元素的一个或多个数据项称为关键字。
主关键字(Primary Keyword): 能够唯一识别数据元素的关键字称为主关键字。
- 举个例子:设有 id,name, score 三个属性的Student 类的一个对象列表,那么这一整个列表都可以看作是数据,Student 小明 这个对象可以是一个数据元素,小明的成绩 score=50 也可以是一个数据元素,其中,score=50 有表示分数为50的独立含义,不可再分割,因此 小明的 score=50 为一个数据项。Student 对象列表 name 可以作为关键字, score也可以作为关键字,name以及其它键的组合也可作为关键字。而主关键字只有id。
数据结构(Data Structure):数据元素之间存在的关系。一个数据结构是由若干个数据元素组成的有限集合,数据元素之间存在某种特定的关系。
1.2 数据的逻辑结构
数据的逻辑结构指数据元素之间的逻辑关系,用一个数据元素的集合与定义再此集合上的若干关系来表示。
我们所学习的数据结构大多指逻辑结构。
1.3 数据的存储结构
数据的存储结构指的是实际上在物理实现层面,数据元素及其关系是如何存储的。
我们在学习《计算机组成原理》的时候就已经了解,指令与数据以同等地位存放于存储器中,因此数据元素的关系也是存储在存储器中的。
数据的存储结构基本形式有两种:
- 顺序存储结构:在一组连续的内存单元依次存放数据元素。存储位置在物理上也是相邻的。
- 链式存储结构:在物理上使用分散的地址单元存储数据元素,逻辑上相邻,在内存中的位置实际上不一定相邻。将数据元素的存储封装成地址域与数据域的形式,通过地址域来表示逻辑上的相邻。一般情况下,存放于地址域的地址就是我们常见的指针。
如今,计算机硬件的发展已经能满足非常多的日常使用场景,一般情况下,我们所使用的硬盘存储性能是过剩的,但是,RAM的空间依然有限。作为软件开发码农,我们日常更关注数据在RAM的存储结构。因此,我们日后要学习的各种数据结构都主要关注数据在内存的存储结构。
2. 常见数据结构
常见的数据结构主要有线性结构、树、图。这也是我们接下来要学习的重点。
本小节我们先来了解一下。
2.1 常见结构
线性结构
线性结构是数据元素之间具有线性关系的数据结构。采用Index确定元素在线性结构中的逻辑次序。
树
树是数据元素之间具有层次关系的一种非线性结构。最顶层的结点称为根节点,根结点没有父母结点,所有结点可以有零到多个后继结点,即孩子结点。
图
图也是非线性结构。每个元素可以有多个前驱元素和多个后继元素。
例如,交通道路图、航班路线图就有图的结构。
2.2 常见操作
数据结构解决了数据在计算机内的存储问题,那么对数据的操作就描述了我们该如何在这些特定的结构中,拿到我们想要的数据,做怎样的操作,如何放回去。
常见的操作有:
存取、遍历、插入、查找、排序等。
我们实现操作的方式就是算法。这就是为什么这门课在一些场合全称为:“数据结构与算法”。
算法特性
曾获图灵奖的著名计算机科学家D.knuth对算法做出以下定义:一个算法是一个有穷规则的集合,其规则确定一个解决某一特定类型问题的操作序列。算法需要满足以下特性:
- 有穷性:操作步骤为有限个
- 确定性:任何条件下,算法都只有一条执行路径
- 可行性
- 有输入、输出
算法目标
- 正确性:能正确解决问题
- 健壮性:能对非法输入、极端条件作适当处理,结果可控
- 高时间效率
- 高空间效率
- 可读性:表达思路清晰,易于理解
算法与数据结构的联系
算法建立在数据结构之上,对数据结构的操作需要用到算法。
3. 时间复杂度
时间复杂度是非常重要的概念,常考,工作中也常用。
时间复杂度用 O(*)
表示。
常见的时间复杂度有:O(1), O(n),O(log2^n),O(nlog2^n),O(n^2),O(n^3)
等等
由于再往上的时间复杂度算法过高,实际执行起来程序太慢,因此这些算法在一般的场景也不会用,LeetCode 刷题根本不会通过,因此我们掌握这几种常见的时间复杂度算法计算即可。
时间复杂度的计算
时间复杂度的计算公式:
以下列举一些时间复杂度的例子,方便理解:
- O(1):单条语句为O(1)
int a = 0;
- O(n):执行n次为O(n)
for(int i = 0; i < n; i++){
}
一些循环也可以达到O(n) ,将次数控制到n即可,如:
for(int i = 0 ; i < n ; i*=2){
for(int j = 0; j < i ; j++){
}
}
我们来计算一下次数:Σ 2^i = 1+2+4+…+2(log2n) = 2n - 1; 忽略常数 , 为 n
- O(log2^n): 指数级别的循环次数
for(int i = 0 ; i < n ; i*=2){
}
- O(nlog2^n): 使用公式,我们可以得出O(nlog2^n)的一个例子
for(int i = 0 ; i < n ; i*=2){
for(int j = 0; j < n ; j++){
}
}
- O(n^2): 经典的二重循环就是O(n^2)
for(int i = 0 ; i < n ; i++){
for(int j = 0; j < n ; j++){
}
}
注意,由于实际运算的时候我们要忽略常数以及取较大的次数,因此下列示例时间复杂度也是O(n^2):
for(int i = 0 ; i < n ; i++){
for(int j = 0; j < i ; j++){
}
}
计算方式: Σi = n*
n / 2 = 1/2 *
n^2 , 忽略 1/2, 为 n^2
4. 空间复杂度
空间复杂度描述的是算法在执行过程中所占的空间, 例如变量的存储空间.
空间复杂度用O 表示为 O(f(n)) , 其中 f(n) 为空间增长率.
举个例子,当我们临时创建一个变量时,这个变量的空间复杂度为O(1).
如今,随着存储资源的"过剩", 实际工作中已很少通过直接计算空间复杂度来衡量算法的效率,而是采用监控工具实时监控系统的空间占用情况. 而且这部分考得较少, 若不是有特殊需求的同学, 这部分了解一下即可.