线性表——单链表的增删查改

本文对比了顺序表与链表的区别,链表在任意位置插入和删除效率高,但不支持随机访问,缓存利用率低;顺序表支持随机访问,缓存利用率高,但插入和删除效率低。还详细介绍了单链表的全部接口实现,包括增删查改等操作。

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

2024-5-11添加

顺序表与链表的区别: 

这里使用带头双向循环和顺序表进行对比。

链表:  

  • 任意位置频繁的插入和删除,只需修改指针,这个的时间复杂度是O(1)
  • 按需申请释放, 没有扩容的消耗(扩容本身有消耗, 而且异地扩要拷贝数据, 而且扩容可能存在空间的浪费, 当按倍数扩大时, 空间可能扩的太多。)
  • 但是链表不支持随机访问
  • 逻辑上时连续的,但是物理上是不一定连续的
  • 缓存的利用效率低

顺序表的优势

  • 支持随机访问O(1)
  • 逻辑上连续, 物理上也连续
  • 元素的高效存储同时可以频繁的访问(因为访问速度是O(1))
  • 任意位置插入和删除的时候可能需要搬动数据, 效率很低O(N)
  • 扩容有消耗,体现在以下几个方面: 扩容本身有消耗, 而且异地扩容要拷贝数据。 同时扩容按照倍数进行扩容后, 可能扩容的过多, 造成空间的浪费。
  • 缓存的利用效率很高

缓存利用率的概念:

        计算机的存储体系是这样的:

在这个存储体系中, 有三级缓存L1, L2, L3。 知道了这点后我们看一下下面这一张图:

        如图的红框框是我们++i的汇编代码。 这里面的意思是先让0赋值给i, 在将i的值赋值给eax寄存器。 然后eax加一。 最后将eax寄存器的值赋值给i。 即完成了++i。

        那么, 这里就要思考, 为什么++i的工作要放在寄存器里面进行呢?直接在内存中不行吗?

答案是因为cpu的计算太快了, cpu并不会去直接访问内存如果要计算的对象很小, 就加载到寄存器当中计算,就像上面的++i;但是如果计算的对象很大, 那么就要加载到三级缓存中进行计算

        那么对于一个顺序表还有一个链表来说, 是如何加载缓存的呢?

       首先加载缓存的规则是一次加载都会加载一段空间到缓存中。下面使用上图进行举例。

        对于顺序表来说, 我们如果要访问1, 那么第一次, 缓存中没有任何东西, cpu在缓存中找不到1. 那么缓存没有命中。 就会将1以及1之后的一段空间内的数据都加载到缓存中。

 

        那么接下来的几次访问, 不再需要再次加载缓存, cpu直接从缓存中就能直接命中要计算的数据。

 

        对于链表来说, cpu在第一次在缓存中寻找数据时也没有命中。 那么链表同样会向缓存中进行加载。 但是, 对于链表来说, 他的每个空间都是不连续的, 所以加载的时候即便加载一段空间。 cpu在下次向缓存中寻找数据时同样无法命中, 那么就会再次加载缓存。 而我们的缓存空间也是有限的, 而这样导致缓存中有大量的无用数据, 从而导致缓存的利用率下降。

本节复习链表的增删查改

首先, 链表不是连续的, 而是通过指针联系起来的。 如图:

这四个节点不是连续的内存空间, 但是彼此之间使用了一个指针来连接。 这就是链表。 

现在我们来实现链表的增删查改。

目录

单链表的全部接口:

 准备文件

建立结构体蓝图

申请链表节点函数接口

单链表的打印函数接口

单链表尾插函数接口

单链表头插函数接口

 单链表尾删函数接口

单链表的头删函数接口

 单链表查找函数接口

单链表pos位置之后插入数据接口

单链表删除pos之后位置的数据

单链表在pos位置之前插入数据接口

单链表删除pos位置数据接口

单链表的销毁


单链表的全部接口:

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);
//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos);
//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x);
//单链表在pos位置删除数据接口
void SListPop(SLNode** pphead, SLNode* pos);
//单链表的销毁函数接口
void SLTDestory(SLNode** pphead);
 

