第一章 绪论——数据结构基本概念和术语

系列文章目录

第一章 绪论——数据结构基本概念和术语


文章目录


前言

例如:本系列为观看bilibili数据结构与算法(青岛大学-王卓老师)的视频笔记,要去观看请点这里视频链接,如果错误,还请见谅。

《数据结构与算法》的学习内容
在这里插入图片描述

在这里插入图片描述


一、数据结构基本概念和术语

1、数据(Data)

数据是所有能输入到计算机中并被计算机程序处理的各种符号的总称(集合)。

  • 是信息的载体。
  • 是对客观事物的符号化表示。
  • 能计算机识别、存储和加工。

数据的类型包括:

  • 数值类型的数据:整型、实型等。
  • 非数值类型的数据:图形、图像、声音、视频及动画等。

2、数据元素(DataElement)

数据元素是组成数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。

  • 在有些情况下,数据元素也称为元素、记录、节点、顶点等。
  • 一个数据元素可由若干个数据项组成。
    例如在下图学生表里,以某位同学一行的信息作为一个数据元素,而不是每个单元格作为一个元素。
    在这里插入图片描述

3、数据项(Data Item)

数据项是组成数据元素的、有独立含义的、不可分割的最小单位。
例如在下图学生表里,每个单元格都可以作为一个数据项,如学号、姓名、性别等都是数据项。
在这里插入图片描述


4、数据对象(DataObject)

数据对象(DataObject)是性质相同的数据元素的集合,是数据的一个子集。
如下图所示
在这里插入图片描述


5、数据、数据对象、数据元素、数据项之间的关系

数据元素——组成数据的基本单位。与数据的关系:是集合的个体。
数据对象——性质相同的数据元素的集合。与数据的关系:是集合的子集。

在这里插入图片描述


二、数据结构内容

数据结构(Data Structure):数据元素不是孤立存在的,它们之间存在着某种关系,我们把数据元素相互之间的关系叫做结构。所以,数据结构为数据元素相互之间存在一种或多种特定关系的数据元素集合。
数据结构包括以下三个方面的内容:

  1. 数据元素之间的逻辑关系,也称为逻辑结构。
  2. 数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数据的存储结构。
  3. 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现。
    在这里插入图片描述

1、逻辑结构

逻辑结构是描述数据元素之间的逻辑关系,它与数据的存储无关,是独立于计算机的,是看从具体问题抽象出来的数学模型。

2、物理结构(存储结构)

物理结构是数据元素及其关系在计算机存储器(ROM、内存)中的结构。(存储方式),是数据结构在计算机中的表示。

3、逻辑结构与存储结构的关系

存储结构是逻辑关系的映象与元素本身的映象。逻辑结构是数据结构的抽象,存储结构是数据结构的实现。两者综合起来建立了数据元素之间的结构关系。


三、数据结构划分

1、逻辑结构种类

1.1、划分方法一:以是否为线性结构划分

  • 线性结构:有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。例如:线性表、栈、队列、串等。
  • 非线性结构:一个节点可能有多个直接前趋和直接后继。例如:树状结构、图状结构等。
    在这里插入图片描述

1.2、划分方法二:以四类基本逻辑机构划分

  • 集合结构:结构中的数据元素之间除了同属于一个集合的关系外,无任何其它关系。
  • 线性结构:结构中的数据元素之间存在着一对一的线性关系。
  • 树状结构:概结构中的数据元素之间存在着一对多的层次关系要。
  • 图状结构:结构中的数据元素之间存在着多对多的任意关系。在这里插入图片描述

2、存储结构种类

2.1、顺序存储结构

用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示。 C语言中用数组来实现顺序存储结构。
如下图所示:把该字符串以相同的顺序存储到内存中。
在这里插入图片描述

在这里插入图片描述

2.2、链式存储结构

用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系用指针来表示,并且,相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。C语言中用指针来实现顺序存储结构。
如下图所示:把该字符串存在内存中时,不仅需要存储内容,还需要存储下一个内容的地址,查找字符串时需要通过链表来寻找字符串内容。
在这里插入图片描述
在这里插入图片描述

2.3、索引存储结构

在存储元素信息的同时,还建立附加的索引表,索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)

2.4、散列存储结构

根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储。
在这里插入图片描述


四、数据类型

1、什么是数据类型?

数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称。说起数据类型其实我们并不陌生,当我们在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明它们所属的数据类型。
Tips:一些最基本数据结构可以用数据类型来实现,如:数组、字符串等,而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示。

2、数据类型作用

  • 约束变量或常量的取值范围。
  • 约束变量或常量的操作。

C语言数据类型如下图所示
在这里插入图片描述


五、抽象数据类型

1、抽象

