图的遍历和从Shortest path tree 问题

本文章是关于CS61b课程的部分算法总结

图的定义:

图由一系列节点和边组成,每个边连接两个节点,由一条边连接的节点称为相邻结点。其中如果一个图满足以下条件:边不可以两端连接同一个结点(不能有节点连接自身);不能有两根边连接两个相同节点(即两节点平行),则称为该图为simple graph(简单图)。(注:所有的树结构都是图)

另外,如下所示,图分为有向图和无向图。

图的遍历

图的遍历是图最常见的一种算法,用于解决节点相连等问题。遍历主要分为深度优先遍历、广度优先遍历,

深度优先遍历(Depth-First Traversal)

从一个图中某个顶点V出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和V有路径相通的顶点都被访问到。深度优先遍历针对一个邻居,总是优先遍历这个邻居的到最深处。如下图所示,如果从0开始遍历,如果一个节点有多个子节点,那么总是优先遍历数值较小的节点,那么下图使用DFT的遍历顺序如下:

0——1——2——5——4——3

到达3之后由于没有未被遍历的相邻节点了,所以向上找,找到5,然后继续遍历

6——7,同样的,再次向上,最后遍历8。遍历结束。

广度优先遍历(BreadthFirstTraversal):

指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。

如下图所示,如果从0开始遍历,如果一个节点有多个子节点,那么总是优先遍历数值较小的节点,那么下图按照BFT的访问顺序则为 

0——1——2——4——5——3——6——8——7

最短路径

对于边的权重不同可以为边添加weight数值,如下所示。如果对于这样的图,想要找从一点s到另一点t的权重最短的路径,称为最短路径问题(shortest path)。解决方法有Dijkstra’s 算法和 A*算法。

Dijkstra’s 算法:

从一个节点出发,道道其余所有节点的最短路径是一个树结构,称之为最短路径树。最短路径树种访问每个节点的路径都是唯一的,且是最短的。Dijkstra的算法如下:首先需要一个节点距离出发点的距离d数据,一个一个在最短路径树中的上一个节点数据,即为edgeTo

1、初始化所有节点距离远点的距离数据D,出发点s的距离为0,其余为正无穷。并将edgeTo 设为null。

2、从队列中取出最近的结点(第一次即S),计算该结点到其相邻结点的距离。

3、将该距离加上其与相邻节点间的距离则为相邻节点到原点的距离,比较该如果比已经有的数据更近,则更新该数据并重新排序。然后更新edgeTo的数据,将edgeto设为2中取出的节点。

4、重复上述过程直至所有节点都被访问到。

故上图的遍历结果如下:初始化后,从中取出S,距离为0,更新其相邻节点1,2的数据,更新后如下 

计算s到其下属结点的距离,到1的d 为2,到2的d为1,更新该数据,并排序;

现在距离最小的为1,结点2,计算2到期下属结点的距离并更新,2到5的距离为15,故更新5经到出发点的距离为1+15 = 16< 正无穷。 故更新5的数据,d = 16,edgeTo = 2并排序。

现在距离s最近的是结点1,距离为2,将其拿出并计算。其到下苏3,4,2的距离更新并计算。

1到2:2+5>1,不更新

1到3:2+11<无穷,更新,D = 2+11 = 13,edgeTo 改为1,同理4也更新,d = 2+3 = 5,edgeTo改为1.

现在距离s的最短结点为4,,距离为5,故更新到5,6的距离和edgeTo:

5没有下属结点,将6拿出,更新3,得到全部结点。获得shortest path tree。

全部遍历结束后的结果:

这样就根据Dijkstra算法得到了最短路径树,也就解决了开头提出的从一个节点到达其他节点的最短路径问题。

 A*算法

相比于Dijkstra算法,A*算法引入了评价函数估计函数H()的概念,该函数能够基于其他算法估计一个节点与目标节点之间的距离。其准确性不高。

下图中H即为节点v,距离goal之间的距离,和dijkstra类似的,将从0出发到达节点V的距离记为D,上一个节点记为edgeto,V到目标节点的距离记为H,按照H+D的大小进行排序,从最小距离开始遍历。

遍历结束后的更新状态为:

 由此就得到了从原点到达目标的最终最短路径。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
