diff --git a/README.md b/README.md index 43e44b0ee..d10557d6e 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ save("mygraph.jgz", g, "mygraph", compress=true) - **distance:** eccentricity, diameter, periphery, radius, center -- **connectivity:** strongly- and weakly-connected components, bipartite checks, condensation, attracting components +- **connectivity:** strongly- and weakly-connected components, bipartite checks, condensation, attracting components, neighborhood - **operators:** complement, reverse, reverse!, union, join, intersect, difference, symmetric difference, blkdiag, induced subgraphs, products (cartesian/scalar) diff --git a/doc/basicmeasures.md b/doc/basicmeasures.md index 0d2496d98..a16825371 100644 --- a/doc/basicmeasures.md +++ b/doc/basicmeasures.md @@ -186,3 +186,17 @@ common_neighbors(g::Union{LightGraphs.DiGraph,LightGraphs.Graph}, u::Int64, v::I ``` Returns the neighbors common to vertices `u` and `v` in `g`. +### neighborhood +```julia +neighborhood(g, v::Int, d::Int; dir=:out) +``` + +Returns a vector of the vertices in `g` at distance less or equal to `d` from `v`. If `g` is a `DiGraph` the `dir` optional argument specifies the edge direction the edge direction with respect to `v` (i.e. `:in` or `:out`) to be considered. + +### egonet +```julia +egonet(g, v::Int, d::Int; dir=:out) +``` + +Returns the subgraph of `g` induced by the neighbors of `v` up to distance `d`. If `g` is a `DiGraph` the `dir` optional argument specifies the edge direction the edge direction with respect to `v` (i.e. `:in` or `:out`) to be considered. This is equivalent to `induced_subgraph(g, neighborhood(g, v, d, dir=dir)).` + diff --git a/doc/build.jl b/doc/build.jl index d89ce8974..1a9c06777 100644 --- a/doc/build.jl +++ b/doc/build.jl @@ -133,7 +133,8 @@ types: ## Neighbors and Degree -{{degree, indegree, outdegree, Δ, δ, Δout, δout, δin, Δin, degree_histogram, density, neighbors, in_neighbors, all_neighbors, common_neighbors}} +{{degree, indegree, outdegree, Δ, δ, Δout, δout, δin, Δin, degree_histogram, density, neighbors, + in_neighbors, all_neighbors, common_neighbors, neighborhood, egonet}} """ @file "centrality.md" """ @@ -354,7 +355,9 @@ Any graph traversal will traverse an edge only if it is present in the graph. W ## Connectivity / Bipartiteness `Graph connectivity` functions are defined on both undirected and directed graphs: -{{is_connected, is_strongly_connected, is_weakly_connected, connected_components, strongly_connected_components, weakly_connected_components, has_self_loop, attracting_components, is_bipartite, condensation, period}} +{{is_connected, is_strongly_connected, is_weakly_connected, connected_components, + strongly_connected_components, weakly_connected_components, has_self_loop, + attracting_components, is_bipartite, condensation, period, neighborhood}} ## Cycle Detection In graph theory, a cycle is defined to be a path that starts from some vertex diff --git a/doc/index.md b/doc/index.md index de3b96809..fffde854d 100644 --- a/doc/index.md +++ b/doc/index.md @@ -118,7 +118,7 @@ save("mygraph.jgz", g, "mygraph", compress=true) - **distance:** eccentricity, diameter, periphery, radius, center -- **connectivity:** strongly- and weakly-connected components, bipartite checks, condensation, attracting components +- **connectivity:** strongly- and weakly-connected components, bipartite checks, condensation, attracting components, neighborhood - **operators:** complement, reverse, reverse!, union, join, intersect, difference, symmetric difference, blkdiag, induced subgraphs, products (cartesian/scalar) diff --git a/doc/pathing.md b/doc/pathing.md index b9b556618..793ecee20 100644 --- a/doc/pathing.md +++ b/doc/pathing.md @@ -33,8 +33,9 @@ This function is a high level wrapper around bfs_tree!, use that function for mo ### dfs_tree ```julia -dfs_tree(g::Union{LightGraphs.DiGraph,LightGraphs.Graph}, s::Int64) +dfs_tree(g, s::Int) ``` + Provides a depth-first traversal of the graph `g` starting with source vertex `s`, and returns a directed acyclic graph of vertices in the order they were discovered. ### maximum_adjacency_visit @@ -62,9 +63,9 @@ Performs a [self-avoiding walk](https://en.wikipedia.org/wiki/Self-avoiding_walk `Graph connectivity` functions are defined on both undirected and directed graphs: ### is_connected ```julia -is_connected(g::LightGraphs.Graph) -is_connected(g::LightGraphs.DiGraph) +is_connected(g) ``` + Returns `true` if `g` is connected. For DiGraphs, this is equivalent to a test of weak connectivity. ### is_strongly_connected @@ -83,7 +84,8 @@ Returns `true` if the undirected graph of `g` is connected. ```julia connected_components(g) ``` -Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory)) of an undirected graph `g` as a vector of components, each represented by a vector of vectors of vertices belonging to the component. + +Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory)) of `g` as a vector of components, each represented by a vector of vertices belonging to the component. ### strongly_connected_components ```julia @@ -111,10 +113,11 @@ Returns a vector of vectors of integers representing lists of attracting compone ### is_bipartite ```julia -is_bipartite(g::Union{LightGraphs.DiGraph,LightGraphs.Graph}) -is_bipartite(g::Union{LightGraphs.DiGraph,LightGraphs.Graph}, s::Int64) +is_bipartite(g) +is_bipartite(g, v) ``` -Will return `true` if graph `g` is [bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). + +Will return `true` if graph `g` is [bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). If a node `v` is specified, only the connected component to which it belongs is considered. ### condensation ```julia @@ -131,13 +134,21 @@ period(g::LightGraphs.DiGraph) ``` Computes the (common) period for all nodes in a strongly connected graph. +### neighborhood +```julia +neighborhood(g, v::Int, d::Int; dir=:out) +``` + +Returns a vector of the vertices in `g` at distance less or equal to `d` from `v`. If `g` is a `DiGraph` the `dir` optional argument specifies the edge direction the edge direction with respect to `v` (i.e. `:in` or `:out`) to be considered. + ## Cycle Detection In graph theory, a cycle is defined to be a path that starts from some vertex `v` and ends up at `v`. ### is_cyclic ```julia -is_cyclic(graph::Union{LightGraphs.DiGraph,LightGraphs.Graph}) +is_cyclic(g) ``` + Tests whether a graph contains a cycle through depth-first search. It returns `true` when it finds a cycle, otherwise `false`. ## Shortest-Path Algorithms @@ -184,16 +195,17 @@ Note that this algorithm may return a large amount of data (it will allocate on ## Path discovery / enumeration ### gdistances ```julia -gdistances(graph::Union{LightGraphs.DiGraph,LightGraphs.Graph}, sources) +gdistances(g, source) -> dists ``` -Returns the geodesic distances of graph `g` from source vertex `s` or a set of source vertices `ss`. + +Returns a vector filled with the geodesic distances of vertices in `g` from vertex/vertices `source`. For vertices in disconnected components the default distance is -1. ### gdistances! ```julia -gdistances!{DMap}(graph::Union{LightGraphs.DiGraph,LightGraphs.Graph}, s::Int64, dists::DMap) -gdistances!{DMap}(graph::Union{LightGraphs.DiGraph,LightGraphs.Graph}, sources::AbstractArray{Int64,1}, dists::DMap) +gdistances!(g, source, dists) -> dists ``` -Returns the geodesic distances of graph `g` from source vertex `s` or a set of source vertices `ss`. + +Fills `dists` with the geodesic distances of vertices in `g` from vertex/vertices `source`. `dists` can be either a vector or a dictionary. ### enumerate_paths ```julia diff --git a/src/LightGraphs.jl b/src/LightGraphs.jl index 47a04724d..41109dc53 100644 --- a/src/LightGraphs.jl +++ b/src/LightGraphs.jl @@ -23,9 +23,9 @@ catch end import Base: write, ==, <, *, isless, issubset, complement, union, intersect, - reverse, reverse!, blkdiag, getindex, show, print, copy, in, + reverse, reverse!, blkdiag, getindex, setindex!, show, print, copy, in, sum, size, sparse, eltype, length, ndims, issym, transpose, - ctranspose, join, start, next, done, eltype + ctranspose, join, start, next, done, eltype, get # core @@ -48,7 +48,7 @@ crosspath, # graph visit SimpleGraphVisitor, TrivialGraphVisitor, LogGraphVisitor, discover_vertex!, open_vertex!, close_vertex!, -examine_neighbor!, visited_vertices, traverse_graph, traverse_graph_withlog, +examine_neighbor!, visited_vertices, traverse_graph!, traverse_graph_withlog, # bfs BreadthFirst, gdistances, gdistances!, bfs_tree, is_bipartite, bipartite_map, @@ -62,7 +62,7 @@ randomwalk, saw, non_backtracking_randomwalk, # connectivity connected_components, strongly_connected_components, weakly_connected_components, is_connected, is_strongly_connected, is_weakly_connected, period, -condensation, attracting_components, +condensation, attracting_components, neighborhood, egonet, # cliques maximal_cliques, @@ -100,7 +100,7 @@ maximum_weight_maximal_matching, MatchingResult, # randgraphs erdos_renyi, watts_strogatz, random_regular_graph, random_regular_digraph, random_configuration_model, StochasticBlockModel, make_edgestream, nearbipartiteSBM, blockcounts, blockfractions, -stochastic_block_model, +stochastic_block_model, #community modularity, community_detection_nback, core_periphery_deg, diff --git a/src/connectivity.jl b/src/connectivity.jl index 8e2e7a653..843dafc54 100644 --- a/src/connectivity.jl +++ b/src/connectivity.jl @@ -2,7 +2,10 @@ # licensing details. -"""connected_components! produces a label array of components +""" + connected_components!(label::Vector{Int}, g::SimpleGraph) + +Fills `label` with the `id` of the connected component to which it belongs. Arguments: label: a place to store the output @@ -21,14 +24,14 @@ function connected_components!(label::Vector{Int}, g::SimpleGraph) # passed to components(a) nvg = nv(g) visitor = LightGraphs.ComponentVisitorVector(label, 0) - colormap = zeros(Int,nvg) - que = Vector{Int}() - sizehint!(que, nvg) + colormap = fill(0, nvg) + queue = Vector{Int}() + sizehint!(queue, nvg) for v in 1:nvg if label[v] == 0 visitor.labels[v] = v visitor.seed = v - traverse_graph(g, BreadthFirst(), v, visitor; colormap=colormap, que=que) + traverse_graph!(g, BreadthFirst(), v, visitor; vertexcolormap=colormap, queue=queue) end end return label @@ -51,25 +54,25 @@ function components_dict(labels::Vector{Int}) return d end -"""components(labels) converts an array of labels to a Vector{Vector{Int}} of components +""" + components(labels::Vector{Int}) + +Converts an array of labels to a Vector{Vector{Int}} of components Arguments: c = labels[i] => vertex i belongs to component c. Output: vs = c[i] => vertices in vs belong to component i. - a = d[i] => if label[v[j]]==i then j in c[a] end + a = d[i] => if labels[v]==i then v in c[a] end """ function components(labels::Vector{Int}) d = Dict{Int, Int}() c = Vector{Vector{Int}}() i = 1 for (v,l) in enumerate(labels) - index = get(d, l, i) - d[l] = index + index = get!(d, l, i) if length(c) >= index - vec = c[index] - push!(vec, v) - c[index] = vec + push!(c[index], v) else push!(c, [v]) i += 1 @@ -78,19 +81,26 @@ function components(labels::Vector{Int}) return c, d end -"""Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory)) -of an undirected graph `g` as a vector of components, each represented by a -vector of vectors of vertices belonging to the component. """ -function connected_components(g) + connected_components(g) + +Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory)) +of `g` as a vector of components, each represented by a +vector of vertices belonging to the component. +""" +function connected_components(g::SimpleGraph) label = zeros(Int, nv(g)) connected_components!(label, g) c, d = components(label) return c end -"""Returns `true` if `g` is connected. -For DiGraphs, this is equivalent to a test of weak connectivity.""" +""" + is_connected(g) + +Returns `true` if `g` is connected. +For DiGraphs, this is equivalent to a test of weak connectivity. +""" is_connected(g::Graph) = length(connected_components(g)) == 1 is_connected(g::DiGraph) = is_weakly_connected(g) @@ -123,7 +133,7 @@ function discover_vertex!(vis::TarjanVisitor, v) end function examine_neighbor!(vis::TarjanVisitor, v, w, w_color::Int, e_color::Int) - if w_color > 0 # 1 means added seen, but not explored; 2 means closed + if w_color != 0 # != 0 means seen while vis.index[w] > 0 && vis.index[w] < vis.lowlink[end] pop!(vis.lowlink) end @@ -150,7 +160,7 @@ function strongly_connected_components(g::DiGraph) for v in vertices(g) if cmap[v] == 0 # 0 means not visited yet visitor = TarjanVisitor(nvg) - traverse_graph(g, DepthFirst(), v, visitor, vertexcolormap=cmap) + traverse_graph!(g, DepthFirst(), v, visitor, vertexcolormap=cmap) for component in visitor.components push!(components, component) end @@ -227,3 +237,46 @@ function attracting_components(g::DiGraph) end return scc[attracting] end + +type NeighborhoodVisitor <: SimpleGraphVisitor + d::Int + neigs::Vector{Int} +end + +NeighborhoodVisitor(d::Int) = NeighborhoodVisitor(d, Vector{Int}()) + +function examine_neighbor!(visitor::NeighborhoodVisitor, u::Int, v::Int, ucolor::Int, vcolor::Int, ecolor::Int) + -ucolor > visitor.d && return false # color is negative for not-closed vertices + if vcolor == 0 + push!(visitor.neigs, v) + end + return true +end + + +""" + neighborhood(g, v::Int, d::Int; dir=:out) + +Returns a vector of the vertices in `g` at distance less or equal to `d` +from `v`. If `g` is a `DiGraph` the `dir` optional argument specifies the edge direction +the edge direction with respect to `v` (i.e. `:in` or `:out`) to be considered. +""" +function neighborhood(g::SimpleGraph, v::Int, d::Int; dir=:out) + @assert d >= 0 "Distance has to be greater then zero." + visitor = NeighborhoodVisitor(d) + push!(visitor.neigs, v) + traverse_graph!(g, BreadthFirst(), v, visitor, + vertexcolormap=Dict{Int,Int}(), dir=dir) + return visitor.neigs +end + + +""" + egonet(g, v::Int, d::Int; dir=:out) + +Returns the subgraph of `g` induced by the neighbors of `v` up to distance +`d`. If `g` is a `DiGraph` the `dir` optional argument specifies +the edge direction the edge direction with respect to `v` (i.e. `:in` or `:out`) +to be considered. This is equivalent to `induced_subgraph(g, neighborhood(g, v, d, dir=dir)).` +""" +egonet(g::SimpleGraph, v::Int, d::Int; dir=:out) = induced_subgraph(g, neighborhood(g, v, d, dir=dir)) diff --git a/src/traversals/bfs.jl b/src/traversals/bfs.jl index d1c109500..52e0f5ea3 100644 --- a/src/traversals/bfs.jl +++ b/src/traversals/bfs.jl @@ -8,70 +8,70 @@ # Breadth-first visit # ################################################# +""" +**Conventions in Breadth First Search and Depth First Search** +VertexColorMap : +- color == 0 => unseen +- color < 0 => examined but not closed +- color > 0 => examined and closed + +EdgeColorMap : +- color == 0 => unseen +- color == 1 => examined +""" type BreadthFirst <: SimpleGraphVisitAlgorithm end function breadth_first_visit_impl!( - graph::SimpleGraph, # the graph - queue::Vector{Int}, # an (initialized) queue that stores the active vertices - colormap::Vector{Int}, # an (initialized) color-map to indicate status of vertices - visitor::SimpleGraphVisitor) # the visitor - + graph::SimpleGraph, # the graph + queue::Vector{Int}, # an (initialized) queue that stores the active vertices + vertexcolormap::AbstractVertexMap, # an (initialized) color-map to indicate status of vertices (-1=unseen, otherwise distance from root) + edgecolormap::AbstractEdgeMap, # an (initialized) color-map to indicate status of edges + visitor::SimpleGraphVisitor, # the visitor + dir::Symbol) # direction [:in,:out] + + fneig = dir == :out ? out_neighbors : in_neighbors while !isempty(queue) u = shift!(queue) open_vertex!(visitor, u) - - for v in out_neighbors(graph, u) - v_color::Int = colormap[v] - # TODO: Incorporate edge colors to BFS - if !(examine_neighbor!(visitor, u, v, v_color, -1)) - return - end - + u_color = vertexcolormap[u] + + for v in fneig(graph, u) + v_color = get(vertexcolormap, v, 0) + v_edge = Edge(u,v) + e_color = get(edgecolormap, v_edge, 0) + examine_neighbor!(visitor, u, v, u_color, v_color, e_color) || return + edgecolormap[v_edge] = 1 if v_color == 0 - colormap[v] = 1 + vertexcolormap[v] = u_color - 1 discover_vertex!(visitor, v) || return push!(queue, v) end end - - colormap[u] = 2 close_vertex!(visitor, u) + vertexcolormap[u] *= -1 end - nothing -end - -function traverse_graph( - graph::SimpleGraph, - alg::BreadthFirst, - s::Int, - visitor::SimpleGraphVisitor; - colormap = zeros(Int, nv(graph)), - que = Vector{Int}()) - - colormap[s] = 1 - discover_vertex!(visitor, s) || return - push!(que, s) - - breadth_first_visit_impl!(graph, que, colormap, visitor) end -function traverse_graph( +function traverse_graph!( graph::SimpleGraph, alg::BreadthFirst, - sources::AbstractVector{Int}, + source, visitor::SimpleGraphVisitor; - colormap = zeros(Int, nv(graph)), - que = Vector{Int}()) + vertexcolormap::AbstractVertexMap = Dict{Int, Int}(), + edgecolormap::AbstractEdgeMap = DummyEdgeMap(), + queue = Vector{Int}(), + dir = :out) - for s in sources - colormap[s] = 1 + for s in source + vertexcolormap[s] = -1 discover_vertex!(visitor, s) || return - push!(que, s) + push!(queue, s) end - breadth_first_visit_impl!(graph, que, colormap, visitor) + breadth_first_visit_impl!(graph, queue, vertexcolormap, edgecolormap + , visitor, dir) end @@ -81,61 +81,42 @@ end # ################################################# -# Get the map of the (geodesic) distances from vertices to source by BFS +########################################### +# Get the map of the (geodesic) distances from vertices to source by BFS # +########################################### immutable GDistanceVisitor <: SimpleGraphVisitor - graph::SimpleGraph - dists::Vector{Int} -end - -function examine_neighbor!(visitor::GDistanceVisitor, u, v, vcolor::Int, ecolor::Int) - if vcolor == 0 - g = visitor.graph - dists = visitor.dists - dists[v] = dists[u] + 1 - end - return true end -"""Returns the geodesic distances of graph `g` from source vertex `s` or a set -of source vertices `ss`. """ -function gdistances!{DMap}(graph::SimpleGraph, s::Int, dists::DMap) - visitor = GDistanceVisitor(graph, dists) - dists[s] = 0 - traverse_graph(graph, BreadthFirst(), s, visitor) - return dists -end + gdistances!(g, source, dists) -> dists -function gdistances!{DMap}(graph::SimpleGraph, sources::AbstractVector{Int}, dists::DMap) - visitor = GDistanceVisitor(graph, dists) - for s in sources - dists[s] = 0 +Fills `dists` with the geodesic distances of vertices in `g` from vertex/vertices `source`. +`dists` can be either a vector or a dictionary. +""" +function gdistances!(g::SimpleGraph, source, dists) + visitor = GDistanceVisitor() + traverse_graph!(g, BreadthFirst(), source, visitor, vertexcolormap=dists) + for i in eachindex(dists) + dists[i] -= 1 end - traverse_graph(graph, BreadthFirst(), sources, visitor) return dists end -"""Returns the geodesic distances of graph `g` from source vertex `s` or a set -of source vertices `ss`. + """ -function gdistances(graph::SimpleGraph, sources; defaultdist::Int=-1) - dists = fill(defaultdist, nv(graph)) - gdistances!(graph, sources, dists) -end + gdistances(g, source) -> dists + +Returns a vector filled with the geodesic distances of vertices in `g` from vertex/vertices `source`. +For vertices in disconnected components the default distance is -1. +""" +gdistances(g::SimpleGraph, source) = gdistances!(g, source, fill(0,nv(g))) + ########################################### # Constructing BFS trees # ########################################### -# this type has been deprecated in favor of TreeBFSVisitorVector and the tree function. -"""TreeBFSVisitor is a type for representing a BFS traversal of the graph as a DiGraph""" -type TreeBFSVisitor <:SimpleGraphVisitor - tree::DiGraph -end - -@deprecate TreeBFSVisitor(x) TreeBFSVisitorVector(x) - """TreeBFSVisitorVector is a type for representing a BFS traversal of the graph as a parents array. This type allows for a more performant implementation. """ @@ -144,15 +125,7 @@ type TreeBFSVisitorVector <: SimpleGraphVisitor end function TreeBFSVisitorVector(n::Int) - return TreeBFSVisitorVector(zeros(Int, n)) -end - -"""TreeBFSVisitor converts a parents array into a DiGraph""" -function TreeBFSVisitor(tvv::TreeBFSVisitorVector) - n = length(tvv.tree) - parents = tvv.tree - g = tree(parents) - return TreeBFSVisitor(g) + return TreeBFSVisitorVector(fill(0, n)) end """tree converts a parents array into a DiGraph""" @@ -170,8 +143,8 @@ end tree(parents::TreeBFSVisitorVector) = tree(parents.tree) -function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Int, v::Int, vcolor::Int, ecolor::Int) - # println("discovering $u -> $v, vcolor = $vcolor, ecolor = $ecolor") +function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Int, v::Int, + ucolor::Int, vcolor::Int, ecolor::Int) if u != v && vcolor == 0 visitor.tree[v] = u end @@ -181,8 +154,8 @@ end function bfs_tree!(visitor::TreeBFSVisitorVector, g::SimpleGraph, s::Int; - colormap=zeros(Int, nv(g)), - que=Vector{Int}()) + vertexcolormap = Dict{Int,Int}(), + queue = Vector{Int}()) # this version of bfs_tree! allows one to reuse the memory necessary to compute the tree # the output is stored in the visitor.tree array whose entries are the vertex id of the # parent of the index. This function checks if the scratch space is too small for the graph. @@ -193,7 +166,7 @@ function bfs_tree!(visitor::TreeBFSVisitorVector, nvg = nv(g) length(visitor.tree) >= nvg || error("visitor.tree too small for graph") visitor.tree[s] = s - traverse_graph(g, BreadthFirst(), s, visitor; colormap=colormap, que=que) + traverse_graph!(g, BreadthFirst(), s, visitor; vertexcolormap=vertexcolormap, queue=queue) end """Provides a breadth-first traversal of the graph `g` starting with source vertex `s`, @@ -217,8 +190,8 @@ type ComponentVisitorVector <: SimpleGraphVisitor seed::Int end -function examine_neighbor!(visitor::ComponentVisitorVector, u::Int, v::Int, vcolor::Int, ecolor::Int) - # println("discovering $u -> $v, vcolor = $vcolor, ecolor = $ecolor") +function examine_neighbor!(visitor::ComponentVisitorVector, u::Int, v::Int, + ucolor::Int, vcolor::Int, ecolor::Int) if u != v && vcolor == 0 visitor.labels[v] = visitor.seed end @@ -235,9 +208,10 @@ end BipartiteVisitor(n::Int) = BipartiteVisitor(zeros(UInt8,n), true) -function examine_neighbor!(visitor::BipartiteVisitor, u::Int, v::Int, vcolor::Int, ecolor::Int) +function examine_neighbor!(visitor::BipartiteVisitor, u::Int, v::Int, + ucolor::Int, vcolor::Int, ecolor::Int) if vcolor == 0 - visitor.bipartitemap[v] = (visitor.bipartitemap[u] == 1)? 2:1 + visitor.bipartitemap[v] = (visitor.bipartitemap[u] == 1) ? 2 : 1 else if visitor.bipartitemap[v] == visitor.bipartitemap[u] visitor.is_bipartite = false @@ -246,20 +220,33 @@ function examine_neighbor!(visitor::BipartiteVisitor, u::Int, v::Int, vcolor::In return visitor.is_bipartite end -"""Will return `true` if graph `g` is -[bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). """ -is_bipartite(g::SimpleGraph, s::Int) = _bipartite_visitor(g, s).is_bipartite + is_bipartite(g) + is_bipartite(g, v) +Will return `true` if graph `g` is [bipartite](https://en.wikipedia.org/wiki/Bipartite_graph). +If a node `v` is specified, only the connected component to which it belongs is considered. +""" function is_bipartite(g::SimpleGraph) cc = filter(x->length(x)>2, connected_components(g)) - return all(x->is_bipartite(g,x[1]), cc) + vmap = Dict{Int,Int}() + for c in cc + _is_bipartite(g,c[1], vmap=vmap) || return false + end + return true end -function _bipartite_visitor(g::SimpleGraph, s::Int) +is_bipartite(g::SimpleGraph, v::Int) = _is_bipartite(g, v) + +_is_bipartite(g::SimpleGraph, v::Int; vmap = Dict{Int,Int}()) = _bipartite_visitor(g, v, vmap=vmap).is_bipartite + +function _bipartite_visitor(g::SimpleGraph, s::Int; vmap=Dict{Int,Int}()) nvg = nv(g) visitor = BipartiteVisitor(nvg) - traverse_graph(g, BreadthFirst(), s, visitor) + for v in keys(vmap) #have to reset vmap, otherway problems with digraphs + vmap[v] = 0 + end + traverse_graph!(g, BreadthFirst(), s, visitor, vertexcolormap=vmap) return visitor end diff --git a/src/traversals/dfs.jl b/src/traversals/dfs.jl index 24984acb7..fb006898f 100644 --- a/src/traversals/dfs.jl +++ b/src/traversals/dfs.jl @@ -9,6 +9,17 @@ # Depth-first visit # ################################################# +""" +**Conventions in Breadth First Search and Depth First Search** +VertexColorMap : +- color == 0 => unseen +- color < 0 => examined but not closed +- color > 0 => examined and closed + +EdgeColorMap : +- color == 0 => unseen +- color == 1 => examined +""" type DepthFirst <: SimpleGraphVisitAlgorithm end @@ -16,8 +27,8 @@ end function depth_first_visit_impl!( graph::SimpleGraph, # the graph stack, # an (initialized) stack of vertex - vertexcolormap::Vector{Int}, # an (initialized) color-map to indicate status of vertices - edgecolormap::Dict{Edge,Int}, # an (initialized) color-map to indicate status of edges + vertexcolormap::AbstractVertexMap, # an (initialized) color-map to indicate status of vertices + edgecolormap::AbstractEdgeMap, # an (initialized) color-map to indicate status of edges visitor::SimpleGraphVisitor) # the visitor @@ -27,19 +38,16 @@ function depth_first_visit_impl!( while !done(udsts, tstate) && !found_new_vertex v, tstate = next(udsts, tstate) - v_color = vertexcolormap[v] + v_color = get(vertexcolormap, v, 0) v_edge = Edge(u,v) - e_color = haskey(edgecolormap, v_edge)? - edgecolormap[v_edge] : edgecolormap[reverse(v_edge)] - examine_neighbor!(visitor, u, v, v_color, e_color) + e_color = get(edgecolormap, v_edge, 0) + examine_neighbor!(visitor, u, v, v_color, e_color) #no return here - if e_color == 0 - edgecolormap[v_edge] = 1 - end + edgecolormap[v_edge] = 1 if v_color == 0 found_new_vertex = true - vertexcolormap[v] = 1 + vertexcolormap[v] = vertexcolormap[u] - 1 #negative numbers discover_vertex!(visitor, v) || return push!(stack, (u, udsts, tstate)) @@ -51,28 +59,20 @@ function depth_first_visit_impl!( if !found_new_vertex close_vertex!(visitor, u) - vertexcolormap[u] = 2 + vertexcolormap[u] *= -1 end end end -function _mkedgecolormap(g::SimpleGraph, n::Integer=0) - d = Dict{Edge, Int}() - for e in edges(g) - d[e] = n - end - return d -end - -function traverse_graph( +function traverse_graph!( graph::SimpleGraph, alg::DepthFirst, s::Int, visitor::SimpleGraphVisitor; - vertexcolormap = zeros(Int, nv(graph)), - edgecolormap = _mkedgecolormap(graph)) + vertexcolormap = Dict{Int, Int}(), + edgecolormap = DummyEdgeMap()) - vertexcolormap[s] = 1 + vertexcolormap[s] = -1 discover_vertex!(visitor, s) || return sdsts = fadj(graph, s) @@ -82,7 +82,6 @@ function traverse_graph( depth_first_visit_impl!(graph, stack, vertexcolormap, edgecolormap, visitor) end - ################################################# # # Useful applications @@ -104,23 +103,26 @@ function examine_neighbor!( vcolor::Int, ecolor::Int) - if vcolor == 1 && ecolor == 0 + if vcolor < 0 && ecolor == 0 vis.found_cycle = true end end discover_vertex!(vis::DFSCyclicTestVisitor, v) = !vis.found_cycle -"""Tests whether a graph contains a cycle through depth-first search. It +""" + is_cyclic(g) + +Tests whether a graph contains a cycle through depth-first search. It returns `true` when it finds a cycle, otherwise `false`. """ -function is_cyclic(graph::SimpleGraph) - cmap = zeros(Int, nv(graph)) +function is_cyclic(g::SimpleGraph) + cmap = zeros(Int, nv(g)) visitor = DFSCyclicTestVisitor() - for s in vertices(graph) + for s in vertices(g) if cmap[s] == 0 - traverse_graph(graph, DepthFirst(), s, visitor, vertexcolormap=cmap) + traverse_graph!(g, DepthFirst(), s, visitor, vertexcolormap=cmap) end visitor.found_cycle && return true end @@ -141,7 +143,7 @@ end function examine_neighbor!(visitor::TopologicalSortVisitor, u::Int, v::Int, vcolor::Int, ecolor::Int) - (vcolor == 1 && ecolor == 0) && error("The input graph contains at least one loop.") + (vcolor < 0 && ecolor == 0) && error("The input graph contains at least one loop.") end function close_vertex!(visitor::TopologicalSortVisitor, v::Int) @@ -155,7 +157,7 @@ function topological_sort_by_dfs(graph::SimpleGraph) for s in vertices(graph) if cmap[s] == 0 - traverse_graph(graph, DepthFirst(), s, visitor, vertexcolormap=cmap) + traverse_graph!(graph, DepthFirst(), s, visitor, vertexcolormap=cmap) end end @@ -177,13 +179,16 @@ function examine_neighbor!(visitor::TreeDFSVisitor, u::Int, v::Int, vcolor::Int, return true end -"""Provides a depth-first traversal of the graph `g` starting with source vertex `s`, +""" + dfs_tree(g, s::Int) + +Provides a depth-first traversal of the graph `g` starting with source vertex `s`, and returns a directed acyclic graph of vertices in the order they were discovered. """ function dfs_tree(g::SimpleGraph, s::Int) nvg = nv(g) visitor = TreeDFSVisitor(nvg) - traverse_graph(g, DepthFirst(), s, visitor) + traverse_graph!(g, DepthFirst(), s, visitor) # visitor = traverse_dfs(g, s, TreeDFSVisitor(nvg)) h = DiGraph(nvg) for (v, u) in enumerate(visitor.predecessor) diff --git a/src/traversals/graphvisit.jl b/src/traversals/graphvisit.jl index fe14d71fe..44e2ae87b 100644 --- a/src/traversals/graphvisit.jl +++ b/src/traversals/graphvisit.jl @@ -15,7 +15,7 @@ discover_vertex!(vis::SimpleGraphVisitor, v) = true open_vertex!(vis::SimpleGraphVisitor, v) = true # invoked when a neighbor is discovered & examined -examine_neighbor!(vis::SimpleGraphVisitor, u, v, vcolor::Int, ecolor::Int) = true +examine_neighbor!(vis::SimpleGraphVisitor, u, v, ucolor::Int, vcolor::Int, ecolor::Int) = true # invoked when all of v's neighbors have been examined close_vertex!(vis::SimpleGraphVisitor, v) = true @@ -28,6 +28,16 @@ end # This is the common base for BreadthFirst and DepthFirst abstract SimpleGraphVisitAlgorithm +typealias AbstractEdgeMap{T} Associative{Edge,T} +typealias AbstractVertexMap{T} Union{AbstractVector{T},Associative{Int, T}} + +type DummyEdgeMap <: AbstractEdgeMap{Int} +end + +getindex(d::DummyEdgeMap, e::Edge) = 0 +setindex!(d::DummyEdgeMap, x::Int, e::Edge) = x +get(d::DummyEdgeMap, e::Edge, x::Int) = x + ########################################################### # @@ -58,7 +68,7 @@ function visited_vertices( sources) visitor = VertexListVisitor(nv(graph)) - traverse_graph(graph, alg, sources, visitor) + traverse_graph!(graph, alg, sources, visitor) visitor.vertices::Vector{Int} end @@ -96,5 +106,5 @@ function traverse_graph_withlog( io::IO = STDOUT ) visitor = LogGraphVisitor(io) - traverse_graph(g, alg, sources, visitor) + traverse_graph!(g, alg, sources, visitor) end diff --git a/src/traversals/maxadjvisit.jl b/src/traversals/maxadjvisit.jl index 23c31e16e..755cb4d14 100644 --- a/src/traversals/maxadjvisit.jl +++ b/src/traversals/maxadjvisit.jl @@ -37,7 +37,7 @@ function maximum_adjacency_visit_impl!{T}( end -function traverse_graph( +function traverse_graph!( graph::SimpleGraph, T::DataType, alg::MaximumAdjacency, @@ -184,7 +184,7 @@ function mincut{T}( ) visitor = MinCutVisitor(graph, distmx) colormap = zeros(Int, nv(graph)) - traverse_graph(graph, T, MaximumAdjacency(), 1, visitor, colormap) + traverse_graph!(graph, T, MaximumAdjacency(), 1, visitor, colormap) return(visitor.parities + 1, visitor.bestweight) end @@ -203,7 +203,7 @@ function maximum_adjacency_visit{T}( io::IO ) visitor = MASVisitor(io, Vector{Int}(), distmx, log) - traverse_graph(graph, T, MaximumAdjacency(), 1, visitor, zeros(Int, nv(graph))) + traverse_graph!(graph, T, MaximumAdjacency(), 1, visitor, zeros(Int, nv(graph))) return visitor.vertices end diff --git a/test/connectivity.jl b/test/connectivity.jl index d6a374be8..0359f4b44 100644 --- a/test/connectivity.jl +++ b/test/connectivity.jl @@ -40,7 +40,6 @@ for m=1:50 end end - # graph from https://en.wikipedia.org/wiki/Strongly_connected_component h = DiGraph(8) add_edge!(h,1,2); add_edge!(h,2,3); add_edge!(h,2,5); @@ -132,3 +131,26 @@ fig8 = DiGraph(fig8) @test attracting_components(fig1) == Vector[[2,5]] @test attracting_components(fig3) == Vector[[3,4],[8]] + +g10 = StarGraph(10) +@test neighborhood(g10, 1 , 0) == [1] +@test length(neighborhood(g10, 1, 1)) == 10 +@test length(neighborhood(g10, 2, 1)) == 2 +@test length(neighborhood(g10, 1, 2)) == 10 +@test length(neighborhood(g10, 2, 2)) == 10 + +g10 = StarDiGraph(10) +@test neighborhood(g10, 1 , 0, dir=:out) == [1] +@test length(neighborhood(g10, 1, 1, dir=:out)) == 10 +@test length(neighborhood(g10, 2, 1, dir=:out)) == 1 +@test length(neighborhood(g10, 1, 2, dir=:out)) == 10 +@test length(neighborhood(g10, 2, 2, dir=:out)) == 1 +@test neighborhood(g10, 1 , 0, dir=:in) == [1] +@test length(neighborhood(g10, 1, 1, dir=:in)) == 1 +@test length(neighborhood(g10, 2, 1, dir=:in)) == 2 +@test length(neighborhood(g10, 1, 2, dir=:in)) == 1 +@test length(neighborhood(g10, 2, 2, dir=:in)) == 2 + +g10 = StarGraph(10) +@test egonet(g10, 1, 0) == Graph(1,0) +@test egonet(g10, 1, 1) == g10 diff --git a/test/traversals/bfs.jl b/test/traversals/bfs.jl index a978057d5..e6cce0e6b 100644 --- a/test/traversals/bfs.jl +++ b/test/traversals/bfs.jl @@ -17,16 +17,9 @@ add_edge!(g,3,4) @test is_bipartite(g) -import LightGraphs: TreeBFSVisitorVector, bfs_tree!, TreeBFSVisitor, tree -g = smallgraph(:house) -n = nv(g) -visitor = TreeBFSVisitorVector(n) -@test length(visitor.tree) == n -parents = visitor.tree -bfs_tree!(visitor, g, 1) -maxdepth = n +import LightGraphs: TreeBFSVisitorVector, bfs_tree!, tree -function istree(parents::Vector{Int}) +function istree(parents::Vector{Int}, maxdepth) flag = true for i in 1:n s = i @@ -42,16 +35,30 @@ function istree(parents::Vector{Int}) return flag end -@test istree(parents) == true -tvis = TreeBFSVisitor(visitor) -@test nv(tvis.tree) == nv(g) -@test typeof(tvis.tree) <: DiGraph +g = smallgraph(:house) +n = nv(g) +visitor = TreeBFSVisitorVector(n) +@test length(visitor.tree) == n +parents = visitor.tree +bfs_tree!(visitor, g, 1) + +@test istree(parents, n) == true t = tree(parents) @test typeof(t) <: DiGraph -@test typeof(tvis.tree) <: DiGraph -@test t == tvis.tree @test ne(t) < nv(t) +# test Dict{Int,Int}() colormap +g = smallgraph(:house) +n = nv(g) +visitor = TreeBFSVisitorVector(n) +@test length(visitor.tree) == n +parents = visitor.tree +bfs_tree!(visitor, g, 1, vertexcolormap = Dict{Int,Int}()) + +@test istree(parents, n) == true +t = tree(parents) +@test typeof(t) <: DiGraph +@test ne(t) < nv(t) g10 = CompleteGraph(10) @test bipartite_map(g10) == Vector{Int}() diff --git a/test/traversals/graphvisit.jl b/test/traversals/graphvisit.jl index c96ddc9f5..6e85e278f 100644 --- a/test/traversals/graphvisit.jl +++ b/test/traversals/graphvisit.jl @@ -14,11 +14,11 @@ function trivialgraphvisit( sources ) visitor = TrivialGraphVisitor() - traverse_graph(g, alg, sources, visitor) + traverse_graph!(g, alg, sources, visitor) end @test trivialgraphvisit(g, BreadthFirst(), 1) == nothing # this just exercises some graph visitors -@test traverse_graph(g, BreadthFirst(), 1, TrivialGraphVisitor()) == nothing -@test traverse_graph(g, BreadthFirst(), 1, LogGraphVisitor(IOBuffer())) == nothing +@test traverse_graph!(g, BreadthFirst(), 1, TrivialGraphVisitor()) == nothing +@test traverse_graph!(g, BreadthFirst(), 1, LogGraphVisitor(IOBuffer())) == nothing