数据结构与算法
声明:笔记主要来源数据结构与算法[青岛大学-王卓],用于对数据结构的复习
https://www.bilibili.com/video/BV1nJ411V7bd
第一章数据结构的基本概念
第一节 数据结构的研究内容
例1以学生管理系统为例
操作对象:每位学生的信息(学号、姓名、性别、籍贯、专业等)
操作算法:查询、插入、修改、删除、
操作对象之间的关系:线性关系。 数据结构:线性数据结构,线性表。
例2人机对弈问题
井字棋
把策略输入到计算机中,根据当前的格局,预测棋局发展的趋势,甚至最后的结局。
计算机的操作对象:各种棋局的状态
计算机的算法:走棋,由一个格局派生出另一个格局。
操作对象之间的关系:非线性关系,树
例3:地图导航—求最短路径
操作对象:各地点及路的信息
计算机算法:设置信号灯,求出各个可同时通行的集合
对象之间的关系:非线性关系、网状结构
第二节基本概念和术语
数据:能够输入计算机且能被计算机处理的各种符号的集合
-
信息的载体
-
是对客观事物符号化的表示
-
能够被计算机识别、储存和加工
包括:
-
数值型数据:整数、实数
-
非数值型的数据:文字、图像、图形、声音。
数据元素:是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
数据项:构成数据元素的不可分割的最小单位。
数据对象:是性质相同的数据元素集合,是数据的一个子集。
例子:.1整数数据对象的集合N={0,±1,±2,……}
.2字母符号数据对象的集合C={‘A’,‘B’,……}
.3学籍表也可以看作一个数据对象
数据元素与数据对象
数据元素——组成数据的基本单位
与数据的关系:是集合的个体
数据对象——性质相同的数据元素的集合
与数据的关系是:集合的子集
数据结构:
数据元素是不孤立存在的,他们之间存在着某种关系,数据元素相互之间的关系称为结构。
是指相互之间存在一种或多种特定关系的数据元素集合。
或者说,数据结构是带结构的数据元素的集合。
注:1.数据元素之间的逻辑关系,也称逻辑结构。
2.数据元素及其关系在计算机内存中的表示(又称映像),称为数据的物理结构或数据的储存结构。
3.数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的储存结构上的实现。
数据结构的两个层次:
逻辑结构和物理结构
1.逻辑结构:
(1).描述数据元素之间的逻辑关系
(2).与数据的储存无关,独立于计算机。
是从具体问题抽象出来的数学模型。
2.物理结构:
(1).数据元素及其关系在计算机储存器中的结构(储存方式)。
(2).是数据结构在计算机中的表示。
逻辑结构和存储结构的关系:
1.储存结构是逻辑关系的映像与元素的映像。
2.逻辑结构是数据结构的抽象,储存结构是数据结构的实现。
逻辑结构的种类:
划分方法一
(1)线性结构
有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前驱和一个直接后继。
例:线性表、栈、队列、串、
(2)非线性结构
一个结点可能有多个直接前驱和直接后继。
例:树、图。
划分方式二——四类基本逻辑结构
(1)集合结构:结构中的数据元素之间除了 同属一个集合 的关系外,没有其他任何关系。
(2)线性结构:结构中的数据元素之间存在着 一对一的线性关系。
(3)树形结构:结构中的数据元素之间存在着一对多的层次关系。
(4)网状结构:结构中的数据元素之间存在多对多 的任意关系。
存储结构的分类:
顺序储存结构、链式储存结构、索引储存结构、散列储存结构。
(1)顺序储存结构:用一组 连续 的储存单元 依次 存储数据元素,数据元素之间的逻辑关系由元素的 储存位置 来表示。
注:C语言中用数组来实现顺序储存结构。
(2)链式存储结构:用一组 任意 的存储单元存储数据元素,数据元素之间的逻辑关系用 指针 来表示。
C语言中用指针来实现链式储存结构。
(3)索引存储结构:在存储结点信息的同时,还建立附加的索引表。
索引表中的每一项称为一个 索引项。
关键字是能唯一识别一个结点的那些数据项。
(4)散列存储结构:根据结点的关键字直接计算出该结点的存储地址。
第二节基本概念和术语(续)
1. 在使用高级程序语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明他们所属的数据类型。
提供 int 、char 、float、double等基本数据类型。
数组、结构、共用体、枚举等构造数据类型。
还有指针、空(void)类型
用户也可用typedef 自己定义数据类型
1.1一些最基本数据结构可以用数据类型来实现,如数组、字符串等
1.2另一些常用的数据结构,如 栈、队列、树、图等,不能直接用数据类型来表示。
2.1 高级语言中的数据类型明显地或隐含规定了在程序执行期间变量和表达式所有可能的取值范围,以及在这些数值范围上所允许进行的操作。
例如,C语言中定义变量 i 为int 类型,就是表示[-mix,max]范围的整数,在这个整数集上可以进行+、-、*、/、%等操作
数据类型的作用:
-
约束变量或常量的取值范围,
-
约束变量或常量的操作。
2.数据类型(Data Type)
- 定义:数据类型是一组性质相同的值的集合以及定义这个集合上的一组操作的总称。
- 数据类型 = 值得集合+值集合上的一组操作。
2.1 抽象数据类型(Abstract Data Type,ADT) :
-
是指一个数学模型以及定义在此数学模型上的一组操作。
-
由用户定义,从问题抽象出数据模型(逻辑结构)
-
还包括定义在数据模型上的一组抽象运算(相关操作)
-
不考虑计算机内的具体存储结构与运算的具体实现算法
2.2抽象数据类型的形式定义
2.3抽象数据类型定义格式如下:
ADT 抽象数据类型名{
Data
数据对象的定义
数据元素之间逻辑关系的定义
Operation
操作1
初始条件
操作结果描述
操作2
……
操作n
……
}ADT 抽象数据类型名
基本操作定义格式说明:
- 参数表:赋值参数只为操作提供输入值。
- 引用参数以&打头,除可提供输入值外,还将返回操作结果。
-
初始条件: 描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。若初始条件为空,则省略之
-
操作结果:说明操作正常完成之后,数据结构的变化状况和应返回的结果。
第三节 抽象数据类型的表示与实现
抽象数据类型(ADT)定义举例:Circle的定义
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
具体代码实现Circle_1:
#include<stdio.h>
typedef struct circle{
float r;
float area;
float peri;
}*Circle;
float area(Circle c);
float peri(Circle c);
float area(Circle c){
c->area = 3.14*c->r*c->r;
return c->area;
}
float peri(Circle c){
c->peri= 2*3.14*c->r;
return c->peri;
}
void main(){
struct circle a;
printf("请输入半径: ");
scanf("%f",&a.r);
printf("The area of this circle is=%.2f\n",area(&a));
printf("The Perimeter of this circle is=%.2f\n",peri(&a));
}
具体代码实现Circle_2:
#include<stdio.h>
#include<assert.h>
#define PI 3.141592654
typedef struct Circle{
float r;
float x;
float y;
}C;
void Circle(C* p){
float n;
printf("请输入半径:");
scanf("%f",&n);
p->r = n;
p->x = 0;
p->y = 0;
}
float Area(const C *p){
assert(p != NULL);
float ret = 0;
float r = p->r;
ret = PI*r*r;
return ret;
}
float perimeter(const C *p){
assert(p != NULL);
float ret = 0;
float r = p->r;
ret = 2*PI*r;
return ret;
}
int main(){
C c = {0};
Circle(&c);
float area = Area(&c);
float peri = perimeter(&c);
printf("The area of this circle is=%.2f\n",area);
printf("The Perimeter of this circle is=%.2f\n",peri);
return 0;
}
抽象数据类型:复数实现:
#include<stdio.h>
typedef struct
{
float Realpart; //实部
float Imagepart; //虚部
}Complex;
Complex Create(float x,float y)
{
//构造一个复数
Complex C;
C.Realpart=x;
C.Imagepart=y;
return C;
}
float GetReal(Complex C)
{
//取复数C=x+yi的实部
return C.Realpart;
}
float GetImag(Complex C)
{
//取复数C=x+yi的虚部
return C.Imagepart;
}
Complex Add(Complex C1,Complex C2)
{
//求两个复数C1和C2的和sum
Complex sum;
sum.Realpart=C1.Realpart+C2.Realpart;
sum.Imagepart=C1.Imagepart+C2.Imagepart;
return sum;
}
Complex Sub(Complex C1,Complex C2)
{
//求两个复数C1和C2的差difference
Complex difference;
difference.Realpart=C1.Realpart-C2.Realpart;
difference.Imagepart=C1.Imagepart-C2.Imagepart;
return difference;
}
int main ()
{
float a,b,c,d;
char e;
Complex C1,C2,C3;
//输入第一组复数中的a和b
printf("请输入复数C1的实部和虚部a和b(用空格隔开):");
scanf("%f %f",&a,&b);
C1=Create(a,b);
//输入第二组复数中的c和d
printf("请输入复数C2的实部和虚部c和d(用空格隔开):");
scanf("%f %f",&c,&d);
C2=Create(c,d);
//进行相加或相减操作
printf("请选择加法或是减法(+/-):");
getchar();
scanf("%c",&e);
if(e=='+')
C3=Add(C1,C2);
if(e=='-')
C3=Sub(C1,C2);
//输出结果
printf("两个复数的和/差为: %.2f+%.2fi",C3.Realpart,C3.Imagepart);
return 0;
}
第四节 算法和算法分析
算法的定义:
对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。
程序 = 数据结构+算法
数据结构通过算法实现操作
算法根据数据结构设计程序
1.4.1算法的特性
1、有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。/
2、确定性算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
3、可行性算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
4、输入一个算法有零个或多个输入/
5、输出―一个算法有一个或多个输出。
1.4.2、算法设计的基本准则:
1、正确性:
1、程序中不含语法错误;
2、程序对于几组输入数据能够得出满足要求的结果;
3、程序对于 精心选择的、典型、苛刻 且 带有刁难性 的几组输入数据能够得出满足要求的结果;
4、程序对于—切合法的输入数据都能得出满足要求的结果。
通常以第三层意义上的正确性作为衡量一个算法是否合格的标准
2、可读性:
1、算法主要是为了人的阅读和交流,其次才是为计算机执行,因此算法应该易于人的理解;
2、另一方面,晦涩难读的算法易于隐藏较多错误而难以调试。
3、健壮性:
1、指当 输入非法数据 时,算法恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。
2、处理出错的方法,不应是中断程序的执行,而应是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理
4、高效性:
要求花费尽量少的时间和尽量低的存储需求
算法效率以下两个方面来考虑:
1.时间效率:指的是算法所耗费的时间;
2.空间效率:指的是算法执行过程中所耗费的存储空间。
注:时间效率和空间效率有时候是矛盾的。
1.4.3 算法的时间复杂度:
1、算法时间效率的度量:
算法时间效率可以用依据该算法编制的程序在计算机上执行 所消耗的时间来度量。
两种度量方法:
(1)事后统计
.将算法实现,测算其的间和空间开销。
·缺点:编写程序实现算法将花费较多的时间和精力;所得实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣
(2)事前分析:
对算法所消耗资源的一种估算方法。
- 算法运行时间= —个简单操作所需的时间×简单操作次数
- 也即算法中每条语句的执行时间之和 又称为语句频度
- 算法运行时间 = 每条语句的执行次数×该语句执行一次所需的时间
例如:两个 n*n 矩阵相乘的算法可描述为:
for(i=1;i<=n;i++) //n+1次
for(j=1j<=n;j++){ // n(n+1)次
c[i][j] == 0; //n *n次
for(k=0;k<n;k++)// n*n*(n+1)次
c[i][j]=c[i][jl+a[i][k]*b[k][j];// n*n*n次
}
//我们把算法所耗费的时间定义为 该算法中每条语句的频度之和,则上述算法的时间消耗T(n)为:
// T(n)=2n3+ 3n2+ 2n +1
2、时间渐进复杂度
若有某个辅助函数f(n),使得当n趋近于无穷大时,T(h)/f(n)的极限值为 不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为 算法的渐进时间复杂度(О是数量级的符号),简称 时间复杂度。
平方阶实例:
×=O; y = 0;
for ( int k = 0; k < n; k++)
x++;
for ( int i = o; i<n; i++ )
for ( int j = 0;j < n; j++)
y++;
立方阶实例:
x = 1;
for(i=1; i<n;i++)
for(j=1;j<i;j++)
for(k=1;k<j;k++)
x++;
对数阶实例:
i=1; //(1)
while(i<=n)
i=i*2; //(2)
若循环执行1次: i=1*2=2
若循环执行2次:i=2*2=2^2
若循环执行3次:i=2*2=2^3,
,
若循环执行x次: i=2^x
设语句②执行次数为x次,由循环条件i< =n
-2x<=n :.x<=log2n
- 最坏时间复杂度:指在最坏情况下,算法的时间复杂度。
- 平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
- 最好时间复杂度:指在最好情况下,算法的时间复杂度。
1.4.4 算法的空间复杂度
空间复杂度:算法所需存储空间的度量,
记作:S(n)=O(f(n))
其中 n 为问题的规模(或大小)
//算法1
for(i=0;i<n/2;i++){
t = a[i];
a[i]=a[n-i-1];
a[n-i-1]=t;
}
//算法2
for(i=0;i<n;i++)
b[i]=a[n-i-1];
for(i=0;i<n;i++)
a[i]=b[i];
算法1空间复杂度为O(1)
算法2空间复杂度O(n)
//(2)
若循环执行1次: i=1*2=2
若循环执行2次:i=2*2=2^2
若循环执行3次:i=2*2=2^3,
,
若循环执行x次: i=2^x
设语句②执行次数为x次,由循环条件i< =n
-2x<=n :.x<=log2n
+ 最坏时间复杂度:指在最坏情况下,算法的时间复杂度。
+ 平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
+ 最好时间复杂度:指在最好情况下,算法的时间复杂度。
1.4.4 算法的空间复杂度
空间复杂度:算法所需存储空间的度量,
记作:S(n)=O(f(n))
其中 n 为问题的规模(或大小)
//算法1
for(i=0;i<n/2;i++){
t = a[i];
a[i]=a[n-i-1];
a[n-i-1]=t;
}
//算法2
for(i=0;i<n;i++)
b[i]=a[n-i-1];
for(i=0;i<n;i++)
a[i]=b[i];
算法1空间复杂度为O(1)
算法2空间复杂度O(n)