数据结构——实现单向链表

news2024/11/25 0:02:14

文章目录

  • :cow:前言
  • :banana:单链表与顺序表的对比
  • :orange:单链表的初始操作及结构体
  • :watermelon:申请一个节点
  • :carrot:打印
  • :strawberry:销毁
  • :apple:尾插
  • :pear:尾删
  • :grapes:头插
  • :peach:头删
  • :pineapple:数据的查找
  • :lemon:数据的修改
  • :tomato:在pos位置之后插入节点
  • :potato:在pos位置之前插入节点
  • :cat:删除pos位置之后的节点
  • :dog:删除pos位置之前的节点
  • :monkey:完整代码
  • :elephant:写在最后

🐮前言

  • 单链表是一种常见的数据结构,用于存储一系列的数据元素,每个节点包含数据和指向下一个节点的指针。

  • 单链表通常用于实现某些算法或数据结构,如链式前向星、哈希表、链式栈、队列等等。

  • 单链表在程序设计中的作用不可忽略,是很多基础算法的核心数据结构之一。

  • 学习单链表有助于提高算法和数据结构的基本能力并增强编程的实践经验。

  • 本篇博客将介绍单链表的基本操作及其算法应用,旨在帮助读者掌握单链表数据结构及相关算法的设计和实现,进一步提高编程的能力和水平。

总之,单链表是一种常见的基础数据结构,与编程和算法设计密切相关,了解和掌握单链表的基本概念和操作,对于提高编程能力和设计程序算法都有帮助。

🍌单链表与顺序表的对比

在这里插入图片描述

单链表的优点:

  • 动态性:单链表可以在任意位置进行插入和删除操作,不必像顺序表那样需要移动元素,操作比较灵活。
  • 空间利用效率高:单链表的结构只需要记录一个指针域和一个数据域,不像顺序表需要预留一定的存储空间,因此空间利用率较高。

单链表的缺点:

  • 存储空间分配不灵活:由于单链表的结构决定了只有通过指针才能访问相邻元素,因此不能随机访问。
  • 存储密度较低:由于每个结点只包含一个指针域和一个数据域,因此存储密度比较低,会占用更多的空间。

顺序表的优点:

  • 存储密度较高:由于所有元素在连续的存储空间中,因此存储密度比较高,可以节省空间。
  • 存取效率高:由于所有元素在连续的存储空间中,因此可以通过元素的下标进行随机存取,存取效率比较高。

顺序表的缺点:

  • 插入和删除操作较慢:由于需要移动元素,因此插入和删除操作比较慢。
  • 维护困难:当顺序表达到一定的长度后,插入和删除操作会变得更加困难,在需要频繁进行插入和删除操作时,顺序表的效率会比较低。

总的来说,单链表和顺序表各有优点和缺点,需要根据具体的应用场景进行选择。如果需要频繁进行插入和删除操作,单链表的效率更高;如果需要频繁进行随机存取操作,顺序表更为适合。
在这里插入图片描述

🍊单链表的初始操作及结构体

  • 单链表的实现也是需要搭建3个文件,一个是SList.h(头文件),SList.c(各函数的实现文件),Test.c(测试文件)。
  • 搭建好之后我们只需要在SList.cTest.c文件开头包含#include"SList.h"即可将三个文件链接起来。
  • 写单链表的结构体时,需要定义包含两个成员的结构体类型,一个成员用于存储结点的数据,另一个成员用于存储下一个结点的指针。一般而言,结构体类型的名称和结构体变量的名称都需要有意义,以便于程序的阅读和理解。
    相关功能代码:
#pragma once
// 输入输出所需头文件
#include<stdio.h>
// malloc 所需头文件
#include<stdlib.h>
// assert 所需头文件
#include<assert.h>

// 每个节点的数据的类型
typedef int SLTDataType;

