线性表之单链表~

article/2025/9/20 8:58:38

这次来介绍一下数据结构里面的单链表。

这是一个简单的链表,它是单向的,没有(哨兵位置)头结点,不循环,有一个头指针指向第一个节点。

和上次介绍线性表一样,我们同样研究它的 增,删,查,改。

目录

1.什么是链表

 2.链表的功能 

2.1 基本功能

1)打印链表

2)创建新结点

3)获取链表长度

 4)查找节点

2.2增加元素

1)头部增加

2)尾部增加

3)中间增加

2.3删除元素

1)头部删除   

2)尾部删除   

 2.4修改元素

2.5销毁链表

测试

1.增加元素的测试

2.删除元素测试

3.修改元素测试


1.什么是链表

首先,链表是线性表的一种,它的存储方式是链式存储。链表是由一个个节点组成,连接每个节点的关键是指针,  每一个节点,我们用结构体实现。

代码如下:

typedef int SLTDataType;//int类型的重定义
//用结构体创建一个节点
typedef struct SListNode
{SLTDataType data;//节点的数据域SListNode *next;//结点的指针域
}SLT;

SLTDataType是int类型的重定义, next 是一个结构体指针,负责存放下一个节点的地址。

我们看一下物理存储结构和逻辑结构:

如图所示:

我们图中有四个节点,1 、2、3、4,节点的前边存的是一个整型数据,后边存的是一个地址。

 这个地址就是后一个节点的地址,最后一个节点,也就是尾结点存的是NULL。

再看看看逻辑结构,对于指针,指针存放的地址,我们认为指针指向这片内存块,所以我们用箭头来表示节点与节点之间的关系,事实上并没有这个箭头,这只是方便我们理解。

链表的结构了解之后,我们来看实现的功能。


 2.链表的功能

1.基本功能1)打印链表2)创建新节点3)获取链表长度 4)查找节点
2.增加元素:1)头部增加  2)尾部增加  3)中间增加
3.删除元素:  1)头部删除     2)尾部删除   3)中间删除
4.修改元素:找到某个元素,把它修改掉        
5.销毁顺序表


2.1 基本功能

1)打印链表

打印链表就是,遍历整个链表,把每个节点存的数据输出出来即可。

代码如下:

void SListPrint( SLT *&phead)
{SLT *cur = phead;if (phead == NULL){cout << "链表为空,无法打印" << endl;return;}while (cur){cout << cur->data << "->";cur = cur->next;}cout << "NULL" << endl;
}

形参是对头指针的引用,如果不了解这个知识的话,这里就把它当做我们头指针的一个别名,当成我们的头指针就行了,它存的是第一个节点的地址。

我们用cur指针来遍历链表,首先,如果链表为空,即我们的头指针存的是NULL,不打印,直接return。链表不为空,一个一个打印。

关键的代码:

cur = cur->next; 

我们用这种写法,把我们下一个节点的地址在上一个节点打印完成后,赋给我们的cur,接下来打印下一个数据。

最后,当cur打印完最后一个数据的时候,前面提到,最后一个节点存的是NULL,把它赋给cur,它为空,然后判断循环,退出循环,打印完毕。

打印效果如下:

2)创建新结点

新结点的创建,就是动态申请一块内存区域,并给它初始化。

代码如下:

SLT *BuyNewNode(SLTDataType x)//创建新的节点
{SLT *newnode = new SLT;//new一个结构体大小的空间
//返回值是结构体指针,用newnode来接收newnode->data = x;//给data赋值newnode->next = NULL;//把里面的指针域置为空return newnode;//返回创造节点的地址
//不了解new的话,用malloc来创建效果是一样的
//SLT *newnode=(SLT *)malloc(sizeof(SLT));
//newnode->data=x;
//return newnode;

如果不了解的new的用法,用注释里面的malloc来实现也是完全OK的。

3)获取链表长度

这个功能是来辅助后边的功能的,比较简单,直接上代码:

SLTDataType SListLength(SLT *&phead)
{if (phead == NULL){return 0;//链表为空返回0}else{SLTDataType count = 0;//count来记录节点个数SLT *cur = phead;//cur来遍历链表while (cur){count++;cur = cur->next;}return count;//返回链表个数}
}

 4)查找节点

这个查找结点在后边的功能中是要用到的,所以我们先来介绍一下。

