当前位置: 首页 > news >正文

【算法思想·二叉树】最近公共祖先问题

本文参考labuladong算法笔记[拓展:最近公共祖先系列解题框架 | labuladong 的算法笔记]

0、引言

如果说笔试的时候经常遇到各种动归回溯这类稍有难度的题目,那么面试会倾向于一些比较经典的问题,难度不算大,而且也比较实用。

本文就用 Git 引出一个经典的算法问题:最近公共祖先(Lowest Common Ancestor),简称 LCA

git pull 这个命令我们经常会用,它默认是使用 merge 方式将远端别人的修改拉到本地;如果带上参数 git pull -r,就会使用 rebase 的方式将远端修改拉到本地。

这二者最直观的区别就是:merge 方式合并的分支会看到很多「分叉」,而 rebase 方式合并的分支就是一条直线。但无论哪种方式,如果存在冲突,Git 都会检测出来并让你手动解决冲突。

那么问题来了,Git 是如何检测两条分支是否存在冲突的呢?

以 rebase 命令为例,比如下图的情况,我站在 dev 分支执行 git rebase master,然后 dev 就会接到 master 分支之上:

这个过程中,Git 是这么做的:

首先,找到这两条分支的最近公共祖先 LCA,然后从 master 节点开始,重演 LCA 到 dev 几个 commit 的修改,如果这些修改和 LCA 到 master 的 commit 有冲突,就会提示你手动解决冲突,最后的结果就是把 dev 的分支完全接到 master 上面。

那么,Git 是如何找到两条不同分支的最近公共祖先的呢?这就是一个经典的算法问题了,下面我来由浅入深讲一讲。

1、寻找一个元素

先不管最近公共祖先问题,我请你实现一个简单的算法:

给你输入一棵没有重复元素的二叉树根节点 root 和一个目标值 val,请你写一个函数寻找树中值为 val 的节点。

函数签名如下:

def find(root: TreeNode, val: int) -> TreeNode:

这个函数应该很容易实现对吧,比如我这样写代码:

# 定义:在以 root 为根的二叉树中寻找值为 val 的节点
def find(root: TreeNode, val: int) -> TreeNode:# base caseif not root:return None# 看看 root.val 是不是要找的if root.val == val:return root# root 不是目标节点,那就去左子树找left = find(root.left, val)if left:return left# 左子树找不着,那就去右子树找right = find(root.right, val)if right:return right# 实在找不到了return None

这段代码应该不用我多解释了,我下面基于这段代码做一些简单的改写,请你分析一下我的改动会造成什么影响。

首先,我修改一下 return 的位置:

def find(root: TreeNode, val: int) -> TreeNode:if not root:return None# 前序位置if root.val == val:return root# root 不是目标节点,去左右子树寻找left = find(root.left, val)right = find(root.right, val)# 看看哪边找到了return left if left else right

这段代码也可以达到目的,但是实际运行的效率会低一些,原因也很简单,如果你能够在左子树找到目标节点,还有没有必要去右子树找了?没有必要。但这段代码还是会去右子树找一圈,所以效率相对差一些。

更进一步,我把对 root.val 的判断从前序位置移动到后序位置:

def find(root: TreeNode, val: int) -> TreeNode:if root is None:return None# 先去左右子树寻找left = find(root.left, val)right = find(root.right, val)# 后序位置,看看 root 是不是目标节点if root.val == val:return root# root 不是目标节点,再去看看哪边的子树找到了return left if left is not None else right

这段代码相当于你先去左右子树找,然后才检查 root,依然可以到达目的,但是效率会进一步下降。因为这种写法必然会遍历二叉树的每一个节点

对于之前的解法,你在前序位置就检查 root,如果输入的二叉树根节点的值恰好就是目标值 val,那么函数直接结束了,其他的节点根本不用搜索。

但如果你在后序位置判断,那么就算根节点就是目标节点,你也要去左右子树遍历完所有节点才能判断出来。

最后,我再改一下题目,现在不让你找值为 val 的节点,而是寻找值为 val1  val2 的节点,函数签名如下:

def find(root: TreeNode, val1: int, val2: int) -> TreeNode:

这和我们第一次实现的 find 函数基本上是一样的,而且你应该知道可以有多种写法,我选择这样写代码:

