数据结构--单链表通过C语言实现

前言

在上一篇博客中,讲述了用C语言实现顺序表。顺序表的好处就在于它的物理结构是连续的,便于操作。可它也有一些缺点使得它有时候并不怎么好用。数据结构--顺序表通过C语言实现~__w_z_j_的博客-CSDN博客1.顺序表的概念顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。1.1 静态顺序表//静态的顺序表#define N 10struct SeqList{int a[N];int size;};静态顺序表的大小是给定的,不能再变的。不灵活。一般大小未知的不用静态顺序表。1.2 动态顺序表//动态的顺序表typedef int SLDataType;typedef struct SeqListhttps://blog.csdn.net/weixin_60720508/article/details/123428720?spm=1001.2014.3001.5501

比如它的头插/任意位置插入时它的时间复杂度为O(N),时间消耗长;再一个就是在增容时,万一后面的空间不够,那么还要重新开辟一个新空间,将原来的数据都拷贝进去。产生了浪费;增容时也不能做到用多少增多少,比如说本来就有100个空间,现在还需要5个空间,这时一般都是增容2倍,那么剩下的95个空间不就浪费了吗。为了解决这样的问题所以单链表就出现了,本篇博客就讲一讲单链表的实现。

1. 单链表的介绍

单链表和顺序表不同的地方就是单链表的物理结构不是连续的,数据元素的顺序逻辑是通过链表中的指针链接实现的。大致结构图:

 从上图可以看出,链表的物理顺序不一定是连续的,但由指针链成的实现的逻辑顺序一定是连续的;并且实际中的结点一般都是在堆上申请出来的,是根据一定的分配策略分配空间的,有时候结点的物理顺序也可能是连续的。

 2. 单链表的实现

实现单链表的增删查插功能:

typedef int SlistType;

typedef struct SlistNode
{
	SlistType data; //val
	struct SlistNode* next; //存储下一个结点地址
}SlistNode;

//打印单链表
void PrintSlist(SlistNode* phead);
//申请一个结点
SlistNode* Buynewnode(SlistType x);
//单链表尾插
void SlistPushBack(SlistNode** pphead, SlistType x);
//单链表头插
void SlistPushFront(SlistNode** pphead, SlistType x);
//单链表尾删
void SlistPopBack(SlistNode** pphead);
//单链表头删
void SlistPopFront(SlistNode** pphead);
//单链表查找
SlistNode* SlistFind(SlistNode* phead, SlistType x);
//在pos前面插
void SListInsertBefore(SlistNode** pphead, SlistNode* pos, SlistType x);
//在pos后面插
void SListInsertAfter(SlistNode* pos, SlistType x);
//删除pos后面的数据
void SlistEraseAfter(SlistNode* pos);
//销毁单链表
void SlistDestory(SlistNode** phead);

2.1 打印单链表函数

void PrintSlist(SlistNode* phead)
{
	SlistNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2.2 申请一个节点函数

SlistNode* Buynewnode(SlistType x)
{
	SlistNode* newnode = (SlistNode*)malloc(sizeof(SlistNode));
	if (newnode == NULL)
	{
		printf("malloc failed\n");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}
	return newnode;
}
//注意申请失败的情况,直接让整个程序退出即可

 2.3查找函数

SlistNode* SlistFind(SlistNode* phead, SlistType x)
{
	SlistNode* str = phead;
	while (str != NULL)
	{
		if (str->data == x)
		{
			return str;
		}
		else
		{
			str = str->next;
		}
	}
	return NULL;
}

2.4 插入类函数

2.4.1 尾插函数

//尾插
void SlistPushBack(SlistNode* pphead, SlistType x)
{
	SlistNode* newnode = Buynewnode(x);
	if (pphead == NULL)
	{
		pphead = newnode;
	}
	else
	{
		//找尾
		SlistNode* tail = pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
//看看这段代码,当pphead为空时,运行起来能实现它的功能吗?

 将这个函数放到测试文件中进行测验:

 看到1并没有如我们所愿被放到链表中。什么原因?是由于传入的函数参数为形参!将pphead的地址拷贝一份放到了另一个空间里,所以对这个空间里的数据进行改变是对pphead没有任何影响的!解决方式应该是将函数的参数变成二级指针指向pphead。

void SlistPushBack(SlistNode** pphead, SlistType x)
{
	SlistNode* newnode = Buynewnode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SlistNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

 2.4.2 头插函数

void SlistPushFront(SlistNode** pphead, SlistType x)
{
	SlistNode* newnode = Buynewnode(x);
	if (*pphead != NULL)
	{
		newnode->next = *pphead;
	}
	*pphead = newnode;
}

 2.4.3 在pos前面插入

void SListInsertBefore(SlistNode** pphead,SlistNode* pos, SlistType x)
{
	assert(pos);
	assert(pphead);
	SlistNode* prev = *pphead;

	if (prev == *pphead)
	{
		SlistPushFront(pphead, x);
	}
	else
	{
		SlistNode* newnode = Buynewnode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next =pos;
		prev->next = newnode;
	}
}

2.4.4 在pos后面插入

void SListInsertAfter(SlistNode* pos, SlistType x)
{
	assert(pos);
	SlistNode* newnode = Buynewnode(x);
	SlistNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}
//在删除pos位置时一般都是使用删除后面的函数。因为这样比较方便
//不需要从头寻找就能将整个单链表链接在一起。
//但是怎么使用还要根据不同的要求来选择。

2.5 删除类函数

2.5.1 头删

void SlistPopFront(SlistNode** pphead)
{
	if (*pphead != NULL)
	{
		SlistNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
	else
		return;
}

2.5.2 尾删

void SlistPopBack(SlistNode** pphead)
{
	if (*pphead != NULL)
	{

		SlistNode* tail1 = *pphead;
		SlistNode* prev = *pphead;
		if (tail1->next != NULL)		//多个节点
		{
			while (tail1->next != NULL)
			{
				prev = tail1;
				tail1 = tail1->next;
			}
            free(tail1);
			prev->next = NULL;
		}
		else                            //一个节点
		{
            free(*pphead);
			*pphead = NULL;
		}
	}
	else
		return;
}

2.5.3 删除pos后面的数据

void SlistEraseAfter(SlistNode* pos)
{
	assert(pos);
	if(pos->next)
	{
		SlistNode* next = pos->next->next;
		free(pos->next);
		pos->next = next;
	}
}

2.6 销毁单链表函数

void SlistDestory(SlistNode** pphead)
{
	assert(pphead);
	SlistNode* cur = *pphead;
	while (cur)
	{
		SlistNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}
//销毁单链表不像销毁顺序表简单,销毁单链表要
//一个结点一个结点释放,最后将head赋为NULL。

小结

  1. 凡是在写要修改head(也就是头指针)的函数时,要传head的地址(二级指针)。
  2. 要实现的函数的参数中有pos时,那么pos一定是事先找到的,所以head一定不为空,在写程序时,head为空的情况就不需要考虑了。
  3. 当我们遇到要删除单链表中的某个结点的程序题时,要考虑用双指针(prev,cur)的方法,因为cur是一直向前走的,当要删除某个结点时,要记录下前一个结点,这时要用prev进行操作以保持单链表的连续性。
  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_w_z_j_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值