// 每个节点的结构体
typedef struct SListNode
{
	// 存放该结点的数据
	SLTDataType data;
	// 存放下一个节点的地址,将整个结构串联成一个单链表
	struct SListNode* next;
}SLTNode;  // 用 typedef 将结构体重命名为 SLTNode 便于后续写程序 

🍉申请一个节点

  • 单链表的节点是单链表中最基本的单位。每个节点由两部分组成,分别是数据域和指针域。数据域存储节点的数据或者信息,而指针域指向下一个节点的地址。因此,单链表是由一连串的节点构成的数据结构。具体来说,单链表的节点通常包含以下信息:

  • 1.数据域:存储节点的数据或信息,可以是任何数据类型,例如整数、字符、字符串、结构体等。

  • 2.指针域:指向下一个节点的地址。在C语言中可以使用结构体来表示一个节点,节点结构体至少包含一个数据项和一个指向下一个节点的指针

单链表的节点是非常灵活和可扩展的,因为每个节点都只需要存储自身的信息和指向下一个节点的指针就可以了,这就使得单链表更易于被实现和扩展。当需要插入或删除节点时,只需更新相应节点的指针域即可。
同时,这种设计也带来了一定的缺点,即单链表不能直接访问任何一个节点的前一个节点。

  • 节点应至少包括一个数据项(例如整数或字符串)和一个指向下一个节点的指针。
  • 申请内存要创建一个新的节点,你需要使用C语言的stdlib.h库中的malloc函数来为其分配内存。将分配一个大小为SListNode结构体的内存块,并将其指针指向newnode。在这个节点被使用完毕之后,应该使用free函数释放它的内存。
  • 经过内存分配后,你需要用正确的值初始化节点。在这种情况下,节点的值可以作为函数的参数进行传递。初始化一个新的节点并返回这个节点的指针。
  • 我们首先为节点分配内存,然后将value赋值给节点的data字段,并把next设为NULL(因为这个节点尚未链接到另一个节点上)。

这样,我们就成功地创建了一个新的节点,并初始化了它的数据元素和指向下一个节点的指针。


SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

🥕打印

  • 单链表的打印操作是指遍历单链表并输出其中所有节点的数据或信息,这是单链表中常用的一种操作,也是单链表中尤为基础的一部分。
  • 定义一个指针变量,指向单链表的头节点,命名为cur。其中,head为单链表头节点的指针。
  • 使用while循环遍历整个单链表,直到当前的指针变量为空。
  • 在循环中,使用p指针变量访问当前节点(即p所指向的节点),并打印其数据或信息。在实际操作中,可以根据需要定制输出样式。
  • 将指针变量p更新为下一个节点的地址,即p = p->next,继续访问下一个节点。直到循环结束,所有的节点都被遍历,并输出了其中所有的数据或信息。
    相关功能代码:
void SLPrint(SLTNode* phead)
{
    struct Node* cur = phead; // 定义指针变量,指向头节点
    while(cur != NULL){ // 循环条件
        printf("%d ", cur->val); // 输出节点数据或信息
        cur = cur->next; // 修改指针变量,指向下一个节点
    }
}

需要注意的是,单链表的打印操作在多种算法或数据结构中都有应用。
同时,其中包含单个强迫离散节点间转移到下一个节点的元素。其执行步骤、方式及相关内容会因场景而异,需要结合实际情况进行设计。

🍓销毁

  • 单链表的销毁是指将单链表中的所有节点都删除,并将链表中的所有内存空间释放
  • 具体步骤:
  • 1.定义一个指针变量pos来保存待删除节点的下一个节点。
  • 2.使用while循环遍历整个单链表,直到当前指针plist为空。
  • 3.在循环中,保存当前节点的下一个节点的指针,即pos = pos->next。
  • 4.使用free函数释放当前节点的内存空间,即free(head),释放节点所占用的内存空间。
  • 5.将指针head指向下一个节点。即plist = pos
    重复步骤3-5,直到整个单链表中的所有节点都被删除并释放了内存空间。最后在返回。
  • 需要注意的是,有指针就别忘了要assert
  • 需要注意的是,在进行单链表的尾插操作时,需要保证单链表不为空。可以在函数外创建一个头节点,排除空链表情况,并将其传入SLPushBack函数中进行操作。另外,为新节点分配内存空间之后,也需要对内存申请情况进行检查,以避免发生内存泄漏等问题。
    相关程序代码:
void SLDestory(SLTNode* plist,SLTNode* pos)
{
	assert(plist);

	while (pos)
	{
		plist = pos;
		pos = pos->next;
		free(plist);
		plist->next = NULL;
	}
	return;
}

还需要注意的是,在使用上述函数时,需要首先保证单链表中所有节点都已经被访问或使用,否则可能会造成内存泄漏。

🍎尾插

  • 单链表尾插是单链表中的一种常用操作,而且非常实用,它可以在单链表的末尾插入一个新节点。
  • 找到单链表的尾部节点,即包含指针域值为NULL的节点。可以用一个循环来找到它。
  • 创建一个新节点,将其数据放入其中。需要调用BuyLTNode函数为新节点分配内存空间。
  • 将新节点连接到单链表的尾部节点上,即将尾部节点的指针域指向新节点。至此,新的节点已经成功插入到了单链表的末尾。
  • 为新节点分配内存空间之后,也需要对内存申请情况进行检查(assert),以避免发生内存泄漏等问题。
    相关程序代码:
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);
	assert(*pphead);
	//1,空链表
	//2,非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}
}

🍐尾删

  • 单链表尾删是指删除单链表中的尾节点,使其前一个节点成为新的尾节点。
  • 找到单链表的倒数第二个节点。可以使用一个循环遍历直到找到该节点。注意,需要判断单链表中是否有节点,否则删除操作无法进行
  • 保存单链表尾节点的指针,以便后续释放它所占用的内存空间。
  • 将尾节点从单链表中删除,即将p节点的指针域设置为NULL
  • 释放单链表尾节点占用的内存空间。
    相关程序代码:
void SLPopBack(SLTNode** pphead)
{
	//assert(pphead);   //链表为空,pphead也不能为空,因为他是头指针plist的地址
	assert(*pphead);  //链表为空,不能头删
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

需要注意的是,在进行单链表的尾删操作时,需要先对单链表进行判空处理,避免出现访问空指针的错误。

🍇头插

  • 单链表头插是指在单链表的头部插入一个新的节点,使其成为新的头节点。
  • 创建一个新的节点,并将新节点的指针域指向原来的头节点。需要调用BuyLTNode函数为新节点分配内存空间。
  • 将头指针指向新节点,即将头指针修改为新节点的地址。
    相关程序代码:
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;	
}

需要注意的是,在进行单链表的头插操作时,需要先对头指针进行检查,避免出现空指针的错误。此外,在新建节点并为其分配内存空间时,也需要进行内存申请情况的检查,以避免出现内存泄漏等问题。

🍑头删

  • 单链表头删是指删除单链表中的头节点,使其后一个节点成为新的头节点。
  • 保存单链表头节点的指针,以便后续释放它所占用的内存空间。
  • 释放原来的头节点所占用的内存空间。
    相关程序代码:
void SLPopFront(SLTNode** pphead)
{
	//assert(pphead);   //链表为空,pphead也不能为空,因为他是头指针plist的地址
	assert(*pphead);  //链表为空,不能头删。

	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
}

需要注意的是,在进行单链表的头删操作时,需要先对头指针进行检查,避免出现空指针的错误。此外,在释放原来的头节点所占用的内存空间时,也需要进行内存申请情况的检查,以避免出现内存泄漏等问题。

🍍数据的查找

  • 在单链表中查找数据通常需要遍历整个链表,找到匹配的节点并返回其位置或指针。
  • 使用一个指针指向链表的头结点,依次遍历链表中的每一个节点。
  • 在每个节点中,检查该节点中存储的元素是否与待查找元素相同。如果相同,则返回该节点的位置或指针。
  • 如果遍历整个链表都找不到相匹配的节点,则返回NULL表示查找失败。
    相关程序代码:

SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}
需要注意的是,在进行单链表中数据查找操作时,需要先对单链表进行判空处理,避免出现访问空指针的错误。 此外,还需注意的是,本算法只能查找链表中第一个匹配的元素,无法查找所有匹配的元素。

🍋数据的修改

  • 在单链表中修改指定节点的数据,通常需要先通过遍历找到该节点,之后直接修改其存储的数据。
  • 使用一个指针指向链表的头结点,依次遍历链表中的每一个节点。
  • 在每个节点中,检查该节点中存储的元素是否与待修改元素相同。如果相同,则直接修改该节点中存储的数据。
  • 如果遍历整个链表都找不到匹配的节点,则返回NULL表示操作失败。
    相关程序代码:
SLTNode* SLModify(SLTNode* phead, int target, int newdata) 
{
	SLTNode* cur = phead;
	while (cur != NULL) {
		if (cur->data == target) {
			cur->data = newdata;
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

需要注意的是,在进行单链表中数据修改操作时,需要先对单链表进行判空处理,避免出现访问空指针的错误。此外,本算法只能修改链表中第一个匹配的元素,无法修改所有匹配的元素。

🍅在pos位置之后插入节点

  • 首先对pos进行判空操作。
  • 创建一个新节点 newnode,调用BuyLTNode函数。
  • newnodenext指向pos位置的next,在用posnext指向newnode
    相关程序代码:
//在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

🥔在pos位置之前插入节点

  • pos位置之前插入,因为单链表具有单向性,因此这里需要遍历单链表找到pos位置的前一个节点,才能进行插入。
  • 但是你会发现,如果是pos刚好指向头结点就可以直接用头插。
  • 因为这里调用了头插,所以这里也要使用二级指针来操作。
    相关程序代码:
//在pos之前插入
void SLInsertFront(SLTNode** pphead,SLTNode* pos,SLTDataType x)
{
	assert(pphead);
	assert(pos);
	//如果pos为第一个节点,直接调用前面的头插
	if (*pphead == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

🐱删除pos位置之后的节点

  • 找到要删除节点的位置。
  • 将要删除节点的 next 指向NULL
  • 找到前一个节点,将其next指向NULL
  • 释放要删除节点及其后继节点内存空间。
    相关程序代码:
void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

🐶删除pos位置之前的节点

  • 首先断言(assert),然后再调用头删(SLPopFront).
  • 之后再将 prevnext 指向要删除节点的next
  • 释放要删除节点的内存空间。
  • 释放要删除及其前面的节点。
    相关程序代码:
void SLEraseFront(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
	}
}

🐒完整代码

SList.h

#pragma once
// 输入输出所需头文件
#include<stdio.h>
// malloc 所需头文件
#include<stdlib.h>
// assert 所需头文件
#include<assert.h>

// 每个节点的数据的类型
typedef int SLTDataType;

// 每个节点的结构体
typedef struct SListNode
{
	// 存放该结点的数据
	SLTDataType data;
	// 存放下一个节点的地址,将整个结构串联成一个单链表
	struct SListNode* next;
}SLTNode;  // 用 typedef 将结构体重命名为 SLTNode 便于后续写程序 

//申请一个节点(包含初始化)
SLTNode* BuyLTNode(SLTDataType x);

//单链表的打印
void SLTPrint(SLTNode* phead);

//头插节点
void SLPushFront(SLTNode** pphead, SLTDataType x);

//尾插节点
void SLPushBack(SLTNode** pphead, SLTDataType x);

//头删节点
void SLPopFront(SLTNode** pphead);

//尾删节点
void SLPopBack(SLTNode** pphead);

//节点的查找
SLTNode* SLFind(SLTNode* phead, SLTDataType x);

//节点的修改
SLTNode* SLModify(SLTNode* phead, int target, int newdata);

//pos位置之前插入节点
void SLInsertFront(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//pos位置之后插入节点
void SLInsertAfter(SLTNode* pos, SLTDataType x);

//pos位置之前删除节点
void SLEraseFront(SLTNode** pphead, SLTNode* pos);

//pos位置之后删除节点
void SLEraseAfter(SLTNode* pos);

//节点的销毁
void SLDestory(SLTNode* plist, SLTNode* pos);

SList.c

#include"SList.h"

//单链表的打印
void SLPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//申请一个节点(包含初始化)
SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

//头插节点
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;	
}

//尾插节点
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);
	assert(*pphead);
	//1,空链表
	//2,非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}
}

//头删节点
void SLPopFront(SLTNode** pphead)
{
	//assert(pphead);   //链表为空,pphead也不能为空,因为他是头指针plist的地址
	assert(*pphead);  //链表为空,不能头删。

	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
}

//尾删节点
void SLPopBack(SLTNode** pphead)
{
	//assert(pphead);   //链表为空,pphead也不能为空,因为他是头指针plist的地址
	assert(*pphead);  //链表为空,不能头删
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

//查找单链表的节点
SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

//修改单链表的节点
SLTNode* SLModify(SLTNode* phead, int target, int newdata) 
{
	SLTNode* cur = phead;
	while (cur != NULL) {
		if (cur->data == target) {
			cur->data = newdata;
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//在pos之前插入节点
void SLInsertFront(SLTNode** pphead,SLTNode* pos,SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}


//在pos之后插入节点
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}


 //删除pos位置之前的节点
void SLEraseFront(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
	}
}

//删除pos位置之后的节点
void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}


//单链表的销毁
void SLDestory(SLTNode* plist,SLTNode* pos)
{
	assert(plist);

	while (pos)
	{
		plist = pos;
		pos = pos->next;
		free(plist);
		plist->next = NULL;
	}
	return;
}

Test,c


#include"SList.h"

void TestSList1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLPrint(plist);

	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);

	SLPrint(plist);

	SLPushBack(&plist, 5);

	SLPrint(plist);

	SLPopBack(&plist, 1);
	SLPopBack(&plist, 2);
	SLPopBack(&plist, 3);
	SLPopBack(&plist, 4);
	SLPopBack(&plist, 5);

	SLPrint(plist);

	SLPopFront(&plist, 1);
	SLPopFront(&plist, 2);
	SLPopFront(&plist, 3);
	SLPopFront(&plist, 4);

	SLPrint(plist);

}

void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLPushFront(&plist, 6);
	SLPushFront(&plist, 7);
	SLPrint(plist);


	SLModify(plist, 1, 999);
	SLPrint(plist);
	SLModify(plist, 2, 999);
	SLPrint(plist); 
	SLModify(plist, 3, 999);
	SLPrint(plist); 
	SLModify(plist, 4, 999);
	SLPrint(plist);
	SLModify(plist, 5, 999);
	SLPrint(plist);
	SLModify(plist, 6, 999);
	SLPrint(plist);
	SLModify(plist, 7, 999);
	SLPrint(plist);
}

