单链表的相关操作(精简版..在修改中)

news2024/11/17 13:35:37

目录

前言:

哨兵位:

链表的概念

链表的相关操作:

链表的创建:

打印链表:

申请新节点:

链表的尾插:

 !!!对于传参中二级指针的解释:

链表的头插:

链表的尾删: 

链表的头删:

寻找结点:

在链表的指定位置前插入:

在链表的指定位置后插入:

删除pos位置的结点:

删除pos位置后的结点:

销毁链表:

最终结果:

SList.h文件:

SList.c文件:

test.c文件:


前言:

等把其它内容看完后再来看前言......

链表一共有八种结构:

  • 带头单向不循环/循环链表
  • 带头双向不循环/循环链表
  • 不带头单向不循环/循环链表
  • 不带头双向不循环/循环链表

但是我们实际中 最常⽤还是两种结构 不带头单向不循环链表 带头双向循环链表
⽆头单向⾮循环链表:
        结构简单,⼀般不会单独⽤来存数据。更多是作为其他数据结构的⼦结构
带头双向循环链表:
        结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构就是它

哨兵位:

        这里的带头与不带头其实指的就是哨兵位,后面我们写的不带头单向不循环链表中是没有哨兵位的,这也是为什么我们要在后续使用二级指针pphead的原因:

       在不带哨兵节点的链表中,如果我们要在链表的头部插入一个有效结点,就需要创建一个指针使其指向该有效结点(链表中的每一个有效结点包括哨兵位都会有一个指针指向它们所在的内存空间)而在函数内部,我们只能修改参数的值,不能修改指向该参数的指针,因此,为了能够进行后续的链表的插入和删除操作,我们需要传递一个二级指针。

关于"只能修改参数的值"的解释这个值就相当于是这里括号中的20,而我们想要操作的是它前面的0x0000008c07371a24:        

至于为什么要操作该地址,请看下面关于尾插过程的调试(只演示了尾插涉及的部分代码):

	SLNode* plist = NULL;	
	//尾插
	SLPushBack(plist, 1);  
    SLPushBack(plist, 2);  
	//我们在后续使用时用的是这样的SLPushBack(&plist, 1);  
	SLPrint(plist);
	//SLByNode函数实现的是申请一块内存空间作为有效结点,同时令node指针指向该结点
    SLNode* node = SLByNode(x);
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
void SLPrint(SLNode* phead)
{
	//循环打印
	//为了能在遍历后仍能找到刚开始的起点,我们就需要利用一个临时指针pcur来存储头结点的地址
	SLNode* pcur = phead;
	//当头结点不为空时进行循环
	while (pcur)
	{
		//打印此时所处结点中的数据
		printf("%d ->", pcur->data);
		//打印结束后让pcur指向下一个结点的地址
		pcur = pcur->next;
	}
	//到最后时现有结点遍历完成,空间为NULL
	printf("NULL\n");
}

未使用二级指针时两次插入后plist的值情况:

这里虽然有data=2的有效结点但是之前那个data=1的呢?它好像并没有被保存下来:

很明显虽然我们进行了插入操作,但是实际上并没有插入成功,最后链表还是为空...... 

使用二级指针并完成更改后的结果:

再次插入时plist中的情况:

此时我们可以看到已经插入成功了......

结论: 传值调用和传址调用的区别

我也初学者对于这一点的内容也有一点懵,可能描述的也不算清楚但是应该会有一定帮助...... 

链表的具体概念

链表是线性表的一种,它就相当于一列火车:

        而链表这辆火车中的“车厢”,我们称之为“ 结点/节点 ”。就像普通车厢中需要存放数据一样,链表中的每个结点也要存储数据,但是与普通车厢可以从头一路走到尾的情况不同的是,链表中的每个"车厢"(结点)在 物理逻辑上不连续,在虚拟逻辑上连续。
对没错就是你想的那个物理和虚拟
结点 当前节点中保存的数据data + 保存下⼀个节点地址的指针变量next(单链表的情况)
为什么需要指针变量来保存下⼀个节点的位置?
        因为链表在内存空间上的存储是非连续的 ,就和火车车厢一样,根据需求进行增加和删除。通俗来讲就是,用到你这节”车厢“时把指向你这节“车厢”中的next指针(火车挂钩)指向你的下一节"车厢",不用你的时候就把你这节”车厢“中的next指针(火车挂钩)置空,你就一边闲着去。