# 定义:在以 root 为根的二叉树中寻找值为 val1 或 val2 的节点
def find(root, val1, val2):# base caseif root is None:return None# 前序位置,看看 root 是不是目标值if root.val == val1 or root.val == val2:return root# 去左右子树寻找left = find(root.left, val1, val2)right = find(root.right, val1, val2)# 后序位置,已经知道左右子树是否存在目标值return left if left is not None else right

为什么要写这样一个奇怪的 find 函数呢?因为最近公共祖先系列问题的解法都是把这个函数作为框架的

2、秒杀五道题目

236. 二叉树的最近公共祖先

给你输入一棵不含重复值的二叉树,以及存在于树中的两个节点 p 和 q,请你计算 p 和 q 的最近公共祖先节点。

比如输入这样一棵二叉树:

如果 p 是节点 6q 是节点 7,那么它俩的 LCA 就是节点 5

当然,p 和 q 本身也可能是 LCA,比如这种情况 q 本身就是 LCA 节点:

两个节点的最近公共祖先其实就是这两个节点向根节点的「延长线」的交汇点,那么对于任意一个节点,它怎么才能知道自己是不是 p 和 q 的最近公共祖先?

如果一个节点能够在它的左右子树中分别找到 p 和 q,则该节点为 LCA 节点

这就要用到之前实现的 find 函数了,只需在后序位置添加一个判断逻辑,即可改造成寻找最近公共祖先的解法代码:

【python】

class Solution:def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':return self.find(root, p.val, q.val)# 在二叉树中寻找 val1 和 val2 的最近公共祖先节点def find(self, root: 'TreeNode', val1: int, val2: int) -> 'TreeNode':if not root:return None# 前序位置if root.val == val1 or root.val == val2:# 如果遇到目标值,直接返回return rootleft = self.find(root.left, val1, val2)right = self.find(root.right, val1, val2)# 后序位置,已经知道左右子树是否存在目标值if left and right:# 当前节点是 LCA 节点return rootreturn left if left else right

在 find 函数的后序位置,如果发现 left 和 right 都非空,就说明当前节点是 LCA 节点,即解决了第一种情况

在 find 函数的前序位置,如果找到一个值为 val1 或 val2 的节点则直接返回,恰好解决了第二种情况:

因为题目说了 p 和 q 一定存在于二叉树中(这点很重要),所以即便我们遇到 q 就直接返回,根本没遍历到 p,也依然可以断定 p 在 q 底下,q 就是 LCA 节点。

这样,标准的最近公共祖先问题就解决了,接下来看看这个题目有什么变体。

1676. 二叉树的最近公共祖先 IV

依然给你输入一棵不含重复值的二叉树,但这次不是给你输入 p 和 q 两个节点了,而是给你输入一个包含若干节点的列表 nodes(这些节点都存在于二叉树中),让你算这些节点的最近公共祖先。

函数签名如下:

def lowestCommonAncestor(root: TreeNode, nodes: List[TreeNode]) -> TreeNode:

比如还是这棵二叉树:

输入 nodes = [7,4,6],那么函数应该返回节点 5

看起来怪吓人的,实则解法逻辑是一样的,把刚才的代码逻辑稍加改造即可解决这道题:

【python】

class Solution:def lowestCommonAncestor(self, root: 'TreeNode', nodes: 'List[TreeNode]') -> 'TreeNode':# 将列表转化成哈希集合,便于判断元素是否存在values = set()for node in nodes:values.add(node.val)return self.find(root, values)def find(self, root: 'TreeNode', values: 'set') -> 'TreeNode':if not root:return None# 前序位置if root.val in values:return rootleft = self.find(root.left, values)right = self.find(root.right, values)# 后序位置,已经知道左右子树是否存在目标值if left and right:# 当前节点是 LCA 节点return rootreturn left if left else right

不过需要注意的是,这两道题的题目都明确告诉我们这些节点必定存在于二叉树中,如果没有这个前提条件,就需要修改代码了

1644. 二叉树的最近公共祖先 II

给你输入一棵不含重复值的二叉树的,以及两个节点 p 和 q,如果 p 或 q 不存在于树中,则返回空指针,否则的话返回 p 和 q 的最近公共祖先节点。

在解决标准的最近公共祖先问题时,我们在 find 函数的前序位置有这样一段代码:

// 前序位置
if (root.val == val1 || root.val == val2) {// 如果遇到目标值,直接返回return root;
}

我也进行了解释,因为 p 和 q 都存在于树中,所以这段代码恰好可以解决最近公共祖先的第二种情况:

但对于这道题来说,p 和 q 不一定存在于树中,所以你不能遇到一个目标值就直接返回,而应该对二叉树进行完全搜索(遍历每一个节点),如果发现 p 或 q 不存在于树中,那么是不存在 LCA 的。

回想我在文章开头分析的几种 find 函数的写法,哪种写法能够对二叉树进行完全搜索来着?

这种:

def find(root: TreeNode, val: int) -> TreeNode:if not root:return None# 先去左右子树寻找left = find(root.left, val)right = find(root.right, val)# 后序位置,判断 root 是不是目标节点if root.val == val:return root# root 不是目标节点,再去看看哪边的子树找到了return left if left else right

那么解决这道题也是类似的,我们只需要把前序位置的判断逻辑放到后序位置即可:

【python】

class Solution:def __init__(self):# 用于记录 p 和 q 是否存在于二叉树中self.foundP = Falseself.foundQ = Falsedef lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:res = self.find(root, p.val, q.val)if not self.foundP or not self.foundQ:return None# p 和 q 都存在二叉树中,才有公共祖先return res# 在二叉树中寻找 val1 和 val2 的最近公共祖先节点def find(self, root, val1, val2):if not root:return Noneleft = self.find(root.left, val1, val2)right = self.find(root.right, val1, val2)# 后序位置,判断当前节点是不是 LCA 节点if left and right:return root# 后序位置,判断当前节点是不是目标值if root.val == val1 or root.val == val2:# 找到了,记录一下if root.val == val1:self.foundP = Trueif root.val == val2:self.foundQ = Truereturn rootreturn left if left else right

这样改造,对二叉树进行完全搜索,同时记录 p 和 q 是否同时存在树中,从而满足题目的要求。

接下来,我们再变一变,如果让你在二叉搜索树中寻找 p 和 q 的最近公共祖先,应该如何做呢?

235. 二叉搜索树的最近公共祖先

给你输入一棵不含重复值的二叉搜索树,以及存在于树中的两个节点 p 和 q,请你计算 p 和 q 的最近公共祖先节点。

把之前的解法代码复制过来肯定也可以解决这道题,但没有用到 BST「左小右大的性质,显然效率不是最高的。

在标准的最近公共祖先问题中,我们要在后序位置通过左右子树的搜索结果来判断当前节点是不是 LCA

对于 BST 来说,根本不需要老老实实去遍历子树,由于 BST 左小右大的性质,将当前节点的值与 val1 和 val2 作对比即可判断当前节点是不是 LCA

假设 val1 < val2,那么 val1 <= root.val <= val2 则说明当前节点就是 LCA;若 root.val 比 val1 还小,则需要去值更大的右子树寻找 LCA;若 root.val 比 val2 还大,则需要去值更小的左子树寻找 LCA

依据这个思路就可以写出解法代码:

【python】

class Solution:def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':# 保证 val1 较小,val2 较大val1 = min(p.val, q.val)val2 = max(p.val, q.val)return self.find(root, val1, val2)# 在 BST 中寻找 val1 和 val2 的最近公共祖先节点def find(self, root: 'TreeNode', val1: int, val2: int) -> 'TreeNode':if not root:return Noneif root.val > val2:# 当前节点太大,去左子树找return self.find(root.left, val1, val2)if root.val < val1:# 当前节点太小,去右子树找return self.find(root.right, val1, val2)# val1 <= root.val <= val2# 则当前节点就是最近公共祖先return root

1650. 二叉树的最近公共祖先 III

再看最后一道最近公共祖先的题目吧,力扣第 1650 题「二叉树的最近公共祖先 III」,这次输入的二叉树节点比较特殊,包含指向父节点的指针。题目会给你输入一棵存在于二叉树中的两个节点 p 和 q,请你返回它们的最近公共祖先。函数签名如下:

class Node:def __init__(self):self.val = Noneself.left = Noneself.right = Noneself.parent = None# 函数签名
def lowestCommonAncestor(p: Node, q: Node) -> Node:

由于节点中包含父节点的指针,所以二叉树的根节点就没必要输入了。

这道题其实不是公共祖先的问题,而是单链表相交的问题,你把 parent 指针想象成单链表的 next 指针,题目就变成了:

给你输入两个单链表的头结点 p 和 q,这两个单链表必然会相交,请你返回相交点。

 【python】

class Solution:def lowestCommonAncestor(self, p: 'Node', q: 'Node') -> 'Node':# 施展链表双指针技巧a, b = p, qwhile a != b:# a 走一步,如果走到根节点,转到 q 节点a = q if a is None else a.parent# b 走一步,如果走到根节点,转到 p 节点return a

