【数据结构】解密链表之旅(单链表篇)

news2025/1/21 12:09:25

前言

哈喽大家好,我是野生的编程萌新,首先感谢大家的观看。数据结构的学习者大多有这样的想法:数据结构很重要,一定要学好,但数据结构比较抽象,有些算法理解起来很困难,学的很累。我想让大家知道的是:数据结构非常有趣,很多算法是智慧的结晶,我希望大家在学习数据结构的过程是一种愉悦的心情感受。因此我开创了《数据结构》专栏,在这里我将把数据结构内容以有趣易懂的方式展现给大家。

 1.线性表链式存储结构定义

在上一篇博客中我们提到了线性表有两种存储方式,一种是顺序存储,一种是链式存储。线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据可以存在内存未被占用的任意位置。

 在之前的顺序结构中,每个数据元素只需要存储数据元素信息就可以了。现在链式结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址。链式存储结构相比于顺序存储结构的优势在于插入和删除操作的高效性。由于链式存储结构中的元素通过指针连接,所以在插入和删除元素时,只需改变指针的指向,不需要移动其他元素,因此效率较高。而顺序存储结构需要移动元素位置,效率较低。线性表的链式存储结构是通过节点之间的指针来实现的,每个节点包含两个部分:数据域(存储数据元素信息的域)和指针域(存储直接后继位置的域)。n个节点链接成一个链表,即为线性表的链式存储结构(链表)。

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的存储结构就与火车车厢相似,淡季时车次的车厢会有所减少,相对的在旺季时车次的车厢会有所增加。这需要将火车的车厢去掉或者加上,不会影响其他的车厢,每节车厢都是独立的。就像下面这张图:

每节车厢都是独立的,每节车厢都有自己的车门。假设每节车厢都是锁住的状态,他们都需要不同的钥匙来解锁且每次只能携带一把钥匙,该如何从车头走向车尾呢?最简单的方法就是:在每节车厢存放下一节车厢的钥匙。那么在链表这个“火车”中,每节“车厢”的情况是什么样子的呢?

 与顺序表不同的是,链表每节“车厢”都是独立申请下来的空间,我们称为“节点/结点”。对于线性表来说,总得有头有尾啊,链表也不能例外。我们把链表中的第一个节点存储的位置叫做头指针,那么整个链表的存取就必须是从头指针开始运行了。之后的每一个节点,其实就是上一个后继指针指向的位置。既然如此,那最后一个节点的指针指向哪里?什么!最后一个?当然就意味着后继不存在了,所以我们规定最后一个节点的指针为空。有时候,我们为了更加方便地对链表进行操作,会在单链表的第一个节点之前附设一个节点,我们成为头节点。下图为在上图基础上加一个头节点:

 头指针和头结点的异同点

  1. 头指针和头节点都是链表的概念,用于表示和操作链表的入口。
  2. 头指针是一个指针变量,存储的是第一个节点的地址;头节点是一个特殊节点,位于链表的第一个位置,不包含有用的数据。
  3. 头指针用于遍历链表中的所有节点;头节点用于简化对链表的操作。
  4. 头指针在链表中的位置是可变的,可以随着节点的插入或删除而改变;头节点在链表中位置固定,一般不会发生变化。

链表的种类非常多样,我们主要根据是否有头节点、单向或双向、是否循环将链表分为8类:

1.带头或者不带头

2.单向或者双向:

 3.是否循环:

虽然有这么多的链表结构,其实我们最常用的还是两种结构:不带头的单向链表和双向循环链表。我们这一篇就主要围绕单链表来介绍。 

2.单链表各个功能的实现

单链表是一种最简单的链表数据结构,它由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储节点的数据,指针域用于指向下一个节点。单链表的特点是节点之间只有一个指针连接,每个节点只能访问下一个节点,不能访问前一个节点。链表的头节点是第一个节点,尾节点是最后一个节点,尾节点的指针域通常指向一个空地址(NULL)。用C语言来描述单链表的结构指针:

typedef int SLNDataType;
typedef struct SListNode 
{
	SLNDataType val;
	struct SListNode* next;
}SLNode;

在这里我们主要详细介绍单链表的插入删除等操作。在单链表中插入有尾插、头插、任意位置插入等操作,每次插入都需要申请空间,每次申请空间的操作都相同,我们干脆写一个函数来实现申请空间,这样能使我们的操作更加方便。

