一、数据结构概念:

对数据之间的关系的结构类型进行总结归纳。

王争的数据结构与算法 王道 数据结构_算法

学好这门课,让我们成为信息革命的参与者。

名词解析:

数据项:您申请一个微博账号,其中姓名,性别这些就是数据项

组合项:您账号的生日是由年月日组成,年月日就是组合项

数据元素:由n个数据项组成的集合

王争的数据结构与算法 王道 数据结构_结点_02

 

 何为结构呢?

 

王争的数据结构与算法 王道 数据结构_数据结构_03

 

王争的数据结构与算法 王道 数据结构_算法_04

就你去设计一个数据库的时候,你需要考虑的就是运用什么逻辑结构设计这个数据库

比如你使用了线性结构,然后就要考虑线性结构的存储结构是什么,有顺序,链式,索引以及散列。

⭐️考点:从逻辑上可以把数据结构分成线性结构和非线性结构

王争的数据结构与算法 王道 数据结构_数据结构_05

 

王争的数据结构与算法 王道 数据结构_结点_06

王争的数据结构与算法 王道 数据结构_数据结构_07

 

王争的数据结构与算法 王道 数据结构_机器学习_08

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_09

数据元素之间的物理关系是什么?

王争的数据结构与算法 王道 数据结构_算法_10

 

王争的数据结构与算法 王道 数据结构_算法_11

 

王争的数据结构与算法 王道 数据结构_机器学习_12

🌟考点:清楚存储结构有两大类,顺序存储与非顺序(链式、索引、散列)

王争的数据结构与算法 王道 数据结构_数据结构_13

王争的数据结构与算法 王道 数据结构_结点_14

王争的数据结构与算法 王道 数据结构_机器学习_15

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_16

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_17

三大结构:

1、线性结构 

2、树型结构 

3、圆型结构 

两大操作:

查找操作 

排序操作 

课后习题

王争的数据结构与算法 王道 数据结构_算法_18

王争的数据结构与算法 王道 数据结构_结点_19

王争的数据结构与算法 王道 数据结构_结点_20

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_21

 

王争的数据结构与算法 王道 数据结构_结点_22

二、算法的基本概念

王争的数据结构与算法 王道 数据结构_机器学习_23

🌟考点:

算法的特性

1、输入输出:可以没有输入,至少一个输出

2、有穷:有限的步骤完成

3、确定:无二义

4、可行:每一步可行

设计算法的要求

1、正确

2、可读性

王争的数据结构与算法 王道 数据结构_数据结构_24

3、健壮

4、高时效低存储

王争的数据结构与算法 王道 数据结构_算法_25

 

王争的数据结构与算法 王道 数据结构_数据结构_26

 三、算法的效率如何度量之时间复杂度

 

王争的数据结构与算法 王道 数据结构_数据结构_27

 

王争的数据结构与算法 王道 数据结构_结点_28

王争的数据结构与算法 王道 数据结构_算法_29

时间复杂度的计算方法是这样的

把代码行用数字标记,然后看每一行的代码会执行几次,最后把每一行执行的次数相加/

最后您可以把计算的这个程序的时间复杂度进行一个总结成一个公式并且化简。

不同的问题公式不同。

您观察以下式子,每个公后面的列对总体计算结构影响可以忽略不计。

甚至连n项前面的k值也可以忽略不计。

当n值趋近于无穷的时候,两个同阶的公式之比为常数。

王争的数据结构与算法 王道 数据结构_结点_30

加法规则:比如o^2+o,那么就保留o^2即可。

乘法规则:如果是两个式子相乘,那么就都保留。

举个例子,如果o^3+o^2log2n,这是加法规则,该保留哪位呢?

王争的数据结构与算法 王道 数据结构_算法_31

王争的数据结构与算法 王道 数据结构_数据结构_32

 这张图需要背下来

 

王争的数据结构与算法 王道 数据结构_机器学习_33

王争的数据结构与算法 王道 数据结构_数据结构_34

 

王争的数据结构与算法 王道 数据结构_结点_35

王争的数据结构与算法 王道 数据结构_结点_36

 

王争的数据结构与算法 王道 数据结构_机器学习_37

 

王争的数据结构与算法 王道 数据结构_数据结构_38

王争的数据结构与算法 王道 数据结构_数据结构_39

王争的数据结构与算法 王道 数据结构_算法_40

算法的效率如何度量之空间复杂度

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_41

 

王争的数据结构与算法 王道 数据结构_数据结构_42

 

王争的数据结构与算法 王道 数据结构_算法_43

 

王争的数据结构与算法 王道 数据结构_数据结构_44

王争的数据结构与算法 王道 数据结构_数据结构_45

课后习题:

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_46

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_47

 

王争的数据结构与算法 王道 数据结构_算法_48

四、线性表的定义和基本操作

线性结构也可以默认是线性表

线性结构=(D,R,O) D:数据集 R:关系/结构 O:操作

线性结构也可以看成是线性表,线性表包括数据之间的物理结构中的线性存储、链式存储、索引存储、散列(暂时不要求掌握)

如果我们要设计若干个班长候选人选票信息,那么我们需要设计数据类型和存储结构

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_49

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_50