通过百度百科我们可以知道抽象的概念,简单的说,抽象就是我们把事物的本质给拿出来,然后把非本质的东西给舍弃掉。
在这里插入图片描述
如下图所示,我们抽象的把这张图概括为圆,我们舍弃了它非本质的东西,比如大小、颜色等
在这里插入图片描述

2、抽象数据类型(Abstract Data Type)

抽象数据类型(Abstract Data Type,简称:ADT) 是指一个数学模型以及定义在这个模型上的一组操作。

  • 由用户定义,从问题抽象出数据模型(逻辑结构)
  • 还包括定义在数据模型上的一组抽象运算(相关操作)
  • 不考虑计算机内的具体存储结构与运算的具体实现算法 。

六、抽象数据类型的表现与实现

1、抽象数据类型的形式定义

在这里插入图片描述

2、基本定义格式

ADT 抽象数据类型名{
	数据对象:<数据对象的定义>
	数据关系:<数据关系的定义>
	基本操作:<基本操作的定义>
	基本操作的格式:
				 基本操作名(参数表)
				 		初始条件:〈初始条件描述)
				 		操作结果:〈操作结果描述》
}ADT 抽象数据类型名

Tips:数据对象、数据关系的定义用伪代码描述

3、基本操作定义格式说明

  • 参数表:赋值参数,只为操作提供输入值。
  • 引用参数以"&"打头,除可提供输入值外,还将返回操作结果。
  • 初始条:描述了操作执行之前数据结构和参数应满足的条件,若初始条件为空,则省略。
  • 操作结果:说明了操作正常完成之后,数据结构的变化状况和应返回的结果。

4、如何用类C语言实现抽象数据类型

类C语言是保留了C语言的最核心的一些东西,同时可以忽略C语言的一些语法细节,让编程人员能够更专注对程序算法的思想分析,而不受语法的约束。
下面代码为我用类C语言写的抽象数据类型,请与C语言写的操作数据类型进行对比
在这里插入图片描述


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 Ciecle

5、如何用C语言实现抽象数据类型

  1. 用已有数据类型定义描述它的存储结构
  2. 用函数定义描述它的操作
    Tips:使用类C语言作为描述工具
    抽象数据类型可以通过固有的数据类型(如整型、实型、字符型寺)来表示和实现,即用已经实现的操作来组合新的操作。

Tips:下面是我用C语言写得抽象数据类型

#include <stdio.h>
//定义一个圆的结构体为Circle数据类型
typedef struct {
	float Radius;	//半径
	float Area;		//面积
	float Circum;	//周长
}Circle;
//申明构造圆的函数
void Assign(Circle* C, float r);
//申明求圆面积的函数
void Area(Circle* C);
//申明求圆周长的函数
void Circum(Circle* C);

void main()
{
	//初始化圆的半径为0,面积和周长也为0
	Circle c = {0,0,0};
	//调用构造函数
	Assign(&c,5);
	//调用求面积函数
	Area(&c);
	//调用求周长函数
	Circum(&c);
	printf("圆的面积是:%f\n圆的周长是: %f", c.Area,c.Circum);
}

//构造圆的函数
void Assign(Circle* C, float r)
{
	printf("请输入半径:");
	scanf_s("%f",&r);
	//通过指针箭头符号,把用户输入半径r直接传递给圆的Radius并且改变其值
	C->Radius = r;	//r半径赋值
}
//构造圆的面积函数
void Area(Circle* C)
{
	C->Area= C->Radius * C->Radius * 3.1415926;
}
//构造圆的周长函数
void Circum(Circle* C)
{
	C->Circum = 2 * 3.1415926 * C->Radius;
}

七、算法

算法:对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。简而言之,算法就是解决问题的方法和步骤。

1、算法的描述

  • 用自然语言描述(中文、英文等);
    如下图所示
    ``在这里插入图片描述

  • 传统流程图,NS流程图描述
    如下图所示
    在这里插入图片描述
    在这里插入图片描述

  • 用伪代码(类C语言)描述(如上一章所写的伪代码)

  • 用程序设计(C、Java…)描述

2、算法与程序的关系

算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,所以一个问题可以有多种算法,而程序是用某种程序设计语言对算法的具体实现。
程序=数据结构+算法

  • 数据结构通过算法实现操作
  • 算法根据数据结构设计程序

3、算法的特性

一个算法必须具有的五个重要特性

  1. 有穷性一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。(这里的有穷并不是一定取它极限的位置,而是取在一个为了解决问题可以接受范围内)
  2. 确定性:算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
  3. 可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
  4. 输入:一个算法有零个或多个输入。
  5. 输出:一个算法必须要有一个或多个输出。

4、算法的设计要求

4.1、正确性(Correctness)

能正确的反映问题的需求,能得到正确的答案。

  1. 程序中不含语法错误
  2. 程序对于几组输入数据能够得出满足要求的结果
  3. 程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果
  4. 程序对于—切合法的输入数据都能得出满足要求的结果

