单链表的介绍和实现

news2024/11/15 20:45:24

前言

Hello,小伙伴们,你们的作者君又回来了,今天我将带领大家继续学习另一种线性表:单链表,

准备好的小伙伴三连打卡上车,你们·的支持就是我更新的动力,一定不要吝啬手中的三连哟,万分感谢!

1.单链表的介绍

1.1什么是单链表

概念:链表是一种物理存储结构上的非连续、非顺序的的存储结构,数据的元素的逻辑顺序都是通过链表的指针链接次序实现的。

 链表的结构跟火车的车厢相似,淡季时车次很少,相应的车厢也可能会减少,旺季时相应的车厢也会减少。只需要将火车里的某节车厢加上或者是减去就好了,每节车厢都是独立的存在,彼此的增减删除不会影响其他的车厢的运作。

车厢是独立存在的,且每节车厢都是有车门的。想象一下这样的场景。假设每节车厢的车门都是上锁的状态,最简单的做法就是:每节车厢都有下一节车相处的钥匙。

在链表里,每节“车厢”大概是什么样的呢?

与顺序表不同,链表的每节“车厢”都是独立申请下来的空间,我们称之为“节点/结点”;

结点组要有两个部分组成:当前节点需要保存的数据和下一个节点的地址(指针变量)

在图中的指针变plist就是链表的头结点。

为什么我们还需要指针变量来保存下一个节点的位置?

链表中的每一个节点都是独立申请的(即在需要插入数据的时候才需要去申请空间),我们就需要通过指针变量来保存下一个节点的位置才能实现从一个节点找的下一个节点的操作。

结合前面学过的知识,我们用结构体来定义我们需要的节点。

假设我们需要的节点数据为整形:
 

struct SListNode
{
int data; //节点数据
struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};

当我们想要保存一个整形数据时,实际上就是向系统申请了一块空间,这个内存不仅保存了整形数据还保存了下一个节点的地址(当下一个节点为空时保存的址也为空!)

2.链表的实现

和实现顺序表的步骤一样我们还是需要创建3个文件

根据前面篇幅关于链表的介绍,我们可以开始定义链表的结构:

typedef int NodeType

 这一步的操作是为了方便后面的一键替换就和前面的顺序表的实现定义一样,为了方便后面的链表可以轻松的存储其他类型的数据。

既然链表是线性表的一种,那我们也可以向实现顺序表那样来实现链表的增删查改操作:
那首先我们先来实现一下链表的尾插吧

2.1链表的尾插

我们先来看该功能的实现函数的定义:

//实现单链表数据的尾插

void ListNodePushBack(ListNode** ps, NodeType x);

再来看看怎样来实现来实现链表的尾插功能:
 

//实现单链表数据的尾插

void ListNodePushBack(ListNode** ps, NodeType x)
{
	ListNode* temp = CreatNode(x);
	if (*ps == NULL)
	{
		*ps = temp;
	}
	else
	{
		ListNode* pcur = *ps;
		while (pcur->next != NULL)
		{
			pcur = pcur->next;

		}
		pcur->next = temp;
	}
}

在这里,我们要注意:为什么我们要传递二级指针呢

其实是为了解决*ps为空指针的情况 ,如果传过来的是一个空指针,直接进行寻找链表的最后一个节点的操作就会出现对空指针的解引用,从而引起报错!!!

2.1.1链表空间节点的申请

在介绍之前我们还要清楚 CreatNode函数的作用:

ListNode* CreatNode(NodeType x)
{
	ListNode* temp = (ListNode*)malloc(sizeof(ListNode));
	if (temp == NULL)
	{
		perror("malloc");
		exit(1);
	}
	else
	{
		temp->next = NULL;//尾节点的next指针指向NULL
		temp->x = x;//让申请下来的空间能存储想要插入的值
		return temp;
	}
}

在这里,我们需要清楚,链表相较于顺序表是没有增容的概念的,每一个节点的空间都是相对独立的,除了中间的指针变量作为引线将他们彼此相连,任何一块“车厢的消失”都不会影响到整个“列车”的运行

2.1.2 尾插的进行