void TestSList3()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLPushFront(&plist, 6);
	SLPushFront(&plist, 7);
	SLPrint(plist);


	SLFind(&plist, 3);
	SLPrint(plist);
}
int main()
{
	//TestSList1();
	//TestSList2();
	TestSList3();
	return 0;
}

🐘写在最后

单链表是一种与数组相对的数据结构,在实际的开发中很常见。它虽然不能像数组那样随机访问,但它具有动态的特点,可以高效地进行插入和删除操作。了解单链表的实现方式和使用方法,有助于我们在实际开发中更加灵活地运用它。

在本篇博客中,我们介绍了单链表的基本知识,包括如何在单链表中插入节点和删除节点。
需要注意的是,在单链表操作的过程中,对指针的操作十分关键,需要谨慎地操作。

感谢各位阅读本小白的博客,希望能帮助到大家!也请大家严厉指出并纠正我在文章中的错误。😄

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

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

相关文章

编辑距离算法

给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符 删除一个字符 替换一个字符 示例 1&#xff1a; 输入&#xff1a;word1 "horse", word2 "ros&quo…

ChatGPT真的可以帮助你申请留学吗?未必!

在留学申请的整个流程中&#xff0c;如果说哪一项是最不可或缺的项目&#xff0c;那“文书写作”一定首当其冲。国外院校对学生的评估参考项目&#xff0c;除了文书以外&#xff0c;也在不断地探寻更多的评估方式来全面地了解申请的学生。 从加州大学的申请流程中可以了解到这方…