王争的数据结构与算法 王道 数据结构_算法_51

现在我们来看第一种线性结构 顺序表

ElemType 数据类型的意思 

SqList是什么?

这里静态动态SeqList和SqList只是这个结构体的名字。在main函数中起一个声明线性表的作用。

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_52

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_53

如果您需要调用malloc和free这两个函数,您需要在代码声明#include <stdib.h>头文件

这一段讲解又看不明白了的话去b站p8 15:00分重新看

定义一个顺序表,这个顺序表的数据结构类型是int类型,*data这个指针指向顺序表中的第一个元素

然后我们实现了一个函数initList用于初始化一个动态分配方式实现的

再增加一个increaseSize函数用于增加动态数组的长度

之后我们再在main函数中调用相关的操作

我们再看这段代码,首先先在main函数中声明一个顺序表。

然后使用InitList来申请一片连续的存储空间,注意此处malloc函数前要用int强制转换成和开头时候int *data同类型的数据。

L.data=(int *)malloc(InitSize*sizeof(int)); //这行是用malloc申请一片连续的存储空间,这片存储空间的大小是能存的下10个int类型数据的大小,然后malloc函数会返回一个指针,其类型要转换成int型,然后把malloc返回的值赋给data。

sizeof:sizeof是C语言的一个操作符,类似于++、–等。sizeof能够告诉我们编译器为某一特定数据或者某一个类型的数据在内存中分配空间时分配的大小,大小以字节为单位

此时需要把顺序表的长度设置为0 L.length=0; //因为是初始化表,所以这里长度为0

把顺序表的最大容量设置为InitSize与L.data中保持一致

此时内存图中的MaxSize为10,length为10,data为橙色部分

王争的数据结构与算法 王道 数据结构_数据结构_54

再看main函数,如果您想往顺序表中再插入五个元素,而此时内存空间已满。

就需要用IncreaseSize函数来动态增加数组的长度

函数内的len表示我要拓展的长度,我们在main函数中传入值5 IncreaseSize(L,5);

第一句我们定义了一个指针p,把顺序表的中的data里的值赋给p。也就是说p指针和data是指向同一个位置

再看下一句 malloc是申请一整片的内存空间,此时能存下目前有的容量MaxSize以及加上现在要扩展的长度len

因为此时malloc申请的内存空间是新的一片内存空间,此时这片内存空间里没有任何数据,我们用data这个指针指向新的内存空间,然后再用for循环把之前内存空间里的数据挪过去

因为内存容量的值增加了五个,所以L.MaxSize=L.MaxSize+len

然后 free函数会把指针p指向的那一片的存储空间给释放掉

王争的数据结构与算法 王道 数据结构_算法_55

王争的数据结构与算法 王道 数据结构_数据结构_56

 

王争的数据结构与算法 王道 数据结构_数据结构_57

为班级30位同学的语数英成绩,设计一个统计系统

//首先先建一个学生信息表
typedef struct Students

{

char sno[11]; //里面的11是这个学号是11位,所以这里放11,因为

char sname[20]; //这里考虑少数民族,所以这里字符数放20位

int c;

int e;

int m;

}

//再定义顺序表
#define M 35 //表的最大长度为35
{
  ElemType data[M]; //静态表存储
  int length; //定义表长,这里int后的变量名可随便取

}

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_58

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_59

 

王争的数据结构与算法 王道 数据结构_机器学习_60

 

王争的数据结构与算法 王道 数据结构_算法_61

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_62

顺序表的基本操作——插入


王争的数据结构与算法 王道 数据结构_机器学习_63

在for循环时候,此时顺序表当前的长度为5(因为有五个元素),如果长度大于你要插入的那个数据的位置,则顺序表中的最后一位向下挪一位 j--

for循环里的第一句的意思是,把data[4]里的数据放到第五位data[5]的位置去。

这里注意!代码里说挪到第五位,这里是位序的概念!当前序列表长度虽然为5,但是处于第五位(最后一位)的数据元素位序为4.

因为现在我们要在位序为3的位置插入数据元素3,所以跳出for循环之后写L.data[i-1]=e;

王争的数据结构与算法 王道 数据结构_结点_64

 其实这个代码还不够健壮,因为如果ListInsert(L,9,3);

而此时这个表的长度还只有6位,这样则插在了位序下标为8的地方,但是之前我们说过顺序表的特性就是必须有前序和后序节点。这样就会报错。

此时我们可以加一个判断 插入i的合法值在[1,length+1],如果再要在第九位插入数据,因为已经超出了合法范围,所以后续结构都不应该再继续。

再者,如果插入的数据合法,但是您的表此时内存已经满了,这时也无法运行。

而此时您除了考虑会遇到的这些意外情况外,还要站在使用者的角度来看,作为使用者,他需要知道自己的数据是否插入成功,程序需要给到使用者一定的反馈。

所以这里用bool判断

王争的数据结构与算法 王道 数据结构_算法_65

王争的数据结构与算法 王道 数据结构_结点_66

 

王争的数据结构与算法 王道 数据结构_机器学习_67

顺序表的基本操作——删除

SqList &L是指看你取的是哪个顺序表,i是要删除这个顺序表中的第几个