SLNode* CreateNode(SLNDataType* x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

2.1单链表的尾插和尾删

单链表的尾插操作步骤:

  1. 创建一个新的节点,设置其数据域为要插入的值,指针域为空。
  2. 检查链表是否为空。若为空,则将新节点作为链表的第一个节点。
  3. 若链表不为空,需要找到链表的最后一个节点。从链表的头节点开始遍历,直到遍历到最后一个节点(即指针域为空的节点)。
  4. 将最后一个节点的指针域指向新节点,将新节点插入到链表中。

我们需要使用单链表指针变量来创建头指针,所以我们在传参时要使用二级指针。我们来实现一下单链表的尾插操作:

void SLPushBack(SLNode** phead, SLNDataType x)
{
	assert(phead);
	SLNode* newnode=CreateNode(x);
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	else
	{
		SLNode* tail = *phead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

单链表的尾删具体操作步骤为:

  1. 判断链表是否为空。如果链表为空,则无法进行尾删操作,直接返回。
  2. 如果链表只有一个结点,则将链表的头指针置为空,删除这个结点即可。
  3. 如果链表有多个结点,则需要遍历到倒数第二个结点,即指针指向要删除结点的前一个结点。
  4. 将前一个结点的 next 指针指向 NULL,断开要删除结点和链表的连接。
  5. 释放要删除结点的内存空间。

我们来实现一下单链表尾删操作:

void SLPopBack(SLNode** phead)
{
	assert(phead);
	assert(*phead);
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	else
	{
		SLNode* prev = NULL;
		SLNode* tail = *phead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;

	}
}

2.2单链表的头插和头删

单链表的头插具体操作步骤为:

  1. 首先,创建一个新的节点,并将要插入的元素值赋给新节点的数据域。
  2. 为了将新节点插入到链表中,需要将新节点的next指针指向链表的第一个节点,即原本的第一个节点。
  3. 然后,将链表的头节点指向新节点,即将新节点设为链表的第一个节点。

我们来实现一下这个操作:

void SLPushFront(SLNode** phead, SLNDataType x)
{
	assert(phead);
	SLNode* newnode = CreateNode(x);
	newnode->next = *phead;
	*phead = newnode;
}

单链表的头删具体操作步骤为:

  1. 检查链表是否为空。如果链表为空,无法进行头删操作,直接返回。
  2. 创建一个临时变量tmp,将其指向链表的第一个节点。
  3. 将链表的头节点指针指向第一个节点的下一个节点,即tmp->next。
  4. 释放temp指向的节点。

我们来实现一下这个操作:

void SLPopFront(SLNode** phead)
{
	assert(phead);
	assert(*phead);
	SLNode* tmp = (*phead)->next;
	free(*phead);
	*phead = tmp;
}

2.3单链表的任意位置插入和任意位置删除

单链表的任意位置插入和顺序表中的任意插入有所不同,在单链表中我们需要先编写一个链表数据元素的查找函数,然后输入一个节点值,接着返回相对应的节点,然后进行插入删除操作。链表数据元素查找函数实现如下:

SLNode* SLFind(SLNode* phead, SLNDataType x)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

我们创建一个单链表节点指针变量接收查找函数的返回信息。单链表的任意位置插入操作的具体步骤为:

  1. 首先,判断要插入的位置是否合法,即判断插入的位置是否超出了链表的范围。如果超出了范围,则不进行插入操作。

  2. 然后我们还要判断一下插入位置是否是在头节点,如果插入的位置是头节点,那我们直接调用头插的函数就行,反之,创建一个新结点,将要插入的数据放入在其中。

  3. 遍历链表,找到插入位置的前一个节点,即要在其后面插入新节点。

  4. 将新节点的指针域指向插入位置的前一个节点原来指向的节点。

  5. 将插入位置的前一个节点的指针域指向新节点。

我们来实现一下这个操作:

void SLInsert(SLNode** phead, SLNode* pos, SLNDataType x)
{
	assert(phead);
	assert(pos);
	if (*phead == pos)
	{
		SLPushFront(phead, x);
	}
	else
	{
		SLNode* prev = *phead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLNode* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

当然还是有人喜欢任意位置之后插入,我们也来实现一下:

void SLInsertAfter(SLNode* pos, SLNDataType x)
{
	assert(pos);
	SLNode* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

讲完任意位置插入了,我们现在来整任意位置删除,任意位置删除的基本操作步骤为:

  1. 首先判断单链表是否为空,若为空则无法进行删除操作,直接返回。
  2. 如果要删除的位置是头节点,则将头节点指向下一个节点,并释放原来的头节点(头删操作)。
  3. 如果要删除的位置不是头节点,需找到要删除节点的前一个节点。遍历单链表,找到要删除节点的前一个节点。可以使用两个指针prev和pos,prev指向要删除节点的前一个节点,pos指向要删除节点。当pos指向要删除节点时,prev指向的即为要删除节点的前一个节点。
  4. 判断要删除节点是否存在,如果pos为空,则说明要删除的节点不存在,直接返回。
  5. 将p的next指针指向q的next指针,即跳过要删除的节点。
  6. 释放要删除节点q的内存空间。

我们来实现一下这个操作:

void SLErase(SLNode** phead, SLNode* pos)
{
	assert(phead);
	if (*phead == pos)
	{
		SLPopFront(phead);
	}
	else
	{
		SLNode* prev = *phead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

 到了这里应该知道我要干什么了吧,实现任意位置之后的删除:

void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* tmp = pos->next;
	pos->next = tmp->next;
	free(tmp);
	tmp = NULL;
}

2.4单链表的销毁

单链表的销毁指的是将整个链表都删除,回收其占用的内存空间。单链表的销毁的具体步骤为:

  1. 首先需要定义一个指针变量,用于遍历链表。假设此变量为cur,并将其初始化为链表的头节点。
  2. 创建一个临时指针变量,用于保存当前节点的下一个节点。假设此变量为next。
  3. 使用循环遍历链表,直到cur指针变量为空。
  4. 在循环中,将next指针变量指向cur指针变量的下一个节点。
  5. 释放cur指针变量指向的节点的内存空间。可以使用free()函数来实现内存的释放。
  6. 将cur指针变量指向next指针变量,即将cur指针变量移动到下一个节点上。
  7. 重复步骤3-6,直到遍历完整个链表,即cur指针变量指向空。

当cur指针变量为空时,说明链表的所有节点都已经被删除,此时整个链表就被销毁了。我们来实现一下这个操作:

void SLDestory(SLNode** phead)
{
	assert(phead);
	SLNode* cur = *phead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*phead = NULL;
}

在进行链表的销毁时要注意:我们需要确保链表中的每个节点都被释放,以避免内存泄漏。同时,需要注意释放节点内存空间前,需要先保存下一个节点的指针,否则在释放当前节点后,就无法访问到下一个节点了。

3.多文件实现单链表

SList.h文件:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLNDataType;
typedef struct SListNode 
{
	SLNDataType val;
	struct SListNode* next;
}SLNode;
void SLprint(SLNode* phead);//链表打印
void SLPushBack(SLNode** phead, SLNDataType x);//链表的尾插
void SLPushFront(SLNode** phead, SLNDataType x);//链表的头插
void SLPopBack(SLNode** phead);//链表的尾删
void SLPopFront(SLNode** phead);//链表的头删
SLNode* SLFind(SLNode* phead, SLNDataType x);//链表的查找
void SLInsert(SLNode** phead, SLNode* pos, SLNDataType x);//链表的插入
void SLErase(SLNode** phead, SLNode* pos);//链表的删除
void SLInsertAfter(SLNode* pos, SLNDataType x);//后面插入
void SLEraseAfter(SLNode* pos);//后面删除
void SLDestory(SLNode** phead);//链表的销毁

SList.c文件:

#include"SList.h"
void SLprint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur!= NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}
SLNode* CreateNode(SLNDataType* x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}
void SLPushBack(SLNode** phead, SLNDataType x)
{
	assert(phead);
	SLNode* newnode=CreateNode(x);
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	else
	{
		SLNode* tail = *phead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void SLPushFront(SLNode** phead, SLNDataType x)
{
	assert(phead);
	SLNode* newnode = CreateNode(x);
	newnode->next = *phead;
	*phead = newnode;
}
void SLPopBack(SLNode** phead)
{
	assert(phead);
	assert(*phead);
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	else
	{
		SLNode* prev = NULL;
		SLNode* tail = *phead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;

	}
}
void SLPopFront(SLNode** phead)
{
	assert(phead);
	assert(*phead);
	SLNode* tmp = (*phead)->next;
	free(*phead);
	*phead = tmp;
}
SLNode* SLFind(SLNode* phead, SLNDataType x)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
void SLInsert(SLNode** phead, SLNode* pos, SLNDataType x)
{
	assert(phead);
	assert(pos);
	if (*phead == pos)
	{
		SLPushFront(phead, x);
	}
	else
	{
		SLNode* prev = *phead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLNode* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
void SLErase(SLNode** phead, SLNode* pos)
{
	assert(phead);
	if (*phead == pos)
	{
		SLPopFront(phead);
	}
	else
	{
		SLNode* prev = *phead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
void SLInsertAfter(SLNode* pos, SLNDataType x)
{
	assert(pos);
	SLNode* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* tmp = pos->next;
	pos->next = tmp->next;
	free(tmp);
	tmp = NULL;
}
void SLDestory(SLNode** phead)
{
	assert(phead);
	SLNode* cur = *phead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*phead = NULL;
}

test.c文件(在这里测试函数功能):

#include"SList.h"
void test()
{
	SLNode* plist = NULL;
	//测试尾插
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLprint(plist);
	//测试头插
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 0);
	SLprint(plist);
	//测试尾删
	SLPopBack(&plist);
	SLprint(plist);
	//测试头删
	SLPopFront(&plist);
	SLprint(plist);
	//测试任意插入
	SLNode* pos = SLFind(plist, 3);
	SLInsert(&plist, pos, 30);
	SLprint(plist);
	//测试任意删除
	pos = SLFind(plist, 30);
	SLErase(&plist, pos);
	SLprint(plist);
}
int main()
{
	test();
	return 0;
}

我们看看对各个函数测试结果:

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

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

相关文章

GPT-4o--真AI助手来临,可免费使用!

​ 今天凌晨&#xff0c;OpenAI又双缀缀发大招了&#xff0c;直接放出新的模型–GPT-4o&#xff0c;并号称可以实时对音频、视觉和文本进行推理。现场直播效果非常炸裂&#xff0c;能听、说、看&#xff0c;而且几乎没有延迟。 在GPT-4o出来以前&#xff0c;GPT也有语音对话功…

win10共享文件夹到ubuntu22

win10共享文件夹 新建用户 新建用户、设置密码。避免共享给EveryOne&#xff0c;导致隐私问题。 点击左下角的开始菜单&#xff0c;选择“设置”&#xff08;WinI&#xff09;打开设置窗口。在设置窗口中&#xff0c;搜索或直接点击“账户”进入账户设置。在账户设置中&…

【全开源】多功能投票小程序支持微信公众号+微信小程序+H5(源码搭建/上线/运营/售后/维护更新)

介绍 多功能投票小程序是一款基于FastAdminThinkPHPuniapp开发的一款多功能投票小程序。 ​用户友好与便捷性&#xff1a; 投票小程序界面简洁直观&#xff0c;操作简便&#xff0c;用户无需下载和安装额外的应用&#xff0c;即可在微信或其他平台上直接使用&#xff0c;大大提…

直线道路线通过霍夫变换进行识别并画出

最近在研究高速直线识别&#xff0c;搜了一圈看了很多文章&#xff0c;确定了以下的主要流程。 霍夫变换 lines cv2.HoughLinesP(image,rho,theta,threshold,lines,minLineLength,maxLineGap)#概率霍夫变换 image:必须是二值图像&#xff0c;推荐使用canny边缘检测的结果图像…

C语言笔记14

指针1 在C语言中给内存单元的编号起了个名字叫做指针&#xff0c;通俗来说就是地址。&#xff08;内存单元编号地址指针&#xff09; 1.指针变量与地址 int a10&#xff1b; int* p&a&#xff1b; * 说明了这里p的是指针变量&#xff1b;int*说明p是一个整形指针&…

【class2】人工智能初步(自然语言处理)

要实现从评价中提取高频关键词&#xff0c;并判别其正负面性&#xff0c;其实是通过人工智能领域中的一个分支&#xff1a;自然语言处理。 在了解自然语言处理之前&#xff0c;我们先来说说&#xff0c;什么是自然语言&#xff08;Natural Language&#xff09;&#xff1f;自…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.1,2,3-GPIO中断控制实验

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

全自动发布小红薯思维导图笔记(附爆款AI提示词)

全自动电脑接管&#xff0c;帮你生成图片、文案&#xff0c;然后还帮你自动发布。 大家好&#xff0c;我是大胡子&#xff0c;专注于研究RPA实战与解决方案&#xff0c;今天来介绍一款全自动生产内容并发布小红薯的神器&#xff0c;最后附有AI提示词&#xff0c;记得收藏点赞…

【C#】学习获取程序执行路径,Gemini 帮助分析

一、前言&#xff1a; 在Delphi中&#xff0c;如果想要获取当前执行程序的目录&#xff0c;程序代码如下&#xff1a; ExtractFilePath(ParamStr(0)); 今天在分析一个别人做的C#程序时看到了一段C#代码&#xff0c;意思是获取执行程序所在的文件目录&#xff1a; public stat…

[图解]实现领域驱动设计译文暴露的问题04

0 00:00:00,960 --> 00:00:03,020 今天我们继续说一下 1 00:00:03,460 --> 00:00:05,350 实现领域驱动设计 2 00:00:05,630 --> 00:00:08,120 译文里面暴露的问题 3 00:00:10,630 --> 00:00:14,740 前面三个视频&#xff0c;我们提到了第①句 4 00:00:15,550 -…

国家网络与信息系统安全产品质量检验检测中心是什么机构?成立于一年?有什么用?

在当今信息化快速发展的时代&#xff0c;网络安全问题日益凸显其重要性&#xff0c;而检测中心则扮演着守护网络安全、保障信息系统稳定运行的关键角色。今天我们就来聊聊国家网络与信息系统安全产品质量检验检测中心是什么机构&#xff1f;成立于一年&#xff1f;有什么用&…

RedisTemplate操作Redis详解之连接Redis及自定义序列化

连接到Redis 使用Redis和Spring时的首要任务之一是通过IoC容器连接到Redis。为此&#xff0c;需要java连接器&#xff08;或绑定&#xff09;。无论选择哪种库&#xff0c;你都只需要使用一组Spring Data Redis API&#xff08;在所有连接器中行为一致&#xff09;&#xff1a;…

基础模型的推理:综述

24年1月论文“A Survey of Reasoning with Foundation Models”&#xff0c;来自香港中文大学、华为、香港大学、上海AI实验室、香港科技大学、大连理工、北京大学、清华大学、合肥工大、中国人民大学、复旦大学和香港科技大学广州分校。 推理是解决复杂问题的关键能力&#x…

Windows/Mailing

Mailing Enumeration nmap 使用 nmap 扫描系统&#xff0c;发现对外开放了如下端口 ┌──(kali㉿kali)-[~/vegetable/HTB] └─$ nmap -sC -sV -oA nmap 10.10.11.14 Starting Nmap 7.93 ( https://nmap.org ) at 2024-05-08 01:46 EDT Nmap scan report for 10.10.11.14 H…

软件各阶段资料(需求设计,系统架构,开发文档,测试文档,运维阶段的部署维护文档,概要设计,详细设计)

一、 引言 &#xff08;一&#xff09; 编写目的 &#xff08;二&#xff09; 范围 &#xff08;三&#xff09; 文档约定 &#xff08;四&#xff09; 术语 二、 项目概要 &#xff08;一&#xff09; 建设背景 &#xff08;二&#xff09; 建设目标 &#xff08;三&#xff0…

linux上用Jmter进行压测

在上一篇中安装好了Jmeter环境&#xff0c;在这一篇中将主要分享如何使用jmeter在linux中进行单机压测。 1.项目部署 在这里我们先简单部署一下测试环境&#xff0c;所用到的项目环境是个jar包&#xff0c;先在linux上home目录下新建app目录&#xff0c;然后通过rz命令将项目ja…

一些近来对内网攻防的思考

我知道我最近托更托了很久了&#xff0c;其实也不是小编懒啊 这小编也是一直在写&#xff0c;但是遇到的问题比较多&#xff08;我太菜了&#xff09;&#xff0c;所以一直拖着。 但是总不能不更吧 那就讲一下进来的一些内网攻防的思考吧 1.CrossC2上线Linux到CS(成功) …

程序设计文档—软件分析报告(Word)

第3章 技术要求 3.1 软件开发要求 第4章 项目建设内容 第5章 系统安全需求 5.1 物理设计安全 5.2 系统安全设计 5.3 网络安全设计 5.4 应用安全设计 5.5 对用户安全管理 5.6 其他信息安全措施 第6章 其他非功能需求 6.1 性能设计 6.2 稳定性设计 6.3 安全性设计 6.4 兼容性设计…

DriveWorld:通过自动驾驶世界模型进行 4D 预训练场景理解

24年5月北大论文“DriveWorld: 4D Pre-trained Scene Understanding via World Models for Autonomous Driving”。 近年来&#xff0c;以视觉为中心的自动驾驶因其较低的成本而受到广泛关注。预训练对于提取通用表示至关重要。然而&#xff0c;当前以视觉为中心的预训练通常依…

【Linux】进程信号(2万字)

目录 前言 一、生活角度的信号 1.1、我们来见一见信号&#xff1a; 1.2、模拟一下 kill 指令 二、信号的处理 三、产生信号的5种方法 3.1、kill命令 3.2、键盘可以产生信号 3.3、3种系统调用 3.4、软件条件 3.5、异常 四、比较 core 和 Term 五、键盘信号产生 六…