(e.g. people, cities, countries, computers).
Nodes can be called vertices as well.
(e.g. friendship, connectedness)
Graphs can be called networks as well.
(e.g. A follows B, A is a student of B)
Does it remind you something?
O- can donate blood to anybody. AB+ can donate blood only to themselves.
Does not change semantics of the graph
(e.g. cost of travel, distance, energy to transition)
In a directional graph cost in one direction could be different than back (e.g. going up hill and downhill).
Number of edges incident on a node.
degree(“1”) = 3; degree(“3”) = 4. What is your node degree on LinkedIn?
[Edge1, Edge2, Edge3, Edge4]
Edge1 = [Node1, Node2]
edge_list = [[0,1],[1,2],[1,3],[2,3]]
[Node1Conns, Node2Conns, Node3Conns, Node4Conns]
Node1Conns = [Node2, Node3]
adj_list = [[1],[0,2,3],[1,3],[1,2]]
More efficient storage options for sparse graphs
adj_list = [[1],[0,2,3],[1,3],[1,2],[],[],[],[],[],[],[],[],[],[]]
adj_dict = {
"v0": ["v1"],
"v1": ["v0","v2","v3"],
"v2": ["v1","v3"],
"v3": ["v1","v2"]
}
adj_dict_weights = {
"v0": {"v1":1},
"v1": {"v0":1,"v2":1,"v3":1},
"v2": {"v1":1,"v3":1},
"v3": {"v1":1,"v2":1}
}
Column names = Nodes, Row names = Nodes, Cells = Edges
adj_matrix = [
[0, 1, 0, 0],
[1, 0, 1, 1],
[0, 1, 0, 1],
[0, 1, 1, 0],
]
Cells are symmetrical across the main diagonal.
adj_matrix = [
[0, 1, 0, 0],
[1, 0, 1, 1],
[0, 1, 0, 1],
[0, 1, 1, 0],
]
Cells can be not symmetrical across the main diagonal.
adj_matrix = [
[0, 1, 0, 0],
[1, 0, 1, 1],
[1, 0, 0, 1],
[0, 0, 0, 0],
]
Cells can represent weights.
adj_matrix = [
[0, 1, 0, 0],
[1, 0, 3, 1],
[2, 0, 0, 1],
[0, 0, 0, 0],
]
class Graph:
def __init__(self, graph_dict = {}, directed = False):
self.graph_dict = graph_dict
self.directed = directed
def __str__(self):
M = self.getMatrix()
S = '\n'.join([''.join(['{:4}'.format(item) for item in row]) for row in M])
return "\n"+S+"\n"
def nodes(self):
return list(self.graph_dict.keys())
def addNode(self, node_id):
self.graph_dict[node_id] = {}
return self
def createEdge(self, node_idA, node_idB, weight = 1):
nodeA_connections = self.graph_dict.get(node_idA)
nodeA_connections.update({node_idB:weight})
self.graph_dict.update({node_idA:nodeA_connections})
return self
def deleteEdge(self, node_idA, node_idB):
del self.graph_dict[node_idA][node_idB]
def removeEdge(self, node_idA, node_idB):
self.deleteEdge(node_idA, node_idB)
if (not self.directed):
self.deleteEdge(node_idB, node_idA)
def removeNode(self, node_id):
connections = self.graph_dict[node_id]
for node_adj in set(connections):
self.deleteEdge(node_id,node_adj)
self.deleteEdge(node_adj,node_id)
del self.graph_dict[node_id]
def addEdge(self, node_idA, node_idB, weight = 1):
self.createEdge(node_idA, node_idB, weight)
if (not self.directed):
self.createEdge(node_idB, node_idA, weight)
return self
def getEdgeWeight(self, node_idA, node_idB):
return self.graph_dict.get(node_idA).get(node_idB,0)
def getMatrix(self):
matrix = [[self.getEdgeWeight(i,j) for j in self.nodes()] for i in self.nodes()]
return matrix
graph = Graph()
graph.addNode("a")
graph.addNode("b")
graph.addNode("c")
print(graph)
# 0 0 0
# 0 0 0
# 0 0 0
graph.addEdge("a","b")
graph.addEdge("a","c")
print(graph)
# 0 1 1
# 1 0 0
# 1 0 0
Process of visiting (checking and/or updating) each node in a graph.
A closed path where all edges are different (0 - 1 - 3 - 2 - 0)
Trees can not have cycles. Graphs can have cycles.
Path that visits each node exactly once. (0 - 2 - 3 - 1 - 0)
Hamiltonian cycle is Hamiltonian path which is a cycle.
Example: a tourist visiting all attractions in the city and coming back to the train station where he started the trip.
Path that visits each edge exactly once. (2 - 3 - 1 - 0 - 2 - 1)
Eulerian cycle is Eulerian path which is a cycle. (Not this case)
Path that visits each edge exactly once. (2 - 3 - 1 - 0 - 2 - 1)
Example: a tourist going through all iconic attractions, without going over the same road again.
Such maze could be presented as a grid graph, with no connections, in places with walls.
Points of interest are nodes. And roads are edges connecting points of interests.
People are nodes. And edges are connections between them.
Visualization of search algorithms:
Start with Node 1 and see how BFS and DFS traverse the graph.
Breadth-first search is an algorithm for traversing or searching tree or graph data structures. It starts at some arbitrary node of a graph and explores the neighbor nodes first, before moving to the next level neighbours.
Worst-case performance = O(|V| + |E|)
All nodes are not visited (tomato color). The node 1 is current.
We discover the neighbors of Node 1: Node 2,…
We discover the neighbors of Node 1: Node 2, and Node 5.
As we discovered all neighbors of Node 1, we mark Node 1 as visited (black) and go to make Node 2 as current.
We discover the nodes of Node 2, which is only 3, as all others are already discovered or visited.
We mark Node 2 as visited, and make Node 5 as the current node.
The single not discovered neighbor of Node 5 is Node 4.
We mark Node 5 as visited and make Node 3 as the current node.
There are no more nodes to discover, so we mark Node 3 as visited and make Node 4 as the current one.
With Node 4 we discover Node 6.
We mark Node 4 as visited and make Node 6 as the current one.
Node 6 does not have any more nodes to discover. We mark Node 6 as visited. No more nodes to visit. The algorithm is acomplished.
\[ \begin{aligned} &Initialize(G,s)\\ &\qquad for \space each \space node \space u \in G.V - \{s\}\\ &\qquad \qquad u.color = WHITE\\ &\qquad \qquad u.d = \infty\\ &\qquad \qquad u.\pi = NIL\\ &\qquad s.color = GRAY \\ &\qquad s.d = 0\\ &\qquad s.\pi = NIL\\ &\qquad Q = \emptyset\\ &\qquad Enqueue(Q,s)\\ \end{aligned} \]
\[ \begin{aligned} &BFS(G,s)\\ &\qquad Initialize(G,s)\\ &\qquad while \space Q \neq \emptyset\\ &\qquad \qquad u = Dequeue(Q) \\ &\qquad \qquad for \space each \space v \in G.Adj[u] \\ &\qquad \qquad \qquad if \space v.color == WHITE\\ &\qquad \qquad \qquad \qquad v.color = GRAY\\ &\qquad \qquad \qquad \qquad v.d = u.d + 1\\ &\qquad \qquad \qquad \qquad v.\pi = u\\ &\qquad \qquad \qquad \qquad Enqueue(Q,v)\\ &\qquad \qquad u.color = BLACK\\ \end{aligned} \]
def initialize(graph, start = None):
nodes_prop = {}
for node in graph.keys():
nodes_prop[node] = {
"color": "white",
"distance": float('inf'),
"parent":None
}
nodes_prop[start] = {
"color":"gray",
"distance": 0,
"parent": None,
}
Q = [start]
return nodes_prop, Q
def bfs(graph, start):
nodes, queue = initialize(graph, start)
while queue:
u = queue.pop(0)
for v in graph[u]:
if nodes[v]["color"] == "white":
nodes[v] = {
"color":"gray",
"distance": nodes[u]["distance"] + 1,
"parent": u
}
queue.append(v)
nodes[u]["color"] = "black"
return nodes
def getPath(nodes, targetNode):
current = targetNode
path = []
while nodes[current]["parent"]:
path.append(nodes[current]["parent"])
current = nodes[current]["parent"]
return path
Breadth-first search is an algorithm for traversing graph data structures. One starts selecting some arbitrary node as the root and explores as far as possible along each branch before backtracking.
Worst-case performance O(|V| + |E|)
Node 1 is current (darkgreen). All other nodes are never visited (tomato color).
Node 1 is current (darkgreen). We discover node 2.
Node 2 is current (darkgreen).
Node 2 is current (darkgreen). We discover Node 3.
Node 3 is current (darkgreen).
Node 3 is current (darkgreen). We discover Node 4.
Node 4 is current (darkgreen).
Node 4 is current (darkgreen). We discover Node 5.
Node 5 is current (darkgreen). No nodes to discover.
Node 5 becomes visited. We go back to the node 4.
We go back to the node 4 and discover node 6.
Node 6 becomes current. No nodes to discover there.
Node 6 becomes visited. Node 4 becomes current.
Node 4 becomes visited. Node 3 becomes current.
Node 3 becomes visited. Node 2 becomes current.
Node 2 becomes visited. Node 1 becomes current.
Node 1 becomes visited. Algorithm finished.
\[ \begin{aligned} &DFS(G,s)\\ &\qquad for \space each \space node \space u \in G.V\\ &\qquad \qquad u.color = WHITE\\ &\qquad \qquad u.\pi = NIL\\ &\qquad time = 0 \\ &\qquad for \space each \space node \space u \in G.V\\ &\qquad \qquad if \space u.color == WHITE\\ &\qquad \qquad \qquad DFSVisit(G,u) \end{aligned} \]
\[ \begin{aligned} &DFSVisit(G,u)\\ &\qquad time = time + 1 \\ &\qquad u.d = time \\ &\qquad u.color = GRAY \\ &\qquad for \space each \space v \in G.Adj[u]\\ &\qquad \qquad if \space v.color == WHITE\\ &\qquad \qquad \qquad v.\pi = u \\ &\qquad \qquad \qquad DFSVisit(G,v) \\ &\qquad u.color = BLACK \\ &\qquad time = time + 1 \\ &\qquad u.f = time \\ \end{aligned} \]
def dfs(graph):
nodes_prop = {}
for u in graph.keys():
nodes_prop[u] = {
"color":"white",
"parent": None
}
global time
time = 0
for u in graph.keys():
if nodes_prop[u]["color"] == "white":
nodes_prop = dfs_visit(graph, nodes_prop, u)
return nodes_prop
def dfs_visit(graph, nodes_prop, u):
global time
time = time + 1
nodes_prop[u]["distance"] = time
nodes_prop[u]["color"] = "gray"
for v in graph[u]:
if nodes_prop[v]["color"] == "white":
nodes_prop[v]["parent"] = u
nodes_prop = dfs_visit(graph, nodes_prop, v)
nodes_prop[u]["color"] = "black"
time = time + 1
nodes_prop[u]["finish"] = time
return nodes_prop
This work is (c) 2017 - onwards by TU Delft and Pavel Kucherbaev and licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license.