&e是一个引用型的参数,用这个参数把此次要删除的数据元素返回。

然后您看main函数,因为上个插入操作时候遗留的表里遗留了六个数据元素

如果您现在想删除这个数据元素的话,首先您要定义一个和你的顺序表中存储的这些数据元素同类型的一个变量,因为我们这个顺序表中存的都是int型,所以int e=-1;这里的-1是我们自己随便写的一个初始值。写这个意味着我们内存中会开辟一小片空间用于存放e这个变量相关的数据。所以我们给它设置了一个初始值。所以此时e这片内存空间里存放的内容是-1这个值。

接下来再调用删除这个基本操作,要删除L这个表中的第三个元素。然后此时把此次要删除的那个元素用e这个变量给返回。

我们再判断里先判断里这个内存值是否合法,如果false,此时我们main函数中的if语句就已经调用失败了。因为我们要删的是第3个,是合法的,所以会执行e=L.data[i-1]; 这行代码会把此次要删除的数据元素的值复制到e这个变量所对应的内存区域当中

大家要注意:

首先我们定义的这个删除操作,e这个变量它是引用型的,由于加了这个&引用符号,所以在ListDelete这个函数里的变量e和main函数里定义的那个变量e在内存中对应的是同一份数据,如果没有加这个&引用符号,在最后输出的时候就还是会输出最开始定义的那个-1.

王争的数据结构与算法 王道 数据结构_算法_68

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_69

王争的数据结构与算法 王道 数据结构_结点_70

顺序表的基本操作——查找

王争的数据结构与算法 王道 数据结构_数据结构_71


王争的数据结构与算法 王道 数据结构_算法_72

如果您还想让您的程序更加健壮一些,您可以在这里判断一下i的值是否合法,与插入,删除一样不再展开

而如果此时您用动态表来查找,初始化顺序表部分里的data其实是一个指针,这个指针指向了顺序表中的第一个数据元素,存储这个顺序表所需要的内存空间是用malloc函数申请的一整片的连续空间。

王争的数据结构与算法 王道 数据结构_算法_73


虽然data是一个指针,但它依然可以用这种数组下标的方式

王争的数据结构与算法 王道 数据结构_数据结构_74

 我们分析一下在计算背后发生了些什么 

初始化顺序表部分里的data变量其实是一个指针,这个data指针指向了malloc函数给它分配的一整片连续内存空间的起始地址,假设此时指针data指向的地址是2000,在下面这张图里一个小格代表一个字节的大小。

注意这里如果一个ElemType占6B,如果您要查找第一个元素,那么从指针data指向的2000这个地址开始,然后您看这个return L.data[i-1],这里在下标就是从为0的数据开始的,但是它要占据6个字节。如果您这个i输入值为2的话,那么就是从data[1]那里开始往后数6位。

我们在ElemType里定义的data指针,它所指向的数据类型是ElemType这种类型,所以如果你按照这种数组下标的方式来写代码的话,计算机在背后会根据你的这个data指针所指向的数据类型它所占用的空间大小来给你计算每一个数组下标它应该对应的是哪几个字节的数据。

王争的数据结构与算法 王道 数据结构_结点_75

如果这里定义类型是int型,p起始指向的地址也是2000的话,而int型一个字节只占4B

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_76

王争的数据结构与算法 王道 数据结构_算法_77

能够实现随机存取的基础在于顺序表中所有的元素在内存表中都是连续存放的,并且这些元素的数据类型相同,也就是说每个数据元素所占的内存空间是一样大的。

只需要知道一个顺序表的起始地址,然后再知道每一个数据元素的大小,就可以立即找到第i个元素存放的位置。这就是按位查找。

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_78

 按值查找就是要找到线性表L当中有没有哪个数据元素和我们传入的这个参数e是相等的,如果能找到这样的数据元素的话,那么就要返回这个数据元素的存放位置。

(tips:在c语言中,=是赋值,==是判断)

王争的数据结构与算法 王道 数据结构_结点_79

在LocateElem函数里传入一个参数e 

然后开始执行for循环,i=0的意思是从这个顺序表最开始的那个元素依次往后开始检索i++。

此处的i时数组下标

if语句L.data[i]==e依次判断顺序表中的各个数据元素和我们传入的这个数据元素e是否相等

如果相等的话,返回这个元素的位序

王争的数据结构与算法 王道 数据结构_算法_80

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_81

 

王争的数据结构与算法 王道 数据结构_数据结构_82

 

王争的数据结构与算法 王道 数据结构_机器学习_83

 

王争的数据结构与算法 王道 数据结构_算法_84

首先我们回顾前十节课的内容,学习了顺序表的基本操作与实现。

所以如果有问题是问物理存储结构的话,那就分为顺序表和链式。

王争的数据结构与算法 王道 数据结构_数据结构_85

线性链表

首先我们先学习单链表

王争的数据结构与算法 王道 数据结构_算法_86

 question1 什么是单链表?

在一片内存空间中存放元素,每一块元素用一个结点存储,该结点除了存放数据元素外,还要存储指向下一个节点的指针。像这样每个结点用一个指针指向下一个结点的表叫单链表

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_87

 顺序表和单链表的优劣点:

单链表的各个节点在物理上可以是离散存放的,所以当我们要拓展单链表的长度时,只需要在内存中随便抠一小块区域作为存放新结点的区域就可以,比如下列图片中红色圆圈区域。

所以说采用链式存储的话,改变容量会很方便,但是采用这种方式,我们需要找到某一个位序的结点,只能从第一个元素开始检索,直到找到我们想要的那个节点。所以这种单链表的方式不支持随机存取。

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_88

 用代码定义一个单链表

单链表是由一个一个的结点组成的,而一个结点当中它需要有一个空间是用于存放数据元素,还需要有另一片空间存放指向下一个结点的指针。 

所以我们定义一个struct类型的结构体用于表示一个结点。这个结点中有一个叫data的变量用于存放这个数据元素,我们把这个称之为数据域。

另外我们需要定义一个指向下一个结点的指针,这个指针变量的名字叫next,我们把这个指针叫指针域。

王争的数据结构与算法 王道 数据结构_结点_89

 了解这个结构体的定义之后,想要往单链表增加一个新结点的话,这里我们可以malloc函数来申请一片存储这个结点的空间,并且用指针p来接收malloc函数的返回值,让它指向这个结点的起始地址。

之后就可以涉及一些代码逻辑,把p结点插入到这个单链表当中,按照我们background color:yellow部分的代码的写法,以后当我们想要定义一个新的结点,或者定义一个想要指向结点的指针的时候,都得写

struct LNode*p=(struct LNode*)malloc(sizeof(struct LNode));

王争的数据结构与算法 王道 数据结构_算法_90

 但因为这么写有点麻烦,每次都得带上struct这个关键字。

教材里使用了typedef(c语言中的关键字———数据类型重命名)

这个关键字可以把数据类型重命名,用法很简单,其实就是在你的代码里先写一个typedef关键字,后面跟一个你想要重命名的数据类型,再空一行写入你想要给它取的另一个别名。

ps:struct也是一种数据类型 只不过此处是结构体数据类型

王争的数据结构与算法 王道 数据结构_结点_91


 

在教材中我们采用这种方式来写,非常简洁。它等价于下面那种先struct定义。

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_92

 这段其实等价于下面这行咱们最开始定义的LNode的数据类型,下面这两句typedef表示您把它先重命名为LNode,并且用*LinkList来表示这是一个指向struct LNode的指针

王争的数据结构与算法 王道 数据结构_机器学习_93

我们要表示一个单链表,只需要声明一个头指针L,这个头指针指向单链表的第一个结点。

由于各个结点是由LNode指针把它们一个一个连起来的,所以只要我们找到了第一个结点,我们就找到了整个单链表。

即然L这个指针指向了某一个结点,我们定义L可以像这样来定义

定义指针

LNode * L;

根据上面那句typedef struct LNode *LinkList的重命名,LNode * L这句还可以等价于

LinkList L;

LNode * L; 和LinkList L;这两种声明方式在效果上来看是一模一样的,但是这样写LinkList L;代码可读性更高

这二者都是声明指针,有什么区别呢?

举个栗子,现在我们有一个基本操作叫GetElem,就是把L这个链表当中第i个结点给取出来,并且return p给返回。

在这行代码中既使用了LNode *,又使用了LinkList L,虽然二者在本质上是等价的,但GetElem这个函数最终是要返回第i个结点,所以它要返回值的类型代码中定义成了LNode *,这里LNode *想强调的是这是我返回的一个结点。

而括号中的LinkList L它想强调的其实是它是一个单链表

所以括号里其实可以用LNode *L的方式来定义L这个参数,但因为这里并不想强调L它是一个结点,而是一个单链表。因此才使用了这样的命名方式。

王争的数据结构与算法 王道 数据结构_机器学习_94

现在我们来看怎么初始化单链表?

首先是不带头结点的单链表

step1 申明一个指向单链表的指针L,执行这句之后,内存中会开辟一小块空间,用于存放头指针L

再往后执行初始化函数,把L的值设为NULL来表示当前是一个空表,做这一步是为了防止之前这一块空间有遗留的脏数据。在bool括号里传入&L这个指针变量时,我们是传入了LinkList L的引用,如果不加&这个符号,那么我们在bool这个函数里修改的这个L就是头指针函数里的一个复制品。

对于这种不带头结点的单链表,判断它是否为空的依据就是看它的头指针L此时是不是为NULL。

或者写的更简洁一些

因为L==NULL直接返回的就过就是true or  false

王争的数据结构与算法 王道 数据结构_算法_95

 然后是带头结点的单链表

step1 申请一个单链表

step2 在初始化单链表的函数中用malloc函数申请一片空间存下LNode这样的结点,并且把malloc返回的地址赋给L,所以我们说头指针式指向了这个节点

step3 需要把L这个指针指向的节点next这个指针域把它设为NULL,意思是这个头节点是不存储数据的,我们设这个头节点是为了后面实现一些基本操作会更方便一些。

这种带头节点的单链表我们要判断它是否为空的话,就判断这个头节点的next指针域是否为空

 

王争的数据结构与算法 王道 数据结构_结点_96

二者就是写代码是否更方便的区别。

