基于对Rust语言的实践经验以及对传统计算机科学与编程方法的学习

作者:禅与计算机程序设计艺术

1.简介

在过去的几年中,Rust编程语言的蓬勃发展与其生态圈带动了很多应用领域的变革。其中一个重要的领域就是优化与搜索领域,例如求解图论中的最短路径问题、寻找矩阵中的路径等。然而,对于某些算法及问题来说,即使使用更加高级的算法如BFS/DFS等,也往往需要耗费大量的时间来尝试所有的可能解法。因此,如何在更短的时间内找到有效的解法成为越来越多研究者和工程师关心的问题。

Rust编程语言提供了一种新型的语言机制——安全性,它可以帮助开发人员避免错误、隐藏数据损坏等问题,提升软件的健壮性和鲁棒性。本文将讨论如何利用Rust语言解决一些经典的优化与搜索问题,这些问题要求使用暴力搜索或启发式方法,并涉及数学、算法和编程相关知识。本文主要基于对Rust语言的实践经验以及对传统计算机科学与编程方法的学习,希望能够提供有价值的参考。

本文假设读者具有初步的Rust语言基础,并且熟悉Rust的核心机制,包括模块化、函数式编程和面向对象编程。

2.基本概念术语说明

2.1 矩阵

矩阵(Matrix)是线性代数的一个基本概念。一个 n i m e s m nimes m nimesm维矩阵是一个 n n n m m m列的数组,每个元素都可以看做是一个标量值。在很多情况下,矩阵可以用来表示二维或三维的空间或物理系统的状态。例如,一个 3 i m e s 4 3imes 4 3imes4的矩阵可以用来描述一个 3 e x t c m i m e s 4 e x t c m 3ext{cm} imes 4ext{cm} 3extcmimes4extcm的矩形的坐标,或者在一个图论问题中,可以使用邻接矩阵来描述节点之间的边连接关系。

在本文中,我们使用的矩阵一般都代表有限个变量的线性方程组。例如,一个 n n n维线性方程组可以用 A ⋅ x = b A\cdot x = b Ax=b的形式来表示,其中 x x x是一个长度为 n n n的一维向量, b b b是一个长度为 n n n的一维向量。 A A A是一个 n i m e s n nimes n nimesn的矩阵,存放着系数, b b b是一个长度为 n n n的一维向量,存放着右端项的值。

2.2 搜索

搜索(Search)是指在一组给定的目标集合(称为搜索空间)中,找到满足特定条件的一个或多个元素。搜索问题的关键是如何有效地构造目标集合,以及如何有效地评估目标集中每个元素的好坏程度。搜索算法通常会按照某种顺序搜索目标集,直到找到一个或多个满足条件的元素为止。

搜索算法也可以被分成两类:一种是确定性算法,另一种则是随机算法。其中,确定性算法通常按照固定次序来访问目标集,而随机算法则随机访问目标集,从而期望获得全局最优解。

2.3 目标函数

目标函数(Objective Function)也称作目标或适应度函数。它是一个单调递增函数,它的输入是一个目标元素,输出是一个非负值。该函数用来衡量一个元素与目标集中其他元素的距离,用来评估元素的优劣。例如,在求解图论中的最短路径问题时,通常使用Dijkstra算法,它采用了一种贪婪算法——每一步只选择一条最短路径——来计算目标元素之间的距离。如果目标集没有明确的定义,就需要借助某个度量标准来定义目标函数。

2.4 启发式搜索

启发式搜索(Heuristic Search)是指根据估计信息得到的启发式指导来搜索目标元素。启发式指导可以是经验、直觉或是基于搜索策略本身的启发式推测。启发式搜索算法往往比全盘搜索算法更快地找到近似解,但可能不是全局最优的。在许多问题中,启发式搜索的效果很不错,比如著名的八皇后问题。

3.核心算法原理和具体操作步骤以及数学公式讲解

3.1 Dijkstra算法

