【数据结构】单链表的实现(C语言实现)

1.什么是链表

1.1链表的概念及结构

链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表
中的 指针链接 次序实现的 。每一个节点分为数据域data和指针域next,最后一个节点的指针域为NULL,其结构如下图所示:

1.2链表的分类

实际中链表的结构非常多样,单向或者双向、带头或者不带头、循环或者非循环都会出现,但是在日常使用和刷题过程中,主要考察的是以下两种链表结构:
1. 无头单向非循环链表: 结构简单 ,一般不会单独用来存数据。实际中更多是作为 其他数据结
构的子结构 ,如哈希桶、图的邻接表等等。另外这种结构在 笔试面试 中出现很多。
2. 带头双向循环链表: 结构最复杂 ,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现反而简单,它也是使用最为方便,最接近完美的链表类型。
本次重点介绍无头单向非循环链表的各个接口的实现。

2.无头单向非循环链表各个接口的实现

2.1链表的定义

//定义链表
typedef struct slistnode
{
	int data;//数据域
	struct slistnode *next;//指针域
}SLTNode;

2.2链表的打印

//链表的打印
void SListprint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)//指针不为空,说明链表没到最后一位
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

2.3创建一个新的节点

//创建一个新的节点
SLTNode* BuySListNode(SLDatatype x)
{
	SLTNode* tail = (SLTNode*)malloc(sizeof(SLTNode));
	tail->data = x;
	tail->next = NULL;
	return tail;
}

2.4尾插

//尾插
//先建立一个新节点,然后遍历找到原链表中的尾节点;
void SListPushback(SLTNode** pphead, SLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);//先建立一个新节点
	
	if(*pphead==NULL)//如果节点为空,则直接将新的节点放置进去
		*pphead = newnode;
		
	else//不为空,遍历找到原链表中的尾节点后插入
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

2.5头插

//头插
void SListPushfront(SLTNode** pphead, SLDatatype x)
{
	//先建立一个新节点
	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;//新节点的下一个节点为原链表的第一个节点
	*pphead = newnode;//令新插入的节点为新链表的第一个节点
}

 2.6尾删

void SListPopback(SLTNode** pphead)
{
	//如果此时链表为空
	if (*pphead == NULL)
	{
		return;
	}
	//此时只有一个节点
	else if ((*pphead)->next==NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//方法一
		SLTNode* tail = *pphead;
		SLTNode* prev = NULL;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;

		//方法二
		/*SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;*/
	}
}

2.7头删

//头删
void SListPopfront(SLTNode** pphead)
{
	SLTNode* tail = (*pphead)->next;
	free(*pphead);
	*pphead = tail;//将tail视为接下来的新链表的第一个节点,继续执行头删
}

2.8查找

//查找
SLTNode* SListFind(SLTNode* phead, SLDatatype x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if(cur->data==x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

2.9在pos值之前插入一个数

//在pos值之前插入一个数
void SListInsert(SLTNode** pphead, SLTNode* pos, SLDatatype x)
{
	if (pos == *pphead)
	{
		SListPushfront(pphead, x);//如果此时链表为空,则直接用头插
	}
	else
	{
		SLTNode* newnode = BuySListNode(x);
		SLTNode* prev = *pphead;
		while (prev->next!=pos)
		{
			prev = prev->next;//找到pos前的那个节点
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

2.10在pos值之后插入一个数

//在pos值之后插入一个数
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLDatatype x)
{
	if (*pphead == NULL)
	{
		SListPushfront(pphead, x);//如果此时链表为空,则直接用头插
	}
	else
	{
		SLTNode* newnode = BuySListNode(x);
		SLTNode* next = pos->next;
		//顺序无所谓
		pos->next = newnode;
		newnode->next = next;
	}
}

2.11删除链表中指定的某一个数

//删除链表中指定的某一个数
SLTNode* RemoveElements(SLTNode* head, int data)
{
	SLTNode* prev = NULL;
	SLTNode* tail = head;
	while (tail)
	{
		if (tail->data == data)//如果此时的值正好是需要删除的数
		{
			if (prev == NULL)//如果第一个节点就是要删除的数字
			{
				head = tail->next;
			}
			else//需要删除的数在链表中,不是在开头
			{
				prev->next = tail->next;
			}
		}
		else//如果此时的值不是需要删除的数
		{
			prev = tail;
		}
		tail = tail->next;
	}
	return head;
}

2.12链表销毁

//链表销毁
void SListDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}
//遍历销毁,销毁前保留下一个节点的位置,然后将该节点销毁,再将cur给到下一个节点,再次销毁cur;

 3.总结

单链表适合头插或者头删,不适合插入或者删除中间某一个值,因为需要从头开始遍历寻找,时间复杂度较高,而双向带头循环链表,是链表所有结构的最优解,后续会有介绍,同时也会更新链表相关的高频面试题。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值