算法 - 图的实例 - 最短路径 (Shortest Path)

本文详细介绍了图的最短路径问题,讲解了迪杰斯特拉算法和弗洛伊德算法的基本思想、设计思路,并提供了C++实现。文章适合对数据结构有一定基础的读者,探讨了如何在有向图中寻找最短路径,以及两种算法的时间复杂度和适用场景。

算法 - 图的实例 - 最短路径 (Shortest Path)

返回分类:全部文章 >> 基础知识

返回上级:编程基础 - 图 (Graph)

本文将介绍最短路径的基础知识,并用C++实现迪杰斯特拉算法(Dijkstra)和弗洛伊德算法(Floyed)。

在查看本文之前,需要一些数据结构和程序语言的基础。

尤其是“矩阵”、“矩阵的压缩(matrix)”“图(graph)”等的知识。



1 最短路径简述 (Introduction)

最短路径问题是找到一个顶点到另一个顶点之间权值之和最小的路径。

  • 一个顶点称为源点

  • 另一个顶点称为终点

其图是有向带权图。


2 迪杰斯特拉算法 (Dijkstra)

迪杰斯特拉算法 (Dijkstra) 适用于:

  • (1)源点只有一个;

  • (2)权值必须都是大于等于零。

2.1 算法基本思想 (Basic Idea)

Dijkstra算法意指按照路径长度的递增次序,逐步的产生最短路径。

  • (1)首先、求出长度最短的一条最短路径;

  • (2)其次,参照它求出长度次短的一条最短路径;

  • (3)最后,重复步骤(2)直到求出所有最短路径。

假设原图,起始点A

10
50
30
90
10
20
70
99
A
B
C
D
E
F

其从A开始到达各个点的权值,取最小值就是路径:

到达点\经过边数 1 2 3
B AB(10)
C ABC(60), ADC(50)
D AD(30)
E AE(90) ADE(100) ABCE(70), ADCE(60)
F

2.2 算法设计思路 (Design Idea)

其算法描述:

  • 假设 n 个顶点, e 条边的图,求得最短路径的集合为 P, 顶点为 { v0, v1, … , vn }, 权值和的数组为 cost[n]。
    • (1)初始化: P = { v0 } , cost[vadj] = Edge[v0][vadj] ;(adj为邻接顶点)
    • (2)获取最短路径,并添加顶点: cost[vmin] = min(cost) , P = { v0, vmin } ;
    • (3)更新路径长度与路径:cost[vminadj] = min(cost[vminadj], cost[vmin] + Edge[vmin][vminadj]) ;
    • (4)重复步骤(2)和(3):直到所有顶点检测完成。

其 n 个顶点的图,时间复杂度 O(n2) 。

2.3 C++代码 (C++ Code)