不带头单向不循环链表的相关操作:

链表的创建:

创建链表需要经历以下操作:

1、定义一个结构体来表示链表的结点(SList.h文件)

//定义一种链表节点的结构(实际应用中有多种,这里只演示最基本的结构)
typedef int SLDataType; //便于切换链表中存储数据的类型
struct SListNode {   
	SLDataType data;  //存储数据
	struct SListNode* next;  //用来保存下一个节点地址的指针变量next
};
typedef struct SListNode SLNode;  //将链表结点的结构体重命名为SLNode
2、编写创建链表的函数(第二点这里的 内容只是为了方便理解后续内容,具体情况请看下面的实际操作,有个简单的了解)
该函数主要进行的操作是:        
①创建新的有效结点并为其开辟内存空间,同时用一个指针node指针指向该有效结点的空间
②将新有效结点的next指针指向下一个结点(node里面存储的就是下一个结点的地址)
//申请结点函数
void slttest()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode)); 
	node1->data = 1;
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	node2->data = 2;
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//打印链表
	SLNode* plist = node1;  
	SLPrint(plist);
}

打印链表:

//用phead表示指向第一个有效结点的指针
void SLPrint(SLNode* phead) 
{	
	//循环打印
	//令pcur指针同样指向第一个有效结点的地址(把它当作phead的拷贝)
	SLNode* pcur = phead;
    //当pcur指向的地址不为空时继续循环
	while (pcur)
	{
    //打印此时所指向结点中的数据
		printf("%d ->", pcur->data);
    //打印结束后让pcur指向下一个结点的地址
		pcur = pcur->next;
	}
    //打印完成后,用NULL在小黑框中标识一下证明此时链表已经打印完了
	printf("NULL\n");
}

申请新节点:

//申请有效结点,并在该结点中存储数据x
SLNode* SLByNode(SLDataType x) 
{
    //为有效结点申请一个新的空间,并用node指针存储该空间的地址(用node指向该空间)
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
    //该节点中存储的数据为x
	node->data = x;
    //将该结点的下一个结点置为空,因为我们也不知道它后面到底还要不要结点了
	node->next = NULL;
    //返回申请的有效结点地址(返回值为node,而node里面存储的就是有效结点所在内存空间的地址)
	return node;
}

链表的尾插:

//链表的尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
    //想要进行链表的插入和删除操作就必须使用一个二级指针pphead
	assert(pphead);

	SLNode* node = SLByNode(x);
    //如果没有第一个有效结点(链表为空)那就让*pphead指向创建的有效结点的地址(让该结点作为链表的第一个有效结点)
	if (*pphead == NULL)
	{
		*pphead = node;(注意=的意思是赋值,而node的值就是有效结点的地址,把有效结点的地址传递给*ppead那么此时它里面存储的值就是有效结点的地址,此时它就相当于node了)
		return;
	}
    //如果有第一个有效结点,则通过循环读取至链表的结尾
	SLNode* pcur = *pphead;//(赋值原理同上)
    //然后利用pcur->next遍历至链表的末尾
	while (pcur->next)
	{
		pcur = pcur->next;//(赋值原理同上)
	}
   //当遍历至链表的末尾时,开始执行插入操作
	pcur->next = node;//(赋值原理同上)
    //还是解释一下:将有效结点的地址交给当前pcur指向结点中的next指针
}

注意在理解这些操作时,一定要清楚的时=的作用就是赋值(感觉理解这点很重要)

链表的头插:

//链表的头插
void SLPushFront(SLNode** pphead, SLDataType x)//相当于两个互相赋值
{
    //判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* node = SLByNode(x);
    //下面两条进行的其实就是简单的交接工作
	//先将当前头指针指向的结点交给了node->next
	node->next = *pphead;
	//然后让头指针指向新节点的地址
	*pphead = node;
}

链表的尾删: 