如果不带头节点的话,那么头指针指向的下一个节点,那个结点就是实际存放数据的结点。

而如果带头结点的话,头指针它所指向的这个结点把它称为头结点,这个头结点是不存放实际数据元素的,只有这个头结点之后的下一个结点才会用于存放数据。

 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_97

王争的数据结构与算法 王道 数据结构_结点_98

王争的数据结构与算法 王道 数据结构_机器学习_99

单链表的插入与删除

王争的数据结构与算法 王道 数据结构_机器学习_100

 1、带头节点的插入

如果我们需要在表L中第i的位置插入元素e,我们需要找到第i-1个结点,并修改其next指针。

假如i=2,那么我们首先需要找到第一个结点,也就是a1,然后用malloc函数申请一个新的结点,再往这个新结点里存入元素e,然后再对其左右结点的指针进行修改,这时这个新结点就变成了第二个结点。

假如i=1,此时就能看到这种带头结点的好处,我们可以把头结点看成是“第0个”结点,以上分析的处理逻辑同样适用。

王争的数据结构与算法 王道 数据结构_结点_101

下面我们看完整代码

解析:
ListInsert函数当中传入了一个&L单链表,这个单链表是带头结点的,int i是指定了此次要插入的位置的位序,ElemType e是给出了新结点当中要存放的数据元素的意思

如果我们要在i为1的位置插入新元素的话

首先代码先来一个判断if(i<1),因为位序是从1开始的,如果i<1就不合法,表示插入失败,则return false。

因为此时i=1,因此这里申明一个指针p,LNode *p

这个指针p指向了和L相同的位置(p = L),即指向了头结点。

int j=0; //我们定义了一个变量j,j表示的是当前p指向的是第几个结点,因为头结点是第0个结点,所以此时j的值应该是0.

注意此时虽然我们杜撰了一个第0个结点,可是它不存数据,实际存放数据的是头结点后面的那些结点们。而结点的编号都是从1开始的,所以我们再if条件里不允许i<1

我们进入while循环,这一步是循环找到第i-1个结点,因为i=1,所以不满足j<1-1,则跳出循环,同时也跳过if(p==NULL)

进入LNode *s =这句,它会申请一个新的结点空间

s->data =e;  //把参数e存到新结点里

s->next=p->next; //让s的指向的这个结点的next指针,让它等于p结点的指针指向的这个位置

王争的数据结构与算法 王道 数据结构_数据结构_102

 p->next=s; //让p结点的next指针指向新的结点s,也就是这样。

王争的数据结构与算法 王道 数据结构_数据结构_103

以下是完整代码:

王争的数据结构与算法 王道 数据结构_数据结构_104

而且由于i=1,代码中while循环直接被跳过,所以这里时间复杂度为0(1)

注意s->next=p->next; 和 p->next=s;这两句不能颠倒

如果i=3

我们再看代码,这里会进入while循环,由于前面的操作,使得j的值为0,同时p也不为null,进入循环。

此时p等于p的next,即让p指向下一个结点

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_105

        ➡️     

王争的数据结构与算法 王道 数据结构_结点_106

然后j的值就变成了1,由于此时j依然是小于2的,所以还要再来一次循环

p还要再指向下一个结点

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_107

此时j值为2,跳出循环,然后再往后和i=1时情况一样 

王争的数据结构与算法 王道 数据结构_机器学习_108

在表尾的位置插入新结点:这种情况 while循环执行次数最多,所以时间复杂度最坏,这里的n指的是表的长度。

 

王争的数据结构与算法 王道 数据结构_结点_109

分析:

当i=6时

王争的数据结构与算法 王道 数据结构_数据结构_110

 当j=5时,就跳出循环执行if(p==NULL)了,而因为i的值太大,直接return false

按位序插入(带头结点) 平均时间复杂度:O(n)

 1、不带头节点的插入

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_111

分析:

当i=1时,进入if(i==1)判断中,申请一个新的结点LNode *s=(LNode *)malloc(sizeof(LNode));

s->data=e; //把e存写到里面

s->next=L; //让新结点的next指针指向L所指向的这个结点

王争的数据结构与算法 王道 数据结构_结点_112

L=s; //最后需要修改头指针L 指向这个新的结点,然后return true表示插入成功

王争的数据结构与算法 王道 数据结构_结点_113

 由此可见,插入/删除第一个元素,如果是不带头结点的情况,需要更改头指针L的指向

所以在这里应该能体会不带头结点写起代码来会更麻烦。 

如果i>1,和带头结点的代码是一样的,只不过int j变成了1,int j=1

王争的数据结构与算法 王道 数据结构_数据结构_114

 此后代码默认是带头结点的写法。

指定结点的后插操作  

给定一个结点,在这个结点之后插入一个数据元素e

LNode *s = (LNode *)malloc(sizeof) 首先先malloc一个结点

if (s==NULL) return false如果malloc执行的结果是返回的一个NULL的话,则说明此次内存分配失败。

若没有这些特殊情况的话,就和直接的操作逻辑顺序一样。

s->data=e;

s->next=p->next;

p->next=s;

王争的数据结构与算法 王道 数据结构_算法_115

