首页 > 其他分享 >C 语言 【单链表】

C 语言 【单链表】

时间:2024-11-16 13:44:04浏览次数:3  
标签:pphead 单链 语言 pList next SLNode NULL 节点

        单链表是一种基本的数据结构,由一系列节点组成,每个节点包含数据域和指针域。‌数据域用于存储实际的数据,而指针域则存储指向下一个节点的地址。单链表的特点包括动态存储、非连续存储、易于插入和删除。

        节点可以定义成一个结构体,每个节点中包含一个数据和下一个节点的地址。

//构造节点
typedef int SLDataType;

struct SLNode
{
	SLDataType data;
	struct SLNode* next;
};

typedef struct SLNode SLNode;

        上面的结构体定义了一个节点,节点中存放的数据类型被定义成了SLDataType类型,节点的名称叫做SLNode。

1、单链表的顺序访问

        这里给出单链表的逻辑结构:首先有一个节点指针pList指向开始的节点,节点中的next存放的是下一个节点的地址。可以通过next访问下一个节点。最后一个节点的next中存放的是空指针NULL;

        这里写一个打印单链表的函数,以示范单链表的访问功能;

void SLPrint(SLNode* phead);

        这里需要说明的是,打印函数在访问链表的过程中不涉及更改链表,所以在设计函数时参数部分用节点的指针就可以。进一步来说,打印函数需要将每一个节点的data打印出来,这个时候可以按照如下的方式进行访问节点;

        当cur为NULL时打印NULL即可,那么函数的实现可以写成如下的形式;(由于链表为空时能进行打印功能,所以不能在函数体最开始进行断言操作) 

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

2、单链表的末尾插入

        链表的末尾插入首先需要创建新的节点,这里可以创建一个新的节点指针,并对里面的内容分进行初始化,由于后面还会涉及到节点的创建,所以在这里选择将其封装成一个函数;  在创建完成时,返回新节点的指针用于后续的拼接。

SLNode* SLBuyNode(SLDataType val)
{
	SLNode* newNode = (SLNode*)malloc(sizeof(SLNode));
	if (newNode == NULL)
	{
		perror("PushBack malloc faile");
		return;
	}

	newNode->data = val;
	newNode->next = NULL;

	return newNode;
}

        在末尾插入节点时会有两种情况:1、链表本身为空  2、链表不为空

        当链表本身为空时,我们需要将新创建的节点与pList进行拼接,此时的操作就会改变pList的值,在写函数时我们要改变的是一级节点指针的内容,所以在函数传参时应该传入二级节点指针才能达到目的效果。

        当链表pList为空时,将新创建的节点的地址newNode赋值给pList.然而在函数调用过程中形参的改变不影响实参的变化。所以在这里选择使用*pphead = newNode ,这样就可以达到修改实参的目的。

        当链表不为空时,需要找到链表的最后一个节点的地址tail,修改最后一个节点的next 使其指向创建的新节点newNode。

        这里需要注意的是,我们需要改变的是最后一个节点中nest的值,那么就需要最后一个节点的地址tail ,当tail->next非空时 将tail->next赋值给tail即可找到最后一个节点的地址。最后将newNode赋值给tail->next即可。这样尾插节点就实现了,下面是代码实现:

void SLPushBack(SLNode** pphead,SLDataType val)
{
	//1、创建新的节点指针
	SLNode* newNode = SLBuyNode(val);

	//进行拼接
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		//找最后一个节点的结构体指针
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

3、单链表的末尾删除

        需要考虑的是当链表为空时就不能进行删除操作,所以需要在函数体开始进行断言操作。第二点:目标链表的节点数为1时,删除完节点之后要修改pList的值,使它的值为NULL;这里就需要更改pList的值,所以在函数传参时就需要传入节点的二级指针。函数声明如下:

void SLPopBack(SLNode** pphead);

        当链表只有一个节点时:直接释放pList 再将其置空

        当链表中的节点个数大于1时,先找到倒数第二个节点的地址,用于将倒数第二个节点中的next置为空,也需要找到倒数第一个结点的地址,直接释放再将其置为空。

        那么单链表的末尾删除就可以写成如下的形式:

void SLPopBack(SLNode** pphead)
{
	assert(*pphead);

	if ((*pphead)->next != NULL)
	{
		//链表节点大于1个
		SLNode* prev = NULL;
		SLNode* tail = *pphead;
		
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;
	}
	else
	{
		free(*pphead);
		*pphead = NULL;
	}
}

4、单链表的头部插入

        链表为空时可以进行插入操作,所以不能进行断言操作。当链表为空时,创建新节点之后需要将新节点的地址赋值给pList,在这个操作中需要修改pList的值,所以在函数传参时需要传入节点的二级指针。单链表的头部插入函数声明如下:

void SLPushFront(SLNode** pphead, SLDataType val);

        在头部插入时,均需要修改pList的值。然后将新节点中next的值修改为原来pList的值

         可以看到,上述的两种情况可以合并为一种情况来写,所以单链表的头部插入函数可以写成如下的形式:

void SLPushFront(SLNode** pphead, SLDataType val)
{
	SLNode* newNode = SLBuyNode(val);
	newNode->next = *pphead;
	*pphead = newNode;
}

5、单链表的头部删除

        链表为空时不能进行删除操作,所以在函数体内要进行断言操作。每次删除一个节点之后,要将第二个节点的地址赋值给pList,在这里也需要修改pList的值,所以在函数传参时,也需要传入节点的二级指针。函数的声明如下:

void SLPopFront(SLNode** pphead);

         按照上面的逻辑就可以定义函数为:

void SLPopFront(SLNode** pphead)
{
	assert(*pphead != NULL);

	SLNode* first = *pphead;
	*pphead = first->next;

	free(first);
	first = NULL;
}

单链表的测试:

void Test1()
{
	SLNode* pList;
	pList = NULL;
	

	SLPushBack(&pList,1);
	SLPushBack(&pList,2);
	SLPushBack(&pList,3);
	SLPushBack(&pList,4);
	SLPrint(pList);

	SLPopBack(&pList);
	SLPopBack(&pList);
	SLPopBack(&pList);
	SLPopBack(&pList);

	SLPrint(pList);


}

void Test2()
{
	SLNode* pList;
	pList = NULL;


	SLPushBack(&pList, 1);
	SLPushBack(&pList, 2);
	SLPushBack(&pList, 3);
	SLPushBack(&pList, 4);
	SLPrint(pList);

	SLPushFront(&pList,1);
	SLPushFront(&pList,2);
	SLPushFront(&pList,3);
	SLPushFront(&pList,4);

	SLPrint(pList);


}

void Test3()
{
	SLNode* pList;
	pList = NULL;

	SLPushFront(&pList, 1);
	SLPushFront(&pList, 2);
	SLPushFront(&pList, 3);
	SLPushFront(&pList, 4);

	SLPrint(pList);

	SLPopFront(&pList);
	SLPrint(pList);
	SLPopFront(&pList);
	SLPrint(pList);
	SLPopFront(&pList);
	SLPrint(pList);
	SLPopFront(&pList);
	SLPrint(pList);
	


}

int main()
{
	Test1();
	Test2();
	Test3();

	return 0;
}

        下附运行结果,可以观察到,上述功能均已实现。

标签:pphead,单链,语言,pList,next,SLNode,NULL,节点
From: https://blog.csdn.net/qq_70199082/article/details/143803955

相关文章

  • Go语言24小时极速学习教程(一)基础语法
    Go语言(也称为Golang)是一种由Google开发的编程语言,以其简洁、高效和并发支持而闻名。从本文开始,将带你快速完成Go语言的学习,如果你之前有过Java或者C语言的基础,学习它将很容易,本教程忽略环境搭建步骤,直奔主题。1.程序结构包声明:每个Go程序都从package声明开始,通常是packag......
  • 探索大型语言模型(LLMs)能否在不泄露私人信息的情况下联合其他大型语言模型共同解决问题
    概述谷歌的GeminiUltra(2023年)和OpenAI的GPT-4(2023年)等大规模语言模型在许多任务中都表现出了令人印象深刻的性能。然而,这些模型不仅推理成本高昂,而且运行于数据中心,而数据中心并非本地环境,无法获得私人数据。另一方面,可以在私人环境中运行的模型,如GeminiNano,可以......
  • C 语言struct 结构
    目录1.简介2.struct的复制3.struct指针4.struct的嵌套5.位字段6.弹性数组成员1.简介C语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用中并不够用。实际使用中,主要有下面两种情况,需要更灵......
  • C 语言typedef 命令
    目录1.简介2.主要好处1.简介typedef命令用来为某个类型起别名。typedeftypename;上面代码中,type代表类型名,name代表别名。typedefunsignedcharBYTE;BYTEc='z';上面示例中,typedef命令为类型unsignchar起别名BYTE,然后就可以使用BYTE声明变量。typedef可......
  • C 语言的内存管理
    目录1.简介2.void指针3.malloc()4.free()5.calloc()6.realloc()7.restrict说明符8.memcpy()9.memmove()10.memcmp()1.简介C语言的内存管理,分成两部分。一部分是系统管理的,另一部分是用户手动管理的。系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量......
  • 使用 Neko 编程语言实现简单的滑动验证码识别
    滑动验证码是一种常见的安全验证方式,要求用户将图块拖动到正确位置。本文将使用Neko编程语言实现一个简单的滑动验证码识别程序,通过基本的图像处理技术自动识别图块匹配位置。实现步骤加载图片:使用Neko的图像处理库加载滑块和背景图片。图像预处理:转换为灰度图并进行边缘......
  • 【落羽的落羽 C语言篇】指针·之其二
    文章目录一、const使用指南1.const修饰变量2.const修饰指针变量二、野指针1.野指针的概念2.野指针的成因3.如何规避野指针三、assert(断言)使用指南四、传值调用和传址调用1.传值调用2.传址调用一、const使用指南1.const修饰变量众所周知,变量是可以修......
  • 什么是C语言中的指针?
    1.基本概念在C语言中,指针是一种变量,它存储的是另一个变量的内存地址。可以把内存想象成一个巨大的公寓楼,每个变量就像住在公寓里的居民,而指针就是写着居民房间号(内存地址)的纸条。例如,假设有一个整型变量a,它存储在内存中的某个位置,指针变量p就可以用来保存变量a的内存地......
  • VMR 实战指南:一站式管理多语言SDK
    1.引言在现代软件开发中,我们经常需要同时使用多种编程语言和工具。管理这些不同语言的SDK版本可能会成为一个令人头疼的问题。VMR(VersionManager)提供了一个统一的界面来管理多种编程语言的SDK,简化了开发环境管理。本文将深入探讨VMR的安装、配置和使用。2.VMR简介......
  • c语言sizeof与strlen的区别详细解析
    char*p="abcdef";printf("%d\n",sizeof(p));p是指针变量(地址),地址就是地址,大小就是4/8字节printf("%d\n",sizeof(p+1));p+1是b的地址,还是地址4/8字节printf("%d\n",sizeof(*p));*p是‘a’,sizeof(*p)计算的是字符的大小,是1字节printf("%d\n"......