Graph



By Prof. Seungchul Lee
http://iai.postech.ac.kr/
Industrial AI Lab at POSTECH

Table of Contents

1. Graph


  • abstract relations, topology, or connectivity
  • Graphs G(V,E)
    • V: a set of vertices (nodes)
    • E: a set of edges (links, relations)
    • weight (edge property)
      • distance in a road network
      • strength of connection in a personal network
  • Graphs can be directed or undirected
  • Graphs model any situation where you have objects and pairwise relationships (symmetric or asymmetric) between the objects


Vertex Edge
People like each other undirected
People is the boss of directed
Tasks cannot be processed at the same time undirected
Computers have a direct network connection undirected
Airports planes flies between them directed
City can travel between them directed

1.1. Adjacent Matrix

  • Undirected graph $G = (V,E)$



$$ \begin{align*}V &= \{1,2,\cdots,7\} \\ E &= \{\{1,2\},\{1,6\},\{2,3\},\{3,4\},\{3,6\},\{3,7\},\{4,7\},\{5,6\} \} \end{align*} $$


$$\text{Adjacency list} = \begin{cases} \;\; \text{adj}(1) = \{2,6\}\\ \;\; \text{adj}(2) = \{1,3\}\\ \;\; \text{adj}(3) = \{2,4,6,7\}\\ \;\; \text{adj}(4) = \{3,7\}\\ \;\; \text{adj}(5) = \{6\}\\ \;\; \text{adj}(6) = \{1,3,5\}\\ \;\; \text{adj}(7) = \{3,4\} \end{cases}$$


$$ \text{Adjacency matrix (symmetric) } A = \begin{bmatrix} 0&1&0&0&0&1&0\\ 1&0&1&0&0&0&0\\ 0&1&0&1&0&1&1\\ 0&0&1&0&0&0&1\\ 0&0&0&0&0&1&0\\ 1&0&1&0&1&0&0\\ 0&0&1&1&0&0&0\\ \end{bmatrix}$$
  • Directed graph $G = (V,E)$



$$ \begin{align*} V &= \{1,2,\cdots,7\} \\ E &= \{\{1,2\},\{1,6\},\{2,3\},\{3,4\},\{3,7\},\{4,7\},\{6,3\},\{6,5\} \} \end{align*} $$


$$\text{Adjacency list} = \begin{cases} \;\; \text{adj}(1) &= \{2,6\}\\ \;\; \text{adj}(2) &= \{3\}\\ \;\; \text{adj}(3) &= \{4,7\}\\ \;\; \text{adj}(4) &= \{7\}\\ \;\; \text{adj}(5) &= \phi\\ \;\; \text{adj}(6) &= \{3,5\}\\ \;\; \text{adj}(7) &= \phi \end{cases}$$


$$ \text{Adjacency matrix (symmetric) } A = \begin{bmatrix} 0&1&0&0&0&1&0\\ 0&0&1&0&0&0&0\\ 0&0&0&1&0&0&1\\ 0&0&0&0&0&0&1\\ 0&0&0&0&0&0&0\\ 0&0&1&0&1&0&0\\ 0&0&0&0&0&0&0\\ \end{bmatrix}$$

1.2. What can We Do with a Graph?

Graph search problem

  • Given: a graph $G=(V,E)$ (directed or undirected) and a start node $s \in V$
  • Find: a set of nodes $\upsilon \in V$ that are reachable from node $s$ (i.e., there exist a path from $s$)
    • Breadth-first search
    • Depth-first search

Path finding problem

  • Given: a graph $G=(V,E)$ (directed or undirected) and a start node $s \in V$
  • Find: path $p$ from $s$ to each node $\upsilon \in V$

Shortest path problem

  • Given: a graph $G=(V,E)$ (directed or undirected) with edge weights $c_{uv} \in \mathbb{R}$ and a start node $s \in V$
  • Find: shortest path length $\delta(s,\upsilon)$ from $s$ to node $\upsilon \in V$


