数据结构入门——单链表

本文详细介绍了链表的定义,包括链表节点结构,以及头插法和尾插法创建链表的方法。随后探讨了链表的删除操作,包括在头部和尾部删除元素,以及在特定位置插入和删除节点。最后讲述了链表的销毁过程。
摘要由CSDN通过智能技术生成

目录

前言

链表的定义

链表的创建

头插法创建链表

尾插法创建链表

链表的删除

在头部删除元素

在尾部删除元素

查找固定元素

在链表中插入元素

在pos位置前插入

在pos位置后插入

删除链表结点

删除pos位置的结点

删除pos后的结点

链表的销毁

写在最后


前言

前面我们学习了顺序表,顺序表访问元素直接用下标就可以,支持随机访问,随即访问固然强大,但是也会有不足之处,顺序表的插入删除元素需要移动大量的元素,倒是操作很不方便,浪费大量的时间。

接下来我们学习的链表就很好的解决了这个问题,链表中的每一个元素都有对应的地址,通过“链"建立元素之间的逻辑关系。

链表的定义

链表的节点包含存储的元素,以及指向下一个结点的指针,当然链表的元素可以是任意元素,下面仅用int类型进行介绍。

typedef struct ListNode
{
	int data;
	struct ListNode* next;
}ListNode;

 

链表的创建

头插法创建链表

头插法创建链表就是每次插入的元素都是在链表的头部进行,利用BuyNewNode(int x)函数申请一个新的结点。

在插入元素时,当时第一个元素时需*pphead = newNode,后面再有元素插入链表时只需更新新结点的next指针和更新*pphead即可。

ListNode* BuyNewNode(int x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL)
	{
		printf("malloc fail\n");
		return NULL;
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}
void LSPushFront(ListNode** pphead,int x)
{
	assert(pphead);
	ListNode* newnode = BuyNewNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
}

尾插法创建链表

尾插法创建链表,故名起义,就是每次插入的元素都是在链表的尾部,和头插法相同的是如果插入的是第一个元素的话就将*pphead的值置为新的结点。

插入其他元素时,因为是尾插法创建链表,所以需要找到链表的最后一个结点,然后将最后一个结点的tail->next指针指向申请的新结点。

void LSPushBack(ListNode** pphead, int x)
{
	assert(pphead);
	ListNode* newNode = BuyNewNode(x);
	if ((*pphead) == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		ListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

 

链表的删除

在头部删除元素

在链表的头部删除元素,就是每次从删除链表的头部结点。

删除时需要分类讨论,如果链表只有一个元素的话就直接free()掉*pphead即可,如果链表的元素个数大于一个时,我们需要记录链表的头结点,然后再更新头结点,最后free()掉头结点。

void LSPopFront(ListNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		ListNode* pnext = *pphead;
		*pphead = (*pphead)->next;
		free(pnext);
		pnext = NULL;
	}
}

在尾部删除元素

与头部删除结点一样,如果是只有一个元素就free()掉*pphead即可

如果不是只有一个元素的话,我们要分别找到链表的最后一个结点tail以及最后一个结点的前驱结点prev,最后free()掉tail,并将prev->next置为NULL。

void LSPopBack(ListNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		ListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		ListNode* prev = *pphead;
		while (prev->next != tail)
		{
			prev = prev->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

 

查找固定元素

遍历链表,找到值为x的结点,找到的话返回相应结点信息,查找失败则返回NULL。

ListNode* LSFind(ListNode* phead, int x)
{
	assert(phead);
	ListNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
			return pcur;
		else
			pcur = pcur->next;
	}
	return NULL;
}

在链表中插入元素

在链表中插入元素,分为前插和后插。

在pos位置前插入

在进行插入操作时,要检查pos位置,若是pos和*pphead相等就是前插,直接调用即可。

若不是,则需找到pos位置的前一个结点prev,再申请结点,最后更改指针即可。

void LSInsertFront(ListNode** pphead, ListNode* pos, int x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		LSPushFront(pphead, x);
	}
	else
	{
		//ListNode* tail = *pphead;
		//ListNode* prev = NULL;
		//while (tail != pos)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}
		//ListNode* newnode = BuyNewNode(x);
		//newnode->next = tail;
		//prev->next = newnode;

		ListNode* prev = *pphead;
		while (prev)
		{
			if (prev->next == pos)
			{
				break;
			}
			else
			{
				prev = prev->next;
			}
		}
		ListNode* newNode = BuyNewNode(x);
		prev->next = newNode;
		newNode->next = pos;
	}
}

在pos位置后插入

与前插操作相比,后插操作要简单很多,申请新节点后吗,修改pos指针和新节点指针即可

void LSInsertBack(ListNode** pphead, ListNode* pos, int x)
{
	assert(pphead);
	assert(pos);
	ListNode* newNode = BuyNewNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

 

删除链表结点

删除pos位置的结点

删除操作开始时先检查删除位置是不是头结点或者尾结点,如果是的话直接调用头插尾插操作即可完成。

因为是删除pos位置的结点,所以我们得找到pos位置的前一个结点prev,然后修改相应指针并释放pos

void LSErase(ListNode** pphead, ListNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	if (pos == *pphead)
	{
		//头删
		LSPopFront(pphead);
	}
	else if (pos->next == NULL)
	{
		LSPopBack(pphead);
	}
	else 
	{
		//ListNode* tail = *pphead;
		//ListNode* prev = NULL;
		//while (tail != pos)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}
		//prev->next = tail->next;
		//free(tail);
		//tail = NULL;

		ListNode* prev = *pphead;
		while (prev)
		{
			if (prev->next == pos)
			{
				break;
			}
			else
			{
				prev = prev->next;
			}
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除pos后的结点

与删除pos位置的结点相比,删除pos后的结点就要简单很多,但是要在删除前检查pos->next不为空,然后记录pos->next指向的结点,然后更新pos->next并且释放要删除的结点

void LSEraseAfter( ListNode* pos)
{
	assert(pos);
	assert(pos->next);
	ListNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

 

链表的销毁

链表的销毁只能一个一个结点的进行销毁。利用循环迭代可完成。

void LSDestory(ListNode** pphead)
{
	//链表的销毁只能一个节点一个节点的进行销毁
	assert(pphead);
	ListNode* pcur = *pphead;
	ListNode* next = NULL;
	while (pcur)
	{
		next = pcur -> next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	free(next);
}

​​​​​​​ 

写在最后

单链表的操作是有点繁杂,但是经过后续的练习,每个小伙伴都可以掌握,后面我们继续努力!!

  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值