//链表的尾删(链表为空的情况下不能尾删)
void SLPopBack(SLNode** pphead)
{    
	//判断传入的头结点plist是否为空
	assert(pphead);
    //判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
    else
    {
    //当不止一个有效结点时
    //未防止删除后空指针的出现,在寻找尾节点的时候我们也要找到尾节点的前一个节点
	//找尾结点和尾结点的前一个结点
    //定义prev为尾结点的前一个结点
	SLNode* prev = NULL;
    //定义ptai为用于找尾结点的指针,先让它接收第一个有效结点的地址
	SLNode* ptail = *pphead;
	while (ptail->next != NULL)
	{
        //先令prev将ptail保存下来,当ptail->next为空时(此时到达尾指针)就不会进入循环将ptail    
        //存入prev中,此时prev保存的就是尾结点的前一个结点
		prev = ptail;
		ptail = ptail->next;
	}
	//此时prev(尾结点的前一个结点)的next指针不再指向ptail(尾结点)而是指向ptail的下一个结点
	prev->next = ptail->next;
	free(ptail);
	ptail = NULL;
    }
}

链表的头删:

//链表的头删
void SLPopPront(SLNode** pphead)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
    //判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		//直接把头结点删除
		free(*pphead);
		*pphead = NULL;
	}
    //当整个链表
	//使用临时指针指向头结点
	SLNode* del = *pphead;
	//令头结点指向新的头结点
	*pphead = (*pphead)->next;
	//将临时指针指向的结点(头结点)释放掉
	free(del);
	del = NULL;
}

寻找结点:

//查找结点
SLNode* SLFind(SLNode** pphead, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//该函数需要与在指定位置插入删除结合,返回的结果使用一个指针来接收,在test.c文件中的使用情况如下:
SLNode* find = SLFind(&plist,2);//查找数据为2的结点
SLInsert(&plist,find,x)//在find(数据为2)的结点前插入含有数据x的新节点

在完成以下代码后需要考虑的三种情况:

1、pos是头结点

2、pos是中间结点

3、pos是最后一个结点

在链表的指定位置前插入:

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
    //约定链表不能为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLByNode(x);

	//有且只有一个有效结点,此时在该有效结点前进行插入操作就相当于头插
	if(pos == *pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//当不只有一个有效结点的时候,先通过循环找到pos的前一个结点
	SLNode* prev = *pphead; 
    //当prev->next指向pos的时候跳出循环
	while (prev->next != pos)
	{
		prev = prev->next;
	}
    //此时循环结束,prev指向pos

    //最后,处理插入位置两边的结点与新结点三者之间的关系prve node pos
    //此时下面的两个操作顺序可以交换
	node->next = pos;
	prev->next = node;
}

在链表的指定位置后插入:

//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
    //确定能找到该结点
	assert(pos);
	SLNode* node = SLByNode(x);
	//pos node pos->next
	node->next = pos->next;
	pos->next = pos;
}

//使用案例:
//SLNode* find = SLFind(&plist,1);
//SLInsertAfter(find,100);

删除pos位置的结点:

//删除pos结点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
    
    //当pos为第一个有效结点时
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
    //当pos不为第一个有效结点时
	//先找到pos的前一个结点,然后(后续内容与之前的操作类似)
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
    //先完成pos两边结点的交接工作,然后再释放pos结点
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

删除pos位置后的结点:

//删除pos结点之后的数据
void SLEraseAfter(SLNode* pos)
{
    //除了pos不为空以外,还需要pos->next不为空,因为pos刚好是最后一个结点你总不能删除一个NULL
	assert(pos && pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

销毁链表:

//销毁链表
void SLDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
    //此时链表所有的有效结点已经结束了,最后将头结点置为空即可
	*pphead = NULL;
}

最终结果:

SList.h文件:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//定义链表节点的结构
typedef int SLDataType;
struct SListNode {   //定义一个表示链表节点的结构体
	SLDataType data;  //链表中用于存储数据的成员(某个节点的数据)
	struct SListNode* next;  //用来保存下一个节点地址的指针变量next
};
typedef struct SListNode SLNode;  //将指向下一个节点的指针类型重命名为SLNode

//创建几个结点组成的链表,并打印链表
void SLPrint(SLNode* phead);  
//链表的尾插
void SLPushBack(SLNode** phead, SLDataType x);
//链表的头插
void SLPushFront(SLNode** phead, SLDataType x);
//链表的尾删
void SLPopBack(SLNode** pphead);
//链表的头删
void SLPopPront(SLNode** pphead);
//找结点,这里传一级指针实际上就可以了,因为不改变头节点,但是这里还是要写成二级指针,因为要保证接口一致性
SLNode* SLFind(SLNode** pphead,SLDataType x);

//链表的在指定位置之前插入
void SLInsert(SLNode** phead, SLNode* pos,SLDataType x);
//链表的指定位置删除
void SLInsertAfter(SLNode* pos, SLDataType x);//此时不需要第一个参数
//删除pos位置的结点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos后的结点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SLDestroy(SLNode** pphead);

SList.c文件:

#include "SList.h"
//用phead表示头结点,它指向链表的第一个结点(如果思路出现混乱,一定要再看一边前面的链表图)
void SLPrint(SLNode* phead)
{
	//循环打印
	//为了能在遍历后仍能找到刚开始的起点,我们就需要利用一个临时指针pcur来存储头结点的地址
	SLNode* pcur = phead;
	//当头结点不为空时进行循环
	while (pcur)
	{
		//打印此时所处结点中的数据
		printf("%d ->", pcur->data);
		//打印结束后让pcur指向下一个结点的地址
		pcur = pcur->next;
	}
	//到最后时现有结点遍历完成,空间为NULL
	printf("NULL\n");
}

//申请有效结点函数(并在该结点中存储数据)
SLNode* SLByNode(SLDataType x)
{
	//为链表的新结点申请一个新的空间
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	//该节点中存储的数据为x
	node->data = x;
	//将该结点的下一个结点置为空,因为我们也不知道它后面到底还要不要结点了
	node->next = NULL;
	//返回申请的新结点
	return node;
}

//链表的尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//如果存在头结点则进行后续操作
	//先申请一个新的有效结点
	SLNode* node = SLByNode(x);
	//如果第一个有效结点为空,则令*pphead指向新创建的有效结点
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
	//如果第一个有效结点不为空,则通过循环读取至链表的结尾
	//先定义一个临时的指针变量pcur,令pcur指向第一个有效结点
	SLNode* pcur = *pphead;
	//然后利用pcur->next遍历至链表的末尾
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	//当遍历至链表的末尾时,让pcur指向新的有效结点
	pcur->next = node;
}

//链表的头插
void SLPushFront(SLNode** pphead, SLDataType x)//相当于两个互相赋值
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* node = SLByNode(x);
	//下面两条进行的其实就是简单的交接工作
	//先将当前头指针指向的结点交给了node->next
	node->next = *pphead;
	//然后让头指针指向新节点的地址
	*pphead = node;
}

//链表的尾删(链表为空的情况下不能尾删)
void SLPopBack(SLNode** pphead)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//当不止一个有效结点时
	//未防止删除后空指针的出现,在寻找尾节点的时候我们也要找到尾节点的前一个节点
	//找尾结点和尾结点的前一个结点
	//定义prev为尾结点的前一个结点
	
	else
	{
		SLNode* prev = NULL;
		//定义ptai为用于找尾结点的指针,先让它接收第一个有效结点的地址
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			//先令prev将ptail保存下来,当ptail->next为空时(此时到达尾指针)就不会进入循环将ptail    
			//存入prev中,此时prev保存的就是尾结点的前一个结点
			prev = ptail;
			ptail = ptail->next;
		}
		//此时prev(尾结点的前一个结点)的next指针不再指向ptail(尾结点)而是指向ptail的下一个结点
		prev->next = ptail->next;
		free(ptail);
		ptail = NULL;
	}
}

//链表的头删
void SLPopPront(SLNode** pphead)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//判断第一个有效结点是否为空,链表为空不能进行尾删
	assert(*pphead);

	//当有且只有一个有效结点时
	if ((*pphead)->next == NULL)
	{
		//直接把头结点删除
		free(*pphead);
		*pphead = NULL;
	}
	//当整个链表
	//使用临时指针指向头结点
	SLNode* del = *pphead;
	//令头结点指向新的头结点
	*pphead = (*pphead)->next;
	//将临时指针指向的结点(头结点)释放掉
	free(del);
	del = NULL;
}