$$\delta(s,\upsilon) =\min_{p\in P_{s\upsilon}} c(p)$$


$\quad \;$ where $P_{s\upsilon} = \{ s \mapsto \upsilon \}$ ia s set of paths from $s$ to $\upsilon$, and $c(p)$ is length (cost) of path $p$

  • Dynamic programming, Dijkstra's algorithm, Bellman-Ford algorithm, A* algorithm

2. Create Graph with NetworkX

2.1. Undirected graphs

In [1]:
import networkx as nx
import matplotlib.pyplot as plt

%matplotlib inline
In [2]:
g = nx.Graph()
g.add_edge('a','b',weight = 0.1)
g.add_edge('b','c',weight = 1.5)
g.add_edge('a','c',weight = 1.0)
g.add_edge('c','d',weight = 2.2)
In [3]:
# draw a graph with nodes and edges
nx.draw(g)
plt.show()
In [4]:
# draw a graph with nodes' labels 

pos = nx.spring_layout(g)

nx.draw_networkx_nodes(g,pos,node_size = 500) # nodes
nx.draw_networkx_edges(g,pos) # edges
nx.draw_networkx_labels(g,pos,font_size = 15,font_family = 'sans-serif') # labels

plt.axis('off')
plt.show()
In [5]:
# draw a graph with nodes' labels and edges' weights

pos = nx.spring_layout(g)

nx.draw_networkx_nodes(g,pos,node_size = 500) # nodes
nx.draw_networkx_edges(g,pos) # edges
nx.draw_networkx_labels(g,pos,font_size = 15,font_family = 'sans-serif') # labels

labels = nx.get_edge_attributes(g,'weight')
nx.draw_networkx_edge_labels(g,pos,edge_labels=labels)

plt.axis('off')
plt.show()
In [6]:
g = nx.Graph()

g.add_node(1)
g.add_node(2)
g.add_node(3)

g.add_edge(1,2)
g[1][2]['weight'] = 1
g.add_edge(1,3,weight=3)

pos = pos = nx.spring_layout(g)

nx.draw(g,pos)
nx.draw_networkx_labels(g,pos,font_size = 15,font_family = 'sans-serif')
labels = nx.get_edge_attributes(g,'weight')
nx.draw_networkx_edge_labels(g,pos,edge_labels=labels)

plt.show()
In [7]:
g = nx.Graph()

g.add_nodes_from([1,2,3])  # a list of nodes
g.add_edges_from([(1,2),(1,3),(2,3)])  # a list of edges

nx.draw(g)
plt.show()
In [8]:
g = nx.Graph()

g.add_nodes_from([1,2,3])  # a list of nodes
g.add_weighted_edges_from([(1,2,1.2),(1,3,2.1),(2,3,2.5)])

pos = nx.spring_layout(g)

nx.draw_networkx_nodes(g,pos,node_size = 500) # nodes
nx.draw_networkx_edges(g,pos) # edges
nx.draw_networkx_labels(g,pos,font_size = 15,font_family = 'sans-serif') # labels

labels = nx.get_edge_attributes(g,'weight')
nx.draw_networkx_edge_labels(g,pos,edge_labels=labels)

plt.axis('off')
plt.show()

2.2. Useful functions in graph

In [9]:
g = nx.Graph()

g.add_nodes_from([1,2,3])  # a list of nodes
g.add_weighted_edges_from([(1,2,1.2),(1,3,2.1),(2,3,2.5)])

pos = nx.spring_layout(g)

nx.draw_networkx_nodes(g,pos,node_size = 500) # nodes
nx.draw_networkx_edges(g,pos) # edges
nx.draw_networkx_labels(g,pos,font_size = 15,font_family = 'sans-serif') # labels

labels = nx.get_edge_attributes(g,'weight')
nx.draw_networkx_edge_labels(g,pos,edge_labels=labels)