---------------------------------------------------------------------------------------------------------------------------------

 准备文件

首先准备好三个文件夹, 一个main.c文件夹, 一个.h文件夹用来声明链表的接口以及定义结构体等。 一个.c文件夹用来实现单链表。

---------------------------------------------------------------------------------------------------------------------------------

建立结构体蓝图

首先包含一下头文件, 定义一下数据类型。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

接着再建立一个链表的结构体

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

---------------------------------------------------------------------------------------------------------------------------------

申请链表节点函数接口

申请链表的节点操作, 在尾插, 头插, 或者特定位置插入的时候都需要, 所以可以封装成一个函数。 后续直接进行复用就可以。

.h函数声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode //创建结构体
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);

.c函数实现

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

 在实现的过程中,可以将数据直接储存到新节点中。 然后让新节点指向NULL, 然后返回该节点。 然后将链表直接连接到这个节点就可以。

---------------------------------------------------------------------------------------------------------------------------------

单链表的打印函数接口

为了便于后续的函数接口的调试, 我们先实现单链表的打印操作。

.h函数声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);

.c函数实现

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

---------------------------------------------------------------------------------------------------------------------------------

单链表尾插函数接口

.h函数声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);

.c函数实现

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);//利用申请节点函数申请节点

	SLNode* cur = *pphead; //让cur指向phead所指向空间
	if (cur == NULL) //cur == NULL代表着phead指向NULL, 这时候让phead改变指向。传送phead指针的原因就是
	{                                              //要改变phead的指向。
		*pphead = newnode;//
	}
	else 
	{
		while (cur->next != NULL) //让cur遍历到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//最后
	}

}

尾插接口时传送phead的指针的原因是因为phead可能改变指向,从空指针变为指向一个节点。要改变phead的指向那就是意味着形参要相对于phead传址调用,  而phead本身就是一个一级指针, phead取地址就是一个二级指针, 所以形参是二级指针。

---------------------------------------------------------------------------------------------------------------------------------

单链表头插函数接口

.h函数接口


////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);

.c函数实现

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);//利用申请节点函数申请节点

	SLNode* cur = *pphead; //让cur指向phead所指向空间
	if (cur == NULL) //cur == NULL代表着phead指向NULL, 这时候让phead改变指向。传送phead指针的原因就是
	{                                              //要改变phead的指向。
		*pphead = newnode;//
	}
	else 
	{
		while (cur->next != NULL) //让cur遍历到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//最后
	}

}

//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;

}

现在我们来利用打印接口调试一下我们写的是否存在问题。 

在main.c中输入如下代码

void TestSListNode()
{
	SLNode* phead = NULL;
	SListPushBack(&phead, 1);
	SListPushBack(&phead, 2);
	SListPushBack(&phead, 3);
	SListPushBack(&phead, 4);
	SListPushBack(&phead, 5);
	SListPushFront(&phead, 0);




	SListPrint(phead);
	printf("\n");

	/*SListPopFront(&phead);
	SListPopFront(&phead);
	SListPopFront(&phead);
	SListPopBack(&phead);
	SListPrint(phead);
	printf("\n");

	SLTDestory(&phead);
	
	SListPrint(phead);
	printf("\n");*/

}

int main() 
{
//	TestTree();
//	TestStack();
//	TestQueue();
//	TestSeqList();
	TestSListNode();
//	TestDSLNode();
//	TestOJ();
	return 0;
}

运行图如下: 

 通过检验,没有问题。 继续往下走。 

---------------------------------------------------------------------------------------------------------------------------------

 单链表尾删函数接口

.h文件声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);

.c函数实现

  首先pphead不能为空, 如果phead指向空的话就直接返回。 然后定义cur和prev两个指针, 遍历寻找尾节点。 cur领先prev一个节点, cur指向尾节点的时候, 就释放掉这个节点。 然后prev指向空节点。 寻找尾节点的过程是这样的:

代码实现

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);//利用申请节点函数申请节点

	SLNode* cur = *pphead; //让cur指向phead所指向空间
	if (cur == NULL) //cur == NULL代表着phead指向NULL, 这时候让phead改变指向。传送phead指针的原因就是
	{                                              //要改变phead的指向。
		*pphead = newnode;//
	}
	else 
	{
		while (cur->next != NULL) //让cur遍历到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//最后
	}

}

//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;

}

//单链表尾删函数接口
void SListPopBack(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	SLNode* prev = *pphead;
	while (cur->next != NULL) //对链表进行遍历。 cur最终会指向尾节点。 prev用来维护链表
	{
		prev = cur;
		cur = cur->next;
	}
	if (prev == cur) 
	{
		free(cur);
		*pphead = NULL;
	}
	else 
	{
		free(cur);
		prev = NULL;
	}
	
}

---------------------------------------------------------------------------------------------------------------------------------

单链表的头删函数接口

.h函数声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);

.c函数实现 

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);//利用申请节点函数申请节点

	SLNode* cur = *pphead; //让cur指向phead所指向空间
	if (cur == NULL) //cur == NULL代表着phead指向NULL, 这时候让phead改变指向。传送phead指针的原因就是
	{                                              //要改变phead的指向。
		*pphead = newnode;//
	}
	else 
	{
		while (cur->next != NULL) //让cur遍历到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//最后
	}

}

//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;

}

//单链表尾删函数接口
void SListPopBack(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	SLNode* prev = *pphead;
	while (cur->next != NULL) //对链表进行遍历。 cur最终会指向尾节点。 prev用来维护链表
	{
		prev = cur;
		cur = cur->next;
	}
	if (prev == cur) 
	{
		free(cur);
		*pphead = NULL;
	}
	else 
	{
		free(cur);
		prev = NULL;
	}
	
}

//单链表的头删函数接口
void SListPopFront(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
}

代码的意思是, 首先pphead不能为空, 然后phead不能指向空。 然后让一个cur指针指向头节点。 然后修改phead的指向, 使其指向第二个节点(当第二个节点是空的时候, 就是指向空)。然后释放cur指向的节点也就是头节点。 如图为过程:

---------------------------------------------------------------------------------------------------------------------------------

 单链表查找函数接口

.h接口声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);

.c接口实现

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);//利用申请节点函数申请节点

	SLNode* cur = *pphead; //让cur指向phead所指向空间
	if (cur == NULL) //cur == NULL代表着phead指向NULL, 这时候让phead改变指向。传送phead指针的原因就是
	{                                              //要改变phead的指向。
		*pphead = newnode;//
	}
	else 
	{
		while (cur->next != NULL) //让cur遍历到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//最后
	}

}

//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;

}

//单链表尾删函数接口
void SListPopBack(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	SLNode* prev = *pphead;
	while (cur->next != NULL) //对链表进行遍历。 cur最终会指向尾节点。 prev用来维护链表
	{
		prev = cur;
		cur = cur->next;
	}
	if (prev == cur) 
	{
		free(cur);
		*pphead = NULL;
	}
	else 
	{
		free(cur);
		prev = NULL;
	}
	
}

//单链表的头删函数接口
void SListPopFront(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
}


//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x) 
{
	SLNode* cur = phead;//定义一个指向头节点的指针, 用于遍历
	while (cur != NULL) //向后遍历
	{
		if (cur->data == x) //找到节点后就返回节点的地址。
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

 代码太长, 之后.c文件的代码只展示相应接口的代码

---------------------------------------------------------------------------------------------------------------------------------

单链表pos位置之后插入数据接口

.h接口声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);

.c接口实现 


//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x) 
{
	assert(pos);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = pos->next;
	newnode->next = cur;
	pos->next = newnode;
}

 该接口的实现过程如下:

令指针cur指向pos的下一个节点, newnode的next指向cur, pos的next指向newnode

---------------------------------------------------------------------------------------------------------------------------------

单链表删除pos之后位置的数据

.h接口声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);
//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos);

.c接口实现


//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos) 
{
	assert(pos);
	//
	SLNode* cur = pos->next;
	pos->next = pos->next->next;
	free(cur);
}

