数据结构1 绪论
1.1 数据结构的研究内容
程序=算法+数据结构
数据结构主要研究内容为:研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作。
1.2基本概念及术语
1.2.1 数据、数据元素、数据项和数据对象
术语 | 概念 | 举例 |
---|---|---|
数据(Data) | 所有能输入到计算机中去的描述客观事物的符号集合 分为数值性数据和非数值性数据(多媒体信息处理)。 信息的载体,对客观事物的符号化表示,能够被计算机识别存储和加工。 | 数学计算中遇到的整数和实数,文本编辑中用到的字符串等。 |
数据元素(Data Element) | 数据的基本单位,也称结点(node)或记录(record)。 | 用于完整的描述一个对象,如学生成绩表中一名学生的记录。 |
数据项(Data Item) | 是组成数据元素的,有独立含义的、不可分割的最小单位也称域(field)。 | 如学生的姓名,性别,学号等。 |
数据对象(Data Object) | 是性质相同的数据元素的集合,是数据的一个子集。 | 整数数据对象N={0,1,2…} |
数据结构(Data Structure) | 是相互之间存在一种或多种特定关系的数据元素的集合,数据结构是带“结构”的数据元素的集合,“结构”就是指数据元素之间存在的关系 |
PS:数据、数据元素、数据项三者之间的关系
数据>数据元素>数据项
例:学生成绩表>个人成绩记录>学号、姓名…
1.2.2 数据结构
**数据结构:**包括三方面的内容。
- 数据元素之间的逻辑关系,也称为逻辑结构。
- 数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数据的存储结构。
- 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现。
1.2.2.1 逻辑结构
- 描述数据元素之间的逻辑关系
- 与数据的存储无关,独立于计算机
- 是从具体问题抽象出来的数学模型
逻辑结构的种类
划分方法一
-
线性结构
有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
如:线性表、栈、队列、串.
-
非线性结构
一个结点可能有多个直接前趋和直接后继。
如:树、图。
划分方法二——四种基本逻辑结构
-
**集合结构:**除了“属于同一集合”的关系外,别无其他关系。
-
**线性结构:**结构中的数据元素之间存在着一对一的线性关系。
-
**树形结构:**结构中的数据元素之间存在着一对多的层次关系。
-
**图状结构或网状结构:**结构中的数据元素之间存在着多对多的任意关系。
1.2.2.2 物理结构(存储结构)
- 数据元素及其关系在计算机存储器中的结构(存储方式)
- 时数据结构在计算机中的表示
存储结构的划分
-
顺序存储结构:
- 用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示。
- C语言中数组来实现顺序存储结构。
-
链式存储结构:
- 用一组任意的存储单元储存数据元素,数据元素之间的逻辑关系用指针来表示。
- C语言中用指针来实现链式存储结构。
-
索引存储结构:
- 在存储结点信息的同时,还建立附加的索引表。
- 索引表中的每一项称为一个索引项。
- 索引项的一般形式是:(关键字,地址)
- 关键字是能唯一标识一个结点的那些数据项。
- 若每个结点在索引表中都有一个索引项,则该索引表称之为稠密索引(Dense Index)。若一组结点在索引表中只对应一个索引项,则该索引表称之为稀疏索引(Sparse Index)。
-
散列存储结构:
- 根据结点的关键字直接计算出该结点的存储地址。
1.2.2.3 逻辑结构与存储结构的关系
-
存储结构时逻辑关系的映像与元素本身的映像。
-
逻辑结构时数据结构的抽象,存储结构是数据结构的实现
-
两者综合起来建立了数据元素之间的结构关系。
1.2.3 数据类型和抽象数据类型
1.2.3.1 数据类型
定义:数据类型是一个值的集合和定义在这个值集上的一组操作的总称。
-
在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明它们所属的数据类型。
-
一些最基本的数据结构可以用数据类型来实现,如数组、字符串等;
-
而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示。
-
高级语言中的数据类型明显或隐含的规定了在程序执行期间变量和表达的所有可能的取值范围,以及在这些数值范围上所允许进行的操作。
数据类型(Data Type):数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称。
数据类型=值的集合+值集合上的一组操作
1.2.3.2 抽象数据类型
**抽象数据类型(Abstract Data Type,ADT):**指一个数学模型以及定义在此数学模型上的一组操作。
- 由用户定义,从问题抽象出数据模型(逻辑结构)
- 还包括定义在数据模型上的一组抽象运算(相关操作)
- 不考虑计算机内的具体存储结构云运算的具体实现算法。
抽象数据类型的形式定义:
抽象数据类型可以用(D,S,P)三元组表示。
其中:D是数据对象;S是D上的关系集;P是对D的基本操作集。
定义格式:
ADT 抽象数据名{
A数据对象:<数据对象的定义>
D数据关系:<数据关系的定义>
T基本操作:<基本操作的定义>
}ADT 抽象数据类型名
其中:
- 基本操作名(参数表)
- 初始条件:<初始条件描述>
- 操作结果:<操作结果描述>
基本操作定义格式说明:
参数表:
赋值参数 只为操作提供输入值。
引用参数 以&打头,除可提供输入值外,还将返回操作结果。
**初始条件:**描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。若初始条件为空,则省略之。
**操作结果:**说明操作正常完成之后,数据结构的变化状况和应返回的结果。
ADT 抽象数据类型名{
Data
数据对象的定义
数据元素之间逻辑关系的定义
Operation
操作1
初始条件
操作结果描述
操作2
……
操作n
……
}ADT 抽象数据类型名
例:定义圆这一抽象数据类型
ADT Circle{
数据对象:D={r,x,y|r,x,y均为实数}
数据关系:R={<r,x,y>|r是半径,<x,y>是圆心坐标}
基本操作:
Circle(&C,r,x,y)
操作结果:构造一个圆。
double Area(C)
初始条件:圆已存在。
操作结果:计算面积。
double Circumference(C)
初始条件:圆已存在。
操作结果:计算周长。
……
}ADT Circle
总结
1.3 抽象数据类型的表示和实现
用C语言实现抽象数据类型
- 用已有的数据类型定义描述它的存储结构
- 用函数定义描述它的操作
抽象数据类型可以通过固有的数据类型(如整型、实型、字符型等)来表示和实现。
- 即利用处理器中已存在的数据类型来说明新的结构,用已经实现的操作来组合新的操作。
1.4 算法和算法分析
逻辑结构:研究对象的特性及其相互之间的关系。
存储结构:有效地组织计算机存储。
算法:有效地实现对象之间的“运算”关系。
- 算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法。
- 程序是用某种程序设计语言对算法的具体实现。
数据结构通过算法实现操作。
算法根据数据结构设计程序。
1.4.1 算法的定义及特性
算法(Algorithm):对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。
简而言之,算法就是解决问题的方法和步骤。
算法的五个重要特性:
- 有穷性 :算法应在执行有穷步后结束
- 确定性:每步指令都是确切、无歧义的
- 可行性:每一条操作都可以通过基本运算来实现
- 输入:有零个或多个输入
- 输出:有一个或多个输出(处理结果)
1.4.2 评价算法优劣的基本标准
- 正确性。
- 可读性。
- 健壮性
- 高效性。(效率和低存储要求)。
算法分析的目的是看算法是否可行,并在同一问题存在多的算法时可进行性能上的比较,以便从中挑选出比较优的算法。
算法效率从以下两个方面来考虑:
- 时间效率:指的是算法所耗费的时间;
- 空间效率:指的是算法执行过程中耗费的存储空间。
时间效率和空间效率有时是矛盾的。
1.4.3 算法的时间复杂度
算法时间效率的度量:算法的时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量。
度量方法
-
事后统计
- 将算法实现,测算其时间和空间开销。
-
事前分析(较多使用)
- 对算法所消耗资源的一种估算方法。
1.4.3.1 问题的规模和语句频度
**问题规模:**是算法求解问题输入量的多少,是问题大小的本质表示,一般用整数n表示。
**语句频度:**一条语句的重复执行次数。
1.4.3.2 算法的时间复杂度定义
1.4.3.3 算法时间复杂度分析举例
**最坏时间复杂度:**在最坏的情况下,算法的时间复杂度。
**平均时间复杂度:**指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
**最好的时间复杂度:**指在最好的情况下,算法的时间复杂度。
复杂度分阶
常数阶 | 对数阶 | 线性阶 | 线性对数阶 | 平方阶 | 立方阶 | … | K次方阶 | 指数阶 |
---|---|---|---|---|---|---|---|---|
O ( 1 ) O(1) O(1) | O ( log n ) O(\log n) O(logn) | O ( n ) O(n) O(n) | O ( n log n ) O(n\log n) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( n 3 ) O(n^3) O(n3) | O ( n k ) O(n^k) O(nk) | O ( 2 n ) O(2^n) O(2n) |
1.4.4 算法的空间复杂度
由其所占用的辅助空间大小决定。
例01.4.6-1
//算法1 原地算法S(n)=O(1)
for(i=0;i<n/2;i++)
{
t=a[i];
a[i]=a[n-i-1];
a[n-i-1]=t;
}
//算法2 辅助空间S(n)=O(n)
for(i=0;i<n;i++)
b[i]=a[n-i-1];
for(i=0;i<n;i++)
a[i]=b[i];
算法一:仅需要借助一个变量t,与问题规模n大小无关,所以其空间复杂度为
O
(
1
)
O(1)
O(1)
算法二:需要另外借助一个大小为n的辅助数组b,所以其空间复杂度为
O
(
n
)
O(n)
O(n)
对于一个算法,其时间复杂度和空间复杂度往往是相互影响的,当追求一个较好的时间复杂度时,可能会导致占用较多的储存空间,即可能会使空间复杂度的性能变差,反之亦然。通常情况下,鉴于运算空间较为充足,人们都以算法的时间复杂度作为算法优劣的衡量指标。
##01.5 错题集
- 以下说法正确的是()
A. 数据元素是数据的最小单位
B. 数据项是数据的基本单位
C. 数据结构是带有结构的各数据项的集合
D. 一些表面上很不相同的数据可以有相同的逻辑结构
解析(D)
A. 数据元素是数据的基本单位。
B. 数据项是数据的最小单位。
C. 数据结构是带“结构”的数据元素的集合
D. 正确
- 通常要求同一逻辑结构中的所有数据元素具有相同的特性,这意味着()
A. 数据具有同一特点
B. 不仅数据元素所包含的数据项个数要相同,而且对应数据项的类型要一致
C. 每个数据元素都一样
D. 数据元素所包含的数据项个数要相等
解析(B)
- 某算法的语句执行频度为
(
3
n
+
n
l
o
g
2
n
+
n
2
+
8
)
(3n+nlog_2n+n^2+8)
(3n+nlog2n+n2+8),其时间复杂度表示()
A. O ( n ) O(n) O(n)
B. O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
C. O ( n 2 ) O(n^2) O(n2)
D. O ( l o g 2 n ) O(log_2n) O(log2n)
解析(C)
- 以下程序中语句“x++;”的语句频度为()
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
for(k=1;k<=j;k++)
x++;
A.
n
(
n
+
1
)
(
2
n
+
1
)
2
\frac{n(n+1)(2n+1)}{2}
2n(n+1)(2n+1)
B.
n
(
n
+
1
)
(
n
+
1
)
2
\frac{n(n+1)(n+1)}{2}
2n(n+1)(n+1)
C.
n
(
n
+
1
)
(
2
n
+
1
)
6
\frac{n(n+1)(2n+1)}{6}
6n(n+1)(2n+1)
D.
n
(
n
+
1
)
(
n
+
2
)
6
\frac{n(n+1)(n+2)}{6}
6n(n+1)(n+2)
解析(D)
∑
i
=
1
n
∑
j
=
1
i
∑
k
=
1
j
1
\sum_{i=1}^{n}\sum_{j=1}^{i}\sum_{k=1}^{j}1
∑i=1n∑j=1i∑k=1j1
=
∑
i
=
1
n
∑
j
=
1
i
j
=\sum_{i=1}^{n}\sum_{j=1}^{i}j
=∑i=1n∑j=1ij
=
∑
i
=
1
n
i
(
i
+
1
)
2
=\sum_{i=1}^{n}\frac{i(i+1)}{2}
=∑i=1n2i(i+1)
=
1
2
(
∑
i
=
1
n
i
2
+
∑
i
=
1
n
i
)
=\frac{1}{2}(\sum_{i=1}^{n}i^2+\sum_{i=1}^{n}i)
=21(∑i=1ni2+∑i=1ni)
=
1
2
(
n
(
n
+
1
)
(
2
n
+
1
)
6
+
n
(
n
+
1
)
2
)
=\frac{1}{2}(\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2})
=21(6n(n+1)(2n+1)+2n(n+1))
=
n
(
n
+
1
)
(
n
+
2
)
6
=\frac{n(n+1)(n+2)}{6}
=6n(n+1)(n+2)
- 下面说法错误的是()。
(1) 算法原地工作的含义是指不需要任何额外的辅助空间
(2) 在相同的规模下n下,复杂度 O ( n ) O(n) O(n)的算法在时间上总是优于复杂度 O ( 2 n ) O(2^n) O(2n)
(3) 所谓时间复杂度是指最坏情况下,估算算法执行时间的一个上界
(4) 某算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),表明该算法的时间与 n 2 n^2 n2成正比
A. (1)
B. (1),(2)
C. (1),(4)
D. (3)
解析(A)
原地算法指算法执行时所需要的辅助空间,其相对于输入量而言是个常数。故(1)错误。
- 设该数据结构A=(D,R),其中D={1,2,3,4},R={r},r={<1,2>,<2,3>,< 3,4>,<4,1>},则该数据结构A是()
A. 线性结构
B. 树结构
C. 图
D. 集合
解析(C)
数据元素间存在多对多的关系。
例
定义复数数据结构及部分操作
在主函数中完成下式求解
z
=
(
8
+
6
i
)
(
4
+
3
i
)
(
8
+
6
i
)
+
(
4
+
3
i
)
z=\frac{(8+6i)(4+3i)}{(8+6i)+(4+3i)}
z=(8+6i)+(4+3i)(8+6i)(4+3i)
#include<stdio.h>
typedef struct {
float realpart; //实部
float imagpart; //虚部
}Complex; //定义复数抽象类型
/*定义操作*/
void assign(Complex* A, float real, float imag); //赋值函数
void add(Complex* c, Complex A, Complex B);//A+B
void minus(Complex* c, Complex A, Complex B);//A-B
void multiply(Complex* c, Complex A, Complex B);//A*B
int divide(Complex* c, Complex A, Complex B);//A/B
void GetReal(Complex c, float* Realpart);//获取实部
void GetImag(Complex c, float* Imagpart);//获取虚部
void assign(Complex* A, float real, float imag)//赋值函数
{
A->realpart = real; //实部赋值
A->imagpart = imag;//虚部赋值
}
//A+B
void add(Complex* c, Complex A, Complex B)
{
c->realpart = A.realpart + B.realpart; //实部相加
c->imagpart = A.imagpart + B.imagpart; //虚部相加
}
//A-B
void minus(Complex* c, Complex A, Complex B)
{
c->realpart = A.realpart - B.realpart;
c->imagpart = A.imagpart - B.imagpart;
}
//A*B
void multiply(Complex* c, Complex A, Complex B)
{
c->realpart = A.realpart * B.realpart - A.imagpart * B.imagpart;
c->imagpart = A.imagpart * B.realpart + A.realpart * B.imagpart;
}
//A/B
int divide(Complex* c, Complex A, Complex B)
{
if (B.realpart* B.realpart + B.imagpart *B.imagpart)
{
c->realpart = (A.realpart * B.realpart + A.imagpart * B.imagpart) / (B.realpart * B.realpart + B.imagpart*B.imagpart);
c->imagpart = (A.imagpart * B.realpart - A.realpart * B.imagpart) / (B.realpart * B.realpart + B.imagpart*B.imagpart);
return 1;
}
else
{
return 0;
}
}
//获取实部
void GetReal(Complex c, float* Realpart)
{
*Realpart = c.realpart;
}
//获取虚部
void GetImag(Complex c, float* Imagpart)
{
*Imagpart = c.imagpart;
}
int main()
{
float Realpart, Imagpart;
Complex z1, z2, z3, z4, z;
assign(&z1, 8.0, 6.0);
assign(&z2, 4.0, 3.0);
add(&z3, z1, z2);
multiply(&z4, z1, z2);
if(divide(&z, z4, z3))
{
GetReal(z, &Realpart);
GetImag(z, &Imagpart);
printf("%f+%f i", Realpart, Imagpart);
}
else
{
printf("无解");
}
}