大话数据结构4 - 初识单链表


前言

在学习了 第一章 - 初识数据和结构 第二章 - 初识算法 第三章 - 线性表的顺序存储结构 之后,今天我们来学习一下线性表的链式存储(单链表)!

一、线性表的链式存储结构是什么?

特点:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。

和顺序存储结构不同,链式结构除了要存放数据元素信息外,还要存储它的直接后继元素的存储地址

这里介绍两个概念:

  1. 数据域:存储数据元素信息的域
  2. 指针域:存储直接后继位置的域,其中存储的信息称为指针或链

这两部分信息组成数据元素 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,如下图
在这里插入图片描述

单链表的读取

思路:

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从1开始;
  2. 当 j<i 时,遍历链表,让 p 的指针向后移动,不断指向下一个结点,j 累加1;
  3. 若看到链表末尾 p 为空,则说明第 i 个元素不存在;
  4. 否则查找成功,返回结点 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
}

单链表的整表删除

思想:

  1. 声明结点 p 和 q;
  2. 将第一个结点赋值给 p;
  3. 循环:
    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)
  1. 若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。
  2. 若需要频繁插入和删除时,宜采用单链表结构。

运用场景举例:比如说游戏开发中,对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读取,所以应该考虑用顺序存储结构。而游戏中的玩家的武器或者装备列表,随着玩家的游戏过程中,可能会随时增加或删除,适合用链表结构


后言

以上内容为 大话数据结构 – 程杰 第三章关于单链表的学习笔记。
虽然和成为Java工程师有一段距离,但是没关系,我是一个一旦确立目标就很有干劲的人,一定能成功!
如果有跟我一起学习的同学可以帮我指正那就更好了,欢迎加入我的交流群 916352394

冲鸭!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

高冷的上官梓芸

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

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

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

打赏作者

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

抵扣说明:

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

余额充值