DAG、Dijkstra 、Bellman - ford :原理、应用与异同

 一、DAG算法(基于拓扑排序计算单源最短路径

  1. 原理
    • DAG 是一种有向图,其中不存在任何有向环。这一特性使得在 DAG 上进行一些操作(如拓扑排序)成为可能。
    • 在 DAG 中,我们可以利用拓扑排序的结果来高效地解决一些问题。例如,如果我们要计算从一个源节点到其他节点的最长或最短路径,可以按照拓扑排序的顺序依次处理节点。因为拓扑排序保证了对于任何一条边 (u, v),u 在排序中的顺序在 v 之前。
  2. 应用
    • 任务调度:在项目管理中,不同的任务之间存在先后顺序关系,并且这种关系可以用 DAG 表示。例如,建筑工程中的各个施工步骤,某些步骤必须在其他步骤完成之后才能开始。
    • 编译系统中的依赖关系:编译程序时,源文件之间可能存在依赖关系,如头文件与源文件的关系。这种依赖关系构成了一个 DAG,编译系统需要按照正确的顺序编译文件以避免错误。
  3. 示例 
    // 假设图以邻接表形式存储,graph为图结构,s为源节点
    // 拓扑排序辅助函数,使用深度优先搜索
    void topologicalSortUtil(int v, bool visited[], stack<int>& st, Graph graph) {
        visited[v] = true;
        AdjListNode* ptr = graph.adjList[v].head;
        while (ptr!= NULL) {
            int i = ptr->dest;
            if (!visited[i]) {
                topologicalSortUtil(i, visited, st, graph);
            }
            ptr = ptr->next;
        }
        st.push(v);
    }
    
    // 拓扑排序
    void topologicalSort(stack<int>& st, Graph graph) {
        bool* visited = new bool[graph.V];
        for (int i = 0; i < graph.V; i++) {
            visited[i] = false;
        }
        for (int i = 0; i < graph.V; i++) {
            if (visited[i] == false) {
                topologicalSortUtil(i, visited, st, graph);
            }
        }
        delete[] visited;
    }
    
    // DAG算法计算单源最短路径
    void dagShortestPath(Graph graph, int s) {
        stack<int> st;
        topologicalSort(st, graph);
        int dist[graph.V];
        for (int i = 0; i < graph.V; i++) {
            dist[i] = INT_MAX;
        }
        dist[s] = 0;
        while (st.empty() == false) {
            int u = st.top();
            st.pop();
            if (dist[u]!= INT_MAX) {
                AdjListNode* ptr = graph.adjList[u].head;
                while (ptr!= NULL) {
                    int v = ptr->dest;
                    if (dist[u] + ptr->weight < dist[v]) {
                        dist[v] = dist[u] + ptr->weight;
                    }
                    ptr = ptr->next;
                }
            }
        }
        for (int i = 0; i < graph.V; i++) {
            if (dist[i] == INT_MAX) {
                printf("从 %d 到 %d 的距离为: INF\n", s, i);
            } else {
                printf("从 %d 到 %d 的距离为: %d\n", s, i, dist[i]);
            }
        }
    }

二、Dijkstra 算法(用于带非负权边的图)

  1. 原理
    • Dijkstra 算法用于解决带权有向图或无向图(所有边的权重为非负)中的单源最短路径问题。
    • 算法维护一个集合 S,表示已经确定了最短路径的节点集合。初始时,S 只包含源节点。同时,为每个节点 v 维护一个距离值 d [v],表示从源节点到 v 的当前最短距离估计。
    • 在每一步中,算法从不在 S 中的节点中选择距离值最小的节点 u,将 u 加入 S,并更新与 u 相邻的节点的距离值。具体来说,对于每个与 u 相邻的节点 v,新的距离估计 d [v]=min (d [v], d [u]+w (u, v)),其中 w (u, v) 是边 (u, v) 的权重。
    • 重复这个过程,直到所有节点都被加入到 S 中。
  2. 应用
    • 路由算法:在计算机网络中,当需要找到从一个源路由器到其他路由器的最短路径时,Dijkstra 算法可以被用来计算。例如,在一个企业网络中,找到从总部路由器到各个部门路由器的最优路径。
    • 导航系统:在地图导航中,计算从用户当前位置(源点)到目的地的最短路径(假设道路的权重可以是路程、时间等)。
  3. 示例
    // 假设图以邻接矩阵形式存储在graph中,V为节点数,s为源节点
    void dijkstra(int graph[][V], int s) {
        int dist[V];
        bool sptSet[V];
        for (int i = 0; i < V; i++) {
            dist[i] = INT_MAX;
            sptSet[i] = false;
        }
        dist[s] = 0;
        for (int count = 0; count < V - 1; count++) {
            int u = -1;
            for (int i = 0; i < V; i++) {
                if (!sptSet[i] && (u == -1 || dist[i] < dist[u])) {
                    u = i;
                }
            }
            sptSet[u] = true;
            for (int v = 0; v < V; v++) {
                if (!sptSet[v] && graph[u][v]!= INT_MAX && dist[u]!= INT_MAX && dist[u] + graph[u][v] < dist[v]) {
                    dist[v] = dist[u] + graph[u][v];
                }
            }
        }
        for (int i = 0; i < V; i++) {
            if (dist[i] == INT_MAX) {
                printf("从 %d 到 %d 的距离为: INF\n", s, i);
            } else {
                printf("从 %d 到 %d 的距离为: %d\n", s, i, dist[i]);
            }
        }
    }

三、Bellman - ford 算法(用于带权有向图,可处理负权边但不能有负权环)

  1. 原理
    • Bellman - ford 算法也用于解决带权有向图中的单源最短路径问题,但它能够处理包含负权重边的图(只要图中不存在负权重环)。
    • 算法通过对所有边进行多次松弛操作来逐渐逼近最短路径。对于一个有 n 个节点的图,最多需要进行 n - 1 次松弛操作。
    • 松弛操作是指:对于边 (u, v),如果 d [v]>d [u]+w (u, v),则更新 d [v]=d [u]+w (u, v)。在 n - 1 次松弛操作之后,如果还能对某些边进行松弛,就说明图中存在负权重环。
  2. 应用
    • 金融领域中的套利检测:在货币兑换网络中,不同货币之间的汇率可以看作是图中的边权重。如果存在一个货币兑换循环,使得通过这个循环可以无风险地获得利润(即存在负权重环),Bellman - ford 算法可以用来检测这种情况。
    • 动态规划问题的建模:一些可以被转化为图上最短路径问题的动态规划问题,当图中可能存在负权重边时,可以使用 Bellman - ford 算法求解。
  3. 示例
    // 假设图以边列表形式存储在edges数组中,E为边数,V为节点数,s为源节点
    void bellmanFord(struct Edge edges[], int E, int V, int s) {
        int dist[V];
        for (int i = 0; i < V; i++) {
            dist[i] = INT_MAX;
        }
        dist[s] = 0;
        for (int i = 0; i < V - 1; i++) {
            for (int j = 0; j < E; j++) {
                int u = edges[j].src;
                int v = edges[j].dest;
                int weight = edges[j].weight;
                if (dist[u]!= INT_MAX && dist[u] + weight < dist[v]) {
                    dist[v] = dist[u] + weight;
                }
            }
        }
        for (int j = 0; j < E; j++) {
            int u = edges[j].src;
            int v = edges[j].dest;
            int weight = edges[j].weight;
            if (dist[u]!= INT_MAX && dist[u] + weight < dist[v]) {
                printf("图中存在负权环\n");
                return;
            }
        }
        for (int i = 0; i < V; i++) {
            if (dist[i] == INT_MAX) {
                printf("从 %d 到 %d 的距离为: INF\n", s, i);
            } else {
                printf("从 %d 到 %d 的距离为: %d\n", s, i, dist[i]);
            }
        }
    }

四、异同点

(一)相同点

  1. 目标相同
    • 三者都用于解决图中的最短路径问题,特别是单源最短路径问题,即找到从一个源节点到图中其他节点的最短路径。

(二)不同点

  1. 图的适用类型
    • DAG 算法:专门用于有向无环图。
    • Dijkstra 算法:适用于带非负权边的有向图或无向图。如果图中存在负权边,可能导致错误结果。
    • Bellman - ford 算法:适用于带权有向图,能处理负权边,但图中不能存在负权环,否则结果无意义。
  2. 算法复杂度
    • DAG 算法:如果拓扑排序使用深度优先搜索实现,时间复杂度为O(V+E) (V 是节点数,E 是边数),计算最短路径时间复杂度为O(V+E) ,总体复杂度为O(V+E)
    • Dijkstra 算法:如果使用二叉堆实现优先队列,时间复杂度为O((V+E)logV) ;如果使用邻接矩阵实现,时间复杂度为O(V^2) 。
    • Bellman - ford 算法:时间复杂度为 O(VE)
  3. 算法思想
    • DAG 算法:基于拓扑排序的顺序进行距离更新。
    • Dijkstra 算法:基于贪心策略,每次选择当前距离源节点最近的节点进行扩展。
    • Bellman - ford 算法:基于多次对所有边进行松弛操作来逼近最短路径。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值