(JAVA)贪心算法、加权有向图与求得最短路径的基本论述与实现

1. 贪心算法

1.1 贪心算法的概述:

贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术

贪心算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,省去了为找最优解要穷尽所有可能而必须耗费的大量时间。

贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解。

虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪心算法不要回溯

1.2 贪心算法适用的问题

贪心策略的前提是:局部最优策略能导致产生全局最优解

实际上,贪心算法使用的情况比较少,一般对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析可以做出判断。

1.3 贪心算法的实现框架

从问题的某一初始解出发:

while (能朝给定总目标前进一步)
{ 利用可行的决策,求出可行解的一个解元素;
}

所有解元素组合成问题的一个可行解。

1.4 贪心策略的选择

用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此一定要注意判断问题是否适合采用贪心算法策略,找到解是否一定是问题的最优解。

1.5 例题分析

1.5.1 分糖果问题

n个小朋友玩完游戏后,老师准备给他们发糖果;每个人有一个分数a[i],如果比左右的人分数高,那么糖果也要比左右的多,并且每个小朋友至少有一颗。问老师最少准备多少糖果?

这个题目不能直接用动态规划去解,比如用dp[i]表示前i个人需要的最少糖果数。
因为(前i个人的最少糖果数)这种状态表示会收到第i+1个人的影响,如果a[i]>a[i+1],那么第i个人应该比第i+1个人多。即是这种状态表示不具备无后效性。
如果是我们分配糖果,我们应该怎么分配?

  • 答案是:从分数最低的开始。
    按照分数排序,从最低开始分,每次判断是否比左右的分数高。

假设每个人分c[i]个糖果,那么对于第i个人有c[i]=max(c[i-1],c[c+1])+1;

(c[i]默认为0,如果在计算i的时候,c[i-1]为0,表示i-1的分数比i高)

但是,这样解决的时间复杂度为O(NLogN),主要瓶颈是在排序。如果提交,会得到Time Limit Exceeded的提示。

  • 因此我们需要对贪心的策略进行优化:
    我们把左右两种情况分开看。
    • 如果只考虑比左边的人分数高时,容易得到策略:
      从左到右遍历,如果a[i]>a[i-1],则有c[i]=c[i-1]+1;否则c[i]=1。
    • 再考虑比右边的人分数高时,此时我们要从数组的最右边,向左开始遍历:
      如果a[i]>a[i+1], 则有c[i]=c[i+1]+1;否则c[i]不变;
  • 这样讲过两次遍历,我们可以得到一个分配方案,并且时间复杂度是O(N)

1.5.2 小船过河问题

n个人要过河,但是只有一艘船;船每次只能做两个人,每个人有一个单独坐船的过河时间a[i],如果两个人(x和y)一起坐船,那过河时间为a[x]和a[y]的较大值。问最短需要多少时间,才能把所有人送过河?

题目给出关键信息:1、两个人过河,耗时为较长的时间;
还有隐藏的信息:2、两个人过河后,需要有一个人把船开回去;
要保证总时间尽可能小,这里有两个关键原则:应该使得两个人时间差尽可能小(减少浪费),同时船回去的时间也尽可能小(减少等待)。
先不考虑空船回来的情况,如果有无限多的船,那么应该怎么分配?

  • 答案:每次从剩下的人选择耗时最长的人,再选择与他耗时最接近的人。