想要进行尾插,就一定要找到尾插对象链表的尾节点才能进行下一步操作:

ListNode* pcur = *ps;
		while (pcur->next != NULL)
		{
			pcur = pcur->next;

		}
		pcur->next = temp;
	}

从上面的代码可以知道,当pcur->next == NULL时循环跳出,此时pcur也正好指向了链表的尾节点

所以此时只需要pcur->next指向的对象变为刚才申请下来的节点就可以完成尾插的工作,这样新申请下来的空间就可以成为新的尾节点。

尾插的效果演示:

在进行尾插效果的展示时,我们为了能够更加直观的观察到效果,我们可以先来实现单链表的数据打印:

单链表数据的打印(LIstNodePrint函数)

先来看看他的申明:

//打印单链表
void ListNodePrint(ListNode* s);

由于打印的操作并不需要链表的数据有任何改变,所以就只需要传递头结点的指针变量即可:

void ListNodePrint(ListNode* s)
{
	while (s)
	{
		printf("%d->", s->x);
		s = s->next;
	}
	printf("NULL");
}
尾插测试: 

此时,我们再来测试我们的代码,看看效果如何: 

 2.2链表的头插

有了尾插的基础,实现头插就要简单一些了

首先我们先看看头插函数的定义:
 

//前插元素
void ListNodePushFront(ListNode** ps, NodeType x);

再看看他的实现代码:

//前插元素
void ListNodePushFront(ListNode** ps, NodeType x)
{
	assert(ps);
	ListNode* temp = CreatNode(x);
	
		temp->next = *ps;
		*ps = temp;//在初始的状态下,*ps指向的是原单链表的首节点,进行头插后就成为了第二个节点
//此时需要将temp赋给*ps才能真正的完成头插的操作
	
}

这里的操作就会简单很多,就是需要申请一个新的节点,存放想要头插的数据并使其成为新的首节点。

头插测试:

此时头插传操作完成!! 

2.3链表的尾删

 

 由上图的分析可知:

想要删除最后一个节点,就必须找到最后一个节点和倒数第二个节点

我们先来看看实现该功能的函数的定义

//尾删元素
void ListNodePopBack(ListNode** ps);

函数的实现代码:

//尾删元素
void ListNodePopBack(ListNode** ps)
{
	assert(ps && *ps);
	//处理只有一个节点的元素
	if ((*ps)->next == NULL)
	{
		free(*ps);
		*ps = NULL;
	}
	else
	{
		ListNode* prev = *ps;
		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = NULL;
	}
}

结合上文的图我们可以知道

prev->next->next == NULL时

就相当于last->next = NULL;

此时prev就是倒数第二个节点

之后就是将最后一个节点释放掉。

尾删测试: 

 2.4链表的头删

我们先来看看头删函数的定义

//头删元素
void ListNodePopFront(ListNode** ps);

再来看看该功能的实现:

//头删元素
void ListNodePopFront(ListNode** ps)
{
	assert(ps && *ps);
	
		ListNode* tem = *ps;
		*ps = (*ps)->next;//考虑特殊的情况,若链表中只有一个元素,这一步就可以将头结点赋值为NULL,无需进行特殊的处理!!!
		free(tem);
		tem = NULL;
	
}

实现了链表的尾删,链表的前删就相较简单一些

这里我们只需要将原链表的第二个节点变为头结点,再将原链表的第一个节点释放了,就能实现单链表的头插功能!!

头删测试:

 接下来我们来实现我们的链表特定数据的查找

2.5链表特定数据的查找

先来看该函数的定义:

我们还是先来看这个函数的定义:

//特定元素的查找
ListNode* LIstNodeFind(ListNode* ps, NodeType x);

再来看看这个函数的实现逻辑:

//特定元素的查找

ListNode* LIstNodeFind(ListNode* ps, NodeType x)
{
	assert(ps);
	while (ps)
	{
		if (ps->x == x)
		{
			return ps;
		}
		ps = ps->next;
	}
	printf("要查找的数据不存在!!!\n");
	return NULL;
}

 想要找到特定元素的位置,就需要去遍历整个单链表,当找到是就返回该节点。

