【数据结构/C语言】单链表的实现

目录

一、单链表的基本概念

单链表的简介

单链表的特点

二、预备知识

三、单链表的基本结构

四、单链表的基本操作

1.链表打印

2.申请节点

3.头插

4.尾插

5.头删

6.尾删

7.查找节点

8.指定位置之前插入

9.指定位置之后插入

10.删除给定节点

11.删除给定节点之后的节点

12.链表销毁

五、示例程序

 SLinkList.h  单链表结构定义及函数声明头文件

SLinkList.c   单链表方法实现源文件 

test.c  测试文件

测试结果

六、实现单链表的常见问题


        个人主页:    倔强的石头的博客

        系列专栏 :C语言指南         C语言刷题系列     数据结构与算法

 

一、单链表的基本概念

单链表的简介

单链表是一种基本的线性数据结构,它通过链式存储方式而非连续的内存位置来存储元素。(逻辑上连续,物理上不一定连续在单链表中,每个元素(或称为节点)包含两部分:数据域和指针域。

数据域用于存储实际的数据,而指针域则存储指向下一个节点的地址。

这样,每个节点都链接到列表中的下一个节点,形成一个链条。

本篇文章要介绍的是无头结点,单向,不循环链表,即通常我们所熟知单链表,以下如无特殊说明,均代表此含义。

 

单链表的特点

  • 动态大小:链表的长度可以在运行时改变,便于灵活地添加和删除元素。
  • 不需要连续空间:与数组不同,链表的节点在内存中不必相邻,这使得它在内存管理上更为灵活。
  • 插入和删除操作高效:在已知节点位置的情况下,插入和删除操作可以仅需常数时间完成,因为只需调整相邻节点的指针即可。
  • 访问速度:相比于随机访问的数组,单链表的元素访问效率较低,因为需要从头节点开始逐个遍历直到目标节点。

 

二、预备知识

  • C语言的基本数据类型和变量
  • 掌握指针的概念和用法
  • 掌握动态内存分配(malloc 和 free

 

三、单链表的基本结构

在单链表中,每个元素(或称为节点)包含两部分:数据域和指针域。

数据域用于存储实际的数据(可以是任意类型的数据),而指针域则存储指向下一个节点的地址。

下面是一个使用结构体定义单链表节点的示例:

typedef int SLTDataType;//类型重定义
typedef struct SListNode
{
	SLTDataType data;//数据
	struct SListNode* next;//指针
}SLTNode;

注意:

  •  例子中int是要存储的数据类型,但为了方便后期修改存储类型,这里先对int重命名
  • 为了方便使用,简化节点的名字为SLTNode(但要注意在结构体中创建指针时,不可以使用重定义后的结构体名称,因为此时结构体还未定义)

 

四、单链表的基本操作

注意:

  • 出于文章篇幅所限,未展示每个方法的独立测试结果,建议读者在实现单链表时,每写好一个方法,都单独测试一下
  • 本篇文章介绍的是无头结点的链表,但为了方便表达,会将第一个节点称为首节点,意为第一个存放数据的有效节点

1.链表打印

void SLTPrint(SLTNode* phead)//链表打印
{
	SLTNode* pcur = phead;
	while (pcur)//pcur!=NULL
	{
		printf("%d->", pcur->data);//打印该节点数据
		pcur = pcur->next;//指针指向下一个节点
	}
	printf("NULL\n");
}

该方法的思路是创建一个临时指针变量接收实参传递的链表首节点指针

然后进入循环,当临时指针pcur不为空时,打印该节点的数据,然后指向下一个节点

当传递来的实参为NULL时,不会进入while循环,而是直接打印NULL 

 

2.申请节点

SLTNode* NewNode(SLTDataType x)//申请节点
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)//注意这里不要写成一个=
	{
		perror("newnode");
		exit(1);//如果申请节点失败,异常退出程序
	}
	newnode->data = x; //数据初始化
	newnode->next = NULL;//指针初始化
	return newnode;//返回新申请节点的地址
}	

该方法的任务是:

  • 动态开辟一块节点大小的空间,判断是否成功
  • 然后分别对数据和指针部分初始化 

 

3.头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{
	assert(pphead);//二级指针不能为空,否则解引用就会报错
	SLTNode* newnode = NewNode(x);
	newnode->next = *pphead;//新节点next指针指向原来的首节点
	*pphead = newnode;//新节点成为首节点
}