plt.axis('off')
plt.show()
In [10]:
print(nx.number_of_nodes(g))
print(nx.number_of_edges(g))
3
3
In [11]:
g.nodes()
Out[11]:
[1, 2, 3]
In [12]:
g.edges()
Out[12]:
[(1, 2), (1, 3), (2, 3)]
In [13]:
g.neighbors(1)
Out[13]:
[2, 3]
In [14]:
A = nx.adjacency_matrix(g)
print(A)
print(A.todense())
  (0, 1)	1.2
  (0, 2)	2.1
  (1, 0)	1.2
  (1, 2)	2.5
  (2, 0)	2.1
  (2, 1)	2.5
[[ 0.   1.2  2.1]
 [ 1.2  0.   2.5]
 [ 2.1  2.5  0. ]]

2.3. Directed Graphs

In [15]:
import networkx as nx
import matplotlib.pyplot as plt

# construct a graph first
dg = nx.DiGraph()
dg.add_nodes_from([0,1,2,3,4])
dg.add_weighted_edges_from([(0,1,1),(0,2,4),(1,2,2),(1,4,11),(2,3,4),(2,4,8),(3,4,3)])

# plot a graph 
pos = nx.spring_layout(dg)

nx.draw(dg,pos,node_size = 500)
nx.draw_networkx_labels(dg,pos,font_size = 10)
labels = nx.get_edge_attributes(dg,'weight')
nx.draw_networkx_edge_labels(dg,pos,edge_labels=labels)
plt.show()
In [16]:
dg.successors(0)
Out[16]:
[1, 2]
In [17]:
dg.predecessors(4)
Out[17]:
[1, 2, 3]
In [18]:
A = nx.adjacency_matrix(dg)
A.todense()
Out[18]:
matrix([[ 0,  1,  4,  0,  0],
        [ 0,  0,  2,  0, 11],
        [ 0,  0,  0,  4,  8],
        [ 0,  0,  0,  0,  3],
        [ 0,  0,  0,  0,  0]], dtype=int32)

3. Alorithms in Graph Theory

In [19]:
import networkx as nx
import matplotlib.pyplot as plt

# construct a graph first
G = nx.Graph()
G.add_nodes_from([1,2,3,4,5,6,7,8])
G.add_edges_from([(1,3),(1,4),(1,5),(1,6),(1,7),(2,4),(2,5),(3,8),(5,7)])

# plot a graph 
pos = nx.spring_layout(G)

nx.draw(G,pos,node_size = 500)
nx.draw_networkx_labels(G,pos,font_size = 10)
plt.show()

3.1. Graph Search Problem

In [20]:
T = nx.dfs_tree(G,8)
nx.draw(T,pos,node_size = 500)
nx.draw_networkx_labels(T,pos,font_size = 10)
plt.show()

print(nx.dfs_successors(G,8))
{1: [4, 6], 2: [5], 3: [1], 4: [2], 5: [7], 8: [3]}
In [21]:
T = nx.bfs_tree(G,8)
nx.draw(T,pos,node_size = 500)
nx.draw_networkx_labels(T,pos,font_size = 10)
plt.show()

print(nx.bfs_successors(G,8))
{8: [3], 1: [4, 5, 6, 7], 3: [1], 4: [2]}

3.2. Path Finding Problem

In [22]:
# plot a graph 
pos = nx.spring_layout(G)

nx.draw(G,pos,node_size = 500)
nx.draw_networkx_labels(G,pos,font_size = 10)
plt.show()
In [23]:
paths = nx.all_simple_paths(G, source=2, target=8)
print(list(paths))
[[2, 4, 1, 3, 8], [2, 5, 1, 3, 8], [2, 5, 7, 1, 3, 8]]

3.3. Shortest Path Problems



In [24]:
import networkx as nx
import matplotlib.pyplot as plt

# construct a graph first
G = nx.Graph()
G.add_nodes_from([0,1,2,3,4])
G.add_weighted_edges_from([(0,1,1),(0,2,4),(1,2,2),(1,4,11),(2,3,4),(2,4,8),(3,4,3)])