查找测试:

2.6在指定位置之前插入节点

我们先来看看这个函数的定义:

//在指定的位置之前插入数据
void ListNodeInsert(ListNode** ps, ListNode* pos, NodeType x);

由此我们可以结合上面实现的制定数据查找的函数使用。

我们来看看他的实现逻辑:

//在指定的位置之前插入数据
void ListNodeInsert(ListNode** ps, ListNode* pos, NodeType x)
{
	assert(ps && pos );
	ListNode* pcur = *ps;
	//当pos == *ps时就相当于头插节点
	if (pos == *ps)
	{
		ListNodePushFront(ps, x);
	}
	else
	{
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		if (pcur == NULL)
		{
			printf("找不到指定的位置!!\n");
			return ;
		}
		else
		{
			ListNode* tem = CreatNode(x);
			pcur->next = tem;
			tem->next = pos;
		}
	}
}

 在这里我们只需要注意一下特殊的情况:
当*ps == pos是就相当于头插数据,此时就可适当的调用我们已经实现的函数来完成我们代码,减小了代码的工程量。

在指定位置插入节点就相当于如图所示的过程:

指定位置之前插入测试:

 

2.7 在指定位置之后插入节点

首先我们还是先来看该函数的定义:

//在指定的位置之后插入的数据
void ListNodeInsertAfer(ListNode* pos, NodeType x);

这里为什么不在需要头结点的位置呢?

 

有图上可知:

关于指定位置的后插:只与pos节点和pos->next有关,所以就不再需要传输头结点了!! 

我们再来看函数的实现逻辑:

//在指定的位置之后插入的数据
void ListNodeInsertAfer(ListNode* pos, NodeType x)
{
	assert(pos);
	ListNode* node = CreatNode(x);
		ListNode* temp = pos->next;
		pos->next = node;
		node->next = temp;

}

指定位置数据后插测试:

2.8删除指定的节点

我门先来看看这个函数的声明:

//删除pos节点
void ListNodeErase(ListNode** ps,ListNode* pos);

再来看看这个函数的实现逻辑:

//删除pos节点
void ListNodeErase(ListNode** ps, ListNode* pos)
{
	assert(ps && pos && *ps);
	ListNode* pcur = *ps;
//这里相当于头删,可以使用我们之前实现过的头删函数
if(*ps == pos)
{
    ListNodePopFront(ps);
}
	else
{
    while (pcur->next != pos)
	{
		pcur = pcur->next;
	}
	pcur->next = pos->next;
	free(pos);
	pos = NULL;
}

其原理就如图所示:

 

删除指定位置的节点测试:

2.9链表的销毁

我们下来看该函数的定义:
 

//销毁该链表:
void ListDestroy(ListNode** ps);

实现该功能的逻辑其实就是遍历链表并将所有的节点都释放掉。
 逻辑的实现:

//销毁该链表:
void ListDestroy(ListNode** ps)
{
	assert(ps && *ps);
	ListNode* next = NULL;
	while (*ps)
	{
		next = (*ps)->next;
		free(*ps);
		*ps = next;
	}
	*ps = NULL;
}

销毁测试: 

3.完整代码的展示

3.1 test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

void Test1()
{
	ListNode* s = NULL;
	ListNodePushBack(&s, 1);
	ListNodePushBack(&s, 2);
  ListNodePushBack(&s, 3);
	ListNodePushBack(&s, 4);
	ListNodePushFront(&s, 0);
	ListNodePushFront(&s, -1);
	ListNodePrint(s);
	ListDestroy(&s);
	ListNodePrint(s);
	
}
int main()
{
	Test1();
	return 0;
}

 3.2 ListNode.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"



void DestroyList(ListNode* ps)
{
	assert(ps);
	while (ps)
	{
		ListNode* cur = ps;
		ps = ps->next;
		free(cur);
	} 
}


//实现单链表数据的尾插
ListNode* CreatNode(NodeType x)
{
	ListNode* temp = (ListNode*)malloc(sizeof(ListNode));
	if (temp == NULL)
	{
		perror("malloc");
		exit(1);
	}
	else
	{
		temp->next = NULL;
		temp->x = x;
		return temp;
	}
}
void ListNodePushBack(ListNode** ps, NodeType x)
{
	ListNode* temp = CreatNode(x);
	if (*ps == NULL)
	{
		*ps = temp;
	}
	else
	{
		ListNode* pcur = *ps;
		while (pcur->next != NULL)
		{
			pcur = pcur->next;

		}
		pcur->next = temp;
	}
}

void ListNodePrint(ListNode* s)
{
	while (s)
	{
		printf("%d->", s->x);
		s = s->next;
	}
	printf("NULL\n");
}


//前插元素
void ListNodePushFront(ListNode** ps, NodeType x)
{
	assert(ps);
	ListNode* temp = CreatNode(x);
	
		temp->next = *ps;
		*ps = temp;
	
}

//尾删元素
void ListNodePopBack(ListNode** ps)
{
	assert(ps && *ps);
	//处理只有一个节点的元素
	if ((*ps)->next == NULL)
	{
		free(*ps);
		*ps = NULL;
	}
	else
	{
		ListNode* prev = *ps;
		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = NULL;
	}
}

//头删元素
void ListNodePopFront(ListNode** ps)
{
	assert(ps && *ps);
	
		ListNode* tem = *ps;
		*ps = (*ps)->next;//考虑特殊的情况,若链表中只有一个元素,这一步就可以将头结点赋值为NULL,无需进行特殊的处理!!!
		free(tem);
		tem = NULL;
	
}


//特定元素的查找

ListNode* LIstNodeFind(ListNode* ps, NodeType x)
{
	assert(ps);
	while (ps)
	{
		if (ps->x == x)
		{
			return ps;
		}
		ps = ps->next;
	}
	printf("要查找的数据不存在!!!\n");
	return NULL;
}

//在指定的位置之前插入数据
void ListNodeInsert(ListNode** ps, ListNode* pos, NodeType x)
{
	assert(ps );
	ListNode* pcur = *ps;
	//当pos == *ps时就相当于头插节点
	if (pos == *ps)
	{
		ListNodePushFront(ps, x);
	}
	else
	{
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		if (pcur == NULL)
		{
			printf("找不到指定的位置!!\n");
			return ;
		}
		else
		{
			ListNode* tem = CreatNode(x);
			pcur->next = tem;
			tem->next = pos;
		}
	}
}

//在指定的位置之后插入的数据
void ListNodeInsertAfer(ListNode* pos, NodeType x)
{
	assert(pos);
	ListNode* node = CreatNode(x);
		ListNode* temp = pos->next;
		pos->next = node;
		node->next = temp;

}

//删除pos节点
void ListNodeErase(ListNode** ps, ListNode* pos)
{
	assert(ps && pos && *ps);
	ListNode* pcur = *ps;
	//当*ps == pos时,就相当于时删除头结点
	if (*ps == pos)
	{
		ListNodePopFront(ps);
	}
	else
	{
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		pcur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}


//销毁该链表:
void ListDestroy(ListNode** ps)
{
	assert(ps && *ps);
	ListNode* next = NULL;
	while (*ps)
	{
		next = (*ps)->next;
		free(*ps);
		*ps = next;
	}
	*ps = NULL;
}

3.3 List.h

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int NodeType;
typedef struct ListNode
{
	NodeType x;
	struct ListNode* next;
}ListNode;


//后面我们也要实现单链表的增删查改!!!

//首先实现单链表的初始化
void InitListNode(ListNode* ps);

//实现单链表的销毁
void DestroyList(ListNode* ps);


//实现单链表数据的尾插

void ListNodePushBack(ListNode** ps, NodeType x);

//打印单链表
void ListNodePrint(ListNode* s);

//前插元素
void ListNodePushFront(ListNode** ps, NodeType x);

//尾删元素
void ListNodePopBack(ListNode** ps);
//头删元素
void ListNodePopFront(ListNode** ps);

//特定元素的查找
ListNode* LIstNodeFind(ListNode* ps, NodeType x);

//在指定的位置之前插入数据
void ListNodeInsert(ListNode** ps, ListNode* pos, NodeType x);

//在指定的位置之后插入的数据
void ListNodeInsertAfer(ListNode* pos, NodeType x);

//删除pos节点
void ListNodeErase(ListNode** ps,ListNode* pos);


//销毁该链表:
void ListDestroy(ListNode** ps);

结语

好,今天的内容就到这里,感谢你的观看,如果喜欢我的内容还给个三连,若是有什么问题,也欢迎大家来评论区理性讨论,咱们下期再见,拜拜!!

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

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

相关文章

微服务实战系列之玩转Docker(一)

前言 话说计算机的“小型化”发展&#xff0c;历经了大型机、中型机直至微型机&#xff0c;贯穿了整个20世纪的下半叶。同样&#xff0c;伴随着计算机的各个发展阶段&#xff0c;如何做到“资源共享、资源节约”&#xff0c;也一直是一代又一代计算机人的不懈追求和历史使命。今…

cleanshot Mac 上的截图工具

笔者闲来无事&#xff0c;最近在找一些mac上好用的工具其中一款就是cleanShot。为什么不用原有的mac自带的呢。因为相对来说编辑功能不算全面&#xff0c;不支持长截图。那有没有一款软件支持关于截图的好用工具呢。 所以笔者找了这款。安装包是直接安装就可使用的。请大家点赞…

校验el-table中表单项

需求&#xff1a; 表格中每一行都有几个必填项&#xff0c;如用户提交时有未填的选项&#xff0c;将该选项标红且给出提示&#xff0c;类似el-form 的那种校验 el-table本身并没有校验的方法&#xff0c;而且每一行的输入框也是通过插槽来实现的&#xff0c;因此我们要自己跟…

VUE前端HTML静默打印(不弹出打印对话框)PDF简单方案

前言 在做打印功能的时候&#xff0c;以前大部分客户端都是用C#做的&#xff0c;静默打印&#xff08;也就是不弹出打印对话框&#xff09;比较简单。 但是使用浏览器作为客户端&#xff0c;静默打印&#xff08;也就是不弹出打印对话框&#xff09;做起来就比较困难。困难的…

LLM-阿里 DashVector + langchain self-querying retriever 优化 RAG 实践【Query 优化】

文章目录 前言self querying 简介代码实现总结 前言 现在比较流行的 RAG 检索就是通过大模型 embedding 算法将数据嵌入向量数据库中&#xff0c;然后在将用户的查询向量化&#xff0c;从向量数据库中召回相似性数据&#xff0c;构造成 context template, 放到 LLM 中进行查询…

css - - - - - 去除图片默认的白色背景(混合模式 mix-blend-mode)

去除图片默认的白色背景&#xff08;mix-blend-mode&#xff09; 1. 需求描述2. 原图展示3. 原代码展示4. 使用混合模式(mix-blend-mode)5.修改后效果 1. 需求描述 图片含有白色地图&#xff0c;想要将其去掉 2. 原图展示 3. 原代码展示 <div><img src*****/> &…

基于高德地图实现Android定位功能实现(二)

基于高德地图实现Android定位功能实现&#xff08;二&#xff09; 在实现的高德地图的基本显示后&#xff0c;我们需要不断完善地图的功能 地图界面设计&#xff08;悬浮按钮等&#xff09; 首先就是地图页面的布局&#xff0c;这个根据大家的实际需求进行设计即可&#xff…

无人机图像目标检测

本仓库是人工智能课程的课程作业仓库&#xff0c;主要是完成无人机图像目标检测的任务&#xff0c;我们对visdrone数据集进行了处理&#xff0c;在yolo和ssd两种框架下进行了训练和测试&#xff0c;并编写demo用于实时的无人机图像目标检测。 requirements依赖&#xff1a; ss…

数据结构之‘串’

目录 一. 串的定义二. 串的基本操作三. 串的存储结构3.1 顺序存储3.2 链式存储3.3 基于顺序存储的基本操作 \quad 一. 串的定义 \quad \quad \quad \quad 二. 串的基本操作 \quad \quad 三. 串的存储结构 \quad \quad 3.1 顺序存储 \quad 得一个一个遍历 结合方案一和方案二的优…

[React 进阶系列] useSyncExternalStore hook

[React 进阶系列] useSyncExternalStore hook 前情提要&#xff0c;包括 yup 的实现在这里&#xff1a;yup 基础使用以及 jest 测试 简单的提一下&#xff0c;需要实现的功能是&#xff1a; yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…

SpringBoot整合阿里云RocketMQ对接,商业版

1.需要阿里云开通商业版RocketMQ 普通消息新建普通主题,普通组,延迟消息新建延迟消息主题,延迟消息组 2.结构目录 3.引入依赖 <!--阿里云RocketMq整合--><dependency><groupId>com.aliyun.openservices</groupId><artifactId>ons-client</…

【C language】扫雷

目录 1.概要2.实现核心思想3.实现过程3.1游戏框架3.2游戏逻辑初始化棋盘 MineInit打印棋盘 MinePrint设置雷 SetMines扫雷 ClearMines 4.总结 1.概要 为了提高C初学者对C语言基本语法的掌控能力&#xff0c;一个极简版的扫雷游戏是十分适合锻炼代码能力的。下面分享仅用数组、…

14_Shell重定向输入输出

14_Shell重定向输入输出 输出重定向&#xff1a;一般情况&#xff0c;输出是在终端直接显示&#xff0c;改变输出位置&#xff0c;改变到文件中&#xff0c;这就是输出重定向 输入重定向&#xff1a;一般情况&#xff0c;输入是读取用户终端输入&#xff0c;改变输入位置&#…

026-GeoGebra中级篇-曲线(2)_极坐标曲线、参数化曲面、分段函数曲线、分形曲线、复数平面上的曲线、随机曲线、非线性动力系统的轨迹

除了参数曲线、隐式曲线和显式曲线之外&#xff0c;还有其他类型的曲线表示方法。本篇主要概述一下极坐标曲线、参数化曲面、分段函数曲线、分形曲线、复数平面上的曲线、随机曲线、和非线性动力系统的轨迹&#xff0c;可能没有那么深&#xff0c;可以先了解下。 目录 1. 极坐…

VScode:前端项目中yarn包的安装和使用

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端 输入 npm install -g yarn 再次输入以下…

IoT数据采集网关在企业应用中扮演的角色-天拓四方

随着物联网&#xff08;IoT&#xff09;技术的不断发展&#xff0c;越来越多的企业开始利用IoT技术实现智能化、自动化的生产和管理。在这个过程中&#xff0c;Iot数据采集网关作为连接物理世界与数字世界的桥梁&#xff0c;发挥着至关重要的作用。 IoT数据采集网关是一种硬件…

4.定时器

原理 时钟源&#xff1a;定时器是内部时钟源&#xff08;晶振&#xff09;&#xff0c;计数器是外部计时长度&#xff1a;对应TH TL计数器初值寄存器(高八位,低八位)对应的中断触发函数 中断源中断处理函数Timer0Timer0_Routine(void) interrupt 1Timer1Timer1_Routine(void) …

c++初阶知识——类和对象(中)

目录 1.类的默认成员函数 2.构造函数 3.析构函数 4.拷贝构造函数 5.运算符重载 5.1 赋值运算符重载 5.2 使用运算符重载等特性实现日期类 6.取地址运算符重载 6.1 const成员函数 6.2 取地址运算符重载 1.类的默认成员函数 默认成员函数就是⽤⼾没有显式实现&#…

网站开发:使用VScode安装yarn包和运行前端项目

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端 输入 npm install -g yarn 再次输入以…

防火墙双机热备带宽管理综合实验

拓扑图和要求如下&#xff1a; 之前的步骤可以去到上次的实验 1.步骤一&#xff1a; 首先在FW3防火墙上配置接口IP地址&#xff0c;划分区域 创建心跳线&#xff1a; 下面进行双机热备配置&#xff1a; 步骤二&#xff1a; 先将心跳线连接起来 注意&#xff1a;一定要将心跳…