Transcript Graphs
CS 261 – Data Structures
Graphs
Graphs
• Used in a variety of applications and algorithms
• Graphs represent relationships or connections
• Superset of trees (i.e., a tree is a restricted form of a graph):
– A graph represents general relationships:
•
•
•
•
Each node may have many predecessors
There may be multiple paths (or no path) from one node to another
Can have cycles or loops
Examples: airline flight connections, friends, algorithmic flow, etc.
– A tree has more restrictive relationships and topology:
•
•
•
•
Each node has a single predecessor—its parent
There is a single, unique path from the root to any node
No cycles
Example: less than or greater than in a binary search tree
Graphs: Vertices and Edges
• A graph is composed of vertices and edges
• Vertices (also called nodes):
– Represent objects, states (i.e., conditions or configurations), positions,
or simply just place holders
– Set {v1, v2, …, vn}: each vertex is unique no two vertices represent the
same object/state
• Edges (also called arcs):
– Can be either directed or undirected
– Can be either weighted (or labeled) or unweighted
– An edge (vi, vj) between two vertices indicates that they are
directly related, connected, etc.
– If there is an edge from vi to vj, then vj is a neighbor of vi (if the
edge is undirected then vi and vj are neighbors or each other)
Graphs: Types of Edges
Undirected
Directed
v1
v1
Unweighted
v4
v3
v1
Weighted
v2
v3
v2
v2
w1-2
v4
v1
w1-4
w1-3
v3
w2-3
w3-4
v4
w1,2
v2
w4,1
w1,3
v3
w2,3
w3,4
v4
Graphs: Directed and Undirected
• An undirected edge e = (vi, vj) indicates that the
relationship, connection, etc. is bi-direction:
– Can go from vi to vj (i.e., vi is related to vj) and vice-versa
– Example: friends – Steve and Alicia are friends
Steve
Alicia
• A directed edge e = (vi, vj) specifies a one-directional
relationship or connection:
– Can only go from vi to vj
– Example: like – George likes Mary
George
Mary
Graphs: Directed and Undirected (cont.)
• A graph will have either directed or undirected edges, but
typically not both.
• Two directed edges can replace any undirected edge:
– If there is an undirected
edge e= (vi, vj), it is replaced with two
directed edges e = (vi, vj) and e = (vj, vi)
vi
vj
vi
vj
– If the graph is unweighted (or the weights for both directed edges are
the same), then a shorthand (graphical) notation can be used
vi
vj
vi
– We will discuss only directed graphs (and use e instead of e)
vj
Graphs: Example
Pierre
Peoria
Pendleton
Pittsburgh
Pueblo
Princeton
Phoenix
Pensacola
Graphs: Representations
Two most common representations:
1. Adjacency matrix:
• Represents graph as a 2-D matrix
• Vertices are used as indices for both the rows and columns of the matrix
– Vertices must be numbered or must associate an integer value with each vertex
• A matrix entry in row i and column j of:
1: indicates an edge from vi to vj
0: no edge from vi to vj
• Weighted representation has weight wi,j instead of 1 and instead of 0
• Requires O(V 2) space for V vertices
2. Edge list:
• Each vertex vi lists the set of directly connected neighbors
• Weighted representation replaces the neighbor set with a Map:
– key vertex vj
– value weight wi,j
• Stores only the edges more space efficient for sparse graph: O(V + E)
Graphs Representation: Adjacency Matrix
Pierre
Peoria
Pendleton
Pittsburgh
Pueblo
Princeton
City
0
1 2 3 4 5 6 7
0: Pendleton
?
0 0 1 0 0 0 1
1: Pensacola
0
?
0 1 0 0 0 0
What about the diagonal
matrix entries?
2: Peoria
0
0
?
Is a vertex connected to
itself?
3: Phoenix
0
0 1
4: Pierre
1
0 0 0
Phoenix
Pensacola
0 0 1 0 1
?
0 1 0 1
?
0 0 0
5: Pittsburgh 0
1 0 0 0
?
0 0
6: Princeton
0
0 0 0 0 1
?
0
7: Pueblo
0
0 0 0 1 0 0
?
Graphs Representation: Adjacency Matrix
Pierre
Peoria
Pendleton
Pittsburgh
Pueblo
Princeton
Phoenix
Pensacola
By convention, a vertex is
usually connected to itself
(though, this is not always the case)
City
0
1 2 3 4 5 6 7
0: Pendleton
1
0 0 1 0 0 0 1
1: Pensacola
0
1 0 1 0 0 0 0
2: Peoria
0
0 1 0 0 1 0 1
3: Phoenix
0
0 1 1 0 1 0 1
4: Pierre
1
0 0 0 1 0 0 0
5: Pittsburgh 0
1 0 0 0 1 0 0
6: Princeton
0
0 0 0 0 1 1 0
7: Pueblo
0
0 0 0 1 0 0 1
Graphs Representation: Edge List
Pierre
Peoria
Pendleton
Pittsburgh
Pueblo
Princeton
Phoenix
Pensacola
Pendleton:
Pensacola:
Peoria:
Phoenix:
Pierre:
Pittsburgh:
Princeton:
Pueblo:
{Pueblo, Phoenix}
{Phoenix}
{Pueblo, Pittsburgh}
{Pueblo, Peoria, Pittsburgh}
{Pendleton}
{Pensacola}
{Pittsburgh}
{Pierre}
Reachability
• A common question to ask about a graph is reachability:
• Single-source:
– Which vertices are “reachable” from a given vertex vi?
– Basic algorithm:
Initialize set of reachable vertices with vi and add vi to a stack
While stack is not empty
Get and remove (pop) last vertex v from stack
For all neighbors, vj, of v
If vj is not is set of reachable vertices, add to stack and reachable set
• All-pairs:
– For all pairs of vertices vi and vj, is vj “reachable” from vi?
– Solves the single source question for all vertices
All-Pairs Reachability: Adjacency Matrix
• How do we compute all-pairs reachability
• Converts adjacency matrix into a “reachability” matrix
– A matrix entry in row i and column j of:
1: indicates that there is a path (of zero or more edges) from vi to vj
0: no path from vi to vj
• Warshall’s algorithm:
– Named after the computer scientist who discovered it
– Three nested loops of order V O(V 3)
– Key idea: each iteration of the outer loop (index k) adds any
path of length 2 that has vertex vk as its center
– Since the adjacency and the reachability matrices are binary (0 or
1 entries), use bitwise operations
Warshall’s Algorithm
void warshall(int [][] a) { // Input: initial adjacency matrix.
int n = a.length;
// Number of vertices.
for (int k = 0; k < n; k++) {// Add paths len 2 through vk.
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) // Add path from vi to vj
a[i][j] |= a[i][k] & a[k][j]; // going through vk.
}
}
void matrixOutput(int [][] a) { // Print matrix.
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[i].length; j++)
print(" " + a[i][j]);
println(" ");
}
}
Warshall’s Algorithm: Initialization
class Warshall {
...
static public void main (String [] args) {
int [][] adjacency = {{1, 0, 0, 1, 0, 0,
{0, 1, 0, 1, 0, 0,
{0, 0, 1, 0, 0, 1,
{0, 0, 1, 1, 0, 1,
{1, 0, 0, 0, 1, 0,
{0, 1, 0, 0, 0, 1,
{0, 0, 0, 0, 0, 1,
{0, 0, 0, 0, 1, 0,
warshall
(adjacency);
matrixOutput(adjacency);
}
}
0,
0,
0,
0,
0,
0,
1,
0,
1}, // Initialize
0}, // adjacency
1}, // matrix.
1},
0},
0},
0},
1}};
// Compute all-pairs reachability matrix.
// Print resulting reachability matrix.
Warshall’s Algorithm: Initialization
Pierre
Peoria
Pendleton
Pittsburgh
Initial adjacency matrix
Pueblo
Princeton
Phoenix
Pensacola
City
0
1 2 3 4 5 6 7
0: Pendleton
1
0 0 1 0 0 0 1
1: Pensacola
0
1 0 1 0 0 0 0
2: Peoria
0
0 1 0 0 1 0 1
3: Phoenix
0
0 1 1 0 1 0 1
4: Pierre
1
0 0 0 1 0 0 0
5: Pittsburgh 0
1 0 0 0 1 0 0
6: Princeton
0
0 0 0 0 1 1 0
7: Pueblo
0
0 0 0 1 0 0 1
Warshall’s Algorithm: After Iteration 0
Pierre
Peoria
Pendleton
Pittsburgh
Add all paths of length 2 that go
through vertex 0 (Pendleton)
Pueblo
Princeton
Phoenix
Pensacola
Define some paths of length 2 in this
(the first) iteration
City
0
1 2 3 4 5 6 7
0: Pendleton
1
0 0 1 0 0 0 1
1: Pensacola
0
1 0 1 0 0 0 0
2: Peoria
0
0 1 0 0 1 0 1
3: Phoenix
0
0 1 1 0 1 0 1
4: Pierre
1
0 0 1 1 0 0 1
5: Pittsburgh 0
1 0 0 0 1 0 0
6: Princeton
0
0 0 0 0 1 1 0
7: Pueblo
0
0 0 0 1 0 0 1
Warshall’s Algorithm: After Iteration 1
Pierre
Peoria
Pendleton
Pittsburgh
Add all paths of length 2 that go
through vertex 1 (Pensacola)
Pueblo
Princeton
Phoenix
Pensacola
Could potentially define paths of
length 3 in this (the second) iteration
City
0
1 2 3 4 5 6 7
0: Pendleton
1
0 0 1 0 0 0 1
1: Pensacola
0
1 0 1 0 0 0 0
2: Peoria
0
0 1 0 0 1 0 1
3: Phoenix
0
0 1 1 0 1 0 1
4: Pierre
1
0 0 1 1 0 0 1
5: Pittsburgh 0
1 0 1 0 1 0 0
6: Princeton
0
0 0 0 0 1 1 0
7: Pueblo
0
0 0 0 1 0 0 1
Warshall’s Algorithm: After Iteration 2
Pierre
Peoria
Pendleton
Pittsburgh
Add all paths of length 2 that go
through vertex 2 (Peoria)
Pueblo
Princeton
Phoenix
Pensacola
Edges already exist (doesn’t add
anything new)
Could potentially define paths of
length 4 in this (the third) iteration
City
0
1 2 3 4 5 6 7
0: Pendleton
1
0 0 1 0 0 0 1
1: Pensacola
0
1 0 1 0 0 0 0
2: Peoria
0
0 1 0 0 1 0 1
3: Phoenix
0
0 1 1 0 1 0 1
4: Pierre
1
0 0 1 1 0 0 1
5: Pittsburgh 0
1 0 1 0 1 0 0
6: Princeton
0
0 0 0 0 1 1 0
7: Pueblo
0
0 0 0 1 0 0 1
Warshall’s Algorithm: After Iteration 3
Pierre
Peoria
Pendleton
Pittsburgh
Add all paths of length 2 that go
through vertex 3 (Phoenix)
Pueblo
Princeton
Phoenix
Pensacola
Notice that it also includes the paths
formed in interations 0 and 1
(resulting, in this case, in new
reachability paths of length 3)
Could potentially define paths of
length 5 in this (the fourth) iteration
City
0
1 2 3 4 5 6 7
0: Pendleton
1
0 1 1 0 1 0 1
1: Pensacola
0
1 1 1 0 1 0 1
2: Peoria
0
0 1 0 0 1 0 1
3: Phoenix
0
0 1 1 0 1 0 1
4: Pierre
1
0 1 1 1 1 0 1
5: Pittsburgh 0
1 1 1 0 1 0 1
6: Princeton
0
0 0 0 0 1 1 0
7: Pueblo
0
0 0 0 1 0 0 1
Warshall’s Algorithm: After Iteration 7
Pierre
Peoria
Pendleton
Pittsburgh
Final result: Every city can reach
every other city, except for Princeton
Pueblo
Princeton
Phoenix
Pensacola
The final matrix has a 1 in row i and
column j if vertex vj is reachable
from vertex vi via some path
City
0
1 2 3 4 5 6 7
0: Pendleton
1
1 1 1 1 1 0 1
1: Pensacola
1
1 1 1 1 1 0 1
2: Peoria
1
1 1 1 1 1 0 1
3: Phoenix
1
1 1 1 1 1 0 1
4: Pierre
1
1 1 1 1 1 0 1
5: Pittsburgh 1
1 1 1 1 1 0 1
6: Princeton
1
1 1 1 1 1 1 1
7: Pueblo
1
1 1 1 1 1 0 1
Warshall’s Algorithm: Another Look
static void warshall(int [][] a) { // Input: initial adjacency matrix.
int n = a.length;
// Number of vertices.
for (int k = 0; k < n; k++) {
// Add paths len 2 through vk.
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
// Add path from vi to vj
a[i][j] |= a[i][k] & a[k][j];
// going through vk.
}
}
• Notice that a[i][k] doesn’t change inside the inner loop:
– If zero, then the inner loop does nothing
– If one, don’t need to perform bitwise AND (&)
– If graph is sparse, the inner loop is not needed very often (at least
initially)
– May get a performance improvement by checking a[i][k] prior
to third loop
Warshall’s Algorithm: Another Look (cont.)
Improved code:
static void warshall(int [][] a) { // Input: initial adjacency matrix.
int n = a.length;
// Number of vertices.
for (int k = 0; k < n; k++) {
// Add paths length 2 through vk.
for (int i = 0; i < n; i++)
if (a[i][k] == 1)
// If there is a path from vi to vk:
for (int j = 0; j < n; j++)
// Add path from vi to vj
a[i][j] |= a[k][j];
// going through vk.
}
}
Weighted Graph: All-Pairs Reachability
• What if we have a weighted graph?
– Example: the distance between cities
• The all-pairs reachability question then becomes:
– Is there a path between any two vertices vi and vk and, if so, what
is weight of the minimum weight path?
• Floyds’s algorithm:
– Named after the computer scientist who discovered it
– Very much the same as Warshall’s algorithm
– Key difference: adjacency graph now has weights instead of
binary values
• In place of bitwise AND, use addition
• In place of bitwise OR, use a minimum-value calculation
Floyds’s Algorithm: Initialization
Pierre
2
Pendleton
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
Initial adjacency matrix
Princeton
3
City
Phoenix
5
Pensacola
0: Pendleton
0
1 2 3 4 5 6 7
0 4 8
1: Pensacola 0 5
2: Peoria
0 5 3
3: Phoenix
4 0 10 3
4: Pierre
2 0
5: Pittsburgh 4 0
6: Princeton
1 0
7: Pueblo
3 0
Floyd’s Algorithm
class Floyd {
static void floyd(double [][] a) { // Input: initial adjacency matrix.
int n = a.length;
// Number of vertices.
for (int k = 0; k < n; k++) {
// Add paths length 2 through vk.
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
// Check path from vi to vj
double m = a[i][k] + a[k][j];
// going through vk.
if (m < a[i][j]) a[i][j] = m;
// Update if less.
}
}
}
...
}
Floyd’s Algorithm: Improved
Of course, we can apply a similar improvement as we did
to Warshall’s algorithm
void floyd(double [][] a) { // Input: initial adjacency matrix.
int n = a.length;
// Number of vertices.
for (k = 0; k < n; k++) {
// Add paths length 2 through vk.
for (i = 0; i < n; i++) {
double m = a[i][k];
// If there is a path from vi to vk:
if (m < Double.POSITIVE_INFINITY)
for (j = 0; j < n; j++) {
// Check path from vi to vj
double m += a[k][j];
// going through vk.
if (m < a[i][j]) a[i][j] = m; // Update if less.
}
}
}
}
...
}
Single Source Reachability: Edge-List
• How do you determine reachability with an edge-list
representation?
– In this case we want to answer the single-source question:
What is reachable from a given vertex vi?
– Should be faster than the all-pairs question
– Basic algorithm:
Initialize set of reachable vertices with vi and add vi to a stack
While stack is not empty
Get and remove (pop) last vertex v from stack
For all neighbors, vj, of v
If vj is not is set of reachable vertices, add to stack and reachable set
Weighted Graphs Representation: Edge List
Pierre
2
Pendleton
8
3
Pueblo
4
Peoria
3
Instead of a Map of Sets, use
a Map of Maps
Pittsburgh
4
2
10
4
3
Phoenix
5
5
Princeton
First key is source, second
key is destination, value is
weight
Pensacola
Pendleton:
Pensacola:
Peoria:
Phoenix:
Pierre:
Pittsburgh:
Princeton:
Pueblo:
{Pueblo:8, Phoenix:4}
{Phoenix:5}
{Pueblo:3, Pittsburgh:5}
{Pueblo:3, Peoria:4, Pittsburgh:10}
{Pendleton:2}
{Pensacola:4}
{Pittsburgh:2}
{Pierre:3}
What about Weighted Graphs?
Pierre
2
Pendleton
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
Princeton
Dijkstra’s algorithm.
Use a priority queue instead
of a stack. Return a map of
city, distance pairs. Pqueue
orders values on shortest
distance
3
Phoenix
5
Pensacola
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Example: What is the distance from Pierre
Pierre
2
Pendleton
-----------Pierre: 0
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
Princeton
3
Phoenix
5
Pensacola
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Example: What is the distance from Pierre
Pierre
2
Pendleton
Pierre: 0
-----------Pendeleton: 2
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
Princeton
3
Phoenix
5
Pensacola
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Example: What is the distance from Pierre
Pierre
2
Pendleton
Pierre: 0, Pendleton: 2
-----------Phoenix: 6, Pueblo: 10
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
Princeton
Notice how the distances
have been added
3
Phoenix
5
Pensacola
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Example: What is the distance from Pierre
Pierre
2
Pendleton
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
3
Phoenix
5
Pensacola
Princeton
Pierre: 0, Pendleton: 2,
Phoenix: 6
-----------Pueblo: 9, Peoria: 10,
Pueblo: 10, Pittsburgh: 16
Notice how values are stored
in the Pqueue in distance
order
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Example: What is the distance from Pierre
Pierre
2
Pendleton
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
3
Phoenix
5
Pensacola
Princeton
Pierre: 0, Pendleton: 2,
Phoenix: 6, Pueblo: 9
-----------Peoria: 10, Pueblo: 10,
Pierre: 13, Pittsburgh: 16
Pierre gets put in queue,
although it is known to be
reachable
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Example: What is the distance from Pierre
Pierre
2
Pendleton
Peoria
5
3
3
Pittsburgh
8
Pueblo
4
2
10
4
4
3
Phoenix
5
Pensacola
Princeton
Pierre: 0, Pendleton: 2,
Phoenix: 6, Pueblo: 9,
Peoria: 10
-----------Pueblo: 10, Pierre: 13,
Pueblo: 13, Pittsburgh: 15,
Pittsburgh: 16
Duplicates only removed
when pulled out of queue
Dijkstra (String startCity, Map[String, Map[String, double]] distances)
Make empty map of distances into variable reachable
Put (StartingCity, 0) into Pqueue
While Pqueue not empty
pull new city from queue, if not ready in reachable, add to reachable
add neighbors to queue, adding weight to distance from starting city
When done with loop, return reachable map
Your Turn
• Do Dijkstra algorithm starting from Pensacola