3.1.1 介绍

Dijkstra算法是一种贪婪算法,用于在一个无向带权连通图中找到从源顶点到所有其他顶点的最短路径。它的主要特点是它可以在 O ( E l o g V ) O(ElogV) O(ElogV)时间内计算出图中的最短路径。

3.1.2 操作步骤

  1. 初始化一个源顶点到所有其他顶点的最短路径的估计值 D [ v ] D[v] D[v] ∞ \infty ,除了源顶点外,其他顶点的最短路径的估计值为0。
  2. 将源顶点放入优先队列中,并将源顶点的最短路径的估计值 D [ s ] D[s] D[s]设置为0。
  3. 从优先队列中取出最短路径估计值最小的顶点 u u u,并更新它的邻居节点中其余各节点的最短路径估计值。具体地,对于 v v v u u u的相邻节点,更新:
    D [ v ] = m i n ( D [ v ] , D [ u ] + c u v ) , D[v] = min(D[v], D[u]+c_{uv}), D[v]=min(D[v],D[u]+cuv),
    c u v c_{uv} cuv为从 u u u v v v的边上的权重。
  4. 如果 D [ v ] D[v] D[v]减小了,则将 v v v重新插入优先队列中。重复步骤3,直至优先队列为空或者已知所有顶点的最短路径估计值。

3.1.3 证明复杂度分析

通过优先队列,Dijkstra算法可以保证每次从优先队列中弹出的顶点都是最短路径估计值最小的。由于每次访问最短路径估计值最小的顶点后,它的邻居节点的最短路径估计值都会被更新,故循环执行此过程一定次数后,图中的所有顶点的最短路径估计值都不会再发生变化。而在每次循环中,从优先队列中取出的顶点最多被处理一次,故总的运行时间与图中的边个数 E E E和顶点个数 V V V呈线性关系,也就是 O ( E + V ) = O ( E l o g V ) O(E+V)=O(ElogV) O(E+V)=O(ElogV).

3.1.4 Rust实现

下面是Rust语言版本的Dijkstra算法实现。首先,定义图的结构体:

struct Graph {
vertex_count: usize, // 顶点数量
edge_count: usize,   // 边数量
edges: Vec<(usize, usize, i32)>,// (起始点,终止点,权重)的列表
}

vertex_count是图中顶点的数量;edge_count是图中边的数量;edges是一个元组的列表,记录了图中每条边的信息。

然后,实现Dijkstra算法:

fn dijkstra(graph: &Graph, source: usize) -> Vec<Option<i32>> {
let mut dist: Vec<_> = vec![None; graph.vertex_count];
dist[source] = Some(0);

let mut queue = BinaryHeap::new();
queue.push((0, source));

while!queue.is_empty() {
let (cost, u) = queue.pop().unwrap();

if dist[u].unwrap() < cost {
continue;
}

for v in graph.edges[u as usize.. ] {
if let Some(&d) = dist[v.0].as_ref() {
if d > cost + v.2 {
dist[v.0] = Some(cost + v.2);

queue.push((dist[v.0].unwrap(), v.0));
}
} else {
dist[v.0] = Some(cost + v.2);

queue.push((dist[v.0].unwrap(), v.0));
}
}
}

dist
}

此实现的基本逻辑与算法步骤相同,只是将优先队列换成了一个Rust库BinaryHeap

3.2 A*算法

3.2.1 介绍

A算法与Dijkstra算法类似,也是一种贪婪算法。不同之处在于A算法在计算最短路径时,还会考虑启发式因子,即预估目标顶点的距离。这样可以使算法更准确地找到全局最优解,而不是局部最优解。

