C语言之旅:探索单链表

目录

一、前言

二、实现链表的功能:

打印

创建节点

尾插

尾删

头插

头删

查找

在指定位置之前插入数据

指定位置删除

在指定位置之后插入数据

打印

销毁

三、全部源码:

四、结语


一、前言

链表是一个强大且基础的数据结构。对于很多初学者来说,链表可能是一个令人望而生畏的主题,因为它涉及到了动态内存分配和指针操作等较为高级的概念。但是,一旦你掌握了链表的基本概念和操作,你会发现它在很多实际应用中都有着广泛的应用。

今天,我们就从C语言的角度,来探索一下单链表的基本概念和实现方法。在接下来的篇幅中,我将带你逐步构建一个单链表的简单实现,并解释其中的关键概念和代码逻辑。

首先,我们需要明确什么是单链表。单链表是一种线性数据结构,它的元素(在C语言中通常称为节点)按照线性的顺序排列,但每个节点并不是在内存中连续存储的。相反,每个节点都包含两个部分:一个是数据域(用于存储实际的数据),另一个是指针域(用于指向下一个节点)。通过这种方式,我们可以将多个节点连接起来,形成一个链状的数据结构。

单链表的定义:

cpp 复制代码
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
	//
}SLTNode;

二、实现链表的功能:

cpp 复制代码
//打印
void SLTPrint(SLTNode* phead);

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

打印

cpp 复制代码
void SLTPrint(SLTNode* phead)
{
	SLTNode* pucr = phead;
	while (pucr != NULL)
	{
		printf("%d->", pucr->data);
		pucr = pucr->next;
	}
	printf("NULL\n");
}

创建节点

cpp 复制代码
SLTNode* SLTBuyNode(SLTDataType x) 
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL) 
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

尾插

cpp 复制代码
void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{
	SLTNode* newNode = SLTBuyNode(x);

	// 如果链表为空,直接让头指针指向新节点  
	if (*pphead == NULL) {
		*pphead = newNode;
		return;
	}

	// 否则,找到链表尾部并插入新节点  
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		current = current->next;
	}
	current->next = newNode;
}

尾删

cpp 复制代码
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* prev = NULL;
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		prev = current;
		current = current->next;
	}
	free(current);
	if (prev == NULL)
	{
		*pphead = NULL;
	}
	else
	{
		prev->next = NULL;
	}
}
  1. 首先,检查传入的二级指针和其指向的头指针是否为空,以确保链表存在。

  2. 接着,定义了两个指针prev和current,用于遍历链表并找到尾节点。

在循环中,prev总是指向current的前一个节点,而current则遍历链表直到找到尾节点(即current->next为NULL的节点)。

  1. 找到尾节点后,释放其内存。

  2. 最后,根据prev是否为空来判断链表是否只有一个节点。如果是,则将头指针置为NULL;否则,将prev的next指针置为NULL,完成尾节点的删除。

头插

cpp 复制代码
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLTBuyNode(x);
	  
	newNode->next = *pphead;
	
	*pphead = newNode;

}

我们首先通过断言确保传入的二级指针pphead不为空。接着,我们调用SLTBuyNode函数来根据给定的数据x创建一个新的节点newNode。然后,我们将新节点的next指针设置为当前的头节点*pphead,这样新节点就连接到了链表的开始位置。最后,我们更新头指针*pphead,使其指向新节点,从而完成了在链表头部插入新节点的操作。

头删

cpp 复制代码
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead=tmp;
}

定义一个临时指针tmp,让它指向当前头节点的下一个节点。接着,我们释放当前头节点的内存。最后,我们更新头指针*pphead,使其指向tmp, 从而完成了头节点的删除操作。如果链表原本只有一个节点,那么删除后头指针*pphead将被设置为NULL,表示链表为空。

查找

cpp 复制代码
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		if (pcur->data == x)
		{
			return pcur;

		}
		pcur = pcur->next;

	}
	return NULL;
}

使用一个指针pcur来遍历链表,从头节点开始,逐个检查每个节点的数据是否与要查找的数据x相等。如果找到了匹配的节点,就返回该节点的指针;如果遍历完整个链表都没有找到匹配的节点,则返回NULL表示未找到。