3、总结

最近公共祖先问题,核心在于去理解pq两个节点所处位置不同,引申出来的代码逻辑不同。

class Solution:def __init__(self):# 对于p,q 不确定有无得情况,需要额外加变量进行跟踪,并将find函数调整为后序遍历方式# 遇到p,q就更新这俩参数self.foundP = Falseself.foundQ = Falsedef lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':return self.find(root, p.val, q.val)# 在二叉树中寻找 val1 和 val2 的最近公共祖先节点def find(self, root: 'TreeNode', val1: int, val2: int) -> 'TreeNode':# 碰到叶子节点,返回None即可if not root:return None'''1、在前序位置遍历到目标值,不论是p还是q,直接返回该节点,其上层函数自有left或right做承接。2、即使给的不是val1和val2,而是一个list,依然只需判决该 root.val in list 即可返回root。3、若将此判决调到后序位置,则find方法会遍历所有节点,在不确定p,q是否存在的情况下用起来很方便。4、对于BST的最小公共祖先问题,只需明确p < q,root.val < p就去find右边,root.val > q就去find左边。'''if root.val == val1 or root.val == val2:# 如果遇到目标值,直接返回return root# find遍历便利的结果自然需要变量做承接,便于后续判决left = self.find(root.left, val1, val2)right = self.find(root.right, val1, val2)'''1、后续位置做left and right的判决,如果此节点left和right都存在,直接起一个截胡效果,不用继续向后遍历,直接确定该节点就是LCA并返回。2、即使给的不是val1和val2,而是一个list,即便某个节点的left and right各自只遍历到list中的一个值,放心地 return root, 因为自然会有上层函数通过这段代码进行“截胡”,依然能正确返回上层那个 left and right 都满足的节点。'''if left and right:# 当前节点是 LCA 节点return root'''1、当后序位置的 left and right判决迟迟未响应时,这行 return 代码做了最后的兜底。2、即使给的不是val1和val2,而是一个list,同理直接对该节点进行return,也是起一个兜底的效果。'''return left if left else right

相关文章:

【算法思想·二叉树】最近公共祖先问题