# plot a graph with weights displayed
pos = nx.spring_layout(G)
labels = nx.get_edge_attributes(G,'weight')

nx.draw(G,pos,node_size = 500)
nx.draw_networkx_labels(G,pos,font_size = 10)
nx.draw_networkx_edge_labels(G,pos,edge_labels=labels)
plt.show()

# find the shortest path in a graph
print(nx.shortest_path(G,0,4,weight = 'weight'))
[0, 1, 2, 3, 4]

4. Alorithms in Graph Theory w/o NetworkX

4.1. Graph Search Problem

  • Given: a graph $G=(V,E)$ (directed or undirected) and a start node $s \in V$
  • Find: a set of nodes $\upsilon \in V$ that are reachable from node $s$ (i.e., there exist a path from $s$)
    • Breadth-first search (when Q = stack)
    • Depth-first search (when Q = queue)



In [25]:
# Q: stack

def Graph_Search_DFS(graph, s):
    Q = [s]
    marked = []

    while Q:
        v = Q.pop()
        
        if v not in marked:
            marked.append(v)
        
        for w in graph[v]:
            if w not in marked:
                Q.append(w)

    return marked        
In [26]:
# Q: queue

def Graph_Search_BFS(graph, s):
    Q = [s]
    marked = []

    while Q:
        v = Q.pop(0)
        
        if v not in marked:
            marked.append(v)
        
        for w in graph[v]:
            if w not in marked:
                Q.append(w)

    return marked       



In [27]:
Adj = {
 0: [1, 5], 
 1: [0, 2],                   
 2: [1, 3, 5, 6], 
 3: [2, 6], 
 4: [5],
 5: [0, 2, 4], 
 6: [2, 3]
}
In [28]:
print(Graph_Search_DFS(Adj, 0))
[0, 5, 4, 2, 6, 3, 1]
In [29]:
print(Graph_Search_BFS(Adj, 0))
[0, 1, 5, 2, 4, 3, 6]

4.2. Path Finding Problem

  • Given: a graph $G=(V,E)$ (directed or undirected) and a start node $s \in V$
  • Find: path $p$ from $s$ to each node $\upsilon \in V$


In [30]:
# will be used to get reverse path from label

def get_path(v, label, p):
    
    p.append(v)
    
    if label[v] == -1:        
        return p
    else:
        p = get_path(label[v], label, p)  
        return p
In [31]:
def Find_Path(graph, s, t):
    
    label = [-1]*len(graph)
    
    if s in [t]:
        return s
    
    Q = [s]
    marked = []    
    
    while Q:
        v = Q.pop()
        
        if v not in marked:
            marked.append(v)
        
        if v in [t]:
            return get_path(v, label, [])
        
        for w in graph[v]:
            if w not in marked:
                label[w] = v
                Q.append(w)
                
    return 



In [32]:
Adj = {
 0: [1, 5], 
 1: [0, 2],                   
 2: [1, 3, 5, 6], 
 3: [2, 6], 
 4: [5],
 5: [0, 2, 4], 
 6: [2, 3]
}
In [33]:
path = Find_Path(Adj, 0, 3)

print(path)
[3, 6, 2, 5, 0]
In [34]:
# Reverse path (= backtracking)
path = path[::-1]

print(path)
[0, 5, 2, 6, 3]

4.3. Shortest Path Problem

  • Given: a graph $G=(V,E)$ (directed or undirected) with edge weights $c_{uv} \in \mathbb{R}$ and a start node $s \in V$
  • Find: shortest path length $\delta(s,\upsilon)$ from $s$ to node $\upsilon \in V$


$$\delta(s,\upsilon) =\min_{p\in P_{s\upsilon}} c(p)$$


$\quad \;$ where $P_{s\upsilon} = \{ s \mapsto \upsilon \}$ ia s set of paths from $s$ to $\upsilon$, and $c(p)$ is length (cost) of path $p$

  • Dynamic programming, Dijkstra's algorithm, Bellman-Ford algorithm, A* algorithm

