算法——图之无向图
来源:互联网 发布:2083欧洲独立宣言 知乎 编辑:程序博客网 时间:2024/05/18 19:21
图的概念
图是算法中是树的拓展,树是从上向下的数据结构,结点都有一个父结点(根结点除外),从上向下排列。而图没有了父子结点的概念,图中的结点都是平等关系,结果更加复杂。
图的分类
图可以分为无向图(简单连接),有向图(连接有方向),加权图(连接带权值),加权有向图(连接既有方向又有权值)。
这篇讨论无向图。
无向图的表示方法:
1.邻接矩阵
2.边的数组
3.邻接表数组
1.邻接矩阵
我们可以使用一个V*V的布尔矩阵graph来表示图。当顶点v和顶点w之间有边相连时,则graph[v][w]和graph[w][v]为true,否则为false。
但是这种方法需要占用的空间比较大,因为稀疏图更常见,这就导致了很多空间的浪费。V*V的矩阵很多时候我们是不能接受的。
2.边的数组
我们可以使用一个数组来存放所有的边,这样的话数组的大小仅有E。但是因为我们的操作总是需要访问某个顶点的相邻节点,对于这种数据类型,要访问相邻节点的话必须遍历整个数组,造成效率的低下,所以我们在这里也不使用这个数据结构。
3.邻接表数组
我们使用一个链表数组来表示,数组中每个元素都是链表表头,链表中存放对应下标的节点所连接的边。
这种数据结构使用的空间为V+E。并且可以相当方便的获取相邻节点。
如图:
实现如下:
import java.util.ArrayList;import java.util.List;public class Graph {private List<Integer>[] adj; // 邻接表private int V;// 顶点数目private int E;// 边的数目public Graph(int V) {this.V = V;adj = (List<Integer>[])new List[V];E = 0;for (int i = 0; i < V; i++) {adj[i] = new ArrayList<Integer>();}}public void addEdge(int v, int w) {adj[v].add(adj[v].size(), w);adj[w].add(adj[w].size(), v);E++;}public List<Integer> adj(int v) {return adj[v];}public int V() {return V;}public int E() {return E;}public String toString() {String s = V + " 个顶点, " + E + " 条边\n";for (int i = 0; i < V; i++) {s += i + ": ";for (Integer node : adj(i)) {s += node + " ";}s += "\n";}return s;}}
到此为止,我们已经完成了图的表示。
有了表示,我们就需要使用图完成一些简单的应用。
例如,图的搜索,图的连通分量,图是否有环等等。
首先我们来实现图的搜索。
我们在这里实现一个模板。并不实际进行搜索。
目标:给定一个起点,在图中进行搜索。
方案:1.深度优先搜索 2.广度优先搜索。
深搜
原理:这里打个比喻,搜索图中所有的节点,就像走迷宫一样,需要探索迷宫中所有的通道。探索迷宫所有的通道,我们需要什么呢?
1.我们需要选择一条没有标记的路,并且一边走一遍铺上一条绳子。
2.标记走过的路。
3.当走到一个标记的地方时,我们需要回退,根据绳子回退到上一个地方。
4.当回退的地方没有可以走的路了,就要继续回退。
也就是说,首先我们需要一直走下去,但是我们一边走就要一边做标记,如果走不下去了,就回退,回退到没有被标记的的路。循环往复,我们就能探索整个图了。
实现:
import java.util.Stack;public class DepthFirstSearch {private boolean[] isMarked;private int begin;private int count;private Integer[] edgeTo; public DepthFirstSearch(Graph g, int begin) {isMarked = new boolean[g.V()];edgeTo = new Integer[g.V()];count = 0;this.begin = begin;dfs(g, begin);}public void dfs(Graph g, int begin) {isMarked[begin] = true;for (Integer i : g.adj(begin)) {if (!isMarked[i]) {edgeTo[i] = begin;count++;dfs(g, i);}}}public boolean hasPath(int v) {return isMarked[v];}public int count() {return count;}public String pathTo(int v) {if (!hasPath(v)) return "";Stack<Integer> stack = new Stack<>();stack.push(v);for (int i = v; i != begin; i = edgeTo[i]) {stack.push(edgeTo[i]);}return stack.toString();}}我们需要一个数组来标记某个节点是否已经走过了,如果走过了,我们就不会再走了。
并且我们有一个数组去保存是从哪个节点到达当前节点。这样,我们往回追朔的时候,就可以找到一条路径了。
这是一个模板,并没有具体的搜索某个节点,而是将所有节点都搜索了一遍,在实际过程中,我们可以判断节点是否找到,找到就停止了。
对于无向图来说,深搜虽然可以找到一条从v到w的路径,但是这条路径是否是最优的并不是可靠的,往往都不是。
如果我们希望找到一条最短的路径,我们就应该使用广搜。
广搜
原理:
广搜并不是先一条路走到黑,而是慢慢的根据距离进行搜索。例如,一开始先根据距离是1进行搜索,先搜索所有距离为1的地方。如果没找到,再搜索距离为2的地方。以此类推。
如果说深搜是一个人在迷宫中搜索,那么广搜就是一组人在朝着各个方向进行搜索。当然不是效率比较高的意思,只是比喻而已。
实现:
import java.util.LinkedList;import java.util.Queue;import java.util.Stack;public class BreadthFirstSearch {private boolean[] isMarked;private Integer[] edgeTo;private int begin;private int count; // 多少个点连通public BreadthFirstSearch(Graph g, int begin) {isMarked = new boolean[g.V()];edgeTo = new Integer[g.V()];this.begin = begin;count = 0;bfs(g, begin);}private void bfs(Graph g, int begin) {Queue<Integer> queue = new LinkedList<>();isMarked[begin] = true;queue.offer(begin);while (!queue.isEmpty()) {Integer temp = queue.poll();for (Integer i : g.adj(temp)) {if (!isMarked[i]) {isMarked[i] = true;count++;edgeTo[i] = temp;queue.offer(i);}}}}public boolean hasPath(int v) {return isMarked[v];}public int count() {return count;}public String pathTo(int v) {if (!hasPath(v)) return "";Stack<Integer> stack = new Stack<>();stack.push(v);for (int i = v; i != begin; i = edgeTo[i]) {stack.push(edgeTo[i]);}return stack.toString();}}
其实广搜和深搜的不同就在于搜索规则的不同,深搜使用的是stack的LIFO(后进先出)的思想,总是搜索最新的节点。而广搜则是使用queue的FIFO(先进先出)的规则。
就如同上面的一样,节点进入队列的顺序是根据距离的,所以我们就可以实现慢慢范围的扩大搜索。
同样的,我们也标记了进入节点的前一个节点,用来追踪路径。因为我们是根据范围搜索的,所以得到的就是最短路径。
我们可以使用广搜和深搜来实现很多应用,例如是否有环,是否是二部图等等。这里我们就不展开了。
我们上面的图的节点都是以数字作为标记的,而对于实际应用来讲,图的节点一般都不会是数字,而是String类型的字符串等。
要实现这种符号图,我们只需要将我们的代码进行一些扩展,使用符号表的方法,将字符串映射到某个整数上就可以了。
例如:
我们只需要在将字符串映射得到一个数字,也就是使用散列表的方式,存储成键值对,就可以继续使用上面的代码了。
- 算法——图之无向图
- 无向图的最短路径求解算法之——Dijkstra算法
- 无向图的最短路径求解算法之——Dijkstra算法【转】
- 无向图的最短路径求解算法之——Dijkstra算法
- 每日一省之————无向图(无向非赋权图)
- 无向图回路算法
- 无向图DFS算法
- 算法4.1 无向图
- 十五、图的算法之无向图
- 无向图最短路之Dijkstra算法思想
- 第四周算法概论作业——无向图的DFS算法
- 无向图的最小割算法
- 无向图的割点算法
- 无向图的邻接表算法
- 无向图的邻接表算法
- 求无向图的关节点算法
- 算法导论 ch22 无向图 桥
- 无向图匹配的带花树算法
- JAVA-POI导出excel表格
- python3 windows 编码问题
- 连接
- IAR 开发STM8 学习
- sin(a+b)的展开式证明
- 算法——图之无向图
- Docker如何通过link方式进行通信
- 在Linux上安装Gearman及配置使用Gearman的PHP扩展环境
- 八数码
- JavaScript原型对象prototype
- java将request接受数据转换成map
- 【leetcode】surrounded-regions
- Spring 使用外部属性文件
- 获取linux系统中mem占用率 -- awk使用详解