该接口实现和单链表在pos位置之后插入数据接口实现方式类似, 都是利用一个cur指针指向pos之后的位置作为记忆保存,然后进行插入或者删除操作。 

---------------------------------------------------------------------------------------------------------------------------------

单链表在pos位置之前插入数据接口

该接口的实现有点复杂, 但是实现该接口之后, 对于尾插还有头插就很好实现了, 尾插和头插是该接口的两个特殊情况。 假如pos是头节点,就是头插, 假如pos是尾节点, 就是尾插。

.h接口声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);
//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos);
//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x);

.c接口实现


//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x) 
{
	assert(pphead);

	//
	SLNode* newnode = BuySListNode(x);
	//
	if (*pphead == NULL) 
	{
		*pphead = newnode;
		return;
	}
	//
	if (*pphead == pos) 
	{
		newnode->next = pos;
		*pphead = newnode;
		return;
	}
	SLNode* cur = *pphead;
	while (cur != NULL && cur->next != pos) 
	{
		cur = cur->next;
	}
	newnode->next = pos;
	cur->next = newnode;
}

该接口分为三种情况:

第一种是链表为空, 这个时候直接插入节点。

第二种情况是pos的位置在第一个节点的位置, 这个时候需要改变phead的指向。 

第三种情况就是最普通的情况, 在除头节点, 链表的任意节点前插入。如图:

 

---------------------------------------------------------------------------------------------------------------------------------

单链表删除pos位置数据接口

和pos位置插入数据接口一样, 实现了该接口对于尾删, 头删接口就很简单了。 头删, 尾删都是单链表删除pos位置数据接口的特殊情况。 pos是头节点, 就是头删, pos是尾节点。 就是尾删。 

.h接口声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);
//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos);
//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x);
//单链表在pos位置删除数据接口
void SListPop(SLNode** pphead, SLNode* pos);

.c接口实现


//单链表在pos位置删除数据接口
void SListPop(SLNode** pphead, SLNode* pos) 
{
	assert(pphead);
	//
	if (*pphead == pos) 
	{
		*pphead = (*pphead)->next;
		free(pos);
	}
	else 
	{
		SLNode* cur = *pphead;
		while (cur->next != pos) 
		{
			cur->next = pos->next;
			free(pos);
		}
	}
}

pos位置删除分两种情况

一种是头删, 需要phead改变指向。

 一种是其他位置删除节点

单链表的销毁

 链表使用完之后应该销毁, 放置内存泄漏

.h接口声明

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);
//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos);
//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x);
//单链表在pos位置删除数据接口
void SListPop(SLNode** pphead, SLNode* pos);
//单链表的销毁函数接口
void SLTDestory(SLNode** pphead);

.c接口实现 


//单链表的销毁函数接口
void SLTDestory(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL) 
	{
		return;
	}
	SLNode* prev = (*pphead);
	SLNode* cur = (*pphead)->next;
	if (cur == NULL) 
	{
		*pphead = NULL;
		free(prev);
	}
	else 
	{
		*pphead = NULL;
		while(cur != NULL)
		{
			free(prev);
			prev = cur;
			cur = cur->next;
		}
		free(prev);
	}

}

销毁需要一步一步的进行, 如下图:

现在来看一下总体代码:

.h文件

////////////////////////////////////链表的增删查改///////////////////////////////////////////////////
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	struct SListNode* next;
	SLTDataType data;
}SLNode;

//////////////////////////////////////链表接口声明/////////////////////////////////////////////////////

//申请链表节点函数接口
SLNode* BuySListNode(SLTDataType x);
//单链表的打印函数接口
void SListPrint(SLNode* phead);
//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x);
//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x);
//单链表尾删函数接口
void SListPopBack(SLNode** pphead);
//单链表的头删函数接口
void SListPopFront(SLNode** pphead);
//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x);
//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x);
//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos);
//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x);
//单链表在pos位置删除数据接口
void SListPop(SLNode** pphead, SLNode* pos);
//单链表的销毁函数接口
void SLTDestory(SLNode** pphead);

.c文件

////////////////////////////////////////单链表函数实现函数接口////////////////////////////////////////