//查找结点
SLNode* SLFind(SLNode** pphead, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//
该函数需要与在指定位置插入删除结合,返回的结果使用一个指针来接收,在test.c文件中的使用情况如下:
//SLNode* find = SLFind(&plist, 2);//查找数据为2的结点
//SLInsert(&plist, find, x)//在find(数据为2)的结点前插入含有数据x的新节点

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	//判断传入的头结点plist是否为空
	assert(pphead);
	//约定链表不能为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLByNode(x);

	//有且只有一个有效结点,此时在该有效结点前进行插入操作就相当于头插
	if (pos == *pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//当不只有一个有效结点的时候,先通过循环找到pos的前一个结点
	SLNode* prev = *pphead;
	//当prev->next指向pos的时候跳出循环
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//此时循环结束,prev指向pos

	//最后,处理插入位置两边的结点与新结点三者之间的关系prve node pos
	//此时下面的两个操作顺序可以交换
	node->next = pos;
	prev->next = node;
}

//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
	//确定能找到该结点
	assert(pos);
	SLNode* node = SLByNode(x);
	//pos node pos->next
	node->next = pos->next;
	pos->next = pos;
}

//使用案例:
//SLNode* find = SLFind(&plist,1);
//SLInsertAfter(find,100);

//删除pos结点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	//当pos为第一个有效结点时
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//当pos不为第一个有效结点时
	//先找到pos的前一个结点,然后(后续内容与之前的操作类似)
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//先完成pos两边结点的交接工作,然后再释放pos结点
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

//销毁链表
void SLDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时链表所有的有效结点已经结束了,最后将头结点置为空即可
	*pphead = NULL;
}

test.c文件:

#include "SList.h"
//申请结点函数
//void slttest()
//{
//	//使用malloc函数动态分配,创建链表的头节点,它不包含任何数据,知识用来指向链表的第一个实际节点
//	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode)); 
//	//head
//	node1->data = 1;
//	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
//	node2->data = 2;
//	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
//	node3->data = 3;
//	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
//	node4->data = 4;
//
//	//实现四个节点的链接
//	//初始化头节点的next指针为node2指针变量
//	node1->next = node2;
//	node2->next = node3;
//	node3->next = node4;
//	node4->next = NULL;
//
//	//打印链表
//	SLNode* plist = node1;  //定义一个SLNode*类型的指针变量plist,他也叫头指针,我们用它指向链表的头节点
//	
//	//注意头节点和头指针的概念是不同的:
//	/*在链表的上下文中,通常将链表的第一个节点称为头节点(Head Node),但是头节点和头指针(Head Pointer)是不同的概念。
//	头节点是链表中的第一个实际节点,它包含数据和指向下一个节点的指针。头节点是链表的起始点,它可以存储实际的数据,也可以只是一个占位符节点,不存储实际的数据。
//	头指针是指向链表的头节点的指针。它是一个指针变量,存储着头节点的地址。通过头指针,我们可以访问链表中的每个节点,或者进行其他链表操作。
//	因此,头节点是链表中的一个节点,而头指针是指向头节点的指针。它们是不同的概念,但在某些情况下,人们可能会将它们混用或将它们视为相同的概念,因为头节点通常通过头指针来访问。*/
//	
//	SLNPrint(plist);
//}

void slttest()
{

	SLNode* plist = NULL;	
	//尾插
	SLPushBack(&plist, 1);  
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);//1->2->3->4->NULL
	SLPrint(plist);
	头插
	//SLPushFront(&plist, 1);
	//SLPushFront(&plist, 2);
	//SLPushFront(&plist, 3);
	//SLPushFront(&plist, 4);//4->3->2->1->NULL
	//SLPrint(plist);
	//尾删
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	头删
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	//SLPopPront(&plist);
	指定位置插入
	//SLNode* find = SLFind(&plist, 4);
	//SLInsert(&plist, find,11);//1->11->2->3->4->NULL
	在指定位置之后插入数据
	//SLInsertAfter(find, 100);
	删除pos位置的节点
	//SLErase(&plist, find);//1->2->3->NULL
	删除pos之后的节点
	//SLEraseAfter(find);
	//
	//销毁链表
	//SLDestory(&plist);
	//检验是否成功销毁
	SLPrint(plist);
}