3.2.2 操作步骤

  1. 初始化一个源顶点到所有其他顶点的最短路径的估计值 f [ v ] f[v] f[v] ∞ \infty ,除了源顶点外,其他顶点的最短路径的估计值为0。同时,初始化一个源顶点到所有其他顶oint的实际路径长度 g [ v ] g[v] g[v] ∞ \infty ,除了源顶点外,其他顶点的实际路径长度为0。
  2. 将源顶点放入优先队列中,并将源顶点的最短路径的估计值 f [ s ] f[s] f[s]设置为0。
  3. 从优先队列中取出最短路径估计值最小的顶点 u u u,并更新它的邻居节点中其余各节点的最短路径估计值。具体地,对于 v v v u u u的相邻节点,更新:
    f [ v ] = g [ u ] + h ( v ) , f[v] = g[u]+h(v), f[v]=g[u]+h(v),
    h ( v ) h(v) h(v)为从 u u u v v v的估计的预估距离。
  4. 如果 f [ v ] f[v] f[v]减小了,则将 v v v重新插入优先队列中。重复步骤3,直至优先队列为空或者已知所有顶点的最短路径估计值。

3.2.3 证明复杂度分析

和Dijkstra算法一样,A*算法也可以保证每次从优先队列中弹出的顶点都是最短路径估计值最小的。当一个顶点的 f f f值变小时,它所依赖的边上所有前驱顶点的 f f f值都可能变小,所以其依赖边都可以重新加入优先队列中进行探索。这种策略下,算法会继续迭代下去,直至找到最短路径。同时,算法会通过边的预估距离来估计实际路径长度,从而保证算法更加准确地找到全局最优解。

3.2.4 Rust实现

下面是Rust语言版本的A*算法实现。首先,定义图的结构体和与Dijkstra算法不同的启发式函数类型:

type HeuristicFn = Box<dyn Fn(usize) -> i32>;

struct Graph {
vertex_count: usize, // 顶点数量
edge_count: usize,   // 边数量
edges: Vec<(usize, usize, i32)>,// (起始点,终止点,权重)的列表
h: Option<HeuristicFn>,  // 启发式函数
}

vertex_countedge_countedges和Dijkstra算法中的source参数与之前保持一致;h是一个可选的函数指针,指向启发式函数,如果没有传入,则默认为 ( ∣ v − u ∣ ) (|v-u|) (vu)

然后,实现A*算法:

fn astar(graph: &Graph, source: usize, target: usize) -> Option<Vec<usize>> {
let mut prev: Vec<_> = vec![None; graph.vertex_count];
let mut dist: Vec<_> = vec![None; graph.vertex_count];
let mut fval: Vec<_> = vec![None; graph.vertex_count];

dist[source] = Some(0);
fval[source] = Some(0);

let mut queue = BinaryHeap::new();
queue.push((0, source));

while!queue.is_empty() {
let (_, u) = queue.peek().unwrap();

if *u == target {
break;
}

for v in graph.edges[*u as usize..] {
let alt = match fval[u] {
None => unreachable!(),
Some(prev_f) => prev_f + v.2,
};

let estimate = match graph.h {
None => ((v.0 as isize - u as isize).abs() +
 (v.1 as isize - target as isize).abs())
as i32,
Some(heuristic) => (*heuristic)(v.1) +
(target as isize - v.1 as isize).abs()
as i32,
};

if let Some(prev_alt) = fval[v.1] {
if alt <= prev_alt {
continue;
}
}

prev[v.1] = Some(*u);
dist[v.1] = Some(alt);
fval[v.1] = Some(estimate + alt);

queue.push((fval[v.1].unwrap(), v.1))
}

queue.pop();
}

let mut path: Vec<usize> = Vec::new();

if let Some(v) = prev[target] {
path.extend([target, v]);

loop {
if let Some(u) = prev[path[path.len()-1]] {
path.push(u);
} else {
return Some(path);
}
}
}

None
}

此实现的基本逻辑与算法步骤相同,只是将优先队列换成了一个Rust库BinaryHeap,并增加了启发式函数的参数。

3.3 模拟退火算法

3.3.1 介绍