//单链表申请节点函数接口
SLNode* BuySListNode(SLTDataType x) 
{
	SLNode* NewNode = (SLNode*)malloc(sizeof(SLNode));
	if (NewNode == NULL) 
	{
		printf("申请节点失败\n");
		return;
	}
	//
	NewNode->data = x;
	NewNode->next = NULL;
}

//单链表的节点打印操作
void SListPrint(SLNode* phead) 
{
	SLNode* cur = phead;

	while (cur != NULL) 
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	if (cur == NULL)//最后打印一个NULL
	{
		printf("NULL");
	}
	
}

//单链表尾插函数接口
void SListPushBack(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);//利用申请节点函数申请节点

	SLNode* cur = *pphead; //让cur指向phead所指向空间
	if (cur == NULL) //cur == NULL代表着phead指向NULL, 这时候让phead改变指向。传送phead指针的原因就是
	{                                              //要改变phead的指向。
		*pphead = newnode;//
	}
	else 
	{
		while (cur->next != NULL) //让cur遍历到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//最后
	}

}

//单链表头插函数接口
void SListPushFront(SLNode** pphead, SLTDataType x) 
{
	assert(pphead);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;

}

//单链表尾删函数接口
void SListPopBack(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	SLNode* prev = *pphead;
	while (cur->next != NULL) //对链表进行遍历。 cur最终会指向尾节点。 prev用来维护链表
	{
		prev = cur;
		cur = cur->next;
	}
	if (prev == cur) 
	{
		free(cur);
		*pphead = NULL;
	}
	else 
	{
		free(cur);
		prev = NULL;
	}
	
}

//单链表的头删函数接口
void SListPopFront(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	//
	SLNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
}


//单链表查找函数接口
SLNode* SListFind(SLNode* phead, SLTDataType x) 
{
	SLNode* cur = phead;//定义一个指向头节点的指针, 用于遍历
	while (cur != NULL) //向后遍历
	{
		if (cur->data == x) //找到节点后就返回节点的地址。
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//单链表在pos位置之后插入数据接口
void SListInsertAfter(SLNode* pos, SLTDataType x) 
{
	assert(pos);
	SLNode* newnode = BuySListNode(x);
	//
	SLNode* cur = pos->next;
	newnode->next = cur;
	pos->next = newnode;
}


//单链表在pos之后的位置删除数据
void SListPopAfter(SLNode* pos) 
{
	assert(pos);
	//
	SLNode* cur = pos->next;
	pos->next = pos->next->next;
	free(cur);
}

//单链表在pos位置之前插入数据接口
void SListInsert(SLNode** pphead, SLNode* pos, SLTDataType x) 
{
	assert(pphead);

	//
	SLNode* newnode = BuySListNode(x);
	//
	if (*pphead == NULL) 
	{
		*pphead = newnode;
		return;
	}
	//
	if (*pphead == pos) 
	{
		newnode->next = pos;
		*pphead = newnode;
		return;
	}
	SLNode* cur = *pphead;
	while (cur != NULL && cur->next != pos) 
	{
		cur = cur->next;
	}
	newnode->next = pos;
	cur->next = newnode;
}

//单链表在pos位置删除数据接口
void SListPop(SLNode** pphead, SLNode* pos) 
{
	assert(pphead);
	//
	if (*pphead == pos) 
	{
		*pphead = (*pphead)->next;
		free(pos);
	}
	else 
	{
		SLNode* cur = *pphead;
		while (cur->next != pos) 
		{
			cur->next = pos->next;
			free(pos);
		}
	}
}

//单链表的销毁函数接口
void SLTDestory(SLNode** pphead) 
{
	assert(pphead);
	if (*pphead == NULL) 
	{
		return;
	}
	SLNode* prev = (*pphead);
	SLNode* cur = (*pphead)->next;
	if (cur == NULL) 
	{
		*pphead = NULL;
		free(prev);
	}
	else 
	{
		*pphead = NULL;
		while(cur != NULL)
		{
			free(prev);
			prev = cur;
			cur = cur->next;
		}
		free(prev);
	}

}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值