// Author: https://blog.youkuaiyun.com/DarkRabbit
// Shortest Path
// 迪杰斯特拉算法
// params:
//      graph:      需要计算的图
//      begin:      起始顶点
//      paths:      输出的顺序
//      costs:      输出的权值和
// return:
//      bool:       是否出错
bool Dujkstra(AMGraphInt* graph, 
              int begin, 
              std::vector<std::vector<int>>& paths, 
              std::vector<double>& costs)
{
   
   
    if (graph == nullptr || !graph->IsOriented()) // 无向图返回
    {
   
   
        return false;
    }

    int size = graph->GetVertexCount(); // 顶点数量
    if (begin < 0 || begin >= size) // 起始点不在图中,直接返回
    {
   
   
        return false;
    }

    double infinity = graph->GetDefaultWeight(); // 没有边的权值,即正无穷
    paths.assign(size, std::vector<int>()); // 初始化空路径
    costs.assign(size, infinity); // 初始化权值和
    std::vector<bool> marks(size, false); // 顶点标记(集合)

    // 起始顶点
    costs[begin] = 0;
    marks[begin] = true;
    paths[begin].push_back(begin);

    // 初始化costs,从起始点开始到达的首个顶点
    double weight = 0;
    for (int adj = graph->FirstNeighbor(begin); 
             adj != -1; 
             adj = graph->NextNeighbor(begin, adj))
    {
   
   
        graph->TryGetWeight(begin, adj, weight);
        paths[adj].push_back(begin); // begin 加入到 adj 的路径
        costs[adj] = weight;
    }

    // 循环,路径最多需要加入 size - 1 个顶点
    for (int i = 1; i < size; i++)
    {
   
   
        int minVertex = -1; // 选出未访问过的,cost最小的顶点
        double minCost = infinity; // 选出的最小cost

        // 选出未访问过的,cost最小的顶点
        for (int j = 0; j < size; j++)
        {
   
   
            // 如果顶点没有访问过,且cost更小
            if (!marks[j] && costs[j] < minCost)
            {
   
   
                minVertex = j;
                minCost = costs[j];
            }
        }

        if (minVertex == -1) // 未选出顶点,说明其它顶点无法到达
        {
   
   
            break;
        }

        marks[minVertex] = true; // 访问此顶点
        paths[minVertex].push_back(minVertex); // 加入路径

        // 查找minVertex的邻接顶点,并更新路径和cost
        for (int adj = graph->FirstNeighbor(minVertex);
                 adj != -1;
                 adj = graph->NextNeighbor(minVertex, adj))
        {
   
   
            graph->TryGetWeight(minVertex, adj, weight);
            
            // 如果新cost更小,则更新,新cost = 到达上一个顶点的cost + 到达此顶点的 cost
            if (!marks[adj] && costs[minVertex] + weight < costs[adj])
            {
   
   
                costs[adj] = costs[minVertex] + weight; // 更新cost
                paths[adj].clear();
                paths[adj].insert(paths[adj].begin(),
                                  paths[minVertex].begin(),
                                  paths[minVertex].end()); // 更新路径
            }
        }
    }

    return true;
}

3 弗洛伊德算法 (Floyed)

弗洛伊德算法 (Floyed) 相比较于 迪杰斯特拉算法 (Dijkstra) 它:

  • (1)可以有多个源点;

  • (2)权值可以是负数,但负数权值不能组成回路。

  • (3)复杂度更高。

3.1 算法基本思想 (Basic Idea)

