数据结构——单链表

本文介绍了如何使用链表结构解决顺序表在插入和删除操作中效率低下的问题。链表允许元素非顺序存储,通过指针链接元素,从而在插入和删除时避免大量元素的移动。文章详细讲解了链表的概念、结构,并展示了单链表的创建、插入、删除等操作的实现,包括头插、头删、尾插、尾删、查找、定位插入和删除等基本操作。此外,还提供了单链表的打印、销毁方法,以及顺序表和链表的主要区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接上文顺序表的缺点有什么办法解决呢?

        前面讲到顺序表的存储结构。顺序表最大的缺点是插入和删除需要移动大量的元素,很耗时间,有什么有好的办法解决呢?

        答案是有的,让我们来看看链表的存储结构。

链表的概念及结构

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

是什么意思呢?元素之间不考虑是否相邻位置了,哪有空位就到哪里,只要让每个元素都知道自己下一个元素的地址在哪就可以了,这样,我们在访问第一个元素的时候就能知道第二个元素的地址,访问第二个元素的时候就能知道第三个元素的地址。以此类推,所有的元素我们都能遍历找到。 

各个接口的实现:

typedef int SLTDatatype;

typedef struct SListNode
{
	SLTDatatype data;
	struct SListNode *next;

}SListNode;

动态申请一个节点

SListNode* BuySListNode(SLTDatatype x)
{
	SListNode* node = (SListNode*)malloc(sizeof(SListNode));
	if (node == NULL)
	{
		printf("malloc fail \n");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;

	return node;
}

单链表尾插

        在链表的最后面插入一个结点,需要考虑2种情况:该链表如果为空怎么处理,如果不为空呢那该如何处理呢?

       

void SListPushBack(SListNode** plist, SLTDatatype x)
{
	assert(plist);
    
    //申请一个结点
	SListNode* node = BuySListNode(x);

	//链表为空
	if (*plist == NULL)
	{
		*plist= node;
	}
	else    //链表不为空遍历找到最后一个
	{
		SListNode* cur = *plist;

		while (cur->next != NULL)
		{
			cur = cur->next;
		}

		cur->next = node;
	}

}

单链表尾删

        删除最后一个结点,有3种情况要考虑:1、链表为空    2、链表只有一个结点   3、链表有多个结点

        

void SListPopBack(SListNode**plist)
{
	assert(plist);

	//处理三种情况:1、链表为空   2、 链表中只有一个结点   3、链表有多个结点
	if (*plist == NULL)
	{
		return;
	}
	else if ((*plist)->next==NULL)
	{
		free(*plist);
		*plist = NULL;
	}
	else
	{
		SListNode* tail = *plist;
		SListNode*prev = NULL;

		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

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

 单链表头插

         单链表头插相对比较简单,申请一个新结点newNode,newNode->next指向*plist,再把newNode的地址给*plist。

void SListPushFront(SListNode**plist, SLTDatatype x)
{
	assert(plist);

       //申请新结点
	SListNode* newNode=BuySListNode(x);

	newNode->next = *plist;
	*plist = newNode;

}

单链表头删

        头删链表:定义一个指针指向该链表的第二个结点的地址,再free(*plist)。如果先free(*plist)就找不到链表其他的结点了。

void SListPopFront(SListNode**p)
{
	assert(p);

	//链表为空返回
	if (*p == NULL)
	{
		return;
	}
	else
	{
		//记录链表的下一个结点
		SListNode* next = (*p)->next;

		free(*p);
		*p = next;
	}
}

单链表查找

        简单粗暴遍历查找,找到该结点直接返回其地址,没找到则返回NULL。

SListNode* SListFind(SListNode*p, SLTDatatype x)
{
	assert(p);

	SListNode* cur = p;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			//找到返回其地址
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

单链表pos位置之后插入

        申请一个新的结点让这个新的结点指向pos的下一个结点,再让pos指向新结点。这里要注意先后顺序,如果先让pos指向新的结点就找不到下一个结点的地址了。

void SListInsertAfter(SListNode*pos, SLTDatatype x)
{
	assert(pos);

	//申请新结点
	SListNode* newnode = BuySListNode(x);

	newnode->next = pos->next;
	pos->next = newnode;
}

单链表pos位置删除

        删除pos位置的结点:需要考虑到如果要删除的结点是头结点的话?就相当于是头删了,可以直接调用上面讲到的头删接口。如果要删除的不是头结点,用一个指针cur遍历查找每个结点指向的下一个结点是不是pos结点,是的话就退出循环。退出循环后,cur就是pos结点的前一个结点,因此cur指向pos的下一个结点地址即可,cur->next=pos->next。最后一步free(pos),pos=NULL。

void SListErase(SListNode**p,SListNode* pos)
{
	assert(pos);

	//pos是头结点相当于头删
	if (*p == pos)
	{
		SListPopFront(p);
	}
	else
	{
		SListNode* cur = *p;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		
		//让cur->next指向pos位置的下一个结点。
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

 打印单链表

void SListPrint(SListNode* plist)
{
	SListNode* cur = p;
	while (cur!= NULL)
	{
		printf("%d ->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

销毁单链表

        销毁链表需要逐步把每个结点删除,需要用2个指针来完成。一个cur来遍历链表,另一个指针next来记录cur的下一个结点。这样就不会出现free一个结点就找不到下一个结点的情况。

void SListDestory(SListNode**plist)
{
	assert(plist);

	SListNode*cur = *plist;
	while (cur != NULL)
	{
        //记录下一个结点的地址
		SListNode*next = cur->next;

		free(cur);

        //把下一个结点地址给cur
		cur = next;
	}
}

顺序表和链表的区别

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值