模拟退火算法(Simulated Annealing)是一种启发式搜索算法,其目的是在模拟退火过程中,自动调整搜索算法的接受温度和降温速率。模拟退火算法的基本思想是,随着搜索进程的不断扩大,算法会逐渐变得更热衷于局部最优解,但当温度降低时,算法会逐渐转向全局最优解。

3.3.2 操作步骤

  1. 设置初始温度 T T T、降温速率 α \alpha α、最大迭代次数 k k k和交换算子。
  2. 生成一个随机解作为当前解 X t X_t Xt
  3. 对每一步迭代:
    1. 在邻域范围内生成新解 X t + 1 X_{t+1} Xt+1
    2. 根据置信度计算新解与当前解的相似度 P = exp ⁡ ( − ∣ ∣ X t + 1 − X t ∣ ∣ 2 / ( k t ) ) ∑ η = 1 m exp ⁡ ( − ∣ ∣ X η − X t ∣ ∣ 2 / ( k t ) ) P=\frac{\exp(-||X_{t+1}-X_t||^2/(kt))}{\sum_{\eta=1}^m\exp(-||X_\eta-X_t||^2/(kt))} P=η=1mexp(∣∣XηXt2/(kt))exp(∣∣Xt+1Xt2/(kt)).
    3. 使用以下公式更新当前解:
      { X t = P X t + 1 + ( 1 − P ) X t T = α T . \begin{cases} X_t = PX_{t+1}+(1-P)X_t \\ T = \alpha T.\end{cases} {Xt=PXt+1+(1P)XtT=αT.
    4. T ≤ e − 5 T\leq e^{-5} Te5, 或者已经达到了最大迭代次数 k k k, 结束迭代。

3.3.3 证明复杂度分析

模拟退火算法是一个模拟退火的动态过程,其每次迭代都会产生一个新的解并把当前解替换掉,所以不存在一个确定性的渐进时间界。但是,为了估计算法的渐进时间界,可以使用分而治之的方法:将算法的运行时间分解成若干个阶段,每一个阶段仅由固定数量的迭代组成,且每一步迭代平均所需的时间为常数。在每个阶段完成后,算法都会降低温度,然后进入下一个阶段,直到收敛或达到最大迭代次数。因为算法中每一步迭代的时间复杂度都为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E),所以这个时间界可以为 O ( ∣ V ∣ + ∣ E ∣ ) O(\sqrt{|V|}+\sqrt{|E|}) O(V +E )

3.3.4 Rust实现

实现模拟退火算法与前两个搜索算法差不多。这里不再赘述,感兴趣的读者可以自行查看源代码。

3.4 启发式排序算法

3.4.1 介绍

启发式排序算法(Heuristic Sorting Algorithm)是指通过一种启发式的方式对元素进行排序。启发式排序算法不像冒泡、快速排序那样直接排序整个序列,而是在每一步中采用启发式方式选择一个元素,然后将其与其余元素比较,移动至合适位置。启发式排序算法的目的是找出全局最优解,而不是局部最优解。

3.4.2 操作步骤

  1. 选择一个启发式规则,例如最大堆或最小堆。
  2. 按照启发式规则初始化一个空堆。
  3. 从初始序列中依次取出元素 e e e,并将其添加到堆中。
  4. 重复第3步,直至堆为空。
  5. 返回堆中元素的有序排列。

3.4.3 证明复杂度分析

每次堆的调整花费 O ( log ⁡ n ) O(\log n) O(logn)时间,故总的时间复杂度为 O ( n k log ⁡ n ) O(nk\log n) O(nklogn)

3.4.4 Rust实现

实现启发式排序算法也是相对简单的。同样,不再赘述,读者可以查看源代码。

4.具体代码实例和解释说明

4.1 Brute Force Optimization

一个典型的应用场景是求解线性规划问题。线性规划问题可以形式化为如下最优化问题:

min ⁡ x c T x s u c h t h a t A x ≥ b , x ≥ 0. \min_{x}\quad c^Tx\\suchthat\quad Ax\geq b,\quad x\geq 0. xmincTxsuchthatAxb,x0.

该问题要求在给定约束条件下,求解使得目标函数最小的 n n n维向量 x x x c c c n n n维列向量, A A A m m m n n n列矩阵, b b b m m m维列向量。

可以通过暴力搜索的方法枚举所有可能的 x x x,然后计算它们的目标函数值,选择使得目标函数最小的解。这种方法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),可以看到对于一些问题,即使是小规模问题,也很难在短时间内找到可行解。

