线性表---单链表

1.链表的概念与结构

前言:为什么要存在链表呢?在很多情况下,我们用数组就能很好的完成工作,而且不会产生太多的差异,那么链表存在的意义是什么?链表相比于数组有什么优势或者不足吗?
首先我们要先知道顺序表的问题
顺序表的问题:
1.中间/头部的插入删除,时间复杂度为O(N)
2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3.增容一般是呈2倍的增长,势必会有一定的空间浪费。

因此需要有一种数据类型与其进行互补!下面我来介绍一下链表:
首先是链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构 ,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
单链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
与顺序表存储的区别:
从本质上来讲,链表与数组的确有相似之处,他们的相同点是都是线性数据结构,这与树和图不同,而它们的不同之处在于数组是一块连续的内存,而链表可以不是连续内存,链表的节点与节点之间通过指针来联系。
在这里插入图片描述
在这里插入图片描述

Alt由于其非连续内存的特性导致链表非常适用于频繁插入、删除的场景,而不见长于读取场景,这跟数组的特性恰好形成互补,链表的特性与数组互补,各有所长,而且链表由于指针的存在可以形成环形链表,在特定场景也非常有用,因此链表的存在是很有必要的。

需要注意的是:
1.链式结构在逻辑结构是连续的,但是在物理结构上不一定连续,也就是说实际上链表是没有那个箭头连接的!!!只是结点结构体的next指向下一个结点的地址。
2.结点,即是一个结构体变量,包含数据域和指针域。一般实在堆上面申请空间的,使用malloc进行内存分配,开辟的空间有可能连续有可能不连续(一般开辟空间很大的时候就不连续)也就是所谓的原地扩容异地扩容
3.链表最后的指针会指向NULL;
4.链表新节点的时候不可以使用realloc进行一个个的增容空间,因为使用realloc进行增容的时候,不管是原地还是异地增容都会在原来的空间后面进行增容的,即增容后的整个空间的物理结构是连续的,但是,链表的物理结构是不连续的。

链表的优点:
1.在链表中,我们只需要指导第一个节点的位置,就可以通过指针把整个链表进行访问,不存在扩容的代价,不存在空间的浪费,因为这是按需索取空间。
2.链表再头插或者尾插又或中间插入数据的时候,都不需要数据进行挪动,通过改变指针就可以完成。

2.链表的分类

实际中要实现的链表结构非常多样,以下情况结合起来就有8中链表结构:

1.链接方向:单向、双向
2.带不带头节点(哨兵卫)
3..循环、非循环

今天我们要介绍的是最简单的一种类型:无头结点的单链表。

3.链表的接口(实现操作)

//创造新节点
LTNode* BuynewNode(LTDatatype x);
//尾插
void LTPushBack(LTNode** pplist, LTDatatype x);
//头插
void LTPushFront(LTNode** pplist, LTDatatype x);
//尾删
void LTPopBack(LTNode** pplist);
//头删
void LTPopFront(LTNode** pplist);
//打印链表
void LTPrint(LTNode** pplist);
//查找附加修改
LTNode* LTFind(LTNode** pplist, LTDatatype x);
//再pos结点位置之后插入x
void LTInsertAfter(LTNode* pos, LTDatatype x);
//再pos位置之前插入x
void LTInsertBefore(LTNode** pplist, LTNode* pos, LTDatatype x);
//删除pos所在位置之后的结点
void LTEraseAfter(LTNode* pos);
//删除pos位置的结点
void LTErase(LTNode** pplist, LTNode* pos);
//销毁链表
void LTDestory(LTNode** pplist);

3.1单链表的定义

typedef int LTDatatype;
typedef struct LinkList
{
	LTDatatype val;		  //数据域
	struct LinkList* next;//指针域
}LTNode;

3.2动态申请一个节点

//创造新节点
LTNode* BuynewNode(LTDatatype x)
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	else
	{
		tmp->val = x;
		tmp->next = NULL;
	}
	return tmp;
}

3.3初始化单链表

//单链表初始化十分简单
void TestLinkList()
{
	LTNode* plist = NULL;
}

3.4打印单链表

