我们在学习顺序表的时候,发现我们利用顺序表去进行插入删除的时候,需要移动大量的元素才能完成这一操作,而且如果存储空间不够的时候,我们又要重新划分一个存储空间给顺序表。
如果存储空间太够又会造成一定程度的浪费。那我们可不可以创造一个随用随取的模式呢?
我们有5个元素,就给5个存储空间,既不会浪费也不会存储不足
欸,这就是我们下面要讲的链表。
链表的定义
我们首先想一下我们链表在存储空间是怎么样的
是不是在存储空间里面创造一个个元素,然后再链接起来
那我们就可以把这些一个个的元素称为结点,结点分为数据域和指针域
长这样,我们称 n 个结点链结成的一个链表,就是线性表的链式存储结构
有些专业术语我们要知道什么意思
头指针:我们把链表中第一个结点的存储位置叫做头指针,意思就是指向链表中第一个节点的指针
头结点:是在链表的首元结点之见附设的一个结点
首元结点:是指链表中存储第一个数据元素a1的结点
具体来讲就是这样
还有一个概念
空表,空表就是没有结点的表呗,具体长这样
要注意,两个都能表示空表
单链表的结构
下面我们来学习单链表的结构
typedef struct Node //创建一个结点
{
ElemType data; //存放单个结点的数据
struct Node *next; //存放下个结点的地址
}Node, *LinkList;
链表的一系列操作
链表的初始化
int InitLL(LinkList *L)
{
(*L) = (LinkList)malloc(sizeof(Lnode)); //申请一块空间用来存放链表
(*L)->next = NULL; //L的next域为NULL
return OK;
}
单链表的销毁
画图不好看我就直接那王卓老师的网课图示来了(感谢王卓老师🙏)
int DestroyList_L(LinkList *L){
Node *p; //创建一个指针
while(L == NULL){
p = L; //根据图示,先让指针指向L
L = L->next; //L再根据下一个地址移动
free(p); //把p释放掉(c++的话可以用delete p也是一样的)
}
return OK;
}
单链表的清空
我们要区分销毁和清空的区别,销毁是把整个链表给销毁掉,而清空则是保留单链表,把单链表的结点给去掉,简单来说就是把单链表变成空表
我们看,他用了两个指针 p,q。一个指向a1,一个指向a2。然后删掉p ,后面让p指向q的位置,q继续下一个,如此循环,最终就只剩下头结点和最后一个空值,所以代码如下
int ClearList(LinkList *l){
Node *p,*q; //创建两个指针p,q
p = L->next; //确定p的位置
while(p == NULL){ //进行删除操作
q = p->next;
delete p;
p = q;
}
L->next = NULL; //最后让头节点的地址指向最后一个
return OK;
}
求单链表的表长
int ListLength_L(LinkList L){
LinkList p;
p = L->next; //从第一个结点开始
i = 0;
while(p){
i++; //遍历一遍所有结点
p = p->next;
} //然后再计数,简简单单
return i;
}
查找单链表中第 i 个元素的内容
算法思想:
①从第一个结点开始,p = L->next
②j用来当作计数器,累计扫过的结点数 j = 1
③p扫过下一个结点,j+1
④当j == i 时,p所指的结点就是我们要找的结点
代码如下
int GetElem_L(LinkList L, int i, ElemType *e){
p = L->next;
j = 1;
while(p == NULL && j<i){
p = p->next;
++j; //遍历i遍到指定的位置获取数据
}
if(!p || j>i)
return ERROR;
e = p->data;
return OK;
}
按值查找
其实就是查找的另一个翻版,思想都是一样的
int LocateElem_L(LinkList L, ElemType *e){
p = L->next;
j = 1;
while(p == NULL && p->data!=e){
p = p->next;
j++; //遍历i遍到指定的位置获取数据
}
if(p == NULL)
return j;
else
return OK;
}
单链表的插入操作
首先我们要想一下该怎么插入(我们以a3,a4之间插入a7为例子)
是不是我们先a3指向a4的指针域去指向a7,然后让a7的指针域指向a4
那我们怎么操作呢,我们具体看a3,a4,a7
算法思想:
1.首先找到 a3 的存储位置 p
2.生成一个数据域为 a7 的新结点 s
3:①(图示) 新结点的指针域指向a4-------【s->next = p->next;】
②(图示)结点a3的指针域指向新结点-----------【p->next = s】
这里我们不能把①和②搞反了,因为如果搞反了我们就会丢失a4的地址,这个一定要注意
具体代码如下
int ListInsert_L(LinkList &L,int i,ElemType e){
p = L; //先找到要插入的位置
j = 0;
while(p && j<i-1){
p = p->next;
++j;
}
if(p == NULL || j>i-1) //插入不合法就error
return ERROR;
//如果找到了要插入的位置
S = new Node; //我们创造一个结点
s->data = e; //结点内容为e
s->next = p->next; //①
p->next = s; //②
return OK;
删除第 i 个链表的结点
和插入差不多,都是指针域的替换
算法思想:
1.找到 a3 的存储位置 p,保存要删除的a7的值。
2.令p->next 指向 a4。【p->next = p->next->next或者 p->next = s->next】
3.释放 a7 空间。
具体代码如下
int ListDelete_L(LinkList &L,int i, ElemType &e){
p = L;
j = 0;
while(p->next && j<i-1){ //首先通过遍历找到我们要删除的元素
p = p->next;
++j;
}
if(p->next == NULL || j>i-1) //判断删除的元素是否合法
return ERROR;
s = p->next; //合法的话
p->next = s->next; //我们就先用s标记要删除的位置
e = s->next; //存储要删除的值
delete s; //释放 s 的空间
return OK;
}
创建链表(头插法)※重点
算法思想:最先插入最后一个
①从空表开始,重复读入数据
②生成新结点
③从最后一个结点开始,依次插入到链表前端
直接看代码
void CreatLL_H(LinkList L, int n)
{
L = new LNode;
L->next = NULL; //先创建一个带头节点的单链表
for(i = n; i>0; --i){ //进行循环
p = new LNode; //生成我们要插入的新节点,也可以用c语言表示p=(LNode*)malloc(sizeof(LNode));
cin>>p->next //输入我们的值,也可以用c语言表达scanf(&p->data);
p->next = L->next; //让我们新创建的结点的next域等于L的next域也就是NULL
L->next = p; //让L的next域指向p结点
}
其实还是很好懂得,对吧
创建链表(尾插法)※重点
头插法我们输入的是edcba才能得到(看上面的图片)abcde
尾插法我们输入的是abcde就能得到abcde了
具体怎么做呢?
算法思想:①创建一个空表,和一个尾指针r
②创建一个结点p
③使得尾指针r指向新节点
④移动尾指针r到尾部
我们看看具体代码和详解
void CreateList_R(LinkList &L,int n){
L = new LNode;
L->next = NULL; //生成新链表
r = L; //尾指针r指向头节点
for(i = 0; i<n; ++i){
p = new LNode; //创建一个新节点
cin>>p->data; //输入数据
p->next = NULL; //让新创建的结点的next域等于空值
r->next = p;
r = p;
好了,单项链表讲完了,自己理解一下,消化一下。
通过这节。我们学到了链表是什么,他的一系列的操作是什么
后面我们要学循环链表双向链表什么的,那就后面再讲吧。