对于问题,我可以帮你解答,但是我不是一个可以执行代码的机器人,只能提供思路和算法。 1. 建立有向的邻接矩阵和邻接表存储结构: 邻接矩阵存储结构: 对于有 N 个顶点的,邻接矩阵是一个 N*N 的矩阵,其中第 i 行第 j 列的值表示从顶点 i 到顶点 j 是否有一条边。如果有,则为 1,否则为 0。 对于该,邻接矩阵如下: ``` 0 1 2 3 4 5 6 0 0 1 0 0 1 1 0 1 0 0 0 1 0 1 1 2 0 0 0 0 1 0 1 3 0 0 1 0 0 0 1 4 0 0 0 0 0 0 1 5 0 0 0 0 0 0 1 6 0 0 0 0 0 0 0 ``` 邻接表存储结构: 对于有 N 个顶点的,邻接表是一个长度为 N 的数组,每个数组元素对应一个顶点,每个元素是一个链表,链表中存储从该顶点出发的所有边所连接的顶点。 对于该,邻接表如下: ``` 0 -> 1 -> 4 -> 5 1 -> 3 -> 5 -> 6 2 -> 4 -> 6 3 -> 2 -> 6 4 -> 6 5 -> 6 6 -> ``` 2. 根据邻接表,实现G的从顶点0开始的深度优先遍历和广度优先遍历运算: 深度优先遍历: 深度优先遍历使用栈来实现。首先将起点入栈,然后弹出栈顶元素,访问该元素,并将其未被访问过的邻居入栈,直到栈为空。 ``` void dfs(int start, vector<vector<int>>& graph, vector<bool>& visited) { stack<int> st; st.push(start); visited[start] = true; while (!st.empty()) { int cur = st.top(); st.pop(); cout << cur << " "; for (int neighbor : graph[cur]) { if (!visited[neighbor]) { visited[neighbor] = true; st.push(neighbor); } } } } ``` 广度优先遍历: 广度优先遍历使用队列来实现。首先将起点入队,然后弹出队头元素,访问该元素,并将其未被访问过的邻居入队,直到队列为空。 ``` void bfs(int start, vector<vector<int>>& graph, vector<bool>& visited) { queue<int> q; q.push(start); visited[start] = true; while (!q.empty()) { int cur = q.front(); q.pop(); cout << cur << " "; for (int neighbor : graph[cur]) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } } } ``` 3. 根据邻接表,实现G的从顶点0开始的深度优先遍历生成树和广度优先遍历生成树: 深度优先遍历生成树: 深度优先遍历生成树使用递归来实现。从起点开始,依次访问与其相邻的未被访问过的顶点,对于每个未被访问过的顶点,将其标记为已访问,并将其加入到该顶点的子树中。 ``` void dfs_tree(int cur, vector<vector<int>>& graph, vector<bool>& visited, vector<int>& parent) { visited[cur] = true; for (int neighbor : graph[cur]) { if (!visited[neighbor]) { parent[neighbor] = cur; dfs_tree(neighbor, graph, visited, parent); } } } ``` 广度优先遍历生成树: 广度优先遍历生成树使用队列来实现。从起点开始,依次访问与其相邻的未被访问过的顶点,对于每个未被访问过的顶点,将其标记为已访问,并将其加入到该顶点的子树中。 ``` void bfs_tree(int start, vector<vector<int>>& graph, vector<bool>& visited, vector<int>& parent) { queue<int> q; q.push(start); visited[start] = true; while (!q.empty()) { int cur = q.front(); q.pop(); for (int neighbor : graph[cur]) { if (!visited[neighbor]) { visited[neighbor] = true; parent[neighbor] = cur; q.push(neighbor); } } } } ``` 4. 输出从顶点5到顶点2的所有长度为3的简单路径和最短路径: 简单路径: 使用深度优先搜索,记录搜索路径,每次搜索到终点时判断路径长度是否为 3,如果是,则输出路径。 ``` void dfs_path(int cur, int dest, int len, vector<vector<int>>& graph, vector<bool>& visited, vector<int>& path) { visited[cur] = true; path.push_back(cur); if (cur == dest && len == 3) { for (int v : path) { cout << v << " "; } cout << endl; } else { for (int neighbor : graph[cur]) { if (!visited[neighbor]) { dfs_path(neighbor, dest, len+1, graph, visited, path); } } } path.pop_back(); visited[cur] = false; } ``` 最短路径: 使用广度优先搜索,记录每个节点的距离和前驱节点,直到搜索到终点,根据前驱节点回溯输出路径。 ``` void bfs_shortest_path(int start, int dest, vector<vector<int>>& graph, vector<int>& distance, vector<int>& parent) { queue<int> q; q.push(start); distance[start] = 0; parent[start] = -1; while (!q.empty()) { int cur = q.front(); q.pop(); if (cur == dest) { break; } for (int neighbor : graph[cur]) { if (distance[neighbor] == -1) { distance[neighbor] = distance[cur] + 1; parent[neighbor] = cur; q.push(neighbor); } } } if (distance[dest] == -1) { cout << "No path exists!" << endl; } else { cout << "Shortest path: "; vector<int> path; for (int cur = dest; cur != -1; cur = parent[cur]) { path.push_back(cur); } reverse(path.begin(), path.end()); for (int v : path) { cout << v << " "; } cout << endl; } } ``` 5. 销毁G的邻接表: 遍历邻接表中的每个链表,释放链表中每个节点的内存,在释放链表的头节点的内存。最后将邻接表的指针置为 nullptr。 ``` void destroy_graph(vector<vector<int>*>& graph) { for (vector<int>* list : graph) { delete list; } graph.clear(); } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值