数据结构与算法——单链表的实现及原理

目录

一、链表的原理

1.什么是链表

2.什么是单链表

3.顺序表与链表的对比

二、单链表的实现

1.单链表的定义

2.创建一个链表节点

4.单链表数据在头部插入

总结


一、链表的原理

1.什么是链表

链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

我们想象中的链表可能是下面的

 但实际上的链表是

这是一种物理存储结构上非连续,非顺序的储存结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 

2.什么是单链表

实际上的链表的结构非常多样,以下情况组合起来就有8种链表结构

1.单向,双向

2.带头,不带头

3.循环,非循环

而我们下面将要实现链表中最简单的结构,单向,不带头,非循环链表

3.顺序表与链表的对比

顺序表:空间连续、支持随机访问
我们可以通过下标来访问顺序表中的任意位置
中间或前面部分的插入删除时间复杂度 O(N)
我们删除数据后必须要移动数组中的元素,导致系统开销比较大
增容的代价比较大。
我们只能选择成倍的扩大顺序表的空间
链表: 以节点为单位存储,不支持随机访问
这就导致了我们想要找到链表的尾是不能直接找到的,我们必须遍历一遍
任意位置插入删除时间复杂度为 O(1)
没有增容问题,插入一个开辟一个空间。

二、单链表的实现

1.单链表的定义

typedef int SlTDataType;

typedef struct ListNode
{
	SlTDataType val;
	struct ListNode* next;
}ListNode;

链表是一个链式结构,在这个节点就可以找到它的下一个节点,同时保存当前节点储存的数据

链表有一个指针域和数据域,一个节点就是一个结构体

2.创建一个链表节点

我们数据结构很多时候都是需要动态开辟内存的,我们每增加一个节点就需要创建一个新节点,所以我们单独写成一个函数来重复调用

//创建新节点
ListNode* BuyListNode(SlTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL)
	{
		perror("BuyListNode::malloc");
	}

	newNode->val = x;
	newNode->next = NULL;
	return newNode;
}

我们每次动态开辟内存之后必须要检查空间是否开辟成功,因为在现实生活中是有可能开辟失败的

同时我们默认新节点的next指针指向NULL,避免出现野指针问题。

3.单链表数据在尾部插入

尾插时我们首先需要创建一个新节点,同时需要找到链表的尾,因为,链表只能从头开始访问

//尾插
void ListNodePushBack(ListNode** pplist, SlTDataType x)
{
	ListNode* newNode = BuyListNode(x);

	if (*pplist == NULL)
	{
		*pplist = newNode;
	}
	else
	{
		ListNode* tail = *pplist;
		while (tail != NULL)
		{
			tail = tail->next;
		}

		tail->next = newNode;
	}
}

同时有一个细节需要注意,如果链表一个节点都没有的情况下,我们就需要将新节点作为头节点

4.单链表数据在头部插入

头插我们就不需要考虑没有节点了,因为链表为空时,它的next指针默认指向NULL,我们直接在头上插一个新节点就可以了

//头插
void ListNodePushFront(ListNode** pplist,SlTDataType x)
{
	ListNode* newNode = BuyListNode(x);
	newNode->next = *pplist;
	*pplist = newNode;
}

5.单链表数据在头部删除

头删我们也需要考虑特殊情况,如果为空怎么办?

为空我们就不能删除了,直接return就可以了

//头删
void ListNodePopFront(ListNode** pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else
	{
		ListNode* next = (*pplist)->next;
		free(*pplist);

		*pplist = next;
	}
}

我们还要注意其中一点,我们如果直接free掉头那么我们就无法访问头后面的节点了,所以我们创建一个next节点来存头的下一个节点的地址

6.单链表数据在尾部删除

尾删就比较特殊了,有三种情况

1.如果一个节点都没有

2.有一个节点

3.有多个节点

我们考虑了这几种情况之后,剩下的思路与头删就差不多了

//尾删
void ListNodePopBack(ListNode** pplist)
{
	//没有节点
	//一个节点
	//多个节点

	if (*pplist == NULL)
	{
		return;
	}
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		ListNode* tail = *pplist;
		ListNode* prev = NULL;
		while (tail != NULL)
		{
			prev = tail;
			tail = tail->next;

		}
		free(tail);
		tail = NULL;
		prev->next = NULL;

	}
}

7.单链表数据在任意地方插入

在任意你的地方插入,我们就需要选在那个节点处插入,我们只实现在某个节点后面插入,因为单链表天然的结构缺陷,我们无法找到当前节点的上一个节点。

//任意位置插入
void ListInsertAfter(ListNode* pos, SlTDataType x)
{
	assert(pos);

	ListNode* newNode = BuyListNode(x);

	newNode->next = pos->next;
	pos->next = newNode;
}

8.打印单链表

//打印链表
void ListNodePrint(ListNode* plist)
{
	assert(plist);

	ListNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}

	printf("\n");
}

9.查找节点

//查找节点
ListNode* ListFind(ListNode* plist, SlTDataType x)
{
	assert(plist);

	ListNode * cur = plist;

	while (cur != NULL)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}


总结

完整代码可从如下网址获得:JAVA-/ListNode at main · 01294268442ww/JAVA- (github.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值