该方法主要任务:

  • 根据传递的数据部分,申请新节点
  • 将新节点next指针指向原来的首节点
  • 新节点成为首节点 

需要注意的是:

  • 因为传递来的实参需要被改变(即首节点指向的节点需要被改变),所以此处需要传址调用,实参传递首节点的地址,形参使用二级指针接收
  • 因为要对二级指针解引用来修改实参,所以实参传递的指针不能为空,否则解引用就会报错
  • 这段代码对于链表空或非空,都可以正确的处理

4.尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);//二级指针不能为空,否则解引用就会报错
	SLTNode* newnode = NewNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;//如果链表为空,新节点即为第一个节点
	}
	else
	{
		SLTNode* pcur = *pphead;
		while (pcur)//找到链表的尾节点
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;//如果不对空链表分别处理
	}						//此处就会对空指针解引用
}

该方法完成的任务:

  • 对二级指针判空
  • 申请新节点
  •  对于空链表和非空链表分开处理
  1. 对于空链表,新节点成为首节点
  2. 对于非空链表,要先找到尾节点,修改尾节点的next指针指向新节点

 

5.头删

void SLTPopFront(SLTNode** pphead)//头删
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	SLTNode* next = (*pphead)->next;//存储第二个节点
	free(*pphead);//删除第一个节点
	*pphead = next;//链表指向第二个节点
}

 该方法完成的任务:

  • 判空
  • 存储下第二个节点,防止删除第一个节点之后找不到第二个节点
  • 删除第一个节点
  • 链表头指针指向第二个节点
  • (这段代码对于链表只有一个节点的情况也可以正常处理)

 

6.尾删

void SLTPopBack(SLTNode** pphead)//尾删
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	if ((*pphead)->next == NULL)//处理链表只有一个节点的情况
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//处理正常情况
	{
		SLTNode* pcur = *pphead;//找到指针的最后一个节点
		SLTNode* prev = *pphead;//找到指针的倒数第二个节点
		while (pcur->next != NULL)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		free(pcur);
		pcur = NULL;
		prev->next = NULL;//如果不做特殊处理,此处就会对空指针解引用
	}
}

  该方法完成的任务:

  • 判空
  • 对于链表只有一个节点或多个节点的情况分别处理
  1. 链表只有一个节点——先删除,再置空
  2. 链表有多个节点——先找到链表的最后一个和倒数第二个节点,最后一个节点删除和置空,倒数第二个节点的next指向NULL

 

7.查找节点

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
			return pcur;//如果找到,返回节点地址
	}
	return NULL;//对未找到的情况和链表为空的情况都可以处理
}

 该方法完成的任务:

  • 创建一个临时指针变量接收实参传递的链表首节点指针
  • while循环当临时指针pcur不为空时,判断该节点数据是否等于要查找的数据相等直接返回节点地址,否则指针指向下一个节点
  • 如果while循环中没有return,说明未找到指定数据,或链表为空,此时返回一个空指针

 

8.指定位置之前插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	assert(pos);//指定位置必须存在
	SLTNode* newnode = NewNode(x);
	if (*pphead == pos)//如果要插入到第一个节点之前
	{
		SLTPushFront(pphead, x);//可以直接调用头插
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//需要先找到pos的前一个节点
		{                        //如果不分别处理,最后就会对空指针解引用
			prev = prev->next;
		}
		newnode->next = pos;//新节点指向pos
		prev->next = newnode;//原pos的前一个节点指向新节点
	}

}

 该方法完成的任务:

  • 判空
  • 申请节点
  • 对于指定的位置分情况处理
  1. 如果指定的位置就是第一个节点,那么直接调用头插
  2. 对于其他情况,需要先找到pos的前一个节点。然后将三个节点链接在一起:原pos的前一个节点-> 新节点->pos节点

 

9.指定位置之后插入

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);//pos节点必须存在
	SLTNode* newnode = NewNode(x);
	newnode->next = pos->next;//新节点的next指针指向原pos的下一个节点
	pos->next = newnode;//pos的next指针指向新节点
}

 该方法完成的任务:

  • 判空
  • 申请节点
  • 新节点的next指针指向原pos的下一个节点
  • pos的next指针指向新节点(注意这两条语句不能颠倒,否则pos之后的节点就会丢失)

