【数据结构与算法】单链表的插入和删除

这篇具有很好参考价值的文章主要介绍了【数据结构与算法】单链表的插入和删除。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:数据结构与算法
🌠 首发时间:2022年9月21日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
🌟 一以贯之的努力 不得懈怠的人生

单链表的定义

什么是单链表?

众所周知,顺序表中的每个结点中只存放数据元素,其优缺点为:

  • 优点:可随机存取,存储密度高
  • 缺点:要求大片连续空间,改变容量不方便

而单链表中的每个结点除了存放数据元素外,还要存储指向下一个节点的指针,其优缺点为:

  • 优点:不要求大片连续空间,改变容量方便
  • 缺点:不可随机存取,要耗费一定空间存放指针

定义单链表

typedef int ElemType;
typedef struct LNode LNode;

//定义单链表结点类型
struct LNode{
	ElemType data;			//每个节点存放一个数据元素
	struct LNode *next;		//指针指向下一个节点
};

当你想要增加一个新的节点时,你需要在内存中申请一个结点所需的空间,并用指针 p 指向这个结点

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

上面这样写是不是很麻烦呢?

所以我们下面用一种更简洁的方式来实现

typedef int ElemType;

typedef struct LNode{
	ElemType data;			//每个节点存放一个数据元素
	struct LNode *next;		//指针指向下一个节点
}LNode, *LinkList;

它等同于下面的代码

typedef int ElemType;

struct LNode{
	ElemType data;			//每个节点存放一个数据元素
	struct LNode *next;		//指针指向下一个节点
};
typedef struct LNode LNode;
typedef struct LNode *LinkList;

这样的话,当我们要表示一个单链表时,只需要声明一个头指针 L,让它指向单链表的第一个结点,具体有下面两种方式:

第一种:

LNode * L;		//声明一个指向单链表第一个结点的指针

第二种:

LinkList L;		//声明一个指向单链表第一个结点的指针

相比起来,第二种方式的可读性更强

具体在写代码时,我们需要记住:

  • 强调这是一个单链表 —— 使用 LinkList
  • 强调这是一个结点 —— 使用 LNode *

不带头结点的单链表

#include <stdio.h>

typedef int ElemType;

typedef struct LNode{
	ElemType data;			//每个节点存放一个数据元素
	struct LNode *next;		//指针指向下一个节点
}LNode, *LinkList;

//初始化一个空的单链表
bool InitList(LinkList &L) {
	L = NULL;	//空表,暂时没有任何结点
	return true;
}

//判断单链表是否为空
bool Empty(LinkList L) {
	return L == NULL;
}

//主函数
int main() {
	LinkList L;		//声明一个指向单链表的指针

	//初始化一个空表
	InitList(L);

	return 0;
}

带头结点的单链表

#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;

typedef struct LNode{
	ElemType data;			//每个节点存放一个数据元素
	struct LNode *next;		//指针指向下一个节点
}LNode, *LinkList;

//初始化一个单链表
bool InitList(LinkList &L) {
	L = (LNode *)malloc(sizeof(LNode));		//分配一个头结点
	if (L == NULL)
		return false;
	L->next = NULL;		//头结点之后暂时还没有结点
	return true;
}

//判断单链表是否为空
bool Empty(LinkList L) {
	return L->next == NULL;
}

//主函数
int main() {
	LinkList L;		//声明一个指向单链表的指针

	//初始化一个空表
	InitList(L);
	
	return 0;
}

无头结点 VS 有头结点

不带头结点,写代码会更加麻烦,对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑;对空表和非空表的处理也需要不同的代码逻辑

头结点不存储数据,只是为了操作方便

单链表的插入操作

按位序插入(带头结点)

ListInsert(&L, i, e):插入操作。在表 L 中的第 i 个位置上插入指定元素 e

代码实现

//在第 i 个位置插入元素 e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e) {
	if (i < 1) return false;

	LNode *p;		//指针p指向当前扫描到的结点
	int j = 0;		//当前p指向的是第几个结点
	p = L;			//L指向头结点,头结点是第 0 个结点

	//循环找到第 i-1 个结点
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}

	if (p == NULL) return false;

	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;		//将结点s连到p之后
	return true;		//插入成功
}

按位序插入(不带头结点)

ListInsert(&L, i, e):插入操作。在表 L 中的第 i 个位置上插入指定元素 e