还记得之前在插入操作的代码吗?其逻辑其实就是找到结点,然后在其后插。

王争的数据结构与算法 王道 数据结构_机器学习_116

 然后现在有了这个后插操作的函数,在while找到要插入的结点之后,直接调用即可。

return InsertNextNode(p,e);

王争的数据结构与算法 王道 数据结构_数据结构_117

指定结点的前插操作:

如果想要找到指定结点的前一个结点,找到头结点即可知道全貌。

如果您想在a3的位置前插一个元素,那么我们找到头指针之后依次遍历各个结点,找到a3的前驱结点,之后再对a2进行后插操作

王争的数据结构与算法 王道 数据结构_数据结构_118

 如果不给你传头指针的话怎么办呢?

看另一种思路

首先申请一个新的结点

王争的数据结构与算法 王道 数据结构_机器学习_119

然后s->next=p->next;

王争的数据结构与算法 王道 数据结构_机器学习_120

p->next=s;然后把这个结点作为p结点的后继结点 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_121

 我的结点不能跑路,但结点里的数据可以跑路 

 s->data=p->data;这句代码会把p结点以前存放的数据元素x给复制到s中

p->data=e; //把新插到数据元素e把它放到p这个结点里

 这种也能实现效果,且时间复杂度是o(1)

王争的数据结构与算法 王道 数据结构_结点_122

 王道书里直接给了结点s,找不到p的前驱结点,就先把s这个结点连接到p之后

再申明一个temp变量,先保存一下p结点里的内容。再把s结点的内容复制到p结点里,也就是p中的x变成了e,最后再把temp的值复制到s里面。

至此就实现了结点的前插操作。

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_123

按位序删除(带头结点)

王争的数据结构与算法 王道 数据结构_机器学习_124

 如果要删除i=1的元素的话,要将指针指向删除的结点之后的那个结点。并且还要用free函数把删除的那个结点内存给释放掉

如果i=4,则代表我们要删除的是a4这个结点,前面代码和插入时候逻辑一样

王争的数据结构与算法 王道 数据结构_数据结构_125

然后我们再定义一个指针q: LNode *q=p->next; q指针指向了p结点的next,也就是指向了第i个结点。

e=q->data; 接下来会把q结点的这个数据元素复制到变量e里面,注意这个变量e要把此次删除的结点的值给带回到这个函数的调用那里 即


王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_126

 所以e这个参数是引用类型的

p->next=q->next //p的next要指向q的next,也就是指向NULL

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_127

 最后调用free函数把q结点给释放掉

王争的数据结构与算法 王道 数据结构_机器学习_128

 由于我们需要用while来一次循环找到第i-1个结点,所以这个算法

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_129

 代码全貌

王争的数据结构与算法 王道 数据结构_结点_130

删除指定结点 

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_131

我们要删除结点p,

LNode *q=p->next; 首先先申明一个结点q为p的后继结点

我们把p的后继结点的数据,把它复制到p结点的数据域中

 

王争的数据结构与算法 王道 数据结构_算法_132

  ➡️ 

王争的数据结构与算法 王道 数据结构_结点_133

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_134

 p->next=q->next; 然后再让p结点的next指针指向q结点之后的位置,可能指向的是一个结点,也可能是指向NULL

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_135

 最后free(q)

王争的数据结构与算法 王道 数据结构_算法_136

 这种时间复杂度为1

思考?

王争的数据结构与算法 王道 数据结构_结点_137

 如果p就是最后一个结点的话,那么在和后继结点交换数据域时就会出错

王争的数据结构与算法 王道 数据结构_数据结构_138

 

王争的数据结构与算法 王道 数据结构_算法_139

 所以到往后我们会学习双链表

 

王争的数据结构与算法 王道 数据结构_机器学习_140

单链表的查找

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_141

1、按位查找

王争的数据结构与算法 王道 数据结构_机器学习_142

按位查找会出现以下极端情况

1、i=0

如果i为0的话直接返回第一个头结点

2、i=8(这种情况大于链表长度)

看代码p=L; //p是指向了头结点,j的值刚开始是0,第一轮while循环后,p指针从头结点指向下一个结点。

然后逐步while循环最终p指针指向NULL,再有这种情况,你基本可以判定i值不合法。

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_143

这些边界情况都是我们在开发中必须考虑到的,这让我们的程序更有健壮性。

3、普通情况i=3

这个算法平均时间复杂度:O(n)这个数量级

平均指的是该程序的i值在合法范围内取值内任何一个数字的概率都等可能的情况。

2、按值查找

ElemType e给你一个数据元素e,LinkLIst L看在这个单链表中有没有哪个结点的值是为e的。

假设本例中ElemType的数据类型是int,如果e这个变量为8:e=8

LNode *p:首先让一个p指针

L->next:指向头结点的下一个结点

即指向第一个数据结点。

王争的数据结构与算法 王道 数据结构_算法_144

之后再进入while循环,此时p不等于NULL满足条件,且p目前所指的这个结点的数据域的值不等于e的值。

p=p->next;  此时p指针指向下一个结点。

在第二轮循环时候,第二个结点的数值正好为8,此时把p结点给返回。

王争的数据结构与算法 王道 数据结构_结点_145