10.删除给定节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	assert(pos);//指定位置必须存在
	if (pos == *pphead)//如果删除的是首节点,或链表只有一个节点
	{
		SLTPopFront(pphead);
	}
	else//链表有多个节点,且删除的不是首节点
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//如果不分开处理,这里就可能对空指针解引用
		{
			prev = prev->next;
		}
		prev->next = pos->next;//找到前一个指针,并将其next指针指向pos的next指针所指节点
		free(pos);
		pos = NULL;
	}
}

该方法完成的任务:

  • 判空
  • 对两种情况分别处理
  1. 如果删除的是首节点,或链表只有一个节点,直接执行头删
  2. 如果链表有多个节点,且删除的不是首节点。那么先找到pos的前一个指针,并将其next指针指向pos的next指针所指节点

 

11.删除给定节点之后的节点

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);//pos节点必须存在且pos之后必须存在节点
	SLTNode* del = pos->next;//预先存储要删除的节点,防止修改指针之后找不到要删除的节点
	pos->next = del->next;
	free(del);
	del = NULL;
}

该方法完成的任务:

  • 判空
  • 预先存储要删除的节点,防止修改指针之后找不到要删除的节点
  • pos的next指针指向要删除节点的next指针所指向的节点(或NULL)
  • 删除pos之后的节点并置空

12.链表销毁

void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;//需要预先存储当前要删除节点的下一个节点
		free(pcur);					//否则就找不到下一个节点了
		pcur = next;
	}
	*pphead = NULL;//删除完所有节点之后,链表置空
}

该方法完成的任务:

  • 判空
  • 创建一个临时指针变量接收实参传递的链表首节点指针
  • while循环当临时指针pcur不为空时,存储下一个节点的地址,并删除该节点,pcur指向下一个节点
  • 删除完所有节点之后,不要忘记给链表指针置为NULL,预防野指针

五、示例程序

 SLinkList.h  单链表结构定义及函数声明头文件

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;//类型重定义
typedef struct SListNode
{
	SLTDataType data;//数据
	struct SListNode* next;//指针
}SLTNode;


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

SLTNode* NewNode(SLTDataType x);//申请节点

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

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

 

SLinkList.c   单链表方法实现源文件 

#include"SLinkList.h"
void SLTPrint(SLTNode* phead)//链表打印
{
	SLTNode* pcur = phead;
	while (pcur)//pcur!=NULL
	{
		printf("%d->", pcur->data);//打印该节点数据
		pcur = pcur->next;//指针指向下一个节点
	}
	printf("NULL\n");
}

SLTNode* NewNode(SLTDataType x)//申请节点
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)//注意这里不要写成一个=
	{
		perror("newnode");
		exit(1);//如果申请节点失败,异常退出程序
	}
	newnode->data = x; //数据初始化
	newnode->next = NULL;//指针初始化
	return newnode;//返回新申请节点的地址
}	

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);//二级指针不能为空,否则解引用就会报错
	SLTNode* newnode = NewNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;//如果链表为空,新节点即为第一个节点
	}
	else
	{
		SLTNode* pcur = *pphead;
		while (pcur)//找到链表的尾节点
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;//如果不对空链表分别处理
	}						//此处就会对空指针解引用
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{
	assert(pphead);//二级指针不能为空,否则解引用就会报错
	SLTNode* newnode = NewNode(x);
	newnode->next = *pphead;//新节点next指针指向原来的首节点
	*pphead = newnode;//新节点成为首节点
}

void SLTPopBack(SLTNode** pphead)//尾删
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	if ((*pphead)->next == NULL)//处理链表只有一个节点的情况
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//处理正常情况
	{
		SLTNode* pcur = *pphead;//找到指针的最后一个节点
		SLTNode* prev = *pphead;//找到指针的倒数第二个节点
		while (pcur->next != NULL)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		free(pcur);
		pcur = NULL;
		prev->next = NULL;//如果不做特殊处理,此处就会对空指针解引用
	}
}