Dijkstra's algorithm

  • Dijkstra’s Algorithm = Dynamic programming on graph
  • Graph-Search with $Q$ $\equiv$ priority queue on $\rho[\upsilon]$
    • a node $\upsilon$ with minimum $\rho[\upsilon]$ is selected first
    • minimum-cost-first-out $\Rightarrow$ Dijkstra

Basic idea:

Initially $\rho[\upsilon] = \infty$ for all nodes. Starting with $s$, mark nodes as Graph-Search. When a node $\upsilon$ is newly marked. For each node $\omega \in Q$ updata $\rho[\omega]$ with


$$\rho[\omega] \leftarrow \min\{\rho[\omega], \rho[\upsilon] + c_{\upsilon \omega} \}$$


When all nodes reachable from $s$ are marked, $\forall \upsilon \in V, \;\rho[\upsilon] = \delta(s,\upsilon)\quad$ ($\rho[\upsilon]= \infty$ if $\upsilon$ is not reachable from $s$)



In [35]:
# example of priority queue

import queue

Q = queue.PriorityQueue()

Q.put(5)
Q.put(1)
Q.put(4)

print(Q.queue)
print(Q.get())
print(Q.get())
[1, 5, 4]
1
4
In [36]:
# example of priority queue, \rho[v]=min_{u \in Q} \rho[u]

q = queue.PriorityQueue()

q.put([1, 5])
q.put([4, 3])
q.put([0, 6])

print(q.get()[1])
6
In [37]:
rho = [float('inf')]*7
rho[2] = 0

print(rho)
[inf, inf, 0, inf, inf, inf, inf]
In [38]:
def Dijkstra(graph, s, t):
    
    label = [-1]*len(graph)
    
    rho = [float('inf')]*len(graph)
    rho[s] = 0
    
    Q = queue.PriorityQueue()
    Q.put([rho[s], s])
    
    marked = []    
    
    while Q:
        v = Q.get()[1]
        
        if v not in marked:
            marked.append(v)
        
        if v in [t]:
            return get_path(v, label, [])
        
        for w in graph[v].keys():
            if w not in marked:
                rho[w] = min(rho[w], rho[v] + graph[v][w])
                if rho[w] == rho[v] + graph[v][w]:                
                    label[w] = v
                Q.put([rho[w], w])
                
    return 



In [39]:
# define graph with weights

Adj = {
 0: {1:2, 2:1},
 1: {2:3, 3:3},
 2: {4:1},
 3: {5:2},
 4: {3:2, 5:5},
 5: {},
}
In [40]:
# get adjacent nodes

print(Adj[2])

print(Adj[2].keys())
{4: 1}
dict_keys([4])
In [41]:
# get distance or weight from 2 to 4

print(Adj[2][4])
1
In [42]:
path = Dijkstra(Adj, 0, 5)
print(path)
[5, 3, 4, 2, 0]
In [43]:
# Reverse path (backtracking)
path = path[::-1]

print(path)
[0, 2, 4, 3, 5]

5. Examples: Probabilistic Road Maps (PRM) for robot path planning

  • a motion planning algorithm in robotics, which solves the problem of determining a path between a starting configuration of the robot and a goal configuration while avoiding collisions.
  • The basic idea behind PRM is
    1. to take random samples from the configuration space of the robot,
    2. testing them for whether they are in the free space, and
    3. use a local planner to attempt to connect these configurations to other nearby configurations.
    4. remove all colliding paths (collision-free)
    5. The starting and goal configurations are added in, and
    6. a graph search algorithm is applied to the resulting graph to determine a path between the starting and goal configurations.
In [44]:
%%html
<center><iframe width="560" height="315" src="https://www.youtube.com/embed/bbf8YCRvAHE?rel=0"
width="560" height="315" frameborder="0" allowfullscreen></iframe></center>
In [45]:
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')