Spring 之 jwt,过滤器,拦截器,aop,监听器

Spring 之 jwt&#xff0c;过滤器&#xff0c;拦截器&#xff0c;aop&#xff0c;监听器 一、jwt编写1.1 pom1.2 JwtUtils1.3 注意1.4 用法 二、过滤器2.1 原理2.2 使用场景2.3 使用步骤2.3.1 自定义过滤器类implements Filter2.3.2 配置类2.3.3 过滤器使用场景 2.4 问题 三、拦…

中美信托金融大厦(总体)建筑能耗管理系统的设计与应用

摘要&#xff1a;大型公共建筑总面积不足城镇建筑总面积的4%&#xff0c;但总能耗却占全国城镇总耗电量的22%&#xff0c;大型公共建筑单位面积年耗电量达到70&#xff5e;300KWh&#xff0c;为普通居民住宅的10&#xff5e;20倍。公共建筑是节能大户和节能主要对象&#xff0c…

这些论文的作者居然是猫、狗、仓鼠……

01 猩猩 Journal of Applied Animal Welfare Science 期刊在2007年发表了论文 Welfare of Apes in Captive Environments: Comments On, and By, a Specific Group of Apes &#xff08;圈养环境中的猿类福利&#xff1a;对特定类群的评论”&#xff09;&#xff0c;作者Sue S…

Web服务组合优化 基于改进哈里斯鹰算法的Web服务组合优化【Matlab代码22#】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第7节&#xff1a;资源获取】1. Web服务2. QoS感知的Web服务组合3. 改进后的CHHO算法3.1 原始HHO算法3.2 CHHO算法 4. 优化目标5. 部分代码展示6. 仿真结果展示7. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章…

详解token已过期含义及解决方 token过期是否需要重新登录

详解token已过期含义及解决方 token过期是否需要重新登录Web应用和用户的身份验证息息相关&#xff0c;从单一服务器架构到分布式服务架构再到微服务架构&#xff0c;用户安全认证和授权的机制也一直在演进&#xff0c;下文对各个架构下的认证机制做个总结。单一服务器架构该架…

day19 - 使用高通滤波提取图像边缘

在OpenCV中&#xff0c;对于图像或者视频的处理都或多或少的会涉及傅里叶变换的概念。在数学上&#xff0c;傅里叶变换是指所有的波形都可以由一系列简单且频率不同的正弦曲线叠加得到。也就是说&#xff0c;人们所看到的波形都是由其他波形叠加得到的。这个概念对操作图像非常…

Linus Torvalds发布了第一个Linux内核6.4候选版本

导读自Linux内核6.3发布和下一个内核系列Linux 6.4的合并窗口开放以来&#xff0c;已经过去了一段时间&#xff0c;近日&#xff0c;Linus Torvalds发布了第一个RC&#xff08;候选发布版&#xff09;的里程碑&#xff0c;供公众测试。 为期两周的Linux内核6.4合并窗口现已关闭…

3D开发程序员,如何在程序中将GLB格式转OBJ