int main()
{
	slttest();
	return 0;
}

注意事项:

1、判断条件的等号都是==

2、冒号是否写了

3、函数或者指针变量的名字是否书写正确 

4、最后的test.c文件实验时可能会存在一些多删之类的问题(函数写多了)请自行检查~

~over~

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

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

相关文章

[C国演义] 第十六章

第十六章 等差数列的划分最长递增子序列 等差数列的划分 力扣链接 子数组 ⇒ dp[i]的含义: yinums[i] 为结尾的所有子数组中的 等差数列数组最多的个数子数组⇒ 状态转移方程: 根据最后一个元素的构成 初始化: 涉及到 i-1, i-2 ⇒ 所以要初始化dp[0] 和 dp[1] 都初始化为 0…

一次不成功的抓包过程

想搞Android app抓包&#xff0c;简单的方法&#xff0c;已经不起作用&#xff0c;打开charles代理工具&#xff0c;抓不到我的目标app任何请求&#xff0c;搞了两三天&#xff0c;也没成功。 我的目标APP里&#xff0c;经过apk反编译出来&#xff0c;看到有libflutter.so文件&…

AM@导数的应用@二阶导数的应用@函数的性态研究@函数图形的绘制

文章目录 概念称呼说明驻点极值和极值点最值极值点和最值比较曲线的凹凸性凹凸性判定定理&#x1f47a;例证明 凹凸性和单调性无必然关系拐点寻找拐点&#x1f47a; 函数图形的绘制例 概念 本文讨论导数的应用:利用导数研究函数的性态相关定理主要通过Lagrange中值定理进行推导…

首篇大模型压缩论文综述

首篇大模型压缩综述来啦&#xff01;&#xff01;&#xff01; 来自中国科学院和人民大学的研究者们深入探讨了基于LLM的模型压缩研究进展并发表了该领域的首篇综述《A Survey on Model Compression for Large Language Models》。 Abstract 大型语言模型&#xff08;LLMs&a…

文件操作 IO