本文参考labuladong算法笔记[拓展&#xff1a;最近公共祖先系列解题框架 | labuladong 的算法笔记] 0、引言 如果说笔试的时候经常遇到各种动归回溯这类稍有难度的题目&#xff0c;那么面试会倾向于一些比较经典的问题&#xff0c;难度不算大&#xff0c;而且也比较实用。 本…...

如何合并pdf文件,四款软件,三步搞定!

在数字化办公的浪潮中&#xff0c;PDF文档因其跨平台兼容性和安全性&#xff0c;成为了我们日常工作中不可或缺的一部分。然而&#xff0c;面对多个PDF文件需要整合成一个文件时&#xff0c;不少小伙伴可能会感到头疼。别担心&#xff0c;今天我们就来揭秘四款高效PDF合并软件&…...

仪表放大器AD620

AD623 是一款低功耗、高精度的仪表放大器&#xff0c;而不是轨到轨运算放大器。它的输入电压范围并不覆盖整个电源电压&#xff08;轨到轨&#xff09;&#xff0c;但在单电源供电下可以处理接近地电位的输入信号。 AD620 和 AD623 都是仪表放大器&#xff0c;但它们在一些关键…...

【Qt网络编程】Tcp多线程并发服务器和客户端通信

目录 一、编写思路 1、服务器 &#xff08;1&#xff09;总体思路widget.c&#xff08;主线程&#xff09; &#xff08;2&#xff09;详细流程widget.c&#xff08;主线程&#xff09; &#xff08;1&#xff09;总体思路chat_thread.c&#xff08;处理聊天逻辑线程&…...

SkyWalking 简介

SkyWalking是什么 skywalking是一个国产开源框架,2015年由吴晟开源 , 2017年加入Apache孵化器。skywalking是分布式系统的应用 程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它是一款优秀的 APM(Application Performance Manag…...

语音合成(自然、非自然)

1.环境 Python 3.10.14 2.完成代码 2.1简陋版 import pyttsx3# 初始化tts引擎 engine pyttsx3.init()# 设置语音速度 rate engine.getProperty(rate) engine.setProperty(rate, rate - 50)# 设置语音音量 volume engine.getProperty(volume) engine.setProperty(volume, …...

redis简单使用与安装

redis redis 是什么 Redis 是一个开源的&#xff0c;使用 C 语言编写的,支持网络交互的,内存中的Key-Value 数据结构存储系统&#xff0c;支持多种语言,它可以用作数据库、缓存和消息中间件。 一、存储系统特性 内存存储与持久化 Redis 主要将数据存储在内存中&#xff0c;这…...

封装 WBXpopup 组件

这是Popup组件基于微博小程序&#xff0c;需要改变标签&#xff0c;以及一写方法 支持四个方向抽屉&#xff0c;以及中间弹出功能 // 用法 <template><wbx-view style"height: 100vh;"><!-- 对话框组件 --><wbx-view><wbx-text click&quo…...

【OJ刷题】双指针问题6

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;OJ刷题入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1…...

详解:Tensorflow、Pytorch、Keras(搭建自己的深度学习网络)

这是一个专门对Tensorflow、Pytorch、Keras三个主流DL框架的一个详解和对比分析 一、何为深度学习框架&#xff1f; 你可以理解为一个工具帮你构建一个深度学习网络&#xff0c;调用里面的各种方法就能自行构建任意层&#xff0c;diy你想要的DNN&#xff0c;而且任意指定学习…...

【CSS in Depth 2 精译_035】5.5 Grid 网格布局中的子网格布局(全新内容)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…...

Java是怎么处理死锁的

文章目录 避免死锁避免嵌套锁资源进行排序超时锁 检测死锁通过Java提供的API检查死锁情况jStack监控工具 Java 本身没有内置的机制自动处理死锁问题&#xff0c;但可以采取一些策略和技术来检测和避免死锁。 避免死锁 避免嵌套锁 尽可能减少嵌套锁操作&#xff0c;避免在一个…...

Effective Java 学习笔记 方法签名设计

目录 谨慎选择方法名称 不要过于追求提供便利的快捷方法 避免过长的参数列表 对于参数类型优先使用接口而不是类 对于boolean参数&#xff0c;要优先使用两个元素的枚举类型 本文接续前一篇文章聚焦Java方法签名的设计&#xff0c;方法签名包括了方法的输入和输出参数以及…...

毛利超70%、超70+智驾客户,这家AI数据训练服务商刚刚止亏

AI训练数据服务第一股海天瑞声终于迎来了“曙光”。 日前&#xff0c;海天瑞声发布2024年半年报显示&#xff0c;上半年其实现营收9242.63万&#xff0c;同比增长24.13%&#xff1b;实现净利润41.64 万元&#xff0c;不过同比去年同期的亏损1724.14万元&#xff0c;扭亏为盈。…...

本地部署高颜值某抑云音乐播放器Splayer并实现无公网IP远程听歌

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

图像压缩编码(4)--H.26x系列视频压缩编码_2

目录 H.261 视频编码标准 H.261的编码与解码 1&#xff09; 帧内/帧间编码 2&#xff09;运动补偿 3&#xff09;量化 4&#xff09;环路滤波器 5&#xff09;缓存器 压缩数据的分层 数据复用结构 H.264的编码与解码 H.261 视频编码标准 实际应用时&#xff0c;要求有…...

JS渲染锻炼输入表单

前言 上篇文章为大家展现了好看的信息窗口&#xff0c;接下来我们跟着流程图看下一步 之前我们的带点击事件已经添加完毕&#xff0c;下一步就是当用户点击的时候&#xff0c;渲染锻炼形式&#xff0c;当然这是一个标签&#xff0c;可以提供给用户输入锻炼形式 实例 ● 我…...

proteus仿真学习(1)

一&#xff0c;创建工程 一般选择默认模式&#xff0c;不配置pcb文件 可以选用芯片型号也可以不选 不选则从零开始布局&#xff0c;没有初始最小系统。选用则有初始最小系统以及基础的main函数 本次学习使用从零开始&#xff0c;不配置固件 二&#xff0c;上手软件 1.在元件…...

决策树+随机森林模型实现足球大小球让球预测软件

文章目录 前言一、决策树是什么&#xff1f;二、数据收集与整理1.数据收集2.数据清洗3.特征选择 三、决策树构建3.1绘制训练数据图像3.2 训练决策树模型3.3 依据模型绘制决策树的决策边界3.4 树模型可视化 四、模型预测五、随机森林模型总结 前言 之前搞足球数据分析的时候&…...

31省市农业地图大数据

1.北京市 谷类作物种植结构&#xff08;万亩&#xff09; 农作物种植结构&#xff08;万亩&#xff09; 2.天津市 谷类作物种植结构&#xff08;万亩&#xff09; 农作物种植结构&#xff08;万亩&#xff09; 3.黑龙江省 谷类作物种植结构&#xff08;万亩&#xff09; 农作物…...

http请求包含什么

HTTP请求通常包含以下几个主要部分&#xff1a; 请求行&#xff08;Request Line&#xff09;&#xff1a; 包含请求方法&#xff08;如 GET、POST、PUT、DELETE 等&#xff09;、请求的目标 URI 和 HTTP 版本。例如&#xff1a;GET /index.html HTTP/1.1 请求头部&#xff08;…...

【基础算法总结】模拟篇

目录 一&#xff0c;算法介绍二&#xff0c;算法原理和代码实现1576.替换所有的问号495.提莫攻击6.Z字形变换38.外观数列1419.数青蛙 三&#xff0c;算法总结 一&#xff0c;算法介绍 模拟算法本质就是"依葫芦画瓢"&#xff0c;就是在题目中已经告诉了我们该如何操作…...

《深度学习》PyTorch 手写数字识别 案例解析及实现 <下>

目录 一、回顾神经网络框架 1、单层神经网络 2、多层神经网络 二、手写数字识别 1、续接上节课代码&#xff0c;如下所示 2、建立神经网络模型 输出结果&#xff1a; 3、设置训练集 4、设置测试集 5、创建损失函数、优化器 参数解析&#xff1a; 1&#xff09;para…...

【笔记】材料分析测试:晶体学

晶体与晶体结构Crystal and Crystal Structure 1.晶体主要特征 固态物质可以分为晶态和非晶态两大类&#xff0c;分别称为晶体和非晶体。 晶体和非晶体在微观结构上的区别在于是否具有长程有序。 晶体&#xff08;长程有序&#xff09;非晶&#xff08;短程有序&#xff09…...

飞塔Fortigate7.4.4的DNS劫持功能

基础网络配置、上网策略、与Server的VIP配置&#xff08;略&#xff09;。 在FortiGate上配置DNS Translation&#xff0c;将DNS请求结果为202.103.12.2的DNS响应报文中的IP地址修改为Server的内网IP 10.10.2.100。 config firewall dnstranslationedit 1set src 2.13.12.2set…...

Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】

Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】 目录 Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】 一、简单介绍 二、状态模式&#xff08;State Pattern&#xff09; 1、什么时候使用状态模式 2、使用状态模式的…...

【RabbitMQ】RabbitMQ 的概念以及使用RabbitMQ编写生产者消费者代码

目录 1. RabbitMQ 核心概念 1.1生产者和消费者 1.2 Connection和Channel 1.3 Virtual host 1.4 Queue 1.5 Exchange 1.6 RabbitMO工作流程 2. AMQP 3.RabbitMO快速入门 3.1.引入依赖 3.2.编写生产者代码 ​3.3.编写消费者代码 4.源码 1. RabbitMQ 核心概念 在安装…...

openmv与stm32通信

控制小车视觉循迹使用 OpenMV 往往是不够的。一般使用 OpenMV 对图像进行处理&#xff0c;将处理过后的数据使用串口发送给STM32&#xff0c;使用STM32控制小车行驶。本文主要讲解 OpenMV 模块与 STM32 间的串口通信以及两种循迹方案&#xff0c;分别是划分检测区域和线性回归。…...

C++ STL全面解析:六大核心组件之一----序列式容器(vector和List)(STL进阶学习)

目录 序列式容器 Vector vector概述 vector的迭代器 vector的数据结构 vector的构造和内存管理 vector的元素操作 List List概述 List的设计结构 List的迭代器 List的数据结构 List的内存构造 List的元素操作 C标准模板库&#xff08;STL&#xff09;是一组高效的…...

【c数据结构】OJ练习篇 帮你更深层次理解链表!(相交链表、相交链表、环形链表、环形链表之寻找环形入口点、判断链表是否是回文结构、 随机链表的复制)

目录 一. 相交链表 二. 环形链表 三. 环形链表之寻找环形入口点 四. 判断链表是否是回文结构 五. 随机链表的复制 一. 相交链表 最简单粗暴的思路&#xff0c;遍历两个链表&#xff0c;分别寻找是否有相同的对应的结点。 我们对两个链表的每个对应的节点进行判断比较&…...