由于不带头结点,所以在处理 i=1 时需要特殊处理

代码实现

//在第 i 个位置插入元素 e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e) {
	if (i < 1) return false;

	//插入第 1 个结点的操作与其他结点操作不同
	if (i == 1){
		LNode *s = (LNode *)malloc(sizeof(LNode));
		s->data = e;
		s->next = L;
		L = s;				//头结点指向新结点
		return true;
	}

	LNode *p;		//指针p指向当前扫描到的结点
	int j = 1;		//当前p指向的是第几个结点
	p = L;			//L指向头结点,头结点是第 0 个结点

	//循环找到第 i-1 个结点
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}

	if (p == NULL) return false;

	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;		//将结点s连到p之后
	return true;		//插入成功
}

到这里,你能体会到不带头结点的麻烦之处了吧

指定结点的后插操作

//后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p, ElemType e) {
	if (p == NULL) return false;

	LNode *s = (LNode *)malloc(sizeof(LNode));
	if (s == NULL) return false;		//内存分配失败
	s->data = e;			//用结点s保存数据元素e
	s->next = p->next;
	p->next = s;			//将结点s连到p之后
	return true;
}

有了这个后插操作,我们前面的插入操作就可以改为

//在第 i 个位置插入元素 e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e) {
	if (i < 1) return false;

	LNode *p;		//指针p指向当前扫描到的结点
	int j = 0;		//当前p指向的是第几个结点
	p = L;			//L指向头结点,头结点是第 0 个结点

	//循环找到第 i-1 个结点
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}

	return InsertNextNode(p, e);
}

指定结点的前插操作

这我就有点懵了,给你一个结点,你又找不到前面的结点,怎么插入啊

//前插操作:在p结点之前插入元素e
bool InsertPriorNode(LNode *p, ElemType e) {
	if (p == NULL) return false;

	LNode *s = (LNode *)malloc(sizeof(LNode));
	if (s == NULL) return false;		//内存分配失败
	s->next = p->next;
	p->next = s;			//将结点s连到p之后
	s->data = p->data;
	p->data = e;
	return true;
}

想不到吧,我的指针跑不了路,数据可以啊

//前插操作:在p结点之前插入结点s
bool InsertPriorNode(LNode *p, LNode *s) {
	if (p == NULL || s == NULL) return false;
	
	s->next = p->next;
	p->next = s;			//将结点s连到p之后
	ElemType temp = p->data;
	p->data = s->data;
	s->data = temp;
	return true;
}

单链表的删除操作

按位序删除(带头结点)

ListDelete(&L, i, &e):删除操作。删除表 L 中第 i 个位置的元素,并用 e 返回删除元素的值

//按位序删除(带头结点)
bool ListDelete(LinkList &L, int i, ElemType &e) {
	if (i < 1) return false;

	LNode *p;		//指针p指向当前扫描到的结点
	int j = 0;		//当前p指向的是第几个结点
	p = L;			//L指向头结点,头结点是第 0 个结点

	//循环找到第 i-1 个结点
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}

	if (p == NULL) return false;
	if (p->next == NULL) return false;

	LNode *q = p->next;		//让q指向要被删除的结点
	e = q->data;			
	p->next = q->next;		//将*q结点断开
	free(q);
	return true;
}

删除指定结点

思路:将指定结点的下一个结点的值传给指定结点,指定结点指向下一个结点的下一个,再删除下一个结点,这样就变相地删除了指定结点

//删除指定结点
bool DeleteNode(LNode *p) {
	if (p == NULL) return false;

	LNode *q = p->next;
	p->data = q->data;
	p->next = q->next;
	free(q);
}

不过这个方法有个 bug,就是当 p 是最后一个结点时,就行不通了,只能从表头开始依次寻找 p 的前驱

至于直接 free(p) 的操作是不行的,因为最后一个结点总得指向 NULL,你直接释放掉 p 怎么行

从这里我们就可以看出单链表的局限性:无法逆向检索,有时候不太方便文章来源地址https://www.toymoban.com/news/detail-724090.html

