Cycle through graph back to starting node - python

having an issue working out how to cycle back to the start node in my graph. Currently, from the graph I create I can cycle from a start node and follow the edges till there are no connected nodes. However I can't work out how to make it cycle through and finish on the start node, if possible.
This is an example of the graph with its connections.
Node & Connection(S) [(0, 4), (1, 5), (1, 8), (3, 1), (4, 0), (4, 3), (5, 0),
(5, 3), (5, 7), (6, 0), (6, 4), (7, 0), (8, 5), (8, 6), (8, 7)]
This is my code to cycle through the graph and follow its edges.
def pathSearch(graph, start, path=[]):
path=path+[start]
for node in graph[start]:
if not node in path:
path=pathSearch(graph, node, path)
return path
print ('Path ', pathSearch(g, 0))
This is what I get as an output starting from node 0:
pathSearch [0, 4, 3, 1, 5, 7, 8, 6]
This is right, but why isn't it doing a full cycle back to the start node?

Related

How to find all the parent nodes of each node in a directed acyclic graph?

For the directed acyclic graph given by the following python code I want to find out all the parent node of each node of the DAG. Please help! For example, node 6,7 and 8 are the parent nodes of node 9. I have to make a function where I have to access all the parent node of each node and act according to my problem. Note: The code was taken from geeksforgeeks
class Graph:
# Constructor to construct a graph
def __init__(self, edges, n):
# A list of lists to represent an adjacency list
self.adjList = [None] * n
# allocate memory for the adjacency list
for i in range(n):
self.adjList[i] = []
# add edges to the directed graph
for (src, dest, weight) in edges:
# allocate node in adjacency list from src to dest
self.adjList[src].append((dest, weight))
# Function to print adjacency list representation of a graph
def printGraph(graph):
for src in range(len(graph.adjList)):
# print current vertex and all its neighboring vertices
for (dest, weight) in graph.adjList[src]:
new_graph[src].append(dest)
print(f'({src} —> {dest}, {weight}) ', end='')
print()
# Input: Edges in a weighted digraph (as per the above diagram)
# Edge (x, y, w) represents an edge from `x` to `y` having weight `w`
edges = [(0, 1, 18), (0, 2, 12), (0, 3, 9), (0, 4, 11), (0, 5, 14),
(1, 7, 19), (1, 8, 16), (2, 6, 23), (3, 7, 27), (3, 8, 23),
(4, 8, 13), (5, 7, 15), (6, 9, 17), (7, 9, 11), (8, 9, 13)]
# No. of vertices (labelled from 1 to 10)
n = 10
# construct a graph from a given list of edges
graph = Graph(edges, n)
new_graph = [[] for i in range(n)]
# print adjacency list representation of the graph
printGraph(graph)
You can use a similar structure as the printGraph function which iterates over all edges. We then can check for each edge if it leads to our child node. If so, we found a parent node. These parent nodes are stored and returned. Here is an example implementation of the function:
# Function to find all parents of a node
def getParent(graph, node):
# collect parents as a set in order to avoid duplicate entries
parents = set()
for src in range(len(graph.adjList)):
# Iterate over all edges from src node
for (dest, weight) in graph.adjList[src]:
# if destiontion is our needed child add source as parent
if dest == node:
parents.add(src)
# Return all parents as a list
return list(parents)
Call it like this to find the parents of node 9:
# Input: Edges in a weighted digraph (as per the above diagram)
# Edge (x, y, w) represents an edge from `x` to `y` having weight `w`
edges = [(0, 1, 18), (0, 2, 12), (0, 3, 9), (0, 4, 11), (0, 5, 14),
(1, 7, 19), (1, 8, 16), (2, 6, 23), (3, 7, 27), (3, 8, 23),
(4, 8, 13), (5, 7, 15), (6, 9, 17), (7, 9, 11), (8, 9, 13)]
# No. of vertices (labelled from 1 to 10)
n = 10
# construct a graph from a given list of edges
graph = Graph(edges, n)
print(getParent(graph, 9))
Output are the parents of node 9 as a list:
[8, 6, 7]

Adding multiple directed edges in networkx

I know this should be very basic but I have no clue how to do this using networkx. What I am trying to do is to create a MultiDiGraph with 20 nodes. There would be 2 edges connecting each nodes to each other, one away from the node and the other going towards the node. I am unable to create those edges. Any help would be greatly appreciated. It should look something like the picture I have attached.
You could create a graph, and then convert it to a directed graph. In this way you get edges in both directions:
import networkx as nx
g = nx.Graph()
g.add_edges_from([(0, 1), (1, 2), (1, 3)])
g = g.to_directed()
>>> g.edges
OutEdgeView([(0, 1), (1, 0), (1, 2), (1, 3), (2, 1), (3, 1)])
If you want to generate a complete directed graph with n nodes:
import networkx as nx
g = nx.complete_graph(4).to_directed()
>>> g.edges
OutEdgeView([(0, 1), (0, 2), (0, 3), (1, 0), (1, 2), (1, 3), (2, 0), (2, 1), (2, 3), (3, 0), (3, 1), (3, 2)])

