In directed graphs, edges are one-way: the pair of vertices that defines each edge is an ordered pair that specifies a one-way adjacency. Many applications (for example, graphs that represent the web, scheduling constraints, or telephone calls) are naturally expressed in terms of directed graphs. The one-way restriction is natural, easy to enforce in our implementations, and seems innocuous; but it implies added combinatorial structure that has profound implications for our algorithms and makes working with directed graphs quite different from working with undirected graphs.
Glossary
Our definitions for directed graphs are nearly identical to those for undirected graphs but they are worth restating. The slight differences in the wording to account for edge directions imply structural properties that will be the focus of this section.
- A directed graph (or digraph) is a set of vertices and a collection of directed edges. Each directed edge connects an ordered pair of vertices.
- A directed path in a digraph is a sequence of vertices in which there is a (directed) edge pointing from each vertex in the sequence to its successor in the sequence. A directed cycle is a directed path with at least one edge whose first and last vertices are the same. A simple cycle is a cycle with no repeated edges or vertices (except the requisite repetition of the first and last vertices). The length of a path or a cycle is its number of edges.
public class Digraph { private final int V; private int E; private Bag<Integer>[] adj; /** * Create an empty digraph with V vertices. * * @throws java.lang.IllegalArgumentException if V < 0 */ public Digraph(int V) { if (V < 0) throw new IllegalArgumentException("Number of vertices in a Digraph must be nonnegative"); this.V = V; this.E = 0; adj = (Bag<Integer>[]) new Bag[V]; for (int v = 0; v < V; v++) { adj[v] = new Bag<Integer>(); } } /** * Create a digraph from input stream. */ public Digraph(In in) { try { this.V = in.readInt(); if (V < 0) throw new IllegalArgumentException("Number of vertices in a Digraph must be nonnegative"); adj = (Bag<Integer>[]) new Bag[V]; for (int v = 0; v < V; v++) { adj[v] = new Bag<Integer>(); } int E = in.readInt(); if (E < 0) throw new IllegalArgumentException("Number of edges in a Digraph must be nonnegative"); for (int i = 0; i < E; i++) { int v = in.readInt(); int w = in.readInt(); addEdge(v, w); } } catch (NoSuchElementException e) { throw new InputMismatchException("Invalid input format in Digraph constructor"); } } /** * Copy constructor. */ public Digraph(Digraph G) { this(G.V()); this.E = G.E(); for (int v = 0; v < G.V(); v++) { // reverse so that adjacency list is in same order as original Stack<Integer> reverse = new Stack<Integer>(); for (int w : G.adj[v]) { reverse.push(w); } for (int w : reverse) { adj[v].add(w); } } } /** * Return the number of vertices in the digraph. */ public int V() { return V; } /** * Return the number of edges in the digraph. */ public int E() { return E; } /** * Add the directed edge v->w to the digraph. * * @throws java.lang.IndexOutOfBoundsException unless both 0 <= v < V and 0 <= w < V */ public void addEdge(int v, int w) { if (v < 0 || v >= V) throw new IndexOutOfBoundsException("vertex " + v + " is not between 0 and " + (V - 1)); if (w < 0 || w >= V) throw new IndexOutOfBoundsException("vertex " + w + " is not between 0 and " + (V - 1)); adj[v].add(w); E++; } /** * Return the list of vertices pointed to from vertex v as an Iterable. * * @throws java.lang.IndexOutOfBoundsException unless 0 <= v < V */ public Iterable<Integer> adj(int v) { if (v < 0 || v >= V) throw new IndexOutOfBoundsException(); return adj[v]; } /** * Return the reverse of the digraph. */ public Digraph reverse() { Digraph R = new Digraph(V); for (int v = 0; v < V; v++) { for (int w : adj(v)) { R.addEdge(w, v); } } return R; } /** * Return a string representation of the digraph. */ public String toString() { StringBuilder s = new StringBuilder(); String NEWLINE = System.getProperty("line.separator"); s.append(V + " vertices, " + E + " edges " + NEWLINE); for (int v = 0; v < V; v++) { s.append(String.format("%d: ", v)); for (int w : adj[v]) { s.append(String.format("%d ", w)); } s.append(NEWLINE); } return s.toString(); } }
Representation. We use the adjacency-lists representation,where an edge v->w is represented as a list node containing w in the linked list corresponding to v. This representation is essentially the same as for undirected graphs but is even more straight forward because each edge occurs just once, as shown on the facing page.
Input format. The code for the constructor that takes a digraph from an input stream is identical to the corresponding constructor in Graph—the input format is the same, but all edges are interpreted to be directed edges.In the list-of-edgesformat,apairv w is interpreted as an edge v->w.
Reversing a digraph. Digraph also adds to the API a method reverse() which returns a copy of the digraph, with all edges reversed. This method is sometimes needed in digraph processing because it allows clients to find the edges that point to each vertex, while adj() gives just vertices connected by edges that point from each vertex.
Symbolic names. It is also a simple matter to allow clients to use symbolic names in digraph applications. To implement a class SymbolDigraph like SymbolGraph, replace Graph by Digraph everywhere.
Single-source reachability. Given a digraph and a source vertex s, support queries of the form Is there a directed path from s to a given target vertex v? DirectedDFS on the facing page is a slight embellishment of DepthFirstSearch which is the answer to this question.
By adding a second constructor that takes a list of vertices, this API supports for clients the following generalization of the problem:
Multiple-source reachability. Given a digraph and a set of source vertices, support queries of the form Is there a directed path from any vertex in the set to a given target vertex v?
public class DirectedDFS { private boolean[] marked; // marked[v] = true if v is reachable // from source (or sources) // single-source reachability public DirectedDFS(Digraph G, int s) { marked = new boolean[G.V()]; dfs(G, s); } // multiple-source reachability public DirectedDFS(Digraph G, Iterable<Integer> sources) { marked = new boolean[G.V()]; for (int v : sources) dfs(G, v); } private void dfs(Digraph G, int v) { marked[v] = true; for (int w : G.adj(v)) { if (!marked[w]) dfs(G, w); } } // is there a directed path from the source (or sources) to v? public boolean marked(int v) { return marked[v]; } }
DFS marks all the vertices in a digraph reachable from a given set of sources in time proportional to the sum of the outdegrees of the vertices marked.
Finding paths in digraphs.
Single-source directed paths. Given a digraph and a source vertex s, support queries of the form Is there a directed path from s to a given target vertex v? If so, find such a path.
public class DepthFirstDirectedPaths { private boolean[] marked; // marked[v] = true if v is reachable from s private int[] edgeTo; // edgeTo[v] = last edge on path from s to v private final int s; // source vertex // single source public DepthFirstDirectedPaths(Digraph G, int s) { marked = new boolean[G.V()]; edgeTo = new int[G.V()]; this.s = s; dfs(G, s); } private void dfs(Digraph G, int v) { marked[v] = true; for (int w : G.adj(v)) { if (!marked[w]) { edgeTo[w] = v; dfs(G, w); } } } // is there a directed path from s to v? public boolean hasPathTo(int v) { return marked[v]; } // return path from s to v; null if no such path public Iterable<Integer> pathTo(int v) { if (!hasPathTo(v)) return null; Stack<Integer> path = new Stack<Integer>(); for (int x = v; x != s; x = edgeTo[x]) path.push(x); path.push(s); return path; } }
Single-source shortest directed paths. Given a digraph and a source vertex s, support queries of the form Is there a directed path from s to a given target vertex v? If so, find a shortest such path (one with a minimal number of edges).
public class BreadthFirstDirectedPaths { private static final int INFINITY = Integer.MAX_VALUE; private boolean[] marked; // marked[v] = is there an s->v path? private int[] edgeTo; // edgeTo[v] = last edge on shortest s->v path private int[] distTo; // distTo[v] = length of shortest s->v path // single source public BreadthFirstDirectedPaths(Digraph G, int s) { marked = new boolean[G.V()]; distTo = new int[G.V()]; edgeTo = new int[G.V()]; for (int v = 0; v < G.V(); v++) distTo[v] = INFINITY; bfs(G, s); } // multiple sources public BreadthFirstDirectedPaths(Digraph G, Iterable<Integer> sources) { marked = new boolean[G.V()]; distTo = new int[G.V()]; edgeTo = new int[G.V()]; for (int v = 0; v < G.V(); v++) distTo[v] = INFINITY; bfs(G, sources); } // BFS from single source private void bfs(Digraph G, int s) { Queue<Integer> q = new Queue<Integer>(); marked[s] = true; distTo[s] = 0; q.enqueue(s); while (!q.isEmpty()) { int v = q.dequeue(); for (int w : G.adj(v)) { if (!marked[w]) { edgeTo[w] = v; distTo[w] = distTo[v] + 1; marked[w] = true; q.enqueue(w); } } } } // BFS from multiple sources private void bfs(Digraph G, Iterable<Integer> sources) { Queue<Integer> q = new Queue<Integer>(); for (int s : sources) { marked[s] = true; distTo[s] = 0; q.enqueue(s); } while (!q.isEmpty()) { int v = q.dequeue(); for (int w : G.adj(v)) { if (!marked[w]) { edgeTo[w] = v; distTo[w] = distTo[v] + 1; marked[w] = true; q.enqueue(w); } } } } // length of shortest path from s (or sources) to v public int distTo(int v) { return distTo[v]; } // is there a directed path from s (or sources) to v? public boolean hasPathTo(int v) { return marked[v]; } // shortest path from s (or sources) to v; null if no such path public Iterable<Integer> pathTo(int v) { if (!hasPathTo(v)) return null; Stack<Integer> path = new Stack<Integer>(); int x; for (x = v; distTo[x] != 0; x = edgeTo[x]) path.push(x); path.push(x); return path; } }
Cycles and DAGs
Directed cycles are of particular importance in applications that involve processing digraphs. To motivate the study of the role of directed cycles in digraph processing we consider, as a running example, the following prototypical application where digraph models arise directly:
Scheduling problems. A widely applicable problem-solving model has to do with arranging for the completion of a set of jobs, under a set of constraints, by specifying when and how the jobs are to be performed. Constraints might involve functions of the time taken or other resources consumed by the jobs. The most important type of constraints is precedence constraints, which specify that certain tasks must be performed before certain others. Different types of additional constraints lead to many different types of scheduling problems, of varying difficulty.
Precedence-constrained scheduling. Given a set of jobs to be completed, with precedence constraints that specify that certain jobs have to be completed before certain other jobs are begun, how can we schedule the jobs such that they are all completed while still respecting the constraints?
For any such problem, a digraph model is immediate, with vertices corresponding to jobs and directed edges corresponding to precedence constraints. In digraphs, precedence-constrained scheduling amounts to the following fundamental problem:
Topological sort. Given a digraph, put the vertices in order such that all its directed edges point from a ver- tex earlier in the order to a vertex later in the order (or report that doing so is not possible).
Cycles in digraphs. If job x must be completed before job y, job y before job z, and job z before job x, then someone has made a mistake, because those three constraints cannot all be satisfied. In general, if a precedence-constrained scheduling problem has a directed cycle, then there is no feasible solution. To check for such errors, we need to be able to solve the following problem: Directed cycle detection. Does a given digraph have a directed cycle? If so, find the vertices on some such cycle, in order from some vertex back to itself. A directed acyclic graph (DAG) is a digraph with no directed cycles.
public class DirectedCycle { private boolean[] marked; // marked[v] = has vertex v been marked? private int[] edgeTo; // edgeTo[v] = previous vertex on path to v private boolean[] onStack; // onStack[v] = is vertex on the stack? private Stack<Integer> cycle; // directed cycle (or null if no such cycle) public DirectedCycle(Digraph G) { marked = new boolean[G.V()]; onStack = new boolean[G.V()]; edgeTo = new int[G.V()]; for (int v = 0; v < G.V(); v++) if (!marked[v]) dfs(G, v); } // check that algorithm computes either the topological order or finds a directed cycle private void dfs(Digraph G, int v) { onStack[v] = true; marked[v] = true; for (int w : G.adj(v)) { // short circuit if directed cycle found if (cycle != null) return; //found new vertex, so recur else if (!marked[w]) { edgeTo[w] = v; dfs(G, w); } // trace back directed cycle else if (onStack[w]) { cycle = new Stack<Integer>(); for (int x = v; x != w; x = edgeTo[x]) { cycle.push(x); } cycle.push(w); cycle.push(v); } } onStack[v] = false; } public boolean hasCycle() { return cycle != null; } public Iterable<Integer> cycle() { return cycle; } // certify that digraph is either acyclic or has a directed cycle private boolean check(Digraph G) { if (hasCycle()) { // verify cycle int first = -1, last = -1; for (int v : cycle()) { if (first == -1) first = v; last = v; } if (first != last) { System.err.printf("cycle begins with %d and ends with %d\n", first, last); return false; } } return true; } }
Depth-first orders and topological sort. Precedence-constrained scheduling amounts to computing a topological order for the vertices of a DAG, a digraph has a topological order if and only if it is a DAG.
Remarkably, it turns out that we have already seen an algorithm for topological sort: a one-line addition to our standard recursive DFS does the job! To convince you of this fact, we begin with the class DepthFirstOrder on page 580. It is based on the idea that depth-first search visits each vertex exactly once. If we save the vertex given as argument to the recursive dfs() in a data structure, then iterate through that data structure, we see all the graph vertices, in order determined by the nature of the data structure and by whether we do the save before or after the recursive calls. Three vertex orderings are of interest in typical applications:
■ Preorder : Put the vertex on a queue before the recursive calls.
■ Postorder : Put the vertex on a queue after the recursive calls.
■ Reverse postorder : Put the vertex on a stack after the recursive calls.
A trace of DepthFirstOrder for our sample DAG is given on the facing page. It is simple to implement and supports pre(), post(), and reversePost() methods that are useful for advanced graph-processing algorithms. For example, order() in Topological consists of a call on reversePost().
public class DepthFirstOrder { private boolean[] marked; // marked[v] = has v been marked in dfs? private int[] pre; // pre[v] = preorder number of v private int[] post; // post[v] = postorder number of v private Queue<Integer> preorder; // vertices in preorder private Queue<Integer> postorder; // vertices in postorder private int preCounter; // counter or preorder numbering private int postCounter; // counter for postorder numbering // depth-first search preorder and postorder in a digraph public DepthFirstOrder(Digraph G) { pre = new int[G.V()]; post = new int[G.V()]; postorder = new Queue<Integer>(); preorder = new Queue<Integer>(); marked = new boolean[G.V()]; for (int v = 0; v < G.V(); v++) if (!marked[v]) dfs(G, v); } // depth-first search preorder and postorder in an edge-weighted digraph public DepthFirstOrder(EdgeWeightedDigraph G) { pre = new int[G.V()]; post = new int[G.V()]; postorder = new Queue<Integer>(); preorder = new Queue<Integer>(); marked = new boolean[G.V()]; for (int v = 0; v < G.V(); v++) if (!marked[v]) dfs(G, v); } // run DFS in digraph G from vertex v and compute preorder/postorder private void dfs(Digraph G, int v) { marked[v] = true; pre[v] = preCounter++; preorder.enqueue(v); for (int w : G.adj(v)) { if (!marked[w]) { dfs(G, w); } } postorder.enqueue(v); post[v] = postCounter++; } // run DFS in edge-weighted digraph G from vertex v and compute preorder/postorder private void dfs(EdgeWeightedDigraph G, int v) { marked[v] = true; pre[v] = preCounter++; preorder.enqueue(v); for (DirectedEdge e : G.adj(v)) { int w = e.to(); if (!marked[w]) { dfs(G, w); } } postorder.enqueue(v); post[v] = postCounter++; } public int pre(int v) { return pre[v]; } public int post(int v) { return post[v]; } // return vertices in postorder as an Iterable public Iterable<Integer> post() { return postorder; } // return vertices in preorder as an Iterable public Iterable<Integer> pre() { return preorder; } // return vertices in reverse postorder as an Iterable public Iterable<Integer> reversePost() { Stack<Integer> reverse = new Stack<Integer>(); for (int v : postorder) reverse.push(v); return reverse; } // check that pre() and post() are consistent with pre(v) and post(v) private boolean check(Digraph G) { // check that post(v) is consistent with post() int r = 0; for (int v : post()) { if (post(v) != r) { StdOut.println("post(v) and post() inconsistent"); return false; } r++; } // check that pre(v) is consistent with pre() r = 0; for (int v : pre()) { if (pre(v) != r) { StdOut.println("pre(v) and pre() inconsistent"); return false; } r++; } return true; } }
Reverse post order in a DAG is a topological sort. With DFS,we can topologically sort a DAG in time proportional to V+E.
public class Topological { private Iterable<Integer> order; // topological order // topological sort in a digraph public Topological(Digraph G) { DirectedCycle finder = new DirectedCycle(G); if (!finder.hasCycle()) { DepthFirstOrder dfs = new DepthFirstOrder(G); order = dfs.reversePost(); } } // topological sort in an edge-weighted digraph public Topological(EdgeWeightedDigraph G) { EdgeWeightedDirectedCycle finder = new EdgeWeightedDirectedCycle(G); if (!finder.hasCycle()) { DepthFirstOrder dfs = new DepthFirstOrder(G); order = dfs.reversePost(); } } // return topological order if a DAG; null otherwise public Iterable<Integer> order() { return order; } // does digraph have a topological order? public boolean hasOrder() { return order != null; } public static void main(String[] args) { String filename = args[0]; String delimiter = args[1]; SymbolDigraph sg = new SymbolDigraph(filename, delimiter); Topological topological = new Topological(sg.G()); for (int v : topological.order()) { System.out.println(sg.name(v)); } } }
相关推荐
源代码可能展示了如何在C#环境中实现力导向图的各个部分,包括数据结构、力的计算、布局算法和图形渲染。为了进一步了解这个项目,你可以解压文件并阅读源代码,理解作者是如何应用上述步骤的。 总的来说,力导向图...
### ACM竞赛常用算法模板与数据结构详解 #### 图论(Graph Theory) ...以上是吉林大学ACM/ICPC代码库中提到的一些重要知识点的概述,通过深入学习这些算法和数据结构,可以显著提升解决实际问题的能力。
6. **图的矩阵表示**:邻接矩阵和邻接表是图的两种常见数据结构,MATLAB中可以方便地处理这两种结构,进行图的构建和操作。 7. **图的剪枝和生成**:如生成树的剪枝操作,以及Euler图和Hamilton图的生成。 8. **...
这些概念是图论的基础,对于理解图数据结构、解决图相关的算法问题以及在大数据分析、数据挖掘等领域应用图算法至关重要。例如,在路由算法、社交网络分析、推荐系统等方面,都会用到图的性质和算法。通过深入学习和...
RGL是用于图形数据结构和算法的框架。 库的设计很大程度上受用C ++编写的Boost Graph Library(BGL)影响。 有关图形数据结构和算法以及BGL的设计原理的更多链接和文档,请参见 。 关于图形术语的全面概述,可以...
在计算机科学与图论领域,有向图(Directed Graphs, 或简称 Digraphs)是一种重要的数据结构,其中的边具有方向性,用于表示从一个顶点到另一个顶点的单向路径。在有向图中,强连通分量(Strongly Connected ...
### 《算法导论》完整课件下载:深入解析图算法与贪心算法 #### 图的概念及表示方法 在《算法导论》这门课程中,图...对于学习计算机科学与算法的学生来说,这些内容至关重要,有助于理解更复杂的算法和数据结构。
在计算机科学领域,图是一种非常重要的数据结构,被广泛应用于各种实际问题的建模与解决。 #### 二、图的基本定义 ##### 1.1 简单图 简单图(simple graph)是由两个集合组成的有序对\( (V, E) \),其中: - \( V \...
总结,NetworkX是Python中强大的图论算法实现工具,提供了丰富的数据结构和算法,能够方便地处理有向图、多图以及进行各种图操作和可视化。通过深入理解和熟练使用这些功能,我们可以解决各种复杂的问题,如社交网络...
此外,还有一些特定的算法分类,如图的绘制(Drawing)、数据结构(Data Structure)、图的类型(Graphtypes)和图的特定视角(GraphViews)。 在图类型方面,手册指导用户如何选择合适的图类来代表和分析不同的...
grapho 是图论数据结构和算法的 Go 实现。 完整的文档可以在找到 图表 有向图和无向图都是使用Graph结构创建的: graph := grapho.NewGraph(false) // true for directed Graphs, false for undirected Nodes ...
它提供了数据结构来表示各种类型的图(graph)以及用于图分析的标准算法。NetworkX 的设计目的是为了促进可读性、清晰度以及模块化。 #### 二、NetworkX用户群体 1. **科研人员**:在社会学、生物学、物理学等领域的...
NetworkX是一个Python的开源库,用于创建、操作复杂网络结构和进行网络分析,它提供了一系列的算法和数据结构来表示、操作和研究结构。networkx包是数据科学领域中处理网络数据的重要工具之一,尤其在社会网络分析、...
在IT领域,图(Graphs)是一种非常重要的数据结构,广泛应用于各种算法设计和问题解决。标题中的"graphs"指的是这个主题,而描述虽然简洁,但暗示我们将探讨与图相关的概念和技术。标签“C”表明我们将从C语言的角度...
NetworkX库提供了多种基础的图(Graphs)类型,包括无向图(Undirected graphs)、有向图(Directed graphs)、多重图(Multigraphs)以及多重有向图(Multidirected graphs)等。用户可以根据需要选择最适合的图...
- **定义**: 一种用于高效查询和更新数组区间的数据结构。 - **应用场景**: 数组区间查询、动态更新等。 - **算法步骤**: - 通过位操作实现快速查询和更新。 ##### 3.4 二维树状数组(2D Tree Array) - **定义**: ...
在IT领域,图和树是数据结构与算法中极为重要的概念,它们广泛应用于网络设计、数据库索引、操作系统、人工智能等多个方面。在这个“Graphs-and-Trees”项目中,我们很可能会探讨C++语言实现图和树的相关知识。C++是...
NetworkX还支持多种图的高效数据结构,能够快速处理大规模的网络数据,并且在算法设计上注重效率和可扩展性。此外,NetworkX的文档编写精细,为用户提供了一个全面了解和使用库功能的资源平台,适用于学习和研究图论...
宠物图(PetGraph)是Rust编程语言中的一个开源库,专门用于处理和操作图形数据结构。这个库为开发者提供了创建、操作...在实际项目中,这个库不仅简化了图形数据结构的实现,还为高效、可靠的图算法提供了坚实的基础。
在计算机科学中,图是一种非常重要的数据结构,用于表示对象之间的关系。本主题将深入探讨两种基本类型的图:有向图(Directed Graph)和无向图(Undirected Graph),并结合Python语言介绍它们的实现方法。 有向图...