到了这里,关于【数据结构与算法】单链表的插入和删除的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法】单链表的排序算法(选择,冒泡,递归)

    目录 选择排序 冒泡排序 快速排序 合并两条链表并排序 选择排序 链表的选择排序思想与数组的排序类似,但是链表需要先找到里面最小或者最大的值,然后将这个值用改链语句进行操作 我们先看这个改链语句的操作(min是笔者打错了应该是max,但是图已经画好了就没有改)

    2024年02月04日
    浏览(56)
  • 【数据结构与算法】单链表的增删查改(附源码)

      这么可爱的猫猫不值得点个赞吗 😽😻 目录 一.链表的概念和结构 二.单链表的逻辑结构和物理结构 1.逻辑结构  2.物理结构 三.结构体的定义 四.增加 1.尾插   SListpushback 2.头插  SListpushfront 五.删除 1.尾删  SListpopback 2.头删  SListpopfront 六.查找  插入  释放   打印 1.查找

    2024年02月02日
    浏览(74)
  • 单链表的建立(头插法、尾插法)(数据结构与算法)

    如果要把很多个数据元素存到一个单链表中,如何操作? 1.初始化一个单链表 2. 每次取一个数据元素,插入到表尾/表头 尾插法建立的单链表元素顺序与输入数据集合的顺序相同,即按照输入数据的顺序排列。 使用尾插法建立单链表的一个常见应用是在计算机科学中进行数据

    2024年04月11日
    浏览(44)
  • 【数据结构与算法】深入浅出:单链表的实现和应用

      🌱博客主页:青竹雾色间. 😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注  ✨ 人生如寄,多忧何为  ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法: 尾插法: 插入(增)操作  删除(删)操作: 查找(查)操作: 修改(改

    2024年02月08日
    浏览(74)
  • 【数据结构】单向链表的增删查改以及指定pos位置的插入删除

    目录  单向链表的概念及结构  尾插 头插 尾删 ​编辑  头删  查找  在pos位置前插  在pos位置后插  删除pos位置  删除pos的后一个位置 总结 代码  概念:链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表中的 指针链接 次序实现的

    2024年02月05日
    浏览(48)
  • C语言简单的数据结构:单链表的有关算法题(2)

    接着我们介绍后面的三道题,虽然代码变多了但我们的思路更加通顺了 题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/ 创建新链表,遍历原链表,将节点值小的进行尾插到新链表中 这里要多次进行对NULL的判断,开始传入列表,中间newHead的判断,循环出来一个为NULL的判断

    2024年04月15日
    浏览(63)
  • 【数据结构】线性表(一)线性表的定义及其基本操作(顺序表插入、删除、查找、修改)

    目录 一、线性表 1. 线性表的定义 2. 线性表的要素 二、线性表的基本操作 三、线性表的顺序存储结构 1. 定义 2. 顺序表的操作       a. 插入操作 b. 删除操作 c. 查找操作 d. 修改操作 e. 代码实例          一个线性表是由零个或多个 具有相同类型的结点 组成的有序集合。

    2024年02月03日
    浏览(69)
  • 数据结构:图文详解双向链表的各种操作(头插法,尾插法,任意位置插入,查询节点,删除节点,求链表的长度... ...)

    目录 一.双向链表的概念 二.双向链表的数据结构 三.双向链表的实现 节点的插入 头插法 尾插法 任意位置插入 节点的删除 删除链表中第一次出现的目标节点 删除链表中所有与相同的节点 节点的查找 链表的清空 链表的长度 四.模拟实现链表的完整代码 前言: 在上一

    2024年02月05日
    浏览(50)
  • 数据结构_链表_单向循环链表的初始化、插入、删除、修改、查询打印(基于C语言实现)

    版本: 2024年4月25日 V1.0 发布于博客园 目录 目录 单向循环链表公式 初始化单向循环链表 构建单向循环链表结点 创建一个空链表(仅头结点) 创建一个新结点 插入数据 头插 中插 尾插 删除数据 头删 中删 尾删 查询打印数据 遍历打印 测试 测试结果: 完整代码 CircularLinkedLis

    2024年04月25日
    浏览(51)
  • 数据结构_链表_双向循环链表的初始化、插入、删除、修改、查询打印(基于C语言实现)

    版本: 2024年4月26日 V1.0 发布于博客园 目录 目录 双向循环链表公式 初始化双向循环链表 构建双向循环链表结点 创建一个空链表(仅头结点) 创建一个新结点 插入数据 头插 中插 尾插 删除数据 头删 中删 尾删 查询打印数据 遍历打印 测试 测试结果: 完整代码 DoubleCirLList.h

    2024年04月27日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包