再考虑只有一条船的情况,假设有A/B/C三个人,并且耗时A<B<C。
那么最快的方案是:A+B去, A回;A+C去;总耗时是A+B+C。(因为A是最快的,让其他人来回时间只会更长,减少等待的原则
如果有A/B/C/D四个人,且耗时A<B<C<D,这时有两种方案:
1、最快的来回送人方式,A+B去;A回;A+C去,A回;A+D去; 总耗时是B+C+D+2A (减少等待原则)
2、最快和次快一起送人方式,A+B先去,A回;C+D去,B回;A+B去;总耗时是 3B+D+A (减少浪费原则)
对比方案1、2的选择,我们发现差别仅在A+C和2B;

  • 为何方案1、2差别里没有D?

因为D最终一定要过河,且耗时一定为D。

如果有A/B/C/D/E 5个人,且耗时A<B<C<D<E,这时如何抉择?
仍是从最慢的E看。(参考我们无限多船的情况)

  1. 方案1,减少等待;先送E过去,然后接着考虑四个人的情况;
  2. 方案2,减少浪费;先送E/D过去,然后接着考虑A/B/C三个人的情况;(4人的时候的方案2)

到5个人的时候,我们已经明显发了一个特点:问题是重复,且可以由子问题去解决。

根据5个人的情况,我们可以推出状态转移方程

  • dp[i] = min(dp[i - 1] + a[i] + a[1], dp[i - 2] + a[2] + a[1] + a[i] + a[2]);

再根据我们考虑的1、2、3、4个人的情况,我们分别可以算出dp[i]的初始化值:

dp[1] = a[1];
dp[2] = a[2];
dp[3] = a[2]+a[1]+a[3];
dp[4] = min(dp[3] + a[4] + a[1], dp[2]+a[2]+a[1]+a[4]+a[2]);

由上述的状态转移方程和初始化值,我们可以推出dp[n]的值。

1.5.3 背包问题

问题描述 有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

在这里插入图片描述

**问题分析: **

  1. 目标函数: ∑pi最大,使得装入背包中的所有物品pi的价值加起来最大。

  2. 约束条件:装入的物品总重量不超过背包容量:∑wi<=M( M=150)

  3. 贪心策略:

    • 选择价值最大的物品
    • 选择价值最大的物品
    • 选择单位重量价值最大的物品

有三个物品A,B,C,其重量分别为{30,10,20},价值分别为{60,30,80},背包的容量为50,

  • 分别应用三种贪心策略装入背包的物品和获得的价值如下图所示:

在这里插入图片描述

算法的三种策略:

  • 算法设计:
  1. 计算出每个物品单位重量的价值
  2. 按单位价值从大到小将物品排序
  3. 根据背包当前所剩容量选取物品
  4. 如果背包的容量大于当前物品的重量,那么就将当前物品装进去。否则,那么就将当前物品舍去,然后跳出循环结束。

2. 加权有向图

与加权无向图不同的时。这种图的特点是边有方向性,即边从一个顶点指向另一个顶点,并且每条边都有一个与之相关的权重,这个权重通常代表了从一点到另一点的成本或距离。

2.1 加权有向图边的表示

2.1.1 API设计

类名DirectedEdge
构造方法DirectedEdge(int v,int w,double weight):通过顶点v和w,以及权重weight值构造一个边对象
成员方法1. public double weight():获取边的权重值
2. public int from():获取有向边的起点
3. public int to():获取有向边的重点
成员变量1. private final int V:起点
2. private final int W:终点
3. private final double weight:当前边的权重

2.2 加权有向图的实现

2.2.1 API 设计

类名EdgeWeightedDigraph
构造方法EdgeWeightedDigraph(DirectedEdge V):创建一个包含V个顶点但不包含边的有向图
成员方法1. public int V():获取图中顶点的数量
2. public int E():获取图中边的数量
3. public void addEdge(DirectedEdge e):向有向图中添加一条边e
4. public Queue<DirectedEdge> adj(int v):获取由v指出的边所连接的所有顶点
5. public Queue<DirectedEdge> edges():获取加权有向图的所有边
成员变量1. private final int V:记录顶点数量
2. private int E:记录边数量
3. private Queue<DirectedEdge>[] adj:邻接表

2.2.2 代码实现

package com.renecdemo.weighted;import com.renecdemo.graph.Queue;/*** 加权有向图*/
public class EdgeWeightedDigraph {private final int V;// 记录顶点数量private int E;// 记录边数量private Queue<DirectedEdge>[] adj;// 邻接表public EdgeWeightedDigraph(int v) {this.V = v;this.E = 0;this.adj = new Queue[V];for (int i = 0; i < adj.length; i++) {adj[i] = new Queue<>();}}public int V(){return V;}public int E(){return E;}/*** 向有向图中添加一条边e* @param e*/public void addEdge(DirectedEdge e){// 因为e是有方向的,所以只需要让e出现在起点的邻接表中即可int v = e.from();adj[v].enqueue(e);E++;}/*** 获取由 v顶点 指出的边所连接的所有顶点 - 返回该顶点所在的邻接表即可* @param v 顶点* @return*/public Queue<DirectedEdge> adj(int v){return adj[v];}/*** 获取加权有向图的所有边* @return*/public Queue<DirectedEdge> edges(){// 遍历图中的每一个顶点Queue<DirectedEdge> allEdges = new Queue<>();for (int v = 0; v < V; v++) {for (DirectedEdge e : adj[v]) {allEdges.enqueue(e);}}return allEdges;}}

3. 最短路径

有了加权有向图之后,我们立刻就可以联想到在实际生活汇总的使用场景,例如在一副地图中,找到顶点a和顶点b之间的路径,这条路径可以是距离最短,也可以是时间最短,也可以是费用最小等,如果我们把距离/时间/费用看作是成本,那么就需要找到顶点a和顶点b之间成本最小的路径。

3.1 最短路径的 定义以及性质

3.1.1 定义:

  • 在一副加权有向图中,从顶点s到顶点t的最短路径是所有从顶点s到顶点t的路径中总权重最小的那条路径

在这里插入图片描述

3.1.2 性质:

  1. 路径具有方向性
  2. 权重不一定等价于距离,权重可以是距离、时间、花费等内容,权重最小指的是成本最低
  3. 只考虑连通图,一副图中并不是所有的顶点都是可达的,如果s和t不可达,那么它们之间也就不存在最短路径。为了简化问题,这里只考虑连通图
  4. 最短路径不一定是唯一的。从一个顶点到达另一个顶点的权重最小的路径可能会有很多条,这里只需要找出一条即可

3.1.3 最短路径树

给定一副加权有向图和一个顶点s,以s为起点的一颗最短路径树是图的一副子图,它包含顶点s以及从s可达的所有顶点。

这颗有向图的根节点为s,树的每条路径都是有向图中的一条最短路径

3.2 最短路径树的API设计

类名DijkstraSP
构造方法pulbic DijkstraSP(EdgeWeightedDigraph G,int s):根据一副加权有向图G和顶点s,创建一个计算顶点为s的最短路径树对象
成员方法1. private void relax(EdgeWeightedDigraph G,int v):松弛图G中的顶点
2. public double disTo(int v):获取从顶点s到顶点v的最短路径的总权重
3. public boolean hasPathTo(int v):判断从顶点s到顶带你v是否可达
4. public Queue<DirectedEdge> pathTo(int v):查询从起点s到顶点v的最短路径中所有的边
成员变量1. private DirectedEdge[] edgeTo:索引代表顶点,指表示从顶点s到当前顶点的最短路径上的最后一条边
2. private double[] distTo:索引代表顶点,值从顶点s到当前的最短路径的总权重
3. private IndexMinPriorityQueue<Double> pq:存放树种顶点到非树中顶点之间的有效横切边

3.2 松弛技术

3.2.1 松弛技术的概述

松弛这个词来源于生活:一条橡皮筋沿着两个顶点的某条路径紧紧展开,如果这两个顶点之间的路径不止一条,还有存在更短的路径,那么把皮筋转移到更短的路径上,皮筋就可以放松了。

在这里插入图片描述

松弛这种简单的原理刚好可以用来计算最短路径树。

在我们的API中,需要用到两个成员变量edgeTo和distTo,分别存储边和权重。一开始给定一幅图G和顶点s,我们只知道图的边以及这些边的权重,其他的一无所知,此时初始化顶点s到顶点s的最短路径的总权重disto[s]=0;顶点s到其他顶点的总权重默认为无穷大,随着算法的执行,不断的使用松弛技术处理图的边和顶点,并按一定的条件更新edgeTo和distTo中的数据,最终就可以得到最短路径树。

3.2.2 边的松弛:

放松边v->w意味着检查从s到w的最短路径是否先从s到v,然后再从v到w?

  • 如果是,则v-w这条边需要加入到最短路径树中,更新edgeTo和distTo中的内容:edgeTo[w]=表示v->w这条边的DirectedEdge对象,distTo[w]=distTo[v]+v->w这条边的权重;

  • 如果不是,则忽略v->w这条边。

在这里插入图片描述

3.2.3顶点的松弛:

顶点的松弛是基于边的松弛完成的,只需要把某个顶点指出的所有边松弛,那么该顶点就松弛完毕。例如要松弛顶点v,只需要遍历v的邻接表,把每一条边都松弛,那么顶点v就松弛了。

如果把起点设置为顶点0,那么找出起点0到顶点6的最短路径0->2->7>3->6的过程如下:

在这里插入图片描述

3.3 Dijkstra 算法的代码实现

package com.renecdemo.dijkstra;import com.renecdemo.graph.Queue;
import com.renecdemo.queue.IndexMinPriorityQueue;
import com.renecdemo.weighted.DirectedEdge;
import com.renecdemo.weighted.EdgeWeightedDigraph;/*** Dijkstra算法*/
public class DijkstraSP {// 索引代表顶点,值表示从顶点s到当前顶点的最短路径上的最后一条边private DirectedEdge[] edgeTo;// 索引代表顶点,值从顶点s到当前的最短路径的总权重private double[] distTo;// 存放树种顶点到非树中顶点之间的有效横切边private IndexMinPriorityQueue<Double> pq;/*** 根据一副加权有向图G和顶点s,创建一个计算顶点为s的最短路径树对象* @param G* @param s*/public DijkstraSP(EdgeWeightedDigraph G, int s){// 初始化成员变量this.edgeTo = new DirectedEdge[G.V()];this.distTo = new double[G.V()];// 初始化权重数组,所有边的权重都为Double类型的无限大for (int i = 0; i < distTo.length; i++) {distTo[i] = Double.POSITIVE_INFINITY;}this.pq = new IndexMinPriorityQueue<>(G.V());// 找到图G中以顶点s为起点的最短路径树// 默认让s进入到最短路径树中distTo[s] = 0.0;pq.insert(s,0.0);while (!pq.isEmpty()){relax(G,pq.delMin()); // 弹出最新规划出的顶点}}/*** 松弛图G中的顶点 - 找出最短路径树* @param G 指定的图* @param v 起点所至的顶点*/private void relax(EdgeWeightedDigraph G,int v){// 遍历 v顶点 的邻接表for (DirectedEdge e : G.adj(v)) {// 获取到该边的终点int w = e.to();// 通过e边 达到 v顶点 的另一个顶点/*通过松弛技术,判断从s到w的最短路径是否需要先从顶点s到顶点v,如何再由顶点v到顶点w判断:从起点到达 v顶点的权重+e边的权重 是否小于 起点直接到达 w顶点的权重*/if (distTo(v)+e.weight() < distTo(w)){// 变换distTo[w] = distTo(v)+e.weight();edgeTo[w] = e;// 判断pq中是否已经存在顶点wif (pq.contains(w)){pq.changeItem(w,distTo(w));}else {pq.insert(w,distTo(w));// 更新pq队列}}}}/*** 获取从顶点s到顶点v的最短路径的总权重* @param v* @return*/public double distTo(int v){return distTo[v];}/*** 判断从顶点s到顶带你v是否可达* @param v* @return*/public boolean hasPathTo(int v){return distTo[v]<Double.POSITIVE_INFINITY;}/*** 查询从起点s到顶点v的最短路径中所有的边* @param v* @return*/public Queue<DirectedEdge> pathTo(int v){// 判断顶点s到顶点v是否可达if (!hasPathTo(v)){return null;}// 创建队列对象Queue<DirectedEdge> allEdges = new Queue<>();// 顶点逆推while (true){// 得到v顶点的边DirectedEdge e = edgeTo[v];if (e==null){break;}allEdges.enqueue(e);// 更新 v顶点 —— e.from():获得e边的除了v顶点的另外一个顶点v = e.from();}// 返回队列return allEdges;}
}

4. 前置文章

  1. 浅入数据结构 “堆” - 实现和理论
  2. 开始熟悉 “二叉树” 的数据结构
  3. 队列 和 符号表 两种数据结构的实现
  4. 队列的进阶结构-优先队列
  5. 2-3树思想与红黑树的实现与基本原理
  6. B树和B+树的实现原理阐述
  7. 图的基本原理和API实现
  8. 有向图与拓扑排序的实现原理与基本实现
  9. 加权无向图和最小生成树的实现与原理概述

5. ES8 如何使用?

快来看看这篇好文章吧~~!!
😊👉(全篇详细讲解)ElasticSearch8.7 搭配 SpringDataElasticSearch5.1 的使用

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

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

相关文章

Python自动化发票处理:使用Pytesseract和Pandas从图像中提取信息并保存到Excel

1. 引言 在财务部门&#xff0c;处理大量的纸质或扫描版发票是一项既耗时又容易出错的任务。通过使用Python中的pytesseract&#xff08;一个OCR工具&#xff09;和pandas库&#xff0c;我们可以自动化这一过程&#xff0c;从而提高工作效率并减少错误。 2. 安装所需库 首先…

Spring基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理

在 Spring 中&#xff0c;事务管理可以通过编程式方式实现&#xff0c;主要有两种方式&#xff1a;基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理。以下是两种方式的示例&#xff1a; 1. 基于底层 API 的编程式事务管理 这种方式直接使用 Spring 提…

Python 函数详解

引言 函数是编程中非常重要的概念&#xff0c;它允许我们将代码组织成可重用的块。Python 提供了灵活且强大的函数功能&#xff0c;包括创建和调用函数、形式参数与实际参数、位置参数、关键字参数、默认参数值、可变参数、返回值、变量作用域以及匿名函数&#xff08;lambda&…

Linux 配置 ssh —— ubuntu

Linux 配置 ssh —— ubuntu 设置 ip 这里我们选择使用系统 DHCP 自动分配的 IP&#xff0c;有些教程推荐使用自定义 IP&#xff0c;但是这样我们就没法上网&#xff0c;所有这里采用自定义 IP 即可 安装并启动 ssh 首先需要安装 ssh&#xff1a; sudo apt-get install op…

【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper+代码——加性注意力(Additive Attention)

【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper代码——加性注意力&#xff08;Additive Attention&#xff09; 【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper代码——加性注意力&#xff08;Additive Attention&#xff09; 文章目录…

修复Oracle MySQL Server 安全漏洞(CVE-2023-0464)

@[TOC](修复Oracle MySQL Server 安全漏洞(CVE-2023-0464)) 对于MySQL的漏洞问题,建议通过防火墙来限制远程访问本地3306端口的方式来处理。如果必须要升级,那么涉及到的具体兼容性问题,新版本安装后会导致的业务异常。 所以,建议采用增加防火墙策略的方式,不建议对mysql进…

计算PSNR, SSIM, VAMF工具

计算PSNR, SSIM, VAMF GitHub - fifonik/FFMetrics: Visualizes Video Quality Metrics (PSNR, SSIM & VMAF) calculated by ffmpeg.exe 绘制码率图 GitHub - fifonik/FFBitrateViewer: Visualizes video bitrate received by ffprobe.exe 视频对比 https://github.com/…

什么是全局污染?怎么避免全局污染?

全局污染&#xff08;Global Pollution&#xff09;是指在编程过程中&#xff0c;过度使用全局变量或对象导致命名冲突、代码可维护性下降及潜在错误增加的问题。在 JavaScript 等动态语言中&#xff0c;尤其需要关注全局污染的风险。 全局污染的影响 1. 命名冲突 3. 意外修改…

【C#】调用本机AI大模型流式返回

【python】AI Navigator的使用及搭建本机大模型_anaconda ai navigator-CSDN博客 【Python】AI Navigator对话流式输出_python ai流式返回-CSDN博客 前两章节我们讲解了使用AI Navigator软件搭建本机大模型&#xff0c;并使用python对大模型api进行调用&#xff0c;使其流式返…

Python Flask 框架下的 API 接口开发与封装示例

API&#xff08;Application Programming Interface&#xff09;接口的开发和封装是构建软件系统的重要环节。以下是关于 API 接口开发和封装的详细步骤&#xff1a; 一、需求分析 在开发 API 接口之前&#xff0c;首先需要明确接口的功能需求。这包括确定接口要提供哪些数据…

“智能科研写作:结合AI与ChatGPT提升SCI论文和基金申请质量“

基于AI辅助下的高效高质量SCI论文撰写及投稿实践 科学研究的核心在于将复杂的思想和实验成果通过严谨的写作有效地传递给学术界和工业界。对于研究生、青年学者及科研人员&#xff0c;如何高效撰写和发表SCI论文&#xff0c;成为提升学术水平和科研成果的重要环节。系统掌握从…

ProteinMPNN中DecLayer类介绍

PositionWiseFeedForward 类的代码 class PositionWiseFeedForward(nn.Module):def __init__(self, num_hidden, num_ff):super(PositionWiseFeedForward, self).__init__()self.W_in = nn.Linear(num_hidden, num_ff, bias=True)self.W_out = nn.Linear(num_ff, num_hidden, …

SAP_FICO模块-资产减值功能对折旧和残值的影响

一、业务背景 由于财务同事没注意&#xff0c;用总账给资产多做了一笔凭证&#xff0c;导致该资产金额虚增&#xff0c;每个月的折旧金额也虚增&#xff1b;现在财务的需求是怎么操作可以进行资产减值&#xff0c;并且减少每个月计提的折旧&#xff1b; 二、实现方式 通过事务码…

linux CentOs7 安装 FastDFS

CentOs7 安装 FastDFS 1. 安装依赖 yum install gcc libevent libevent-devel -y#进入src目录 cd /usr/local/src2. 安装 libfastcommon 库 libfastcommon 库是 FastDFS 文件系统运行需要的公共 C 语言函数库 # 下载 wget https://github.com/happyfish100/libfastcommon/a…

使用梧桐数据库进行销售趋势分析和预测

在当今竞争激烈的商业环境中&#xff0c;企业需要深入了解销售数据&#xff0c;以便做出明智的决策。销售趋势分析和预测是帮助企业把握市场动态、优化库存管理、制定营销策略的重要工具。本文将介绍如何使用SQL来创建销售数据库的表结构&#xff0c;插入示例数据&#xff0c;并…

6.2024.10.22

2024.10.22 2024.10.22 2024.10.22 今天没怎么学习嵌入式的&#xff0c;找时间补上今天学习的空缺。

qt EventFilter用途详解

一、概述 EventFilter是QObject类的一个事件过滤器&#xff0c;当使用installEventFilter方法为某个对象安装事件过滤器时&#xff0c;该对象的eventFilter函数就会被调用。通过重写eventFilter方法&#xff0c;开发者可以在事件处理过程中进行拦截和处理&#xff0c;实现对事…

go 语言 Gin Web 框架的实现原理探究

Gin 是一个用 Go (Golang) 编写的 Web 框架&#xff0c;性能极优&#xff0c;具有快速、支持中间件、crash处理、json验证、路由组、错误管理、内存渲染、可扩展性等特点。 官网地址&#xff1a;https://gin-gonic.com/ 源码地址&#xff1a;https://github.com/gin-gonic/gi…

Shell重定向输入输出

我的后端学习大纲 我的Linux学习大纲 重定向介绍 标准输入介绍 从键盘读取用户输入的数据&#xff0c;然后再把数据拿到Shell程序中使用&#xff1b; 标准输出介绍 Shell程序产生的数据&#xff0c;这些数据一般都是呈现到显示器上供用户浏览查看; 默认输入输出文件 每个…

重新认识Linux下的硬链接和软链接

文章目录 前言1、软链接&#xff1f;1.1 工作原理1.2 特点 2、硬链接2.1 工作原理2.2 特点 3、 总结 前言 让自己永远保持一颗好奇心 今天无意间听别人提到了硬链接和软链接&#xff0c;起初我想这么基础的知识我肯定是知道的&#xff0c;毕竟大学接触Linux到现在工作了那么多…