Graph is common data structure that represents the relationships
between different entities using edges and vertices. The behavior of
search algorithm can be changed by plugging in different data
structures, such as using a stack yields depth-first search, and using a
queue gives breadth-first search.
Brief
In the previous
blog post, we have discussed the basic concept of graph traversal
algorithms. Here is a very simple and good illustration of the graph
scan, derived from IDEA.
In practice, a graph can be extermely large, and there is a need to
traverse or search the graph along the edges. The breadth-first search
and depth-first search are the most commonly used traversal and search
algorithm. In this post, I would like to practice and discuss the
implementations of directed and undirected graphs, breath-first and
depth-first search algorithms. Also, practice some exercises associated
with graphs search algorithm.
Graph Representation
and Implementation
A graph is made up of vertices (also called nodes or points) which
are connected by edges (also called links or lines). A distinction is
made between undirected graphs, where edges link two vertices
symmetrically, and directed graphs, where edges link two vertices
asymmertically. Here is the concise implementation of UndirectedGraph
and DirectedGraph classes, which inherit from Graph:
class Graph(): """Docstring for Graph. """ def __init__(self) -> None: """TODO: to be defined. """ # Adjacency set representation. # key: node name, value: adjacency node name set self.edges = dict()
def add_node(self, node: Union[int, str]) -> None: """TODO: Docstring for add_node. """ if node not in self.edges: self.edges[node] = set()
def get_nigihbors(self, node: Union[int, str]) -> Set[Union[int, str]]: """TODO: Docstring for get_nigihbors. """ if node not in self.edges: raise RuntimeError(f"Node {node} does not exist in the graph.")
for u in self.edges[node]: yield u
def get_num_nodes(self): """TODO: Docstring for get_num_nodes. """ return len(self.edges)
1 2 3 4 5 6 7 8 9 10 11 12 13
class UndirectedGraph(Graph): """Docstring for UndirectedGraph. """ def __init__(self): """TODO: to be defined. """ super(UndirectedGraph, self).__init__()
if node not in self.parents: raise RuntimeError(f"Node {node} does not exist in the graph.")
return self.parents[node]
def get_children(self, node: Union[int, str]) -> Set[Union[int, str]]: """TODO: Docstring for get_children. """ if node not in self.children: raise RuntimeError(f"Node {node} does not exist in the graph.")
return self.children[node]
Traversal and Search
Implementation
Graph traversal refers to the process of visiting each vertex in a
graph. Such traversals are classified by the order in which the vertices
are visited. If each vertex in a graph is to be traversed by a
tree-based algorithm, then the algorithm must be called at least once
for each connected component of the graph. Here is the concise
implementation of breadth first traversal, iterative depth first
traversal and recursive depth first traversal:
def breadth_first_traversal(graph: Graph, start_nodes: List[int]) -> List[int]: """TODO: Docstring for breadth_first_traversal. BFS supports multiple start nodes, the data structure used by queue O(V+E) where V is the number of nodes and E is the number of edges """ search_order = [] num_nodes = graph.get_num_nodes()
# Store the nodes that have been visited visited = set()
# FIFO q = Queue(maxsize=num_nodes)
# Mark the source node as visited and enqueue it for node in start_nodes: q.put(node) visited.add(node)
while not q.empty(): src_node = q.get() search_order.append(src_node) for neighbor in graph.get_nigihbors(src_node): if neighbor not in visited: q.put(neighbor) visited.add(neighbor)
def iterative_depth_first_traversal(graph: Graph, start_node: int) -> List[int]: """TODO: Docstring for iterative_depth_first_traversal. DFS supports the data structure used by stack O(V+E) where Vis the number of nodes ad E is the number of edges """ search_order = []
# Store the nodes that have been visited visited = set()
# LIFO (in python, the list data structure can be used as stack) stack = []
stack.append(start_node)
while len(stack) != 0: src_node = stack.pop()
if src_node not in visited: search_order.append(src_node) visited.add(src_node) for neighbor in graph.get_nigihbors(src_node): stack.append(neighbor)
def recursive_depth_first_traversal(graph: Graph, start_node: int) -> List[int]: """TODO: Docstring for recursive_depth_first_traversal. Recursive algorithm is not favored for large graphs because if stack overflow O(V+E) where V is the number of nodes and E is the number of edges """ def depth_first_recursion(graph: Graph, visited: Set, src_node: int, search_order: List[int]): """TODO: Docstring for depth_first_recursion. """ visited.add(src_node) search_order.append(src_node)
for neighbor in graph.get_nigihbors(src_node): if neighbor not in visited: depth_first_recursion(graph=graph, visited=visited, src_node=neighbor, search_order=search_order)
search_order = []
# Store the nodes that have been visited visited = set()
if start_node not in visited: depth_first_recursion(graph=graph, visited=visited, src_node=start_node, search_order=search_order)
return search_order
Note: BFS explores all the nodes at the current level before it
explores the next level. To achieve this, it uses first-in-first-out
(FIFO) queue and traverse all the connected nodes; DFS explores as far
as possible along each branch before bracktracking. It can be
implemented recursively or iteratively. The recursive algorithm
naturally explores the depth first but it not favored for large graphs
because of stack overflow. The iterative algorithm uses a
last-in-first-out (LIFO) stack to explore the depth first.
Experiments
We created a simple example of directed and undirected graphs, the
network is same as previous
blog post, to test around with BFS and DFS. Here is how we invoke a
method:
You want to build a house on an empty land which reaches all
buildings in the shortest amount of distance. You can only move up, down
left, and right. You are given a 2D grid of values 0, 1, 2, where:
Each 0 marks an empty land which you can pass by freely.
Each 1 marks a building which you cannot pass through.
Each 2 marks an obstacle which you cannot pass through.
// Search & Traversal for (int i = 0; i < row; ++i) { for (int j = 0; j < column; ++j) { if (gird[i][j] == 1) { num_building ++; auto tmp_grid = grid; bfs(i, j, tmp_grid, distance, visited); } }
}
// Find the shortest distance for (int i = 0; i < row; ++i) { for (int j = 0; j < column; j++) { if (visited[i][j] == num_building) ans = std::min(ans, distance[i][j]); } }
There are a servers numbered from 0 to n-1 connected by undirected
server-to-server connections forming a network where connections[i] =
[a, b] represents a connection between servers a and b. Any server can
reach any other server directly or indirectly through the network.
A critical connection is a connection that, if removed, will make
some server unable to reach some other server.
Return all critical connections in the network in any order.
The solution was initially inspired by Tarjan's
algorithm, and then reworked. The basic functions for determining
critical connections are as follows:
Constructing undirected graph
Do a recursive DFS traversal, labeling node with increaseing
visited time and track the smallest low link value
Determine critical connection (bridge) depending on when the low
link value is equal to visited times.
Note: Low link value of a node is defined as the smallest visited
time from current node when doing a DFS, including itself. And the
bridge in graphy theory is any edge in a graph whose removal increases
the number of connected components.
Due to the most time on my job, we need to type many git command
lines. So that this article is used to sort several git commands
frequently used for easily searching.
"Most players gain pleasure from feeling accepted or belonging to the
group. The good player however, gains pleasure from his ability to cope
with the realities of the game"
In our daily life, most of us usually use Google Map or Apple Maps to help us to
navigate to a place. We can see traffic info, public transit and also
choose our mode of transportation.
"After going on your life-changing journey, you now realize that you
don’t want what you thought you wanted. What you really wanted was
inside you all along."
The HTML canvas can be used for sketching/drawing by mouse. The
canvas API provides a means for drawing graphics via JavaScript and the
HTML element. It largely focuses on 2D graphics. In this post, I would
like to describe how to quickly go about implementing this.
The HTML canvas can be used for sketching/drawing either by mouse or by touch. In the previous post, I quickly discuss how to use Canvas API to implement a canvas with mouse sketching. In this post, I would like to describe how go about implementing for sketching/drawing by mouse and touch.
Either computer data storage or telecommunication, regardless of the
data storages and transmission, is non-zero probabilities that the data
could be changed while it's being stored or transmitted. There is always
a code-word with block length without free bit-errors. That means the
data probably could be changed while it is being processed or
transmitted. If the machine can't locate the position of the error and
correct it, the information might be lost forever.
"Graph theory is the study of graphs, which are mathematical
structures used to model pairwise relations between objects. A graph in
this context is made up of vertices (also called nodes or points) which
are connected by edges (also called links or lines). Graphs are one of
the principal objects of study in discrete mathematics." ... from Wiki
page.