该算法的平均时间复杂度:O(n)

 情况2 e=6,不能找到的情况

如果结果返回NULL,则说明该链表中没有e=6的数据。

如果ElemType是更复杂的结构类型呢?例struct

自行百度两个struct类型如何判断其相等。

王争的数据结构与算法 王道 数据结构_结点_146

 该算法时间复杂度:O(n)

思考一下不带头结点呢?

王争的数据结构与算法 王道 数据结构_算法_147

    单链表的建立

王争的数据结构与算法 王道 数据结构_机器学习_148

 

王争的数据结构与算法 王道 数据结构_机器学习_149

step1第一步在前面几节课已经讲过

1、尾插法建立单链表 

由于我们每次都要把数据元素插入到单链表的表尾,所以我们可以设置一个变量length记录链表长度,再写一个while循环,每次取出一个数据元素e,然后调用位序插入ListInsert这个基本操作,每一个都把数据元素e都插入到第length+1的位置,像下面这个例子,length+1等于4,也就是插入到红色箭头的位置,也就是表尾的位置,每次插入一个新的元素之后,都会导致单链表的长度length+1,这样的方式就可以实现用尾插法建立一个单链表

王争的数据结构与算法 王道 数据结构_算法_150

 用这种方式实现的话,当你每次要在表尾的位置插入一个元素的时候,它都会用这个while循环从表头的位置开始依次往后遍历,直到找到最后一个结点,按照这个逻辑,当我们要插入第一个元素的时候,也就是只有一个头结点的时候,这个while循环可以直接跳过,循环次数是0次,我们要插入第二个元素的时候,while循环一次,插入第三个元素的时候,while循环两次,以此类推。所以我们要插入n个元素的话,总共需要循环n-1次,算出来时间复杂度是O(n^2)

王争的数据结构与算法 王道 数据结构_结点_151

这个时间复杂度很高,而且我们没必要每次都从头开始找。

解决方案:

设置一个表尾指针r,之后再在尾部插入结点,则对尾部进行一个后插操作即可。

int x;首先声明了一个局部变量x

L=(LinkList)malloc(sizeof(LNode)));然后用malloc函数申请了一个头结点,也就是说这里面做了初始化一个单链表的操作,只不过我们自己初始化一个单链表的时候会把头结点的指针先把它设为NULL,而该代码没有这样操作,因为头结点的指针会在后面被修改。

王争的数据结构与算法 王道 数据结构_机器学习_152

LNode *s,*r=L; //申明了两个指针,这两个指针都指向了头结点。

王争的数据结构与算法 王道 数据结构_机器学习_153

scanf("%d",&x); //让用户从键盘里输入一个整数,这个整数x就是此次要单链表中的数据元素

while(x!9999)这里随便写了个9999,如果用户输入该数,这里就结束。

我们输入数字10,因此进入循环

s=(LNode*)malloc(sizeof(LNode)); //malloc申请一个新的结点,让s这个指针指向新结点

s->data=x; //并且把新结点的这个数值设为x,也就是此次输入的这个数字。

王争的数据结构与算法 王道 数据结构_算法_154

r->next=s; //把r结点的next指针指向s这个结点。

王争的数据结构与算法 王道 数据结构_算法_155

r=s; //最后再让r指针指向s这个结点

 

王争的数据结构与算法 王道 数据结构_算法_156

 假设我们下一个数输入数字16,则继续执行while循环,设置一个新结点

王争的数据结构与算法 王道 数据结构_王争的数据结构与算法_157

                   ⬇️

王争的数据结构与算法 王道 数据结构_结点_158

 如果输入9999,则跳过循环

r->next=NULL;  //让r结点的next指向NULL

return L; //最后再调用返回这个红色圆圈的头指针L

王争的数据结构与算法 王道 数据结构_结点_159

王争的数据结构与算法 王道 数据结构_结点_160

头插法建立单链表

王争的数据结构与算法 王道 数据结构_数据结构_161

王争的数据结构与算法 王道 数据结构_数据结构_162

双链表

为什么要要使用双链表:

  • 单链表:无法逆向检索,有时候不太方便
  • 双链表:可进可退,但是存储密度更低一丢丢

双链表就是在单链表的基础上再增加一个指针域prior,属于前驱结点。

双链表中的结点我们把它命名为DNode。(D指的就是double)

王争的数据结构与算法 王道 数据结构_结点_163

双链表的初始化(带头结点):

void{

DLinklist L //申明了一个指向头结点的指针L

IinitDLinkList(L); //调用双链表的初始化函数

}

InitDLinkList{

L=(DNode *)malloc(sizeof(DNode)); //申请一片内存空间用来存放头结点,并且让L指针指向这个头结点

「 L-prior=NULL; L->next=NULL;」//前后指针都设为NULL

}

然后您再看typedef里声明链表,和单链表类似。

DNode *和DLinklist等价,而您反观bool函数和void有的方法类似,有的地方使用DLinklist L是想强调它是一个链表,而有的地方使用DNode *是想强调是一个结点。

如果您想要判断双链表是否为空,您就判断头结点的next指针是否为NULL就可以了

如果为NULL,说明此时链表暂时没有存入任何数据元素