void SLTPopFront(SLTNode** pphead)//头删
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	SLTNode* next = (*pphead)->next;//存储第二个节点
	free(*pphead);//删除第一个节点
	*pphead = next;//链表指向第二个节点
}

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
			return pcur;//如果找到,返回节点地址
	}
	return NULL;//对未找到的情况和链表为空的情况都可以处理
}

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	assert(pos);//指定位置必须存在
	SLTNode* newnode = NewNode(x);
	if (*pphead == pos)//如果要插入到第一个节点之前
	{
		SLTPushFront(pphead, x);//可以直接调用头插
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//需要先找到pos的前一个节点
		{						//如果不分别处理,最后就会对空指针解引用
			prev = prev->next;
		}
		newnode->next = pos;//新节点指向pos
		prev->next = newnode;//原pos的前一个节点指向新节点
	}

}

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);//pos节点必须存在
	SLTNode* newnode = NewNode(x);
	newnode->next = pos->next;//新节点的next指针指向原pos的下一个节点
	pos->next = newnode;//pos的next指针指向新节点
}

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	assert(pos);//指定位置必须存在
	if (pos == *pphead)//如果删除的是首节点,或链表只有一个节点
	{
		SLTPopFront(pphead);
	}
	else//链表有多个节点,且删除的不是首节点
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//如果不分开处理,这里就可能对空指针解引用
		{
			prev = prev->next;
		}
		prev->next = pos->next;//找到前一个指针,并将其next指针指向pos的next指针所指节点
		free(pos);
		pos = NULL;
	}
}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);//pos节点必须存在且pos之后必须存在节点
	SLTNode* del = pos->next;//预先存储要删除的节点,防止修改指针之后找不到要删除的节点
	pos->next = del->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);//二级指针不能为空,链表不能为空
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;//需要预先存储当前要删除节点的下一个节点
		free(pcur);					//否则就找不到下一个节点了
		pcur = next;
	}
	*pphead = NULL;//删除完所有节点之后,链表置空
}

 

test.c  测试文件

#include"SLinkList.h"

void test1()
{
	SLTNode* plist = NULL;
	SLTPrint(plist);//打印初始链表
	SLTPushBack(&plist, 1);//尾插
	SLTPrint(plist);//打印链表
	SLTPushFront(&plist, 2);//头插
	SLTPushFront(&plist, 3);//头插
	SLTPrint(plist);//打印链表
	SLTPopBack(&plist);//尾删
	SLTPrint(plist);//打印链表
	SLTPopFront(&plist);//头删
	SLTPrint(plist);//打印链表
	printf("\n");
	SLTNode* find=SLTFind(plist, 2);//查找
	SLTInsert(&plist, find, 5);//指定位置之前插入数据
	SLTPrint(plist);//打印链表
	SLTInsertAfter(find, 6);//指定位置之后插入数据
	SLTPrint(plist);//打印链表
	SLTEraseAfter(find);//删除指定之后的节点
	SLTPrint(plist);//打印链表
	SLTErase(&plist, find);//删除指定节点
	SLTPrint(plist);//打印链表
	SListDesTroy(&plist);//销毁链表
	SLTPrint(plist);//打印链表
}

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

 

测试结果

 

六、实现单链表的常见问题

  1. 内存泄漏
    • 当创建新节点并为其分配内存后,如果在某个时候不再需要该节点,但没有正确地释放其占用的内存,就会发生内存泄漏。这可能会导致程序在长时间运行后占用越来越多的内存,甚至耗尽系统资源。
  2. 野指针
    • 如果一个指针被赋予了一个非法的内存地址(例如,一个已经被释放的内存地址),那么这个指针就被称为野指针。尝试访问或操作野指针指向的内存可能导致程序崩溃或产生不可预测的行为。
    • 在单链表中,当删除一个节点时,必须确保没有其他的指针(例如,遍历链表的指针)仍然指向该节点,否则这些指针就可能变成野指针。
  3. 空指针解引用
    • 在C语言中,解引用一个空指针(即值为NULL的指针)是未定义的行为,通常会导致程序崩溃。在单链表操作中,必须始终检查指针是否为空,以避免空指针解引用。
  4. 链表断裂
    • 在插入或删除节点时,如果操作不当,可能会导致链表的断裂,即链表中某些节点失去了与前后节点的连接。这通常是由于没有正确更新指针所导致的。
  5. 链表遍历错误
    • 在遍历链表时,如果没有正确设置遍历的起始和结束条件,就可能导致遍历错误。例如,如果遍历的起始节点不是链表的头节点,或者没有正确地检查当前节点是否为NULL就尝试访问其next指针,就可能导致问题。
  6. 函数参数传递不当

    • 使用一级指针而非二级指针:在需要修改链表头指针的函数中,如果没有使用二级指针,就无法在函数内部正确修改头指针。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