Can anyone explain this implementation of depth first search?

So i am learning about search algorithms at the minute, and would appreciate it if someone could provide an explanation of how this implementation of depth first search works, i do understand how depth first search works as a algorithm, but i am struggling to grasp how it has been implemented here.
Thanks for your patience and understanding, Below is the code:
map = {(0, 0): [(1, 0), (0, 1)],
(0, 1): [(1, 1), (0, 2)],
(0, 2): [(1, 2), (0, 3)],
(0, 3): [(1, 3), (0, 4)],
(0, 4): [(1, 4), (0, 5)],
(0, 5): [(1, 5)],
(1, 0): [(2, 0), (1, 1)],
(1, 1): [(2, 1), (1, 2)],
(1, 2): [(2, 2), (1, 3)],
(1, 3): [(2, 3), (1, 4)],
(1, 4): [(2, 4), (1, 5)],
(1, 5): [(2, 5)],
(2, 0): [(3, 0), (2, 1)],
(2, 1): [(3, 1), (2, 2)],
(2, 2): [(3, 2), (2, 3)],
(2, 3): [(3, 3), (2, 4)],
(2, 4): [(3, 4), (2, 5)],
(2, 5): [(3, 5)],
(3, 0): [(4, 0), (3, 1)],
(3, 1): [(4, 1), (3, 2)],
(3, 2): [(4, 2), (3, 3)],
(3, 3): [(4, 3), (3, 4)],
(3, 4): [(4, 4), (3, 5)],
(3, 5): [(4, 5)],
(4, 0): [(5, 0), (4, 1)],
(4, 1): [(5, 1), (4, 2)],
(4, 2): [(5, 2), (4, 3)],
(4, 3): [(5, 3), (4, 4)],
(4, 4): [(5, 4), (4, 5)],
(4, 5): [(5, 5)],
(5, 0): [(5, 1)],
(5, 1): [(5, 2)],
(5, 2): [(5, 3)],
(5, 3): [(5, 4)],
(5, 4): [(5, 5)],
(5, 5): []}
visited = []
path = []
routes = []
def goal_test(node):
if node == (5, 5):
return True
else:
return False
found = False
def dfs(visited, graph, node):
global routes
visited = visited + [node]
if goal_test(node):
routes = routes + [visited]
else:
for neighbour in graph[node]:
dfs(visited, graph, neighbour)
dfs(visited, map, (0, 0))
print(len(routes))
for route in routes:
print(route)
This implementation employs several bad practices:
map is a native Python function, so it is a bad idea to create a variable with that name.
visited should not need to be initialised in the global scope: the caller has no interest in this as it only plays a role in the DFS algorithm itself
routes should not have to be initialised to an empty list either, and it is bad that dfs mutates this global variable. Instead dfs should return that information to the caller. This makes one dfs call self-contained, as it returns the possible routes from the current node to the target. It is up to the caller to extend the routes in this returned collection with an additional node.
The body of goal_test should be written as return node == (5, 5). The if ... else is just translating a boolean value to the same boolean value.
The function goal_test seems overkill when you can just pass an argument to the dfs function that represents the target node. This makes it also more generic, as you don't need to hard-code the target location inside a function.
path and found are initialised but never used.
dfs would run into a stack overflow if the graph would have cycles. It does not happen with the given graph, because that graph is acyclic, but it would be better if you could also rely on this function when giving it cyclic graphs.
dfs will visit the same cell multiple times, as it can be found via different paths (like for instance (2,2)), and so from there it will perform the same DFS search it already did before. This could be made slightly more efficient by storing the result it got from a previous visit to that cell, i.e. we could use memoization. The gain is small, as most time is spent on creating and copying paths. The gain (of using memoization) would be significant if the function would only count the number of paths, and not build them.
Here is an implementation that deals with the above mentioned points. It uses a wrapper function to hide the use of memoization to the caller, and to reduce the number of arguments that need to be passed to dfs:
def search(graph, source, target):
# Use memoization to avoid repetitive DFS from same node,
# Also used to mark a node as visited, to avoid runnning in cycles
memo = dict() # has routes that were already collected
def dfs(node):
if node not in memo: # not been here before
if node == target:
memo[node] = [[target]]
else:
# Mark with None that this node is on the current path
# ...avoiding infinite recursion on a cycle
memo[node] = None # temporary value while not yet back from recursion
memo[node] = [
[node] + route
for neighbour in graph[node]
for route in dfs(neighbour)
if route
]
return memo[node]
return dfs(source)
graph = {(0, 0): [(1, 0), (0, 1)],
# ...etc ...
}
routes = search(graph, (0, 0), (5, 5))
print(len(routes))
for route in routes:
print(route)