有一种改进的方法是使用启发式搜索,比如A算法。A算法在每次搜索时,都会同时评估两个目标:第一,与当前顶点的距离;第二,距离目标顶点的估计距离。因此,在计算到目标顶点的距离时,可以考虑先计算目标顶点的启发式估计值。由于启发式估计值是当前点到目标点的估计距离,因此在评估到目标点的距离时,算法更倾向于选择启发式估计值较小的点。

因此,可以修改A*算法,在搜索过程中,每次计算新解时,同时对当前点到目标点的启发式估计值进行更新。具体地,计算新解的启发式估计值 g ∗ ( u ) g^\ast(u) g(u)和距离目标点的估计距离 h ∗ ( u ) h^\ast(u) h(u),然后更新启发式估计值 g ( u ) g(u) g(u)和距离目标点的估计距离 h ( u ) h(u) h(u)

KaTeX parse error: Undefined control sequence: \qquadext at position 60: …ht|+\gamma h(u)\̲q̲q̲u̲a̲d̲e̲x̲t̲{(new)}ag{*}
KaTeX parse error: Undefined control sequence: \qquadext at position 79: …amma g^\ast(u))\̲q̲q̲u̲a̲d̲e̲x̲t̲{(new)}ag{**}

δ \delta δ为距离目标点的估计距离的上界, γ \gamma γ为控制启发式估计值的影响的参数。在迭代的每一步中,使用启发式估计值较小的点,并评估距离目标点的估计距离较大的点。

综上,对线性规划问题使用暴力搜索时,其时间复杂度为 O ( n 4 ) O(n^4) O(n4),远远超过最优解。而使用启发式搜索时,其时间复杂度可以降低到 O ( n 2 + γ m n ) O(n^2+\gamma mn) O(n2+γmn),其中 m m m是约束条件的数目。

4.2 Path Finding Problem

路径查找问题的基本模型是寻找一条从源顶点到目标顶点的路径。路径查找问题可以形式化为如下最优化问题:

min ⁡ a ∣ a ⃗ ∣ s u c h t h a t ∀ s ∈ V , ∀ t ∈ V , ∀ j ∈ E , s e q t : [ a ⃗ s + a ⃗ s t = j t + a ⃗ t ] ∧ [ a s j t = a t s ] \min_{a}\quad |\vec{a}|\\suchthat\quad \forall s\in V,\forall t\in V,\forall j\in E,\quad s eq t:\left[\vec{a}_s+\vec{a}_{st}=j_t+\vec{a}_t\right]\land [a_sj_t=a_{ts}] amina suchthatsV,tV,jE,seqt:[a s+a st=jt+a t][asjt=ats]

其中, a ⃗ = ( a 1 , … , a n ) \vec{a}=(a_1,\ldots,a_n) a =(a1,,an) n n n维向量, V V V为顶点集合, E E E为边集合。变量KaTeX parse error: Expected '}', got 'EOF' at end of input: …1\}^{nimes|E|\}为边的贴标签,表示是否存在一条从 i i i j j j的路径。 A = [ a i j ] A=[a_{ij}] A=[aij] n i m e s ∣ E ∣ nimes |E| nimesE矩阵,表示边的连接情况。 B = [ j t ] B=[j_t] B=[jt] n i m e s ∣ E ∣ nimes |E| nimesE矩阵,表示对应边的容量。 c ⃗ s \vec{c}_s c s n n n维向量,表示顶点 s s s的外部流量, c ⃗ s t \vec{c}_{st} c st n n n维向量,表示从 s s s t t t的内部流量。 C = [ c s i , c s t ] C=[c_{si},c_{st}] C=[csi,cst] n i m e s n nimes n nimesn矩阵,表示顶点间的边流量。 d m a x d^{max} dmax n n n维向量,表示顶点到其自身的距离的上界。