Tips:因为我们不可能把—切合法的输入数据都能得出满足要求的结果,因此一般情况下我们把层次3作为算法是否正确的标准。

4.2、可读性(Readability)

算法主要是为水人的阅读和交流,其次才是为了计算机执行,因此算法应该易于人的理解,另一方面,晦涩难读的算法易于隐藏较多错误而难以调试

4.3、健壮性(Robustness)

  • 当输入非法数据时,算法恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。
  • 处理出错的方法,不应是中断程序的执行,而应是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。

4.4、高效性(Efficiency)

要求花费尽量少的时间和尽量低的存储需求。


八、算法分析

1、算法比较

由于相同的问题,可以存在多个算法,所以算法分析的目的是看算法实际是否可行,并在同一问题存在多个算法时可进行性能上的比较,以便从中挑选出比较优的算法。
一个好的算法首先要具备正确性,然后是健壮性,可读性,在几个方面都满足的情况下,主要考虑算法的效率通过算法的效率高低来评判不同算法的优劣程度。
算法效率主要从以下两个方面来考虑:

  1. 时间效率:指的是算法所耗费的时间
  2. 空间效率:指的是算法执行过程中所耗费的存储空间。
    Tips:时间效率和空间效率有时候是矛盾的。

2、算法时间效率

算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量。
衡量算法时间效率的方法主要有两类:

2.1、事后统计法

事后统计法:事后统计法需要先将算法实现,然后测算其时间和空间开销。
缺点:首先必须把算法转换成可执行的程序,而编写程序实现算法将花费较多的时间和精力,同时所得实验结果依赖于计算机的软硬件等环境因素,可能会掩盖算法本身的优劣,所以我们通常采用事前分析估算法

2.2、事前分析法

事前分析法为对算法所消耗的资源的一种估算方法

  1. 一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的时间与算法中进行的简单操作次数乘积。
  2. 算法运行时间 = 一个简单操作所需的时间 × 简单操作次数
    算法运行时间 = ∑ 每条语句的执行次数(语句频度) × 该语句执行一次所需的时间
    算法运行时间 = ∑ 每条语句频度 × 该语句执行一次所需的时间

    每条语句执行一次所需的时间,一般是随机器而异的。取决于机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的,它与算法无关。所以,我们可假设执行每条语句所需的时间均为单位时间。此时对算法的运行时间的讨论就可转化为讨论该算法中所有语句的执行次数,即频度之和了。

2.3、渐近时间复杂度

代码如下所示

for(i = 0;i <= n;i++) ///执行n+1次
{
	for(j = 0;j <= 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][j] + a[i][k] + b[k][j];//执行n*n*n次
	}
}

我们把算法所耗费的时间定义为该算法中每条语句的频度之和,则上述算法的时间消耗T(n)为:
T ( n ) = 2 n ³ + 3 n ² + 2 n + 1 T(n) = 2n³+3n²+2n+1 T(n)=2n³+3n²+2n+1
因为我们需要去把每条执行语句执行次数给数出来,然后再把它们加起来计算,假如有个程序执行语句非常的多,那么这样就会很麻烦,所以为了方便比较不同算法的时间效率,我们仅比较它们的数量级。
若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为
不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号),简称时间复杂度。

所以,当n → oo时,T (n)/n3→2这表示n充分大时,T(n)与n3是同阶或同数量级,引入大“O”记号,则T(n)可记作:
T ( n ) = O ( n 3 ) T(n) = O(n3) T(n)=O(n3)
这就是求解矩阵相乘问题的算法的渐进时间复杂度
Tips:这里用n³作为f(n)是因为n³为原式T(n)最高项的同阶,当n趋于无穷大时,原式T(n)里的3n²+2n+1趋近于0,这里如果没理解,可以去看下高等数学的极限、无穷小、导数等章节

  • 一般情况下,不必计算所有操作的执行次数,而只考虑算法中基本操作执行的次数,它是问题规模n的某个函数,用T(n)表示。
  • 算法中基本语句重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作:T(n)=Of(n))
  • 它表示随着n的增大,算法执行的时间的增长率和f(n)的增长率相同,称渐近时间复杂度。
    如下图所示,忽略所有低次幂项和最高次幂系数,体现出增长率的含义
    在这里插入图片描述

2.4、分析算法时间复杂度的基本方法
  1. 找出语句频度最大的那条语句作为基本语句
  2. 计算基本语句的频度得到问题规模n的某个函数f(n)
  3. 取其数量级用符号“O”表示

例1、如下图所示

x = 0; //执行1次
y = o;	//执行1次
for ( int k = 0; k < n; k ++)	//执行n次
   X++;					 	//执行1次
