第三章:线性表 - 链式存储结构
前言
在学习了 第一章 - 初识数据和结构 、第二章 - 初识算法 、第三章 - 线性表的顺序存储结构 之后,今天我们来学习一下线性表的链式存储(单链表)!
一、线性表的链式存储结构是什么?
特点:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。
和顺序存储结构不同,链式结构除了要存放数据元素信息外,还要存储它的直接后继元素的存储地址
这里介绍两个概念:
- 数据域:存储数据元素信息的域
- 指针域:存储直接后继位置的域,其中存储的信息称为指针或链
这两部分信息组成数据元素 ai 的存储映像,称为结点(Node)
n 个结点 ( ai 的存储映像) 链结成一个链表,即为线性表( a1 a2,…, an) 的链式存储结构,此链表的每个结点中只包含一个指针域,所以叫做单链表
头指针:链表中第一个结点的存储位置
一般最后一个结点指针为NULL
为了方便操作,会在单链表第一个结点前附设一个结点, 称为头结点,头结点的数据域可以不存储任何信息,也可以存储线性表长度等附加信息。
头指针和头结点的异同
头指针 | 头结点 |
---|---|
是链表指向第一个结点的指针,若链表有头指针,则指向头结点的指针 | 放在第一元素的结点之前,数据域一般无意义(也可存放链表的长度) |
无论链表是否为空,头指针均不为空 | 头结点不是链表的必须要素 |
头指针具有标识作用,所以常用头指针冠以链表的名字 |
单链表存储示意图
带头结点的单链表示意图
空链表示意图
二、用结构指针描述单链表(C语言)
// 线性表的单链表存储结构 //含有自定义变量,需要加上前几章的define才能运行
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; //定义LinkList
这里我们知道,结点由存放数据元素的数据域和存放后继结点地址的指针域组成。
结点 ai 的数据域可以用 p-data 表示 | 结点 ai 的指针域可以用 p->next 表示 |
---|
如果 p->data =ai,那 p->next->data 则=ai+1,如下图
单链表的读取
思路:
- 声明一个结点 p 指向链表第一个结点,初始化 j 从1开始;
- 当 j<i 时,遍历链表,让 p 的指针向后移动,不断指向下一个结点,j 累加1;
- 若看到链表末尾 p 为空,则说明第 i 个元素不存在;
- 否则查找成功,返回结点 p 的数据;
实现代码算法如下:
// 初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
// 操作结果:用e返回L中第i个数据元素的值
Status GetElem(LinkList L,int i,ElemType *e){
int j; //j为计数器
LinkList p; //声明结点p
p = L->next; //L为头指针,p为头结点地址
j = 1;
while(p && j<i){ //p不为空或者计数器小于目标值i时
p = p->next; //p指向下一结点
++j;
}
if( !p || j>i ) //第一个元素不存在,j>i 明显是为了在while未执行的情况下判断变量 i 是否小于1(略有多余但使得该算具有健壮性)
return ERROR;
*e = p->data; //取值并返回
return OK;
}
单链表结构没有定义表长,不能事先知道要循环多少次,因此更适合用while来控制循环。
由代码可见,数据的读取时间复杂度为O(n)
单链表的插入和删除
插入思想:先接上尾部,再接上头部,如下图
核心思想:s ->next = p->next;p->next = s;(顺序不可换)
插入算法
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
while(p && j<i){ //寻找第i个结点 ,可以插在头结点之后、第一个结点之前==相当于放在第一位
p = p->next;
++j;
}
if(!p || j>i)
return ERROR;
s = (LinkList)malloc(sizeod(Node)); //生成新结点,返回类型为LinkList类型,分配的大小为sizeof(Node)
s->data = e;
s->next = p->next; //接尾巴
p->next = s; //接头
return OK;
}
malloc函数:动态分配内存,生成一个新的结点,类型与Node结构一样( LinkList是Node的实现 )
删除算法
删除思想:记下 p->next 为 q ,重新记录 p->next =q->next
核心思想:q=p->next; p - >next=q- >next ;
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:删除L中第i个数据元素,并用e返回其值,L的长度减1
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = *L;
j = 1;
while(p->next && j<i){ //寻找第i个结点,只能从第一个结点开始删除,不得删除头结点,也不得删除空结点,要下一项存在才能取下一项
p = p->next;
++j;
}
if(!(p->next) || j>i) //这里p->next 的意思是,若空,则!空==真
return ERROR; //j>i,在找到尾部还没找到,此时j+1就会比i大,所以可以判断第i个元素不存在
q = p->next; //记录中间值
p->next = q->next; //接上尾巴
*e = q->data;
free(q); //系统回收此结点,释放内存
return OK;
}
单链表的创建
单链表是一种动态结构,所占空间的大小和位置都不需要预先分配,可根据系统的情况和实际的需求即时生成。
头插法(少用)
代码算法如下:在头结点和首结点之间插入
// 随机产生n个元素的值,建立带头结点的单链线性表L
void CreateListHead(LinkList *L,int n){
LinkList p;
srand(time(0)); //设置随机函数种子为系统时间,种子不同产生的随机数不同
*L = (LinkList)malloc(sizeof(Node)); //分配内存给头结点L
(*L)->next = NULL;
for(int i=0;i<n;i++){
p = (ListList)malloc(sizeof(Node));
p->data = rand()%100+1; //随机生成100以内的数字
p->next = (*L)->next; //接尾巴
(*L)->next = p; //接头
}
}
尾插法(常用)
将新结点都放在最后面,符合排队思维(先来后到)
代码算法如下:
// 随机产生n个元素的值,建立带头结点的单链线性表L
void CreateListTail(LinkList *L,int n){
LinkList p,r;
srand(time(0)); //设置随机函数种子为系统时间,种子不同产生的随机数不同
*L = (LinkList)malloc(sizeof(Node)); //分配内存给头结点L
r=*L; //r为指向尾部的结点
for(int i=0;i<n;i++){
p = (Node*)malloc(sizeof(Node));
p->data = rand()%100+1; //随机生成100以内的数字
r->next = p; //先接头
r = p; //在移动尾部指标结点r到最新尾部
}
r->next = NULL; //最后设置尾部next结点为NULL
}
单链表的整表删除
思想:
- 声明结点 p 和 q;
- 将第一个结点赋值给 p;
- 循环:
1. 将下一结点赋值给 q ;
2. 释放 p ;
3. 将 q 赋值给 p ;
实现代码如下:
//初始条件:顺序线性表L已存在,操作结果:将L重置为空表
Status ClearList(LinkList *L){
LinkList p,q;
p = (*L)->next; //p记录第一个结点地址
while(p){ //没到表尾
q = p->next; //q暂时记录p->next
free(p); //释放p内存
p = q;
}
(*L)->next = NULL; //头结点指针域为空
return OK;
}
三、简单对比链式存储和顺序存储
单链表 | 顺序存储结构 |
---|---|
不需要分配存储空间,元素个数不受限 | 需要预先分配存储空间,分大了浪费,分小了上溢 |
存储空间任意 | 连续的存储单元依次存储 |
查找时间复杂度O(n) | 查找时间复杂度O(1) |
插入、删除时间复杂度O(1) | 插入、删除时间复杂度O(n) |
- 若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。
- 若需要频繁插入和删除时,宜采用单链表结构。
运用场景举例:比如说游戏开发中,对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读取,所以应该考虑用顺序存储结构。而游戏中的玩家的武器或者装备列表,随着玩家的游戏过程中,可能会随时增加或删除,适合用链表结构
后言
以上内容为 大话数据结构 – 程杰 第三章关于单链表的学习笔记。
虽然和成为Java工程师有一段距离,但是没关系,我是一个一旦确立目标就很有干劲的人,一定能成功!
如果有跟我一起学习的同学可以帮我指正那就更好了,欢迎加入我的交流群 916352394。