文件(File) 狭义的文件: 指的是硬盘上的文件和目录 广义的文件: 泛指计算机中很多软硬件资源(操作系统中把很多硬件和软件资源抽象成了文件, 按照文件的方式同意管理) 本章内容只讨论狭义的文件 路径 绝对路径: 以c: , d: 盘符开头的路径相对路径: 以当前所在的目录为基准(…

【问题思考总结】如何求椭圆的切线?【过椭圆外一点】

问题 今天做2009年数一的真题&#xff0c;发现第17题求切线十分难顶&#xff0c;我用的方法是切线和椭圆方程联立&#xff0c;还有切线斜率和椭圆上一点和远点斜率相乘等于-1的方法。 思考 经过思考后&#xff0c;我认为之前的那个属于是高中方法&#xff08;还不完全是&…

Prometheus接入AlterManager配置邮件告警(基于K8S环境部署)

文章目录 一、配置AlterManager告警发送至邮箱二、Prometheus接入AlterManager配置三、部署PrometheusAlterManager(放到一个Pod中)四、测试告警 注意&#xff1a;请基于 PrometheusGrafana监控K8S集群(基于K8S环境部署)文章之上做本次实验。 一、配置AlterManager告警发送至邮…

手把手入门Node框架Egg.js

0.介绍 Egg.js 是一个面向企业级应用开发的 Node.js 框架&#xff0c;它建立在 Koa.js 之上&#xff0c;提供了一种更简单、灵活的开发方式。Egg.js 提供了一些默认约定和最佳实践&#xff0c;可以帮助开发者快速构建可靠、可扩展的应用程序。 基于 Koa.js&#xff1a;Egg.js …

spacy.load(“en_core_web_trf“)报错TypeError: issubclass() arg 1 must be a class

使用spacy时遇到的问题 写在最前面&#xff1a; 安装spacy和en_core_web_trf时需要保证二者版本一致 安装及查看对应spacy版本 安装 pip install spacy查看版本 import spacy spacy.__version__安装en_core_web_trf 直接安装&#xff08;如果可以的话&#xff09; pytho…

【论文阅读】以及部署BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework

BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework BEVFusion&#xff1a;一个简单而强大的LiDAR-相机融合框架 NeurIPS 2022 多模态传感器融合意味着信息互补、稳定&#xff0c;是自动驾驶感知的重要一环&#xff0c;本文注重工业落地&#xff0c;实际应用 融…

反转链表review

反转链表 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ class …

Rust逆向学习 (2)

文章目录 Guess a number0x01. Guess a number .part 1line 1loopline 3~7match 0x02. Reverse for enum0x03. Reverse for Tuple0x04. Guess a number .part 20x05. 总结 在上一篇文章中&#xff0c;我们比较完美地完成了第一次Rust ELF的逆向工作&#xff0c;但第一次编写的R…

JVM(Java Virtual Machine)垃圾收集器篇

前言 本文参考《深入理解Java虚拟机》一书&#xff0c;本文主要介绍几个经典的垃圾收集器&#xff1a;Serial、ParNew、parallelScavenge、CMS、Serial Old、Parallel Old、G1 本系列其他文章链接&#xff1a; JVM&#xff08;Java Virtual Machine&#xff09;内存模型篇 JV…

2434: 【区赛】[慈溪2013]统计方格

题目描述 给出一张 n 行 m 列仅由黑白方格组成的黑白图片&#xff08;行从上到下 1 到 n 编号&#xff0c;列从左到右 1 到 m 编号&#xff09;。如下图是一张由 17 行 18 列方格构成的黑白图片&#xff0c;图片中的任意一个方格要么是白色&#xff0c;要么是黑色。 仔细观察这…

介绍Sigmoid函数的平移、平滑和翻转【基于Python可视化分析】

文章目录 简介Sigmoid函数Sigmoid函数曲线调控参数设置python可视化参考 简介 本篇博客介绍了具有S型曲线的Sigmoid函数&#xff0c;以及如何设置、调整Sigmoid函数的参数实现S曲线的平滑、平移和翻转操作。博客给出了Python代码示例&#xff0c;更加深刻形象。&#x1f606;&…

hdlbits系列verilog解答(两输入与门)-06

文章目录 wire线网类型介绍一、问题描述二、verilog源码三、仿真结果 wire线网类型介绍 wire线网类型是verilog的一种数据类型&#xff0c;它是一种单向的物理连线。它可以是输入也可以是输出&#xff0c;它与reg寄存器数据类型不同&#xff0c;它不能存储数据&#xff0c;只能…

数据结构与算法 | 第二章:线性表

本文参考网课为 数据结构与算法 1 第二章线性表&#xff0c;主讲人 张铭 、王腾蛟 、赵海燕 、宋国杰 、邹磊 、黄群。 本文使用IDE为 Clion&#xff0c;开发环境 C14。 更新&#xff1a;2023 / 10 / 22 数据结构与算法 | 第二章&#xff1a;线性表 线性表总览线性结构概念特…

大数据技术学习笔记(三)—— Hadoop 的运行模式

目录 1 本地模式2 伪分布式模式3 完全分布式模式3.1 准备3台客户机3.2 同步分发内容3.2.1 分发命令3.2.2 执行分发操作 3.3 集群配置3.3.1 集群部署规划3.3.2 配置文件说明3.3.3 修改配置文件3.3.4 分发配置信息 3.4 SSH无密登录配置3.4.1 配置ssh3.4.2 无密钥配置 3.5 单点启动…

人工智能(6):机器学习基础环境安装与使用

1 库的安装 整个机器学习基础阶段会用到Matplotlib、Numpy、Pandas等库&#xff0c;为了统一版本号在环境中使用&#xff0c;将所有的库及其版本放到了文件requirements.txt当中&#xff0c;然后统一安装 新建一个用于人工智能环境的虚拟环境 mkvirtualenv ai matplotlib3.8…

Mybatis应用场景之动态传参、两字段查询、用户存在性的判断

目录 一、动态传参 1、场景描述 2、实现过程 3、代码测试 二、两字段查询 1、场景描述 2、实现过程 3、代码测试 4、注意点 三、用户存在性的判断 1、场景描述 2、实现过程 3、代码测试 一、动态传参 1、场景描述 在进行数据库查询的时候&#xff0c;需要动态传入…