90、动态规划-最长的有效括号

思路&#xff1a; 找出有效括号并且是最长的有效括号 dp[i]表示以i结尾的括号最长是多少 然后从1开始 因为从0位置不管是左括号还是右括号都是无法形成一个完成的括号。所以dp[0]0&#xff1b; 当i1时候&#xff0c;判断括号是否是&#xff09;如果不是那么无法结尾&#x…

cmake进阶:变量的作用域说明一(从函数作用域方面)

一. 简介 如同 C 语言一样&#xff0c;在 cmake 中&#xff0c;变量也有作用域的概念&#xff0c;本文我们就来聊一聊关于 cmake 中变量作用域的问题。 接下来从三个方面进行介绍&#xff1a;函数作用域、目录作用域以及全局作用域。 二. 函数作用域 我把这个作用域叫做函数…

pycharm安装pandas包

import pandas时提示未安装pandas&#xff0c;点击下图红框选项&#xff0c;进行pandas安装 pycharm底部会有安装中的提示 pycharm底部提示红框的内容&#xff0c;说明安装成功 这个时候就可以看到import pandas不再报错了

LeetCode 611. 有效三角形的个数

原题链接&#xff1a;611. 有效三角形的个数 - 力扣&#xff08;LeetCode&#xff09; 题目说&#xff0c;给定一个包含非负整数的数组 num&#xff0c;返回其中可以组成三角形三条边的三元组个数。 示例&#xff1a; nums [4, 2, 3, 4]&#xff1b; 有效组合如下&#xff1a;…

NIO和NIO.2对比

Java NIO (New Input/Output) 是从Java 1.4版本开始引入的一个新的I/O API&#xff0c;用于替代原来的BIO&#xff08;Blocking I/O&#xff09;API。NIO提供了更加灵活和高效的网络通信方式&#xff0c;特别适合于高吞吐量的网络编程。NIO的主要特点是非阻塞模式&#xff0c;它…

数据结构(C):玩转顺序表

&#x1f37a;0.前言 &#x1f3b7;1.线性表 &#x1f3b8;2.顺序表 &#x1f4c0;动态顺序表的实现 &#x1f4bf;初始化 &#x1f4bf;检查容量是否满了&#xff0c;进行扩容 &#x1f4bf;插入&#xff1a;头插和尾插 &#x1f4bf;删除&#xff1a;头删和尾删 &…

Python实现2048游戏

提供学习或者毕业设计使用,功能基本都有,不能和市场上正式游戏相提比论,请理性对待! 在这篇博客中,我们将使用 Python 和 Pygame 库来编写经典的 2048 游戏。2048 是一个益智类游戏,通过在 4x4 网格上滑动方块并合并它们来创建一个新的数字,直到获得数字 2048 或者无法继…

bfs之走迷宫

文章目录 走迷宫广度优先遍历代码Java代码打印路径 走迷宫 给定一个 nm 的二维整数数组&#xff0c;用来表示一个迷宫&#xff0c;数组中只包含 0或 1&#xff0c;其中 0表示可以走的路&#xff0c;1表示不可通过的墙壁。 最初&#xff0c;有一个人位于左上角 (1,1) 处&#…

leetcode-岛屿数量-99

题目要求 思路 1.使用广度优先遍历&#xff0c;将数组中所有为1的元素遍历一遍&#xff0c;遍历过程中使用递归&#xff0c;讲该元素的上下左右四个方向的元素值也置为0 2.统计一共执行过多少次&#xff0c;次数就是岛屿数量 代码实现 class Solution { public:int solve(vec…

mac电脑如何安装python及环境搭建

&#xff08;1&#xff09;进入官网&#xff1a;Download Python | Python.org&#xff0c;根据自己电脑选择python (2)这里我选择的是mac,点击&#xff1a;macos&#xff0c;选择最近版本并点击进入 (3)选择mac版本&#xff1a; (4)点击就可以进入下载&#xff1a; (5)下载好之…