我们的单链表有一个特点,就如它的名字一样,单向的,有些功能不做特殊处理,实现起来很尴尬。就比如我们的在中间某个节点前面插入数据,如果仅仅找找到这个节点,把新节点放在那里,我们发现,很尴尬,不知道前一个节点的地址,没法通过找到的那个节点来确定前边节点,这也是它的一个缺点,所以,干脆点,我们就找待找节点前面一个位置的节点,这个问题就解决了。

所以,代码如下:

SLT * NodeFind(SLT *&phead, SLTDataType i)//查找某个节点,返回前一个位置的地址
{SLTDataType len = SListLength(phead);if (i<0 || i>len){cout << "输入的节点不合法!!" << endl;return NULL;}SLTDataType pos = 1;SLT *cur = phead;while (pos<i-1){pos++;cur = cur->next;}return cur;
}

这里我们先判断输入的节点是否合理,前面说的获取链表长度的函数就有用了。为了找到目标节点前一个位置的节点,我们最关心的就是循环的次数,什么时候算找到。其实当我们限定了节点的输入范围,循环结束后,肯定是能找到它的。

看我们的循环条件,i-1,i是1或2的时候,我们返回的都是首节点的地址,1的时候是头插,比较特特殊,2的时候正常,就是第二个节点前边的节点,也就是首节点地址。

其它情况,比如,我们i为6,i-1为5,从1到5(不包括5),刚好四次,找到了第五个节点,即找到第六个节点前边的节点。

这样,查找功能就实现了。

单链表这样找前一个节点,看上去有点麻烦,后边会介绍双链表,它的查找直接找目标节点就可以了。


2.2增加元素

1)头部增加

在链表的头部增加节点。

思路是:让新的头结点指向旧的头结点,头指针指向新的头结点即可。

代码如下:

void SListPushFront(SLT *&phead, SLTDataType x)//头插
{SLT *newnode = BuyNewNode(x);//创建新节点newnode->next = phead;//新的首节点 指向旧的首节点phead = newnode;//头节点指向新的首节点
}

2)尾部增加

在链表尾部增加节点。

 思路是:找到尾,连接新节点。

考虑,如果链表为空,这个时候的尾部增加,就是头插。

代码如下:

void SListPushBack(SLT *&phead, SLTDataType x)//尾插
{SLT *newnode = BuyNewNode(x);if (phead == NULL)//链表为空的尾插就是头插{phead = newnode;}else//不为空,单链表的尾插就要找尾节点{SLT *tail = phead;while (tail->next != NULL){tail = tail->next;//这种类型的表达是链表最常用写法}                     //这种写法让指针在链表中移动起来//退出循环,tail就在尾部tail->next = newnode;//新节点接在尾部}
}

while(tail->next != NULL,看这个循环条件,我们要找到的是尾节点,我们判断tail当前位置的下一个位置是否为空,如果为空,那么它就是尾节点,我们退出循环。

但是while(tail != NULL),如果这样写,会出现很尴尬的情况,就是我们的tail最后会指向空,而不是刚好停留在尾节点上。

3)中间增加

中间增加,就是找到目标节点前面的节点,在它的后边增加即可,这时候,前面写的查找函数就用上了。

代码如下:

void SListInsert(SLT *&phead, SLTDataType x,SLTDataType pos)//任意位置插入(前边)
{if (phead == NULL){cout << "链表为空,无法插入" << endl;return;}else{if (pos == (SListLength(phead) + 1))//特殊情况,尾插{SListPushBack(phead, x);}else{SLT *temp = NodeFind(phead, pos);if (temp == phead&&pos==1)//特殊情况,pos为1时,返回的是首节点的地址,这时相当于头插{SListPushFront(phead, x);}else{SLT *newnode = BuyNewNode(x);SLT *cur = temp->next;temp->next = newnode;newnode->next = cur;}}}
}

看代码,因为我们的插入是向某个节点的前面插入,所以当链表为空的时候,我们直接退出不让插入。

不为空的时候

第一考虑尾插,因为我们的插入是向某个节点的前面插入,所以尾插很尴尬,我们只能单独判断用户输入的节点,调用尾插函数。

第二考虑头插,就是前面我们提到的,查找函数返回值是首元素地址的时候的一种情况。

看一般情况:

               {SLT *newnode = BuyNewNode(x);//创建新节点SLT *cur = temp->next;//记录目标节点temp->next = newnode;//目标节点前一个节点指向新节点newnode->next = cur;//新节点指向目标节点}

我们先创建一个新节点,然后我们通过SLT *cur = temp->next; 来保存我们想要找的节点,因为temp存的是目标节点前一个位置的地址,然后我们就把它们连接起来就好了。


2.3删除元素

1)头部删除   

这个比较简单, 因为我们的头指针指向的就是首节点,但要注意的是,删除之前,要把首节点后边的一个节点的地址给头指针,不然直接释放它,就找不到后边的节点了。

代码如下:

void SListPopFront(SLT *&phead)//头删
{if (phead == NULL){cout << "链表为空,无法删除!!" << endl;return;}else{SLT *second = phead->next;delete phead;//free(phead);phead = second;//两种写法都可以//SLT *first = phead;//SLT *second = first->next;//phead = second;//delete first;free(first)}
}

首先,链表为空,不删除。

其次就是正常的释放空间了,上面给了两种写法,其实是一样的,只不过第二种是把phead的值作了传递,最后还是释放首节点,效果是一样的,只是或许看上去会更好理解一点。

delete不太熟悉,就用free,但是new必须和delete搭配,malloc和free必须搭配,切不可混着用。

2)尾部删除   

尾部删除就得找尾,单链表找尾,我们选择的是遍历找尾。

代码如下:

void SListPopBack(SLT *&phead)//尾删
{if (phead == NULL){cout << "链表为空,无法删除!!" << endl;}else if (phead->next==NULL)//特殊情况,只有一个节点{delete phead;phead = NULL;}else//一般情况{SLT *tail = phead;//找尾SLT *cur = NULL;//记录tail前一个节点while (tail->next != NULL){cur = tail;tail = tail->next;}cur->next = NULL;//cur即新的尾,尾要指向空delete tail;//free(tail);}
}

首先链表为空,我们不删。

我们先考虑一般情况,tail是用来找尾的,cur是用来找新的尾节点,while (tail->next != NULL)这循环条件,最终tail会停到尾节点上,cur刚好停在尾结点前面一个节点上,即新的尾节点。我们找到尾结点后,释放它,再让新的尾结点指向空,就完成了。

再说特殊情况,假如我们的链表只有一个节点,还按照一般情况处理,就会出问题。

这个时候,tail->next是为空的,循环不会进去,cur也是空,再写cur->next,就不合适了,对NULL进行->操作,也就是解引用操作是不可以的。

所以,我们要单独考虑,即直接释放第一个节点,在让头指针指向空就好了。

3)中间删除

找到它,释放掉,连接它前后的节点。

代码如下:

void SListDelete(SLT *&phead, SLTDataType pos)//任意位置删除
{if (phead == NULL){cout << "链表为空,无法删除!!" << endl;return;}else{SLT *temp = NodeFind(phead, pos);//删除位置前一个节点的地址if (temp == phead&&pos==1)//特殊情况,头删{SListPopFront(phead);return;}else{SLT *cur = temp->next;//要删除的节点SLT *next = temp->next->next;//我们还要找到删除节点后一个节点temp->next = next;//连接前后节点delete cur;}}
}

同样的,链表为空,我们不删。再来,特殊情况,pos为1那么就是头删,其它情况。我们temp找的是待删节点前面的一个,所以为了找到后边的节点,我们必须写出这样的代码SLT *next = temp->next->next,cur记录要删的节点,链接完成之后,释放cur指向的节点就好了。

不知道大家有没有疑惑,为什么找节点,这么麻烦,直接根据数据元素找不就行了,但你有没有想过,数据元素是可以重复的,那么这样你只能找到第一个相同的元素,找不到后边相同的元素。

而且为啥非得找前一个,直接找它不就好了,这个刚刚我也提到了,这就是单链表的一个缺点,在之后的双链表中,这个问题可以很好的解决。


 2.4修改元素

这个简单,明白我们如何查找之后,修改它就很容易实现了。

代码如下:

void SListModify(SLT *&phead, SLTDataType pos, SLTDataType y)
{if (phead == NULL){cout << "链表为空,无法修改" << endl;return;}else{SLT *temp = NodeFind(phead, pos);if (temp == phead&&pos == 1)//特殊情况,修改第一个元素{phead->data = y;}else{SLT *cur = temp->next;//记录要修改的节点cur->data = y;//修改它的值}}
}

首先,链表为空,不修改。

其次,特殊情况,修改第一个节点的值单独考虑,再来,其它的情况,就通过temp找到它,用cur来修改就好了。


最后,销毁链表~~

2.5销毁链表

链表的销毁也很简单,但要注意的是,销毁前一个节点之前,要把后一个节点的地址记录一下,然后用循环一次销毁就好了。

代码如下:
 

void SListDestory(SLT *&phead)//销毁链表
{if (phead == NULL){return;//链表为空,没有需要销毁的节点,直接推出}else{   SLT *cur = phead;//要释放的节点SLT *next = NULL;while (cur){next = cur->next;//记录下一个要释放的节点delete cur;//释放当前节点cur = next;//后节点传递}phead=cur = next = NULL;}
}

首先,链表为空,就不用销毁了,也不用头指针置为空了。

再来,分析过程,我们知道,next是指向cur后边一个的节点,那么当我们的cur成功释放掉最后一个节点时,next是指向NULL的,那么赋值后,cur为空,那么循环就结束了。

最后,把指针都置为空,这个功能就实现了。

以上就是,我们单链表的常用功能!!!


老规矩,接下来,我们做测试!!!

测试

1.增加元素的测试

代码如下:

void test03()
{SLT *phead = NULL;int i = 0;for (i = 0; i < 5; i++){SListPushBack(phead, i + 1);}cout << "初始状态,链表值为:" << endl;SListPrint(phead);cout << endl;//头插区SListPushFront(phead, 450);cout << "头插450后,链表值为:" << endl;SListPrint(phead);SListPushFront(phead, 500);cout << "头插500后,链表值为:" << endl;SListPrint(phead);//-------------////尾插区SListPushBack(phead, 300);cout << "尾插300后,链表值为:" << endl;SListPrint(phead);SListPushBack(phead, 700);cout << "尾插700后,链表值为:" << endl;SListPrint(phead);//-------------////中间插SListInsert(phead, 100, 5);//一般情况cout << "第5个位置插入100后,链表值为:" << endl;SListPrint(phead);SListInsert(phead, 74, 1);//头插cout << "用Insert头插后,链表值为:" << endl;SListPrint(phead);SListInsert(phead, 600, 2);//第二个位置插入cout << "第2个位置插入600后,链表值为:" << endl;SListPrint(phead);SListInsert(phead, 780, SListLength(phead) + 1);//尾插cout << "用Insert尾插780后,链表值为:" << endl;SListPrint(phead);//-------------//
}

运行结果如下:

2.删除元素测试

代码如下:

void test04()
{SLT *phead = NULL;int i = 0;for (i = 0; i < 10; i++){SListPushBack(phead, i + 1);}cout << "初始状态,链表值为:" << endl;SListPrint(phead);cout << endl;//头删区SListPopFront(phead);cout << "头删 1 后,链表的值为" << endl;SListPrint(phead);cout << endl;//尾删区SListPopBack(phead);cout << "尾删10后,链表的值为:" << endl;SListPrint(phead);cout << endl;//中间删SListDelete(phead, 1);cout << "用Delete头删 2 后,链表的值为:" << endl;SListPrint(phead);cout << endl;SListDelete(phead, 2);cout << "用Delete 删 4 后,链表的值为:" << endl;SListPrint(phead);cout << endl;SListDelete(phead, SListLength(phead));//尾删cout << "用Delete尾删 9 后,链表的值为:" << endl;SListPrint(phead);cout << endl;
//-------------//
}

运行结果如下:

3.修改元素测试

代码如下:

void test05()
{SLT *phead = NULL;int i = 0;for (i = 0; i < 10; i++){SListPushBack(phead, i + 1);}cout << "初始状态,链表值为:" << endl;SListPrint(phead);cout << endl;SListModify(phead, 1, 30);cout << "用Modify修改把第1个节点的数据修改为30后,链表的值为:" << endl;SListPrint(phead);cout << endl;SListModify(phead, 2, 45);cout << "用Modify修改把第2个节点的数据修改为45后,链表的值为:" << endl;SListPrint(phead);cout << endl;SListModify(phead, 6, 78);cout << "用Modify修改把第6个节点的数据修改为78后,链表的值为:" << endl;SListPrint(phead);cout << endl;SListModify(phead,SListLength(phead), 100);cout << "用Modify修改把尾节点的数据修改为100后,链表的值为:" << endl;SListPrint(phead);cout << endl;SListDestory(phead);
}

 运行结果:

 销毁也在这里测试了。

测试结果很好!!!


综上,就是我们单链表的全部内容啦~~

创作不易,如果帮到你的话,博主在这里求一个三连~~~

有什么问题,欢迎大佬点评~~~

如果需要源代码,可以去博主的Gitee的仓库自取哦~~

链接在这:

琦琦的日常代码: 平时做的程序 (gitee.com)https://gitee.com/qiqi-loves-to-knock-code/qiqis-daily-code


那么,我们下期再见~~~ 


http://chatgpt.dhexx.cn/article/YU655Kpy.shtml

相关文章

链表(Linked List)----单链表

链表是有序的列表,但是它在内存中是存储如下的: 链表是以节点的方式存储的,链式存储每个节点包含data域,next域指向下一个节点如上图:链表的各个节点不一定是连续存放的.链表分带头节点的链表和没有头节点的链表(根据实际的需求来确定) 使用带head头的单项链表实现-水浒英雄排行…

单链表的插入与删除

链表是数据结构中的一种线性结构&#xff0c;表示数据运算之间的一种抽象关系。 1、单链表的结构如下&#xff1a; typedef struct node{datatype data; //数据struct node *next; //指向下一个节点的指针 }linklist_s; 其中包括一个数据域和一个指针域&#xff0c;向单链表…

详解单链表(内有精美图示哦)

全文目录 引言链表链表的定义与结构链表的分类 单链表的实现及对数据的操作单链表的创建与销毁创建销毁 单链表的打印单链表的头插与头删头插头删 单链表的尾插与尾删尾插尾删 单链表的查找单链表在pos位置后插入/删除插入删除 单链表在pos位置插入/删除插入删除 总结 引言 在…

【数据结构】单链表(超全)

目录 一、什么是链表&#xff1f;1.1 定义1.2 链表的分类 二、无头单向非循环链表2.1 结构2.2 如何遍历链表数据2.3 尾插2.4 创建新节点2.5 头插2.6 尾删2.7 头删2.8 单链表查找2.9 在pos位置之前插入2.10 删除pos位置数据2.11 在pos位置的后面插入2.12 删除pos位置后面的数据 …

数据结构之单链表的插入

单链表的完整的代码在这篇文章下面&#xff0c;链接&#xff1a; https://blog.csdn.net/six_teen/article/details/113253545 什么是链表&#xff1f; 如上图&#xff0c;链表和生活中的链条很相似&#xff0c;链表在逻辑结构上是以链条的形式连接而成的&#xff0c;但物理结构…

单链表的定义,插入与删除,查找,建立。

链表分为&#xff1a;单链表&#xff0c;双链表&#xff0c;循环链表&#xff0c;静态链表 一&#xff0c;单链表的定义 在内存空间中&#xff0c;各个节点在逻辑上相邻&#xff0c;但在物理上不相邻。 在单个的结点内部需要存放 数据域 和 指针域&#xff08;存放指向下一个…

单链表的定义

前言 在前面的文章中&#xff0c;我们系统的介绍了线性表的顺序存储实现——顺序表。紧接着我们要介绍线性表的链式存储实现——链表。而链表中又有许多的链表&#xff1a; 单链表双链表循环链表静态链表 这一篇文章中&#xff0c;我们先来介绍单链表。 单链表的定义 什么…

(c语言)详解单链表

1&#xff1a;什么是单链表。 我们知道顺序表底层原理其实就是一块可以自由控制大小的数组&#xff0c;顺序表可以实现在任何地方进行插入一个数据&#xff0c;如果顺序表的缺点在于如果要在起始位置插入一个数据就要把后面的每一个数据都往后挪&#xff0c;这样会大量消耗我们…

双向链表中插入元素的几种方式

dnode的结构如下&#xff1a;由前驱prior指针、后继next指针以及数据data&#xff0c;现需要在A、B节点中间插入C节点&#xff0c;给出了A的地址&#xff0c;以及C的地址。 1、利用将链表拆分然后插入方式进行&#xff1a; 先将节点C完全插入到B的前面&#xff0c;再将A指向C&…

单链表的使用方法.数据结构(三)[上]

前言 提示&#xff1a;文本为数据解构(三)单链表&#xff1a; 本文具体讲解单链表的具体使用方法 提示&#xff1a;以下是本篇文 系列文章目录 第一章 数据解构(一) 第二章 顺序表的具体使用方法.数据解构(二) 文章目录 前言 系列文章目录 文章目录 一、单链表视图 二、…

Linux设置ssh免密登录

目录 1.在/root目录下输入命令 2.进入.ssh目录 3.将公钥id_rsa.pub写入到一个认证文件夹中 4.开启远程免密登录配置 5.免密远程登录本机 1.在/root目录下输入命令 [rootlocalhost ~]# ssh-keygen -t rsa -P "" Generating public/private rsa key pair. Enter …

Linux SSH 免密登录

Linux SSH 免密登录 本篇我们来 看看 Linux 的免密登录的原理 以及实际操作一番 概述 什么是 Linux SSH 免密登录&#xff0c;我觉得大家应该都 多少听过 或者操作过&#xff0c;那你真的理解整个免密登录的过程吗&#xff1f; Linux SSH 免密登录 就是 可以不输入密码 就可以…

SSH登录和SSH免密登录

在了解ssh的时候产生了概念混淆&#xff0c;发现ssh登录和ssh免密登录是两码事。 可以从目的和过程对比这两个概念&#xff1a; 1.目的 1.1 SSH登录 简单来说就是&#xff1a;建立客户端和服务器之间安全的远程连接&#xff0c;登录远程服务器&#xff0c;以访问文件系统 。…

VSCode——SSH免密登录

文章目录 本地PC端&#xff08;一般为Windows&#xff09;1. 检查自己是否已经生成公钥2. 配置VScode的SSH config 远程服务器端1. 服务器新建授权文件2. 赋权限3. 重启远程服务器的ssh服务 最全步骤&#xff1a;【设置ssh免密不起作用&#xff1f;彻底搞懂密钥】vscode在remot…

SSH免密登录功能配置

文章目录 一、SSH免密登录功能配置&#xff08;1&#xff09;master虚拟机免密登录master虚拟机&#xff08;2&#xff09;将公钥拷贝到所创建的虚拟机上 一、SSH免密登录功能配置 ssh密钥登录比密码登录安全&#xff0c;主要是因为他使用了非对称加密&#xff0c;登录过程中需…

CentOS开启SSH免密登录

CentOS开启SSH免密登录 要实现SSH免密登录&#xff0c;首先需要准备一组公钥和私钥。将公钥放到服务器上&#xff0c;将私钥放到客户机上。当客户机连接服务器时&#xff0c;服务器会根据自身的公钥校验客户机的私钥&#xff0c;如果校验通过则允许连接。 一、创建密钥 在客…

Ubuntu开启SSH免密登录

Ubuntu开启SSH免密登录 要实现SSH免密登录&#xff0c;首先需要准备一组公钥和私钥。将公钥放到服务器上&#xff0c;将私钥放到客户机上。当客户机连接服务器时&#xff0c;服务器会根据自身的公钥校验客户机的私钥&#xff0c;如果校验通过则允许连接。 一、创建密钥 在客…

git SSH免密登录

git系列文章目录 第八章 git SSH免密登录的使用 文章目录 git系列文章目录前言一、生成密钥二、使用步骤1.使用VSCODE打开.pub文件复制其中的内容2.打开github或者gitee进入设置选项设置密钥3.使用密钥 总结 前言 虽然Windows系统提供了凭据功能&#xff0c;但是介绍ssh提交&a…

配置SSH免密登录

配置SSH免密登录 1. 在各个虚拟机&#xff08;master、s1、s2&#xff09;家目录执行ssh-keygen -b 1024 -t rsa&#xff0c;输入2次回车&#xff0c;输入y/yes再继续回车 ssh-keygen -b 1024 -t rsa 2. 进入到.ssh目录中 ls -all #查看所有文件和文件夹 cd .ssh 查看目录 ls…

SSH免密登录原理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 SSH免密登录原理 前言一、配置ssh二、无密钥配置1、免密登录原理2、生成公钥和私钥3、将公钥拷贝到要免密登录的目标机器上 三、.ssh文件夹下&#xff08;~/.ssh&#xff09;…