王争的数据结构与算法 王道 数据结构_数据结构_164

双链表的插入:

step1 把s结点的next指针指向p结点的下一个结点 (s指向橙色方块y)

step2 把p结点的后继结点,它的前项指针指向此次新插入的s这个结点 (橙块y指向s)

step3 把s结点的前项指针指向p结点

step4 把p结点的后继指针指向s结点

王争的数据结构与算法 王道 数据结构_数据结构_165

  • 小心如果p结点为最后一个结点产生的空指针问题,因此循环链表应运而生(详见后面的循环链表插入删除)
  • 注意指针的修改顺序

双链表的删除:


王争的数据结构与算法 王道 数据结构_算法_166

双链表的遍历:

王争的数据结构与算法 王道 数据结构_结点_167

循环链表

循环单链表与单链表的区别:

单链表:

  • 表尾结点的next指针指向NULL
  • 从一个结点出发只能找到后续的各个结点

循环单链表:

  • 表尾结点的next指针指向头结点
  • 从一个结点出发可以找到其他任何一个结点

循环单链表初始化:

王争的数据结构与算法 王道 数据结构_数据结构_168

循环双链表:

循环双链表的初始化:

  • 从头结点找到尾部,时间复杂度为O(n)
  • 如果需要频繁的访问表头、表尾,可以让L指向表尾元素(插入、删除时可能需要修改L)
  • 从尾部找到头部,时间复杂度为O(1)

    循环双链表与双链表的区别:
    双链表:
  • 表头结点的prior指向NULL
  • 表尾结点的next指向NULL
  • 表头结点的prior指向表尾结点
  • 表尾结点的next指向头结点

循环链表的插入:

 

王争的数据结构与算法 王道 数据结构_机器学习_169

注意点:

写代码时候注意以下几点,以此规避错误:

如何判空

如何判断结点p是否是表尾/表头元素(后向/前向遍历的实现核心)

如何在表头、表中、表尾插入/删除一个结点

静态链表

什么是静态链表:

分配一整片连续的内存空间,各个结点集中安置

每个结点由两部分组成:data(数据元素)和next(游标)

0号结点充当“头结点”,不具体存放数据

游标为-1表示已经到达表尾

游标充当“指针”,表示下个结点的存放位置,下面举一个例子:

每个数据元素4B,每个游标4B(每个结点共8B),设起始地址为addr,e1的存放地址为addr + 8*2(游标值)

定义静态链表:


方法2:

王争的数据结构与算法 王道 数据结构_数据结构_170

基本操作:

初始化:

把a[0]的next设为-1

把其他结点的next设为一个特殊值用来表示结点空闲,如-2

插入位序为i的结点:

找到一个空的结点,存入数据元素(设为一个特殊值用来表示结点空闲,如-2)

从头结点出发找到位序为i-1的结点

修改新结点的next

修改i-1号结点的next

删除某个结点:

从头结点出发找到前驱结点

修改前驱结点的游标

被删除结点next设为-2

总结:

静态链表:用数组的方式实现的链表

优点:增、删操作不需要大量移动元素

缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变

适用场景:①不支持指针的低级语言;②数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

顺序表和链表的比较

逻辑结构:

都属于线性表,都是线性结构

存储结构:

顺序表:

优点:支持随机存取、存储密度高

缺点:大片连续空间分配不方便,改变容量不方便

链表:

优点:离散的小空间分配方便,改变容量方便

缺点:不可随机存取,存储密度低

基本操作:

顺序表:

创建

需要预分配大片连续空间。

若分配空间过小,则之后不方便拓展容量;

若分配空间过大,则浪费内存资源

静态分配:静态数组实现,容量不可改变

动态分配:动态数组(malloc、free)实现,容量可以改变但需要移动大量元素,时间代价高

销毁

修改Length = 0

静态分配:静态数组,系统自动回收空间

动态分配:动态数组(malloc、free),需要手动free

增删

插入/删除元素要将后续元素都后移/前移

时间复杂度O(n),时间开销主要来自移动元素

若数据元素很大,则移动的时间代价很高

按位查找:O(1)

按值查找:O(n)若表内元素有序,可在O(log2n)时间内找到

链表:

创建

只需分配一个头结点(也可以不要头结点,只声明一个头指针),之后方便拓展

销毁

依次删除各个结点(free)

增删

插入/删除元素只需修改指针即可

时间复杂度O(n),时间开销主要来自查找目标元素

查找元素的时间代价更低

按位查找:O(n)

按值查找:O(n)

用哪个:

表长难以预估、经常要增加/删除元素——链表

表长可预估、查询(搜索)操作较多——顺序表

开放式问题的解题思路:

问题: 请描述顺序表和链表的bla bla bla…实现线性表时,用顺序表还是链表好?

答案:

顺序表和链表的逻辑结构都是线性结构,都属于线性表。

但是二者的存储结构不同,顺序表采用顺序存储…(特点,带来的优点缺点);链表采用链式存储…(特点、导致的优缺点)。

由于采用不同的存储方式实现,因此基本操作的实现效率也不同。

当初始化时…;当插入一个数据元素时…;当删除一个数据元素时…;当查找一个数据元素