【数据结构】单链表 && 双链表(链式和数组实现)_取单链表中第二个数据结构(1)

前言

今天给大家带来四个内容,一个是单链表非带头的实现,一个是双链表带头循环的实现。剩下的就是数组模拟单链表和双链表。

单链表

链表,在内存中并不是连续存储的。而链表通常会有next指针指向它的下一个节点。链表的最后一个节点一定指向空。

image-20230326183443474

头插

头插我们需要让新节点指向头节点,然后更换头节点为新节点即可。

链表1

尾插

我们只需要找到最后一个节点,让它的next指向新节点,再把新节点指向NULL即可。

链表2

头删

先保存头节点的下一个节点,然后删除头节点。并把保存下来的下一个节点设置为新的头节点。

链表3

尾删

尾删需要记录一下最后一个节点的前一个节点。然后删除最后一个节点,把前一个节点指向NULL。否则会出现指向野指针的情况。

链表4

指定位置后插入

我们这里只讲解在指定位置的后一个元素插入,因为单链表在指定节点的前一个插入,需要前一个节点。但是单链表只指后不指前。所以想拿到前一个节点必须再遍历一次,所以不建议单链表用前插。在指定位置后插,我们只需要保存一下这个位置的下一个节点,然后让新节点指向这个位置的下一个节点。这个位置指向新节点。

假设我们在pos位置后面插入:

链表5

指定位置后删除

我们还是删除指定位置后的元素,因为如果删除指定元素,我们也需要它的前一个节点。单链表无法直接获取前一个节点。指定位置后删除,我们只需要保存下一个节点的下一个节点,然后删除下一个节点,然后让这个位置指向保存下来的的节点。

链表6

单链表的代码实现:

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

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode\* next;
}SListNode;


// 动态申请一个节点
SListNode\* BuySListNode(SLTDateType x)
{
	SListNode\* newNode = (SListNode\*)malloc(sizeof(SListNode)); //开辟空间
	newNode->data = x; //空间值为x
	return newNode;
}
// 单链表打印
void SListPrint(SListNode\* plist)
{
	SListNode\* cur = plist;
	while (cur) //遍历一遍链表
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode\*\* pplist, SLTDateType x)
{
	assert(pplist);
	if (\*pplist == NULL)//如果链表为空调用头插
	{
		SListPushFront(pplist, x); 
		return;
	}
    //找到尾部节点
	SListNode\* tail = \*pplist;
	while (tail->next)
	{
		tail = tail->next;
	}
    //复用SListInsertAfter函数插入
	SListInsertAfter(tail, x);
}
// 单链表的头插
void SListPushFront(SListNode\*\* pplist, SLTDateType x)
{
	assert(pplist);
	SListNode\* newNode = BuySListNode(x);
	if (\*pplist == NULL) //链表为空需要更新头节点
	{
		\*pplist = newNode;
		newNode->next = NULL;
		return;
	}
	newNode->next = \*pplist;
	\*pplist = newNode;
}
// 单链表的尾删
void SListPopBack(SListNode\*\* pplist)
{
	assert(pplist && \*pplist);
	if ((\*pplist)->next == NULL) //链表就一个元素,调用头删
	{
		SListPopFront(pplist);
		return;
	}
	SListNode\* tail = \*pplist;
	SListNode\* prev = NULL;
	
    //遍历链表并删除
	while (tail->next)
	{
		prev = tail;
		tail = tail->next;
	}
	free(tail);
	prev->next = NULL;
}
// 单链表头删
void SListPopFront(SListNode\*\* pplist)
{
	assert(pplist && \*pplist);
    //头删
	SListNode\* next = (\*pplist)->next;
	free(\*pplist);
	\*pplist = next;
}
// 单链表查找
SListNode\* SListFind(SListNode\* plist, SLTDateType x)
{
	SListNode\* cur = plist;
	while (cur)
	{
		if (x == cur->data)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode\* pos, SLTDateType x)
{
	assert(pos);
	SListNode\* next = pos->next;
	SListNode\* newNode = BuySListNode(x);
	pos->next = newNode;
	newNode->next = next;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode\* pos)
{
	assert(pos);
	SListNode\* nextnext = pos->next->next;
	free(pos->next);
	pos->next = nextnext;
}
// 单链表的销毁
void SListDestroy(SListNode\* plist)
{
	SListNode\* cur = plist;
	SListNode\* next = plist->next;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}

双链表

这里我们实现一个带头循环的双链表,因为循环链表可以从头节点直接找到最后一个节点。带头是因为更好的确定链表的尾部,带头节点在这里就冲当了尾节点的作用。

image-20230326191419793

指定位置前插入

双链表我们只需要写指定插入和指定删除,就可以复用这两个接口实现头插,头删,尾插,尾删了。指定位置前插入和单链表类似。先保存前一个节点坐标,然后让前一个节点和新节点连接,再把自己的prev指针指向新节点。

为了方便观看,我把首尾节点指向的线省略了,实际上指针是指向首尾的。我们在pos前插入一个节点。

链表5

动图演示可能线连接的不怎么好看,但最后都是这样的

image-20230326193149650

指定位置删除

指定位置删除,我们只需要存下前一个节点和后一个节点。然后把两个节点连接起来,再释放当前节点即可。

链表8

双链表代码实现:

#include <assert.h>
#include<stdio.h>

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;

typedef struct \_ListNode
{
	LTDataType _data;
	struct \_ListNode\* _next; //指向后一个节点
	struct \_ListNode\* _prev; //指向前一个节点
}ListNode;



//创建节点
ListNode\* CreateListNode(LTDataType x)
{
	ListNode\* newNode = (ListNode\*)malloc(sizeof(ListNode));
	newNode->_data = x;
	return newNode;
}

// 创建链表,返回链表的头结点.
ListNode\* ListCreate()
{
	ListNode\* head = CreateListNode(0);
	head->_next = head; //自己指向自己
	head->_prev = head; //自己指向自己
	return head;
}

// 双向链表销毁
void ListDestory(ListNode\* pHead)
{
	ListNode\* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode\* next = cur->_next;
		free(cur);


![img](https://img-blog.csdnimg.cn/img_convert/deb1c01ca67b091f0b9cb4c5f8e3766c.png)
![img](https://img-blog.csdnimg.cn/img_convert/91bb86e48b55649c3c1e164a8a742f5d.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**


[外链图片转存中...(img-e9GTbheJ-1714273418252)]
[外链图片转存中...(img-dqtkrtN6-1714273418253)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值