在指定位置之前插入数据

cpp 复制代码
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		// 找到pos节点的前一个节点  
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		// 在pos之前插入新节点  
		newnode->next = pos;
		prev->next = newnode;
	}
}

指定位置删除

cpp 复制代码
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		*pphead = pos->next;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)// 遍历链表,找到要删除节点的前一个节点
		{
			prev = prev->next;
		}
		prev->next = pos->next;

	}

}

在指定位置之后插入数据

cpp 复制代码
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

设置新节点的next指针指向pos的下一个节点。最后,它将pos的next指针指向新节点,从而将新节点插入到pos之后。

打印

cpp 复制代码
//打印
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++) 
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

销毁

cpp 复制代码
//销毁
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur != NULL)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

三、全部源码:

cpp 复制代码
// 打印链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* pucr = phead;
	while (pucr != NULL)
	{
		printf("%d->", pucr->data);
		pucr = pucr->next;
	}
	printf("NULL\n");
}
//初始化
SLTNode* SLTBuyNode(SLTDataType x) 
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL) 
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}
// 尾部插入  
void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{
	SLTNode* newNode = SLTBuyNode(x);

	// 如果链表为空,直接让头指针指向新节点  
	if (*pphead == NULL) {
		*pphead = newNode;
		return;
	}

	// 否则,找到链表尾部并插入新节点  
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		current = current->next;
	}
	current->next = newNode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* prev = NULL;
	SLTNode* current = *pphead;
	while (current->next != NULL)
	{
		prev = current;
		current = current->next;
	}
	free(current);
	if (prev == NULL)
	{
		*pphead = NULL;
	}
	else
	{
		prev->next = NULL;
	}
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLTBuyNode(x);
	  
	newNode->next = *pphead;
	
	*pphead = newNode;

}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead=tmp;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		if (pcur->data == x)
		{
			return pcur;

		}
		pcur = pcur->next;

	}
	return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		// 找到pos节点的前一个节点  
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		// 在pos之前插入新节点  
		newnode->next = pos;
		prev->next = newnode;
	}
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		*pphead = pos->next;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;

	}

}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos&&pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;	

}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur != NULL)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

四、结语

总之,C语言中的单链表之旅是一次富有成效的学习经历。它不仅让我们掌握了链表的基本操作,还提高了我们的编程技能和问题解决能力。希望这个旅程能为你在数据结构和算法领域的进一步探索打下坚实的基础。

相关推荐
AI备案指南-满满41 分钟前
人工智能拟人化互动服务安全自评估报告的评估要点有哪些?
人工智能·算法·安全·机器人·大模型备案·算法备案
LuminousCPP1 小时前
数据结构‑二叉树(二):二叉堆从零实现|Heap结构设计 + 建堆优化 + 堆排序深度解析
c语言·数据结构·笔记·二叉树
土司大王2 小时前
LeetCode hot100——两两交换链表中的节点
算法·leetcode·职场和发展
大熊背3 小时前
树莓派IspPipeline LSC模块原理详解
算法·lsc·isppipeline·mesh lsc
zander2584 小时前
LeetCode 300. 最长递增子序列
算法·leetcode·深度优先
程序员与背包客_CoderZ4 小时前
高性能分布式KV存储引擎RocksDB入门与C/C++编码实战
c语言·开发语言·数据库·c++·分布式·分布式数据库·rocksdb
william_yangshun4 小时前
【大模型部署实战】kimi-k3-in-c 中文版:用 176KB 纯 C99 引擎在 CPU 上跑 Kimi K3 上手指南
c语言·开发语言
祖力554 小时前
Linux应用软件编程:目录IO与framebuffer
linux·运维·算法·framebuffer·目录io
名字还没想好☜4 小时前
Go 用 slices/maps 标准库泛型函数:告别手写 Contains、Sort、去重(Go 1.21)
开发语言·后端·算法·golang·go
地平线开发者5 小时前
【模型轻量化专题】深度学习模型为什么需要轻量化
算法