Aspose.3D 是一个功能丰富的游戏软件和计算机辅助设计&#xff08;CAD&#xff09;的API&#xff0c;可以在不依赖任何3D建模和渲染软件的情况下操作文档。API支持Discreet3DS, WavefrontOBJ, FBX (ASCII, Binary), STL (ASCII, Binary), Universal3D, Collada, glTF, GLB, PLY…

微信小程序的基本使用以及安装教程

目录 一、使用微信开发者工具1、第一步先进行安装微信开发者工具2、使用方式安装完成后的使用步骤项目的大概界面 二、注册小程序账号在此处申请AppID&#xff0c;用于微信开发者工具的建立项目使用 三、使用微信官方文档 一、使用微信开发者工具 1、第一步先进行安装微信开发…

ASP-IIS中间件文件解析与写权限

ASP-IIS中间件文件解析与写权限 IIS文件解析 IIS 6 解析漏洞 1、该版本默认会将*.asp;.jpg 此种格式的文件名&#xff0c;当成Asp解析 2、该版本默认会将*.asp/目录下的所有文件当成Asp解析。 如&#xff1a;logo.asp;.jpg xx.asp/logo.jpgIIS 7.x 解析漏洞 在一个文件路…

Dubbo 3.2.1预览版发布,很多方面有改变

导读Dubbo 3.2.1预览版发布&#xff0c;很多方面有改变&#xff0c;比如&#xff1a;错误修正&#xff0c;代码增强&#xff0c;依赖性升级等。 改变了什么 功能介绍 添加追踪启动器 && 添加 zipkin 自动配置 by conghuhu in #12013QoS支持匿名命令允许列表 by Album…

2023智源大会议程公开 | 基于认知神经科学的大模型

2023年&#xff0c;人工智能新研究、新系统、新产品竞放——我们即将见证另一场有关智能的惊叹演化。6月9日&#xff0c;2023北京智源大会&#xff0c;将邀请这一领域的探索者、实践者、以及关心智能科学的每个人&#xff0c;共同拉开未来舞台的帷幕&#xff0c;你准备好了吗&a…

设备远程运维,实现设备管理智能化!

一、设备管理现状 设备运行状况&#xff1a;设备分布各地&#xff0c;无法远程监测设备运行参数、故障情况&#xff0c;对名下设备的运营情况懵然不知&#xff0c;能耗产量等关键数据无从知晓。 设备运营服务&#xff1a;当设备发生故障时&#xff0c;无法判断故障原因&#x…

IDEA重装后打开的一些设置

文章目录 1. 支持正版&科学永久2. 打开本地项目成功运行2. 修改IDEA的一些基本设置 1. 支持正版&科学永久 略&#xff08;狗头&#xff09;~ 2. 打开本地项目成功运行 刚打开项目&#xff0c;application是红叉状态点击Edit Configuration,配置好settings.xml和mave…

分布式项目10. js中src进行服务器之间的访问和优化使用jsonp的ajax请求处理访问

一般使用ajax来访问不同服务器的数据&#xff0c;可行吗&#xff1f; 做个实验&#xff1a; 第一步&#xff1a;在本服务器中使用ajax技术访问本服务器数据 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>测试JSON跨域…

【SpringMVC】SpringMVC的入门程序——HelloWorld(有点详细)

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

GP05丨多因子IC对冲

量化策略开发&#xff0c;高质量社群&#xff0c;交易思路分享等相关内容 大家好&#xff0c;今天我们分享股票社群第5期量化策略——多因子IC对冲。 在前几期中&#xff0c;我们分享了GP01多因子、ETF轮动策略及Plus版本、网格等等。本期我们继续分享多因子策略。 策略背景与…

大数据治理入门系列:数据血缘关系

血缘关系在人类社会中扮演着重要角色。大多数家庭是基于血缘关系形成的&#xff0c;而家庭作为社会的基本单元&#xff0c;对维系社会稳定发挥着重要关系。其实&#xff0c;数据之间也存在类似的血缘关系。数据从产生、加工、流转&#xff0c;一直到消亡&#xff0c;每个环节必…