线性表之单链表

链表

1.链表的概念及结构

概念:连表示一种屋里存储结构上的非连续,非吮吸的储存结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

物理上:1.链式结构在逻辑上是连续的,但在物理上不一定连续。

2.现实中的节点一般都是从对上申请出来的。

3.从对上申请空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

2.链表的分类

1.单向或者双向

2.带头或者不带头

3.循环或者非循环

共计8种

最常用的两种链表:无头单向非循环链表  和  带头双向循环链表

1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际更多是作为其他数据结构的子结构,如:哈希桶,图的邻链表。另外在笔试中出现很多。

2.带头双向循环链表:结构最复杂,一般用于单独存储数据。实际中使用的链表结构,都涉及带头双向循环链表。另外,这个结构看似复杂,实际使用代码实现会带来很多优势,实现反而更简单。

3.链表的实现

无头单向非循环链表

建立结构体变量

typedef int SLDataType;//方便更改数据类型
typedef struct SList
{
	SLDataType data;
	struct SList* next;//节点,存下一个链表的地址。
}SList;

链表不需要单独封装一个函数初始化

1.链表打印

基本思路:1)断言指针

2)为空直接打印NULL

3)创建临时变量,判断条件 cur!=NULL

void SListPrint(SList* phead)
{
	if (phead == NULL)
		printf("NULL\n");
	else
	{
		SList* cur = phead;//一般情况下不要动头节点。
		while (cur != NULL)//为空结束循环
		{
			printf("%d->", cur->data);
			cur = cur->next;
		}
		printf("NULL\n");
	}
}

2.创建一个新的节点

后续插入数据都需要创建新新节点,独立封装一个函数是最优解。

基本思路:1)malloc节点,判断节点是否成功创建,否就直接终止程序。

2)结构体成员初始化。

SList* BuySListNode(SLDataType x)
{
//创建一个节点
	SList* tmp = (SList*)malloc(sizeof(SList));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);//直接退出程序
	}
	else
	{
		SList* newnode = tmp;
		//初始化
		newnode->data = x;
		newnode->next = NULL;
		return newnode;
	}
}

2.尾插数据

基本思路:1)创建一个新节点

2)传递指针要传递二级指针,创建变量创建的是结构体指针,修改数据要在地址上修改。

3)判断链表是否为空,为空直接赋值,不为空找到为节点,链接上即可。

配图:

代码实现:

void SListPushBack(SList** pphead, SLDataType x)
{
	//不需要断言。
	//创建一个节点。
	SList* newnode = BuySListNode(x);
	//如果为空,直接赋值
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//遍历链表,找到尾节点
		SList* tail = *pphead;
		while (tail->next != NULL)//下一个节点为空,上一个节点就是尾节点。
		{
			tail = tail->next;//找下一个节点
		}
		tail->next = newnode;
		//新节点初始化时指针是指NULL,可以不用二次链接
	}
}

 

3.头插数据

基本思路:1)创建新节点

2)判断空与非空,是否需要额外讨论。

画图:

1)非空

2)空

两者无区别

代码:

void SListPushFront(SList** pphead, SLDataType x)
{
	SList* newnode = BuySListNode(x);
	//plist非空
	newnode->next = *pphead;
	*pphead = newnode;
	//为空时不需要额外讨论,画图解释。
}

 

4.尾删数据

基本思路:1)断言

2)链表为空,直接返回

3)找到尾节点,free掉

i.只有2个节点,尾删就是置空头节点。

ii.多个节点,采用双指针的方法prev,tail

画图:

1)链表为空 return;

2)链表有2个节点

3)链表有多个节点

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

free(tail);

代码:

void SListPopBack(SList** pphead)
{
	assert(pphead);
	//空节点
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个节点
	else
	{
		SList* tail = (*pphead)->next;
		SList* prev = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
			prev = prev->next;
		}
		free(tail);
		prev->next = NULL;
	}
}

5.头删数据

基本思路:1)修改传递二级指针

2)如果是空节点直接返回

3)优良二或多个节点,创建一个next指针,保存头节点的

     下一个节点,free掉头节点,将头节点换成next

画图:

当只有一个节点的时候,next= NULL,改变与多个节点的情况相同,不需要额外讨论。

代码:

void SListPopFront(SList** pphead)
{
	assert(pphead);
	//空节点
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		//两个或多个节点
		SList* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

 

6.查找x

基本思路:1)断言,防止传递空指针

2)遍历查找x,找到返回节点,找不到返回NULL

实现代码:

SList* SListFind(SList* phead, SLDataType x)
{
	assert(phead);
	SList* cur = phead;
	while (phead != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
			cur = cur->next;
	}
	return NULL;
}

7.在pos后插入x

基本思路:1)断言pos,以防传递空指针

2)创建新的节点newnode

3)进行插入的两种思路

思路一:创建临时变量tmp 保存pos->next节点,随便链接即可。

思路二:直接链接,但是要注意从后往前链接,防止出现连接未完成,pos找不到原先的pos->next。

void SistInsertAfter(SList* pos, SLDataType x)
{
	assert(pos);
	方法一:
	//SList* tmp = pos->next;//保存节点,以防止pos指向原先的节点失效,引发bug
	//SList* newnode = BuySListNode(x);//插入就要创建新的节点
	//pos->next = newnode;//pos链接新的节点
	//newnode->next = tmp;//实现插入

	//方法二:
	SList* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

8.在pos前插入x

基本思路:1)断言pos;

      2)创建新的节点

      3)分情况讨论

i若是pos指向的是头节点,那就调用头插函数接口。

ii一般情况,创建一个cur指针变量,找到pos前的节点,进行插入,与7相似的处理手段。

实现代码:

void SListInsertBefore(SList** pphead,SList* pos, SLDataType x)
{
	assert(pos);
	SList* newnode = BuySListNode(x);
	if (pos == *pphead)
	{
		SListPushFront(pphead,x);
	}
	else
	{
		SList* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

9.删除pos后的节点

基本思路:1)断言pos

2)创建临时节点next,记录pos->next,即要free的节点

3)进行链接pos 和 next->next

4)坑点,若pos后面为空,那么就会出现next->next访问出错,就需要处理下。

使用if语句进行处理。

画图:

代码实现:

void SListPopAfter(SList* pos)
{
	assert(pos);
	if (pos->next != NULL)//pos后为NULL就不执行代码
	{
		SList* next = pos->next;
		pos->next = next->next;
		free(next);
		next = NULL;
	}
	/*else
		printf("pos已经是尾节点了\n");*///单纯测试代码
}

 

10.删除pos前的节点

基本思路:1)断言

2)分情况讨论:

i.pos如果是头节点,直接返回

ii.如果是两个节点,那就直接调用头删函数解决

iii.如果是多个节点,创建cur节点,cur->next->next为pos时,就是要找的cur

    free节点。

3)由于使用一般情况的节点数必须超过3个,所以<3的就需要额外讨论。

画图:

i

ii

iii

代码实现:

void SListPopBefore(SList* pos,SList** pphead)
{
	assert(pos);
	//一个
	if (pos == *pphead)
	{
		return;
	}
	else if ((*pphead)->next == pos)
	{
		SListPopFront(pphead);
	}
	else
	{//多个
		SList* cur = *pphead;
		while (cur->next->next != pos)
		{
			cur = cur->next;
		}
		
		SList* prev = cur->next;
		cur->next = pos;
		free(prev);
		prev = NULL;
	}
}

11.删除pos位置的值

基本思路:1)断言pos和pphead

2)分类讨论

i如果pos指向的是头节点,那就调用头删函数

ii创建临时变量cur遍历链表,找到pos前的节点,记为cur,链接cur和pos->next

free掉pos

画图:

代码实现:

void SListPopPos(SList** pphead, SList* pos)
{
	assert(pphead&&pos);
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else 
	{
		SList* cur = *pphead;
		while (cur->next != pos)//找到pos前的节点
		{
			cur = cur->next;
		}
		cur->next = pos->next;//链接
		free(pos);//释放pos
		pos = NULL;
	}
}

12.删除链表

基本思路:1)遍历链表,一个节点一个节点的删

2)free前创建临时变量next记录cur->next,循环条件就是cur!=NULL

3)头节点最后置空

void SListDestroy(SList** pphead)
{
	SList* cur = *pphead;
	while (cur)//一个节点一个节点的删
	{
		SList* next = cur->next;
		free(cur);//释放
		cur = next;//循环调整
	}
	*pphead = NULL;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值