for ( int i = O; i < n; i+)	//执行n+1次
   for ( int j = 0; j < n; j++)//执行n*(n+1)次
   	y++;					//执行n*n次
∵ 以嵌套循环最深的语句为基本语句f
∴ f(n)=n²
∴ T(n) = O()

例2、如下图所示

void exam ( float x[ ][ ], int m, int n ) 
{
   float sum[ ];
   for ( int i = 0; i< m; i++ ) 	//执行了m次
   {
   	sum[i] = 0.0;
   	for ( int j = o; j < n; j++ )	//执行了m*n次
   		sum[]+= x[i][j];	//执行了m*n次
   }
   for ( i = 0; i < m; i++ )
   	cout << i << ":"<< sum[i] << endl;
}
∵ 以嵌套循环最深的语句为基本语句f
∴ f(n) = m*n
∴ T(n) = O(m*n)

当去数循环太麻烦的时候,我们可以通过级数去计算

例3、如下图所示

{
   i = 1;
   while(i <= n)
   	i += i *2; 
}
当运行1次,i = 2;
当运行2次,i = 2²;
当运行3次,i = 2³;
......
当运行x次,i = 2^x;
∵ i <= n;2^x <= n;
∴ x <= log₂n
∵ 以嵌套循环最深的语句为基本语句f(n)为循环次数
  2^f(n) <= n;f(n) <= log₂n
∴ f(n)取最大值
∴ T(n) = O(log₂n)T(n) = O(logn)

2.5、最好、最坏和平均时间复杂度

有的情况下,算法中基本操作重复执行的次数还随问题输入的数据集不同而不同。
顺序查找,在数组a[i]中查找值等于e的元素,返回其所在位置。

for (i = O;i < n; i++)
{
	if (a[i]==e) 
		return i+1; /找到,则返回是第几个元素
	else
		return 0;
}

最好的情况:执行1次
最坏的情况:执行n次
平均时间复杂度:O(n)
  • 最坏时间复杂度:指在最坏情况下,算法的的时间复杂度。
  • 平均时间复杂度:指在所有输入实例在等概率出现的情况下,算法的期望运行时间。
  • 最好时间复杂度:指在最好情况下,算法的的时间复杂度。
    一般总是考虑在最坏情况下或者平均的时间复杂度,以保证算法的运行时间不会比它更长。**

2.6、复杂算法的计算法则

对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大O加法法则和乘法法则,计算算法的时间复杂度。

  1. 加法规则
    T ( n ) = T ₁ ( n ) + T ₂ ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n)=T₁(n)+T₂(n)=O(f(n))+O(g(n))=O(max(f(n),g(n))) T(n)=T(n)+T(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))

  2. 乘法规则
    T ( n ) = T ₁ ( n ) × T ₂ ( n ) = O ( f ( n ) ) x O ( g ( n ) ) = O ( f ( n ) × g ( n ) ) T(n)=T₁(n)×T₂(n)=O(f(n))xO(g(n))=O(f(n)×g(n)) T(n)=T(n)×T(n)=O(f(n))xO(g(n))=O(f(n)×g(n))

  3. 算法时间效率比较
    当n取得很大时,指数时间算法和多项式时间算法在所需时间上非常悬殊
    如下图所示
    在这里插入图片描述
    在这里插入图片描述

时间复杂度T(n)按数量级递增顺序为:在这里插入图片描述


2.7、渐近空间复杂度

算法的空间复杂度:算法所需存储空间的度量,记作:
S ( n ) = O ( f ( n ) ) ( n 为 问 题 的 规 模 或 大 小 ) S(n) = O(f(n))(n为问题的规模或大小) S(n)=O(f(n))(n)
算法要占据的空间

  • 算法本身要占据的空间,输入/输出、指令、常数、变量等。
  • 算法要使用的辅助空间。

例:将一维数组a中的n个数逆序存放到原数组中。
算法1

for(i = O;i < n/2; i++)
{
	t=a[i];
	a[i]=a[n-i-1];
	a[n-i-1]=t;
}
//空间复杂度
S(n) = O(1)
T(n) = O((n-1)/2)

算法2

for(i=0;i<n;i++)
	b[]=a[n-i-1];
for(i=0;i<n;i++)
	a[i]=b[i];
//空间复杂度
S(n) = O(n)
T(n) = O(n)

所以,相同的问题算法1的时间复杂度和空间复杂度都比算法2的低,所以这里可以说算法1比算法2好。
但是有些时候,鱼和熊掌不可兼得,时间复杂度和空间复杂度是互相矛盾的,所以如何选择算法还是要看需求,但是一般情况下,时间复杂度更重要一些,我们宁愿多分配一些内存空间也要提升程序的执行速度。


总结

本章绪论基本写完,如有新增再行修改。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

应景丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值