需要注意的是,这一问题是一个组合优化问题,其目标函数不能直接对原问题进行求解。此处,我们考虑用启发式搜索来解决这一问题。

按照以前的启发式搜索策略,在计算边的贴标签时,应该考虑到内部流量的限制。由于每个顶点只能向一个方向流出流量,因此可以通过设置一个全局参数 λ \lambda λ来对贴标签进行修正。

另外,为了防止出现循环,需要增加顶点的顺序限制,也就是说,对于从 i i i j j j的路径,不能使用其上的边。

因此,可以修改A*算法,在搜索过程中,每次计算新解时,同时考虑到顶点间的距离限制和边贴标签。具体地,计算新解的启发式估计值 g ∗ ( u ) g^\ast(u) g(u)、距离目标点的估计距离 h ∗ ( u ) h^\ast(u) h(u)、顶点间的距离限制 d ∗ ( u ) d^\ast(u) d(u)和边贴标签 a ∗ ( u ) a^\ast(u) a(u),然后更新启发式估计值 g ( u ) g(u) g(u)、距离目标点的估计距离 h ( u ) h(u) h(u)、顶点间的距离限制 d ( u ) d(u) d(u)和边贴标签 a ( u ) a(u) a(u)

g ∗ ( u ) = g ( p ) + δ ∣ → E ∣ + γ h ( u ) a g ∗ g^\ast(u)=g(p)+\delta\left|\rightarrow E\right|+\gamma h(u)\\ag{*} g(u)=g(p)+δE+γh(u)ag
h ∗ ( u ) = m i n ( h ( u ) , δ ∣ → E ∣ − γ g ∗ ( u ) ) a g ∗ ∗ h^\ast(u)=\mathrm{min}(h(u),\delta\left|\rightarrow E\right|-\gamma g^\ast(u))\\ag{**} h(u)=min(h(u),δEγg(u))ag
d ∗ ( u ) = m i n ( d ( u ) , d ( p ) + λ C p s a g ∗ ∗ ∗ d^\ast(u)=\mathrm{min}(d(u),d(p)+\lambda C_{ps}\\ag{***} d(u)=min(d(u),d(p)+λCpsag
a ∗ ( u ) = m i n ( a ( u ) , a ( p ) − B p j ) i f ℓ i u e q i a g ∗ ∗ ∗ ∗ a^\ast(u)=\mathrm{min}(a(u),a(p)-B_{pj})\quad{if }\ell_{iu} eq iag{****} a(u)=min(a(u),a(p)Bpj)ifiueqiag
a ∗ ( u ) = a ( p ) o t h e r w i s e a g ∗ ∗ ∗ ∗ ∗ a^\ast(u)=a(p)\quad{otherwise}ag{*****} a(u)=a(p)otherwiseag

δ \delta δ, γ \gamma γ, λ \lambda λ, ℓ i u \ell_{iu} iu分别为上述四个参数, ℓ i u \ell_{iu} iu为顶点 i i i的前驱顶点。在迭代的每一步中,使用启发式估计值较小的点,并评估距离目标点的估计距离较大的点。

求解源点到顶点 G G G的最短路径:

令目标点为顶点 F F F,源点为顶点 S S S,设置边容量矩阵为 [ [ 1 ] , [ 1 ] , [ 1 ] , [ 1 ] , [ 1 ] , [ 1 ] , [ − ∞ ] , [ − ∞ ] ] [[1],[1],[1],[1],[1],[1],[-\infty],[-\infty]] [[1],[1],[1],[1],[1],[1],[],[]],顶点间的边流量矩阵为 [ [ − ∞ , − 1 , 2 , 0 , − 1 , 0 , 0 , 0 ] , [ 0 , − ∞ , 0 , 1 , 0 , − 1 , 0 , 0 ] , [ − ∞ , 0 , − ∞ , 0 , 0 , 0 , 2 , 0 ] , [ 0 , − 1 , 0 , − ∞ , 1 , 0 , − 1 , 0 ] , [ − ∞ , 0 , 0 , − 1 , − ∞ , 1 , 0 , 2 ] , [ 0 , 0 , 0 , 0 , 0 , − ∞ , − ∞ , 1 ] , [ − ∞ , 0 , 2 , 0 , 0 , − ∞ , 0 , − ∞ ] , [ − ∞ , 0 , 0 , 0 , 1 , 0 , 0 , − ∞ ] ] [[-\infty,-1,2,0,-1,0,0,0],[0,-\infty,0,1,0,-1,0,0],[-\infty,0,-\infty,0,0,0,2,0],[0,-1,0,-\infty,1,0,-1,0],[-\infty,0,0,-1,-\infty,1,0,2],[0,0,0,0,0,-\infty,-\infty,1],[-\infty,0,2,0,0,-\infty,0,-\infty],[-\infty,0,0,0,1,0,0,-\infty]] [[,1,2,0,1,0,0,0],[0,,0,1,0,1,0,0],[,0,,0,0,0,2,0],[0,1,0,,1,0,1,0],[,0,0,1,,1,0,2],[0,0,0,0,0,,,1],[,0,2,0,0,,0,],[,0,0,0,1,0,0,]],顶点到自身的距离矩阵为 [ [ 0 , 2 , 1 , 3 , 4 , 5 , 6 , 7 ] , [ 2 , 0 , 1 , 3 , 4 , 5 , 6 , 7 ] , [ 1 , 1 , 0 , 1 , 2 , 3 , 4 , 5 ] , [ 3 , 3 , 1 , 0 , 1 , 2 , 3 , 4 ] , [ 4 , 4 , 2 , 1 , 0 , 1 , 2 , 3 ] , [ 5 , 5 , 3 , 2 , 1 , 0 , 1 , 2 ] , [ 6 , 6 , 4 , 3 , 2 , 1 , 0 , 1 ] , [ 7 , 7 , 5 , 4 , 3 , 2 , 1 , 0 ] ] [[0,2,1,3,4,5,6,7],[2,0,1,3,4,5,6,7],[1,1,0,1,2,3,4,5],[3,3,1,0,1,2,3,4],[4,4,2,1,0,1,2,3],[5,5,3,2,1,0,1,2],[6,6,4,3,2,1,0,1],[7,7,5,4,3,2,1,0]] [[0,2,1,3,4,5,6,7],[2,0,1,3,4,5,6,7],[1,1,0,1,2,3,4,5],[3,3,1,0,1,2,3,4],[4,4,2,1,0,1,2,3],[5,5,3,2,1,0,1,2],[6,6,4,3,2,1,0,1],[7,7,5,4,3,2,1,0]],设置 δ = 3 \delta=3 δ=3 γ = 2 \gamma=2 γ=2 λ = 1 \lambda=1 λ=1,求解源点 S S S到目标点 F F F的最短路径。

按照要求,可以修改A*算法,使用如下启发式规则:

  • 计算目标点到当前顶点的距离限制;
  • 计算边贴标签时,如果顶点 i i i的前驱顶点是 j j j,则不考虑该顶点。

最终求解结果为 a = [ 0 , 1 , 1 , 0 , 0 , 0 , 0 , 1 ] T \mathbf{a}=\left[0,1,1,0,0,0,0,1\right]^T a=[0,1,1,0,0,0,0,1]T

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

禅与计算机程序设计艺术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值