Generate Complete DFS Paths using networkx

I am trying to generate complete path list instead of the optimized one. Better explained using the below example.
import networkx as nx
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
G.add_edges_from([(0, 1), (1, 2), (2, 4)])
G.add_edges_from([(0, 5), (5, 6)])
The above code create a Graph with edges 0=>1=>2=>3 and 0=>1=>2=>4 and 0=>5=>6
All I want is to extract all paths from 0.
I tried:
>> list(nx.dfs_edges(G, 0))
[(0, 1), (1, 2), (2, 3), (2, 4), (0, 5), (5, 6)]
All I want is:
[(0, 1, 2, 3), (0, 1, 2, 4), (0, 5, 6)]
Is there any pre-existing method from networkx which can be used? If not, any way to write an optimal method that can do the job?
Note: My problem is limited to the given example. No more corner cases possible.
Note2: For simplification the data is generated. In my case, the edges list is coming from data set. Assumption is given a graph and a node (Say 0), Can we generate all paths?
Give this a try:
import networkx as nx
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
G.add_edges_from([(0, 1), (1, 2), (2, 4)])
G.add_edges_from([(0, 5), (5, 6)])
pathes = []
path = [0]
for edge in nx.dfs_edges(G, 0):
if edge[0] == path[-1]:
# node of path
path.append(edge[1])
else:
# new path
pathes.append(path)
search_index = 2
while search_index <= len(path):
if edge[0] == path[-search_index]:
path = path[:-search_index + 1] + [edge[1]]
break
search_index += 1
else:
raise Exception("Wrong path structure?", path, edge)
# append last path
pathes.append(path)
print(pathes)
# [[0, 1, 2, 3], [0, 1, 2, 4], [0, 5, 6]]

How to stop Networkx from changing the order of head and tail nodes(u,v) to (v,u) in an edge?

I've got a simple graph created using networkx.
import networkx as nx
import matplotlib.pyplot as plt
from pprint import pprint
G = nx.Graph()
head_nodes = range(0, 9)
tail_nodes = range(1, 10)
edge_ls = list(zip(head_nodes, tail_nodes))
G.add_nodes_from(range(0, 10))
G.add_edges_from(edge_ls)
pprint(G.nodes())
nx.draw(G)
plt.show()
I want to remove the edge between node 0 and 1 and add three new nodes (say node 10,11,12). Then, edges have
to be created between node 0 and 10, 10 and 11, 11 and 2.
I'm using G.remove_edge(0,1) to remove the edge between node 0 and 1.
Could someone suggest which function can be used to add n new nodes?
Also, if n new nodes are added, will these nodes be numbered automatically?
I intend to do this in a loop, delete an edge that already exists between two nodes and add n new nodes and edges connecting these nodes.
EDIT:
I tried the following to add n new edges
G = nx.Graph()
head_nodes = range(0, 9)
tail_nodes = range(1, 10)
edge_ls = list(zip(head_nodes, tail_nodes))
G.add_nodes_from(range(0, 10))
G.add_edges_from(edge_ls)
head = 0
tail = 1
G.remove_edge(head, tail)
Nnodes = G.number_of_nodes()
newnodes = [head, Nnodes+1, Nnodes+2, Nnodes+3, tail] # head and tail already exists
newedges = [(x, y) for x, y in zip(newnodes[0:len(newnodes)-1], newnodes[1:len(newnodes)])]
G.add_edges_from(newedges)
pprint(G.edges())
Output:
EdgeView([(0, 11), (1, 2), (1, 13), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (11, 12), (12, 13)])
Expected Output:
EdgeView([(0, 11), (1, 2), (13, 1), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (11, 12), (12, 13)])
I'm not sure why the edge that was added in the order (13,1)(head, tail) is stored as (1,13). Any suggestion on how to preserve the order of head and tail node while adding a new edge?
EDIT2:
replacing nx.Graph() with nx.OrderedGraph() also doesn't help.
A Graph is an undirected graph, where (1, 13) and (13, 1) mean the same thing, the edges have no 'arrows'.
What you want is a DiGraph, meaning a directed graph. See https://networkx.github.io/documentation/stable/reference/classes/index.html
An OrderedGraph is something else - it just means that when you iterate over nodes and edges, they come out in a particular order (similar to lists vs sets).

Categories

Resources