网站防御XSS攻击的有效策略与实施步骤

随着互联网应用的普及与发展&#xff0c;网站安全已成为众多企业关注的焦点&#xff0c;而XSS&#xff08;Cross-Site Scripting&#xff09;攻击作为最常见的Web安全漏洞之一&#xff0c;对用户数据安全构成严重威胁。本文将详细介绍网站如何有效防御XSS攻击&#xff0c;并提供…

Spring JdbcTemplate使用临时表+事务会话管理实现数据新增、查询及自动清除功能

需求描述&#xff1a; 由于某些情况下当查询过滤参数过大时&#xff0c;执行sql由于参数过大而报错&#xff0c;此时 需要使用临时表的方式&#xff0c;即 当参数超过某个阀值&#xff08;如 1000&#xff0c;可调整&#xff09;新增一张临时表&#xff0c;将原表 与 该临时表进…

2024精武杯部分复现

首先是计算机部分&#xff0c;这里是题目 做完才发现其实很多东西在火眼里面已经有更快的捷径了&#xff0c;但是自己当时没发现&#xff0c;还去傻傻的开虚拟机去找&#xff0c;说到底&#xff0c;还是对取证软件的理解不够&#xff0c;也不怎么会用。不废话了&#xff0c;直接…

怎么给word文件名批量替换部分文字?word设置批量替换文字教程

批量替换Word文件名中的几个字&#xff0c;对于经常处理大量文件的人来说&#xff0c;是一项非常实用的技能。以下是一个详细的步骤指南&#xff0c;帮助你快速完成这项任务。 首先&#xff0c;你需要准备一个可以批量重命名文件的工具。市面上有很多这样的工具可供选择&#x…

人工智能的发展将如何重塑网络安全

微信搜索关注公众号网络研究观&#xff0c;获取更多信息。 人们很容易认为人工智能 (AI) 真正出现是在 2019 年&#xff0c;当时 OpenAI 推出了 ChatGPT 的前身 GPT-2。 但现实却有些不同。人工智能的基础可以追溯到 1950 年&#xff0c;当时数学家艾伦图灵发表了题为“计算机…

密码学《图解密码技术》 记录学习 第十四章

目录 十四章 14.1 本章学习的内容 14.2 什么是 SSL/TLS 14.2.1 Alice 在 Bob 书店买书 14.2.2 客户端与服务器 14.2.3 АSSL/TLS 承载HTTP 14.2.4 SSL/TLS的工作 14.2.5 SSL/TLS也可以保护其他的协议 14.2.6 密码套件 14.2.7 SSL 与 TLS 的区别 14.3 使用 SSL/TLS 进…

产业观察:电机驱动成为人形机器人的动力核心

前不久&#xff0c;波士顿动力发布一则“再见&#xff0c;液压Atlas”视频&#xff0c;宣告其著名的液压驱动双足人形机器人Atlas正式退役。这则视频引起全球所有Atlas粉丝的高度关注。然而紧接着&#xff0c;波士顿动力便又推出了全部由电机驱动的新一代Atlas机器人&#xff0…

【Git】【MacOS】Github从创建与生成SSH公钥

创建账号 这一步不过多赘述&#xff0c;根据自己的邮箱新创建一个账号 配置SSH公钥 本人是macOS系统&#xff0c;首先从终端输入 cd ~/.ssh进入.ssh目录,然后通过 ls查看有没有一个叫做id_rsa.pub的文件 本人之前生成过SSH公钥,如果没有的话&#xff0c;通过 ssh-keygen -t…

luci框架相关笔记

luci架构 LuCI 架构采用了MVC&#xff08;Model-View-Controller&#xff09;设计模式&#xff0c;各个目录的作用如下&#xff1a; model&#xff08;模型&#xff09;: 位于 /usr/lib/lua/luci/model 下&#xff0c;存放了与系统配置相关的模型脚本。这些脚本负责与底层系统…

cmd输入mysql -u root -p无法启动

问题分析&#xff1a;cmd输入mysql -u root -p无法启动 解决方法&#xff1a;配置系统环境变量 1.找到mysql安装文件下的bin文件&#xff1a;&#xff08;复制改文件地址,如下图所示&#xff09; 2.电脑桌面下方直接搜索环境变量并进入&#xff0c;如下图 3.点击环境变量&a…
最新文章