对有 n n n 个顶点, e e e 条边,权值为 ( e i j ) n × n (e_{ij})_{n \times n} (eij)n×n 的图:

  • (1)它是通过一个 n n n 阶方阵求出每两点间的最短路径,即

    A = { A ( 0 ) , A ( 1 ) , ⋯ &ThinSpace; , A ( n ) } A = \{ A^{(0)}, A^{(1)}, \cdots, A^{(n)} \} A={ A(0),A(1),,A(n)}

    其中:
    A ( 0 ) = ( a i j ) n × n ( 0 ) , ( a i j ) ( 0 ) = e i j A ( k ) = ( a i j ) n × n ( k ) , ( a i j ) ( k ) = min ⁡ { ( a i j ) ( k − 1 ) , ( a i k ) ( k − 1 ) + ( a k j ) ( k − 1 ) } , k ∈ [ 1 , n ] \begin{aligned} A^{(0)} &amp;=(a_{ij})^{(0)}_{n \times n}, &amp;(a_{ij})^{(0)} &amp;=e_{ij} \\ A^{(k)} &amp;=(a_{ij})^{(k)}_{n \times n}, &amp;(a_{ij})^{(k)} &amp;= \min \{(a_{ij})^{(k-1)}, (a_{ik})^{(k-1)} + (a_{kj})^{(k-1)}\}, &amp; k \in [1, n] \end{aligned} A(0)A(k)=(aij)n×n(0),=(aij)n×n(k),(aij)(0)(aij)(k)=eij=min{ (aij)(k1),(aik)(k1)+(akj)(k1)},k[1,n]

  • (2) ( a i j ) ( 0 ) (a_{ij})^{(0)} (aij)(0) 是从顶点 v i v_i vi v j v_j vj ,以 v 0 v_0 v0 为中介的最短路径长度;

  • (3) ( a i j ) ( k ) (a_{ij})^{(k)} (aij

### 蚁群算法实现商队旅行最短路径计算 #### 算法原理 蚁群算法模仿自然界中蚂蚁群体的行为模式,特别是它们在寻找食物源时留下信息素痕迹的方式。每只蚂蚁根据局部的信息素浓度决定移动的方向,在迭代过程中不断更新信息素分布,最终形成一条较优的路径[^1]。 #### 数学模型 设 \( n \) 表示城市的数目;\( d_{ij} \) 是城市 i 和 j 间的距离矩阵;\( τ_{ij}(t) \) 表示 t 时刻边 (i, j) 上的信息素强度;\( η_{ij}=1/d_{ij} \),表示启发因子即能见度;\( α \) 控制信息素重要程度;\( β \) 控制可见性的重要程度;\( ρ(0<ρ<1) \) 是信息素蒸发系数;\( Q>0 \) 是常数用来调整新加入信息素的数量;\( Δτ_k^{ij}(t)=Q/L_k(t),\text{if ant }k\text{ travels from city }i\text{ to }j,\text{ otherwise it is zero}\)[^3]。 #### 实现步骤 为了简化说明,这里给出一个基于上述理论框架的具体Python代码实例: ```python import numpy as np from random import randint class AntColonyOptimizationTSP: def __init__(self, distances, num_ants=10, alpha=1, beta=5, evaporation_rate=.5, q_value=100): self.distances = distances self.pheromone = np.ones(self.distances.shape) / len(distances) self.all_inds = range(len(distances)) self.num_ants = num_ants self.alpha = alpha self.beta = beta self.evaporation_rate = evaporation_rate self.q_value = q_value def run(self, iterations): shortest_path = None all_time_shortest_path = ("placeholder", np.inf) for _ in range(iterations): paths = [] for _ in range(self.num_ants): cities = self._select_next_city() path_distance = sum([self.distances[cities[i % len(cities)],cities[(i+1)%len(cities)]]for i in range(len(cities))]) if path_distance < all_time_shortest_path[1]: all_time_shortest_path = (cities, path_distance) paths.append((cities, path_distance)) self._update_pheromones(paths) return all_time_shortest_path def _select_next_city(self): start_city = randint(0,len(self.distances)-1) visited_cities = [start_city] while len(visited_cities)<len(self.distances): move_to = self._pick_move(self.pheromone[start_city], self.distances[start_city]) visited_cities.append(move_to) start_city = move_to return visited_cities def _pick_move(self, pheromone, dists): row = ((pheromone ** self.alpha)*((1.0/np.array(dists))**self.beta)) norm_row=row/row.sum() choice=np.nonzero(np.random.multinomial(1,norm_row))[0][0] return choice def _update_pheromones(self, ants_paths): new_pheromone_matrix = [[0]*len(i) for i in self.distances] for path,distance in ants_paths: for move_from,move_to in zip(path,path[1:]+path[:1]): new_pheromone_matrix[move_from][move_to]+=self.q_value/distance self.pheromone=(self.pheromone*(1-self.evaporation_rate))+new_pheromone_matrix distances=[ [0,749,826,144], [749,0,146,664], [826,146,0,726], [144,664,726,0]] aco_tsp_solver = AntColonyOptimizationTSP(distances,num_ants=20,alpha=1,beta=5,evaporation_rate=.5,q_value=100) shortest_path = aco_tsp_solver.run(iterations=1000) print(f'Shortest Path found by ACO TSP Solver:{shortest_path}') ``` 此段程序定义了一个名为 `AntColonyOptimizationTSP` 的类用于解决给定的城市间距离矩阵上的旅行销售员问题(TSP)。通过多次运行该算法可以获得接近全局最优解的结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值