//打印单链表
void LTPrint(LTNode** pplist)
{
	//在此处不可以直接断言plist,因为,链表可能为空链表,当链表为空链表的时候,打印出来就是空链表,只有NULL
	//若在此处对plist进行断言,如果链表为空链表的话,在此就会直接报错,而我们想要的是,若链表为空链表时,就打印出空链表,而不是直接报错、
	LTNode* cur = *pplist;
	while (cur)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

3.5头插法

void LTPushFront(LTNode** pplist, LTDatatype x)
{
	LTNode* newNode = BuynewNode(x);
	if (*pplist == NULL)
	{
		*pplist = newNode;
	}
	else
	{
		newNode->next = *pplist;
		*pplist = newNode;
	}
}

3.6尾插法

void LTPushBack(LTNode** pplist, LTDatatype x)
{
	LTNode* tmp = BuynewNode(x);
	//如果*ppplist是空的话直接将开辟的新节点作为头结点
	if (*pplist == NULL)
	{
		*pplist = tmp;
	}
	else
	{
		//非空链表、
		//先遍历链表进行找尾、
		LTNode* ptail = *pplist;
		while (ptail->next)//注意这是不带哨兵卫的需要判断的是ptail->next是否为空
		{
			ptail = ptail->next;
		}
		ptail->next = tmp;
	}
}

3.8头删法

void LTPopFront(LTNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	//if (*pplist == NULL)
	//{
	//	return; 
	//}
	//只有一个结点
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		LTNode* cur = *pplist;
		*pplist = (*pplist)->next;
		free(cur);
		cur = NULL;
	}
}

3.9尾删法

void LTPopBack(LTNode** pplist)
{
	//重点!!!
	// 注意点一:
	//即使链表为空链表, 即结构体指针变量pplist为空指针NULL,但是结构体指针变量pplist的 地址 一定不是空指针NULL, 并且该结构体指针变量
	//pplist的地址一定不能是空指针NULL,若为空指针NULL,下面对其解引用,即*ppplist相当于对空指针NULL进行解引用,会出现错误、
	assert(pplist);

	//注意点二:
	//一定要分链表只有一个结点和多个结点如果只有一个结点就不能用 ptail->next->next 去判断了 因为ptail->next为NULL NULL->next err
	if (*pplist == NULL)
	{
		return;
	}
	//只有一个结点
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	//多个结点
	else
	{
		LTNode* ptail = *pplist;
		while (ptail->next->next)
		{
			ptail = ptail->next;
		}
		free(ptail->next);
		ptail->next = NULL;
	}
}

3.10查找附加修改

LTNode* LTFind(LTNode** pplist, LTDatatype x)
{
	//当链表为空时,plist等于NULL,此时查找的话想得到的结果就是找不到即可,并不需要直接断言从而报错,太暴力、
	//assert(plist);
	LTNode* cur = *pplist;
	while (cur)
	{
		if (cur->val == x)
		{
			cur->val *= 10;//修改成10倍
			return cur;
		}
		cur = cur->next;
	}
	return cur;

}

3.11在pos结点位置之后插入x

void LTInsertAfter(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newNode = BuynewNode(x);
	LTNode* next = pos->next;
	pos->next = newNode;
	newNode->next = next;
}

3.12在pos位置之前插入x

void LTInsertBefore(LTNode** pplist, LTNode* pos, LTDatatype x)
{
	assert(pplist);
	assert(pos);
	//LTNode* newNode = BuynewNode(x);
	//LTNode* cur = *pplist;
	//注意!! 插入时需要判断是否只有一个结点
	//方法一:
	//if ((*pplist)->next == NULL)
	//{
	//	newNode->next = *pplist;
	//	*pplist = newNode;
	//}
	//else
	//{
	//	while (cur->next != pos)
	//	{
	//		cur = cur->next;
	//	}
	//	newNode->next = pos;
	//	cur->next = newNode;
	//}

	//方法二
	//如果pos的位置恰好为第一个节点的位置时就相当于是头删、
	//判断pos所在的位置是否为第一个节点、
	if ((*pplist) == pos)
	{
		LTPushFront(pplist, x);
	}
	else
	{

		LTNode* cur = *pplist;
		//pos所在位置不是第一个节点的位置
		while (cur->next != pos)
		{
			cur = cur->next;
		}

		LTNode* newNode = BuynewNode(x);
		newNode->next = pos;
		cur->next = newNode;

	}

}

3.12删除pos位置之后的结点

void LTEraseAfter(LTNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		LTNode* next = pos->next;
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}```
### 3.13删除pos所在位置的结点

```c
void LTErase(LTNode** pplist, LTNode* pos)
{
	assert(pos);
	if (*pplist == pos)
	{
		LTNode* next = (*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
	else
	{
		LTNode* prev = *pplist;
		LTNode* next = pos->next;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = next;
		free(pos);
		pos = NULL;

	}
}

3.13销毁链表

void LTDestory(LTNode** pplist)
{
	assert(pplist);
	//逐一销毁
	LTNode* cur = *pplist;
	while (cur)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pplist = NULL;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值