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

news2024/11/18 19:33:56

 本节复习链表的增删查改

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

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

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

目录

单链表的全部接口:

 准备文件

建立结构体蓝图

申请链表节点函数接口

单链表的打印函数接口

单链表尾插函数接口

单链表头插函数接口

 单链表尾删函数接口

单链表的头删函数接口

 单链表查找函数接口

单链表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);
	}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1475987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue项目导出excel ,文件过大导致请求超时的处理方法

一、因为文件过大&#xff0c;请求时间较长&#xff0c;就会产生请求超时的情况&#xff0c;处理方式是可以分为三个接口&#xff0c;接口1用来获取id值&#xff0c;接口2利用id值发起请求&#xff0c;询问是否准备好下载&#xff0c;如果没准备好&#xff0c;则没隔一秒再次发…

搜维尔科技:OptiTrack 提供了性能最佳的动作捕捉平台

OptiTrack 动画 我们的 Prime 系列相机和 Motive 软件相结合&#xff0c;产生了世界上最大的捕获量、最精确的 3D 数据和有史以来最高的相机数量。OptiTrack 提供了性能最佳的动作捕捉平台&#xff0c;具有易于使用的制作工作流程以及运行世界上最大舞台所需的深度。 无与伦比…

Linux Centos7配置SSH免密登录

Linux Centos7配置SSH免密登录 配置SSH免密登录说明&#xff1a; 分两步 第一步、给Server A生成密钥对 第二步、给Server B授权 生成密钥对之后&#xff0c;我们可以看看它保存的目录下的文件。 接下来我们就要把Server A&#xff08;10.1.1.74&#xff09;的公钥拷贝到Se…

【踩坑】修复xrdp无法关闭Authentication Required验证窗口

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 问题如下&#xff0c;时不时出现&#xff0c;有时还怎么都关不掉&#xff0c;很烦&#xff1a; 解决方法一&#xff1a;命令行输入 dbus-send --typemethod_call --destorg.gnome.Shell /org/gnome/Shell org.gn…

ElasticSearch之找到乔丹的空中大灌篮电影

写在前面 本文看一个搜索的实际例子&#xff0c;找到篮球之神乔丹的电影Space Jam&#xff0c;即空中大灌篮。 正式开始之前先来看下要查询的目标文档&#xff0c;以及查询的text&#xff1a; 要查询的目标文档 {..."title": "Space Jam",..."ove…

加密与安全_深入了解哈希算法

文章目录 Pre概述哈希碰撞常用的哈希算法Codejava.security.MessageDigestMD5SHA-1SHA-256MessageDigest支持算法 哈希算法的用途彩虹表攻击基本原理攻击过程 防御彩虹表攻击基本原理用途 小结 Pre PKI - 01 散列(Hash)函数 概述 哈希算法&#xff08;Hash&#xff09;又称摘…

TikTok云手机可以运营多少个账号

在社交媒体平台上&#xff0c;尤其是像TikTok这样的热门应用中&#xff0c;账号运营已经成为了许多人的日常工作。而利用云手机技术&#xff0c;一台手机能够同时运营多个TikTok账号&#xff0c;为用户带来了更大的便利和灵活性。本文将探讨 TikTok云手机能够运营多少个账号&am…

【生成式AI】ChatGPT原理解析(1/3)- 对ChatGPT的常见误解

Hung-yi Lee 课件整理 文章目录 误解1误解2ChatGPT真正在做的事情-文字接龙 ChatGPT是在2022年12月7日上线的。 当时试用的感觉十分震撼。 误解1 我们想让chatGPT讲个笑话&#xff0c;可能会以为它是在一个笑话的集合里面随机地找一个笑话出来。 我们做一个测试就知道不是这样…

Linux课程四课---Linux开发环境的使用(vim编辑器的相关)

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

【Linux】协程简介

【Linux】协程简介 一、什么是协程&#xff1f;简介优点 二、为什么使用协程&#xff1f;三、协程的种类1、对称协程2、非对称协程 四、协程栈1、静态栈2、分段栈3、共享栈4、虚拟内存栈 五、协程调度1、栈式调度2、星切调度3、环切调度 六、常见协程库参考文献 一、什么是协程…

Spring Task的应用

介绍 Spring Task是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 定位&#xff1a; 定时任务框架 作用&#xff1a; 定时自动执行某段Java代码 应用场景&#xff1a; 引用卡每月还款提醒、银行贷款每月还款提醒、火车票售票系统处理未支…

SpringCloud(17)之SpringCloud Stream

一、Spring Cloud Stream介绍 Spring Cloud Stream是一个框架&#xff0c;用于构建与共享消息系统连接的高度可扩展的事件驱动微服务。该框架提供了一个灵活的编程模型&#xff0c;该模型建立在已经建立和熟悉的Spring习惯用法和最佳实践之上&#xff0c;包括对持久发布/子语义…

软件性能测试工具RunnerGo安装步骤

现在安装RunnerGo仅需要一条命令&#xff01;目前支持系统&#xff1a;Centos、Debian、Ubuntu三种。下面给大家介绍一下RunnerGo安装使用流程&#xff1a; Step1&#xff1a;复制以下命令 wget https://img.cdn.apipost.cn/running_go/img/wiki/runnergo.tar && ta…

探索 SPA 与 MPA:前端架构的选择与权衡

查看本专栏目录 关于作者 还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#x…

论文阅读-CheckFreq:频繁、精细的DNN检查点操作。

论文名称&#xff1a;CheckFreq: Frequent, Fine-Grained DNN Checkpointing. 摘要 训练深度神经网络(DNNs)是一项资源密集且耗时的任务。在训练过程中&#xff0c;模型在GPU上进行计算&#xff0c;重复地学习权重&#xff0c;持续多个epoch。学习到的权重存在GPU内存中&…

机器遗忘同等重要,AI如何忘记不良数据,保护用户隐私?

引言&#xff1a;大语言模型中的机器遗忘问题 在人工智能领域&#xff0c;大语言模型&#xff08;LLMs&#xff09;因其在文本生成、摘要、问答等任务中展现出的卓越能力而备受关注。然而&#xff0c;这些模型在训练过程中可能会记住大量数据&#xff0c;包括敏感或不当的信息…

【MySQL】学习和总结标量子查询

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-kLo6jykc7AcEVEQk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

消息中间件之RocketMQ源码分析(二十二)

Broker主从同步流程 配置数据同步流程 配置数据包含4种类型:Topic配置、消费者位点、延迟位点、订阅关系配置。每种配置数据由一个继承自ConfigManager的类来管理&#xff0c;继承关系如图。Slave如何从Master同步这些配置呢?我们先来看一下初始化服务的步骤 第一步:Maste…

【JavaEE进阶】图书管理系统开发日记——捌

文章目录 &#x1f343;前言&#x1f38d;统一数据返回格式&#x1f6a9;快速入门&#x1f6a9;存在问题&#x1f388;问题原因&#x1f388;代码修改 &#x1f6a9;统一格式返回的优点 &#x1f340;统一异常处理&#x1f332;前端代码的修改&#x1f6a9;登录页面&#x1f6a…

openai.CLIP多模态模型简介

介绍 OpenAI CLIP&#xff08;Contrastive Language–Image Pretraining&#xff09;是一种由OpenAI开发的多模态学习模型。它能够同时理解图像和文本&#xff0c;并在两者之间建立联系&#xff0c;实现了图像和文本之间的跨模态理解。 如何工作 CLIP模型的工作原理是将来自…