0% found this document useful (0 votes)
58 views78 pages

Graphs: CS 302 - Data Structures Section 9.3

Graph is a data structure consisting of vertices and edges connecting the vertices. It can be represented using an adjacency matrix or linked list. Graph searching algorithms like depth-first search (DFS) and breadth-first search (BFS) are used to find paths between vertices. DFS uses a stack and explores as far as possible along each branch before backtracking. BFS uses a queue and searches all vertices at the current level before moving to the next level.

Uploaded by

Jazib Alikhan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
58 views78 pages

Graphs: CS 302 - Data Structures Section 9.3

Graph is a data structure consisting of vertices and edges connecting the vertices. It can be represented using an adjacency matrix or linked list. Graph searching algorithms like depth-first search (DFS) and breadth-first search (BFS) are used to find paths between vertices. DFS uses a stack and explores as far as possible along each branch before backtracking. BFS uses a queue and searches all vertices at the current level before moving to the next level.

Uploaded by

Jazib Alikhan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 78

Graphs

CS 302 – Data Structures


Section 9.3
What is a graph?
• A data structure that consists of a set of nodes
(vertices) and a set of edges between the vertices.
• The set of edges describes relationships among the
vertices.

1 2

3 4
Applications

Schedules Computer networks


Maps

Hypertext Circuits
Formal definition of graphs
• A graph G is defined as follows:
G=(V,E)
V: a finite, nonempty set of vertices
E: a set of edges (pairs of vertices)
Undirected graphs
• When the edges in a graph have no
direction, the graph is called undirected
undirected graph

The order of vertices in E


is not important for
undirected graphs!!
Directed graphs
• When the edges in a graph have a direction,
the graph is called directed.

The order of vertices in E


is important for
directed graphs!!

E(Graph2) = {(1,3) (3,1) (5,9) (9,11) (5,7)


Trees vs graphs
• Trees are special cases of graphs!!

I
Graph terminology
• Adjacent nodes: two nodes are adjacent if
they are connected by an edge

7 is adjacent from 5
5 7 or
5 is adjacent to 7

7 is adjacent from/to 5
5 7 or
5 is adjacent from/to 7
Graph terminology
• Path: a sequence of vertices that connect
two nodes in a graph.
• The length of a path is the number of edges
in the path.

1 2

e.g., a path from 1 to 4:


<1, 2, 3, 4>

3 4
Graph terminology
• Complete graph: a graph in which every
vertex is directly connected to every other
vertex
Graph terminology (cont.)
• What is the number of edges E in a
complete directed graph with V vertices?

E=V * (V-1)

or E=O(V2)
Graph terminology (cont.)
• What is the number of edges E in a
complete undirected graph with V vertices?

E=V* (V-1) / 2

or E=O(V2)
Graph terminology (cont.)
• Weighted graph: a graph in which each edge
carries a value
Graph Implementation
• Array-based
• Linked-list-based
Array-based implementation
• Use a 1D array to represent the vertices
• Use a 2D array (i.e., adjacency matrix) to
represent the edges
Array-based implementation (cont’d)
Array-Based Implementation (cont.)
• Memory required
– O(V+V2)=O(V2)
• Preferred when
– The graph is dense: E = O(V2)
• Advantage
– Can quickly determine
if there is an edge between two vertices

• Disadvantage
– No quick way to determine the vertices adjacent
from another vertex
x ?
Linked-list-based implementation
• Use a 1D array to represent the vertices
• Use a list for each vertex v which contains the
vertices which are adjacent from v (adjacency
list)
Linked-list-based implementation (cont’d)
Link-List-based Implementation (cont.)
• Memory required O(V) for sparse graphs since E=O(V)
– O(V + E)
O(V2) for dense graphs since E=O(V2)
• Preferred when
– for sparse graphs: E = O(V)

• Disadvantage
– No quick way to determine whether
there is an edge between vertices u and v

• Advantage
– Can quickly determine the
vertices adjacent from a given vertex
x ?
Graph specification based on
adjacency matrix representation
const int NULL_EDGE = 0;
private:
template<class VertexType> int numVertices;
class GraphType { int maxVertices;
public: VertexType* vertices;
GraphType(int); int **edges;
~GraphType(); bool* marks;
void MakeEmpty(); };
bool IsEmpty() const;
bool IsFull() const;
void AddVertex(VertexType);
void AddEdge(VertexType, VertexType, int);
int WeightIs(VertexType, VertexType);
void GetToVertices(VertexType, QueType<VertexType>&);
void ClearMarks();
void MarkVertex(VertexType);
bool IsMarked(VertexType) const;
template<class VertexType>
GraphType<VertexType>::GraphType(int maxV)
{
numVertices = 0;
maxVertices = maxV;

vertices = new VertexType[maxV];

edges = new int[maxV];


for(int i = 0; i < maxV; i++)
edges[i] = new int[maxV];

marks = new bool[maxV];


}
template<class VertexType>
GraphType<VertexType>::~GraphType()
{
delete [] vertices;

for(int i = 0; i < maxVertices; i++)


delete [] edges[i];
delete [] edges;

delete [] marks;
}
void GraphType<VertexType>::AddVertex(VertexType
vertex)
{
vertices[numVertices] = vertex;

for(int index = 0; index < numVertices; index++) {


edges[numVertices][index] = NULL_EDGE;
edges[index][numVertices] = NULL_EDGE;
}

numVertices++;
}
template<class VertexType>
void GraphType<VertexType>::AddEdge(VertexType
fromVertex, VertexType toVertex, int weight)
{
int row;
int column;

row = IndexIs(vertices, fromVertex);


col = IndexIs(vertices, toVertex);
edges[row][col] = weight;
}
template<class VertexType>
int GraphType<VertexType>::WeightIs(VertexType
fromVertex, VertexType toVertex)
{
int row;
int column;

row = IndexIs(vertices, fromVertex);


col = IndexIs(vertices, toVertex);
return edges[row][col];
}
template<class VertexType>
void GraphType<VertexType>::GetToVertices(VertexType vertex,
QueTye<VertexType>& adjvertexQ)
{
int fromIndex;
int toIndex;

fromIndex = IndexIs(vertices, vertex);


for(toIndex = 0; toIndex < numVertices; toIndex++)
if(edges[fromIndex][toIndex] != NULL_EDGE)
adjvertexQ.Enqueue(vertices[toIndex]);
}
Graph searching
• Problem: find if there is a path between two
vertices of the graph (e.g., Austin and
Washington)
• Methods: Depth-First-Search (DFS) or
Breadth-First-Search (BFS)
Depth-First-Search (DFS)
• Main idea:
– Travel as far as you can down a path
– Back up as little as possible when you reach a
"dead end" (i.e., next vertex has been "marked"
or there is no next vertex)
• DFS uses a stack !
startVertex endVertex

Depth-First-Search (DFS) (cont.)


found = false
stack.Push(startVertex)
DO
stack.Pop(vertex)
IF vertex == endVertex
found = true
ELSE
“mark” vertex
Push all adjacent, not “marked”, vertices onto stack
WHILE !stack.IsEmpty() AND !found

IF(!found)
Write "Path does not exist"
startVertex endVertex

(initialization)
template <class VertexType>
void DepthFirstSearch(GraphType<VertexType> graph,
VertexType startVertex, VertexType endVertex)
{
StackType<VertexType> stack;
QueType<VertexType> vertexQ;

bool found = false;


VertexType vertex;
VertexType item;

graph.ClearMarks();
stack.Push(startVertex);
do {
stack.Pop(vertex);
if(vertex == endVertex)
found = true;
(continues)
else
if(!graph.IsMarked(vertex)) {
graph.MarkVertex(vertex);
graph.GetToVertices(vertex, vertexQ);

while(!vertexQ.IsEmpty()) {
vertexQ.Dequeue(item);
if(!graph.IsMarked(item))
stack.Push(item);
}
}

} while(!stack.IsEmpty() && !found);

if(!found)
cout << "Path not found" << endl;
}
Breadth-First-Searching (BFS)
• Main idea:
– Look at all possible paths at the same depth
before you go at a deeper level
– Back up as far as possible when you reach a
"dead end" (i.e., next vertex has been "marked"
or there is no next vertex)
• BFS uses a queue !
startVertex endVertex

Breadth-First-Searching (BFS) (cont.)


found = false
queue.Enqueue(startVertex)
DO
queue.Dequeue(vertex)
IF vertex == endVertex
found = true
ELSE
“mark” vertex
Enqueue all adjacent, not “marked”, vertices onto queue
WHILE !queue.IsEmpty() AND !found

IF(!found)
Write "Path does not exist"
startVertex endVertex

(initialization)
Duplicates: should we
mark a vertex when it is
Enqueued
or when it is Dequeued ?
....
template<class VertexType>
void BreadthFirtsSearch(GraphType<VertexType> graph,
VertexType startVertex, VertexType endVertex);
{
QueType<VertexType> queue;
QueType<VertexType> vertexQ;

bool found = false;


VertexType vertex;
VertexType item;

graph.ClearMarks();
queue.Enqueue(startVertex);
do {
queue.Dequeue(vertex);
if(vertex == endVertex)
found = true;

(continues)
else
“mark” when dequeue a vertex
if(!graph.IsMarked(vertex)) {
 allow duplicates!
graph.MarkVertex(vertex);
graph.GetToVertices(vertex, vertexQ);

while(!vertxQ.IsEmpty()) {
vertexQ.Dequeue(item);
if(!graph.IsMarked(item))
queue.Enqueue(item);
}
}

} while (!queue.IsEmpty() && !found);

if(!found)
cout << "Path not found" << endl;
}
Time Analysis
template<class VertexType>
void BreadthFirtsSearch(GraphType<VertexType> graph,
VertexType startVertex, VertexType endVertex);
{
QueType<VertexType> queue;
QueType<VertexType> vertexQ;

bool found = false;


VertexType vertex;
VertexType item;

graph.ClearMarks();
O(V)
queue.Enqueue(startVertex);
do {
queue.Dequeue(vertex); O(V) times
if(vertex == endVertex)
found = true;

(continues)
else { O(V) – arrays
if(!graph.IsMarked(vertex)) { O(Evi) – linked lists
graph.MarkVertex(vertex);
graph.GetToVertices(vertex, vertexQ);

while(!vertxQ.IsEmpty()) {
vertexQ.Dequeue(item);
if(!graph.IsMarked(item))
O(EVi) times
queue.Enqueue(item);
}
}
}
} while (!queue.IsEmpty() && !found);

if(!found)
cout << "Path not found" << endl;
}

Arrays: O(V+V2+Ev1+Ev2+…)=O(V2+E)=O(V2)
else { O(V) - arrays
if(!graph.IsMarked(vertex)) { O(Evi) – linked lists
graph.MarkVertex(vertex);
graph.GetToVertices(vertex, vertexQ);

while(!vertxQ.IsEmpty()) {
vertexQ.Dequeue(item);
if(!graph.IsMarked(item))
O(EVi) times
queue.Enqueue(item);
}
}
}
} while (!queue.IsEmpty() && !found);

if(!found)
cout << "Path not found" << endl;
}
O(V2) dense
Linked Lists: O(V+2Ev1+2Ev2+…)=O(V+E)
O(V) sparse
Shortest-path problem
• There might be multiple paths from a source
vertex to a destination vertex
• Shortest path: the path whose total weight
(i.e., sum of edge weights) is minimum

AustinHoustonAtlantaWashington:
1560 miles

AustinDallasDenverAtlantaWashington:
2980 miles
Variants of Shortest Path
• Single-pair shortest path
– Find a shortest path from u to v for given vertices u
and v

• Single-source shortest paths


– G = (V, E)  find a shortest path from a given
source vertex s to each vertex v  V
Variants of Shortest Paths (cont’d)
• Single-destination shortest paths
– Find a shortest path to a given destination vertex t
from each vertex v
– Reversing the direction of each edge  single-source

• All-pairs shortest-paths
– Find a shortest path from u to v for every pair of
vertices u and v
Notation
t x
• Weight of path p = v0, v1, . . . , vk
6
3 9
3
4
2 1
k s 0
w( p )   w( vi 1 , vi )
2 7
5 3
i 1 5 11
6
y z

• δ(v): shortest-path weight from s to v:


p
min w(p) : s v if there exists a path from s to v
δ(v) =
∞ otherwise
Negative Weights and
Negative Cycles
• Negative-weight edges may form a
-4
b

negative-weight cycles. 3 4
c 6 d g
5 8
s 0
• If negative cycles are reachable 2
y
3
-3
7

from the source, the shortest -6


e f
path is not well defined.
– i.e., keep going around the cycle, and get
w(s, v) = -  for all v on the cycle
Could shortest path solutions
contain cycles?
• Negative-weight cycles
– Shortest path is not well defined

• Positive-weight cycles:
– By removing the cycle, we can get a shorter path

• Zero-weight cycles
– No reason to use them
– Can remove them to obtain a path with same weight
Shortest-path algorithms
• Solving the shortest path problem in a brute-force
way requires enumerating all possible paths.
– There are O(V!) paths between a pair of vertices in a
acyclic graph containing V nodes.

• We will discuss two algorithms


– Dijkstra’s algorithm
– Bellman-Ford’s algorithm
Shortest-path algorithms (cont’d)
• Dijkstra’s and Bellman-Ford’s algorithms are
“greedy” algorithms!
– Find a “globally” optimal solution by making “locally”
optimum decisions.
• Bellman-Ford’s algorithm
– Handles negative weights but not negative cycles
reachable from the source.
• Dijkstra’s algorithm
– Does not handle negative weights.
Shortest-path algorithms (cont’d)
• Both Dijkstra’s and Bellman-Ford’s
algorithms are iterative:

– Start with a shortest path estimate for every


vertex: d[v]

– Estimates are updated iteratively until


convergence:
d[v]δ(v)
Shortest-path algorithms (cont’d)
• Two common steps:
– Initialization
– Relaxation (i.e., update step)
Initialization Step
– Set d[s]=0 (i.e., source vertex)
– Set d[v]=∞ (i.e., large value) for v  s

t 5 x
 
6 -2
-3
8 7
s 0
-4
7 2
 
9
Relaxation Step
• Relaxing an edge (u, v) implies testing whether we can
improve the shortest path to v found so far by going
through u:
If d[v] > d[u] + w(u, v)
we can improve the shortest path to v
 d[v]=d[u]+w(u,v)

s s
u v u v
2 2
5 9 5 6

RELAX(u, v, w) RELAX(u, v, w)

u v u v
2 2
5 7 5 6 no change
Bellman-Ford Algorithm
• Can handle negative weights.
• Detects negative cycles reachable from the source.

• Returns FALSE if negative-weight cycles are


reachable from the source s  no solution
Bellman-Ford Algorithm (cont’d)
• Each edge is relaxed |V–1| times by making |V-1|
passes over the whole edge set.
• To make sure that each edge is relaxed exactly
|V – 1| times, it puts the edges in an unordered list
and goes over the list |V – 1| times.

(t, x), (t, y), (t, z), (x, t), (y, x), (y, z), (z, x), (z, s), (s, t), (s, y)
t 5 x
 
6 -2
-3
8 7
s 0
-4
7 2
 
9
y z
Example
t 5 x t 5 x
  
6 
6 -2 6 -2
-3 -3
8 7 8 7
s 0 Pass 1 s 0
-4 -4
7 2 7 2
  
7 
9 9
y z y z

E: (t, x), (t, y), (t, z), (x, t), (y, x), (y, z), (z, x), (z, s), (s, t), (s, y)
Example (t, x), (t, y), (t, z), (x, t), (y, x), (y, z), (z, x), (z, s), (s, t), (s, y)

t 5 x t x
Pass 1 5
 Pass 2
(from
6 6 
4
11
6 -2 6 -2
previous -3 -3
slide) s 0 8 7 8 7
s 0
-4 -4
7 2 7 2
7  7 
2
9 9
y z y z
Pass 3 t 5 x Pass 4 t 5 x
2
6 
4
11 2
6 
4
11
6 -2 6 -2
-3 -3
8 7 8 7
s 0 s 0
-4 -4
7 2 7 2
7 
2 7 2
-2
9 9
y z y z
Detecting Negative Cycles:
needs an extra iteration
s b
for each edge (u, v)  E do 2
0 
if d[v] > d[u] + w(u, v)
then return FALSE -8 3

return TRUE
c
1st pass 2nd pass
s b s b
Consider edge (s, b):
2 2
0
-3 
2 -6
-3 -1
2
d[b] = -1
-8 3 -8 3 d[s] + w(s, b) = -4

5 5
2
c c d[b] > d[s] + w(s, b)
(s,b) (b,c) (c,s)  d[b]=-4
(d[b] keeps changing!)
BELLMAN-FORD Algorithm
1. INITIALIZE-SINGLE-SOURCE(V, s) O(V)
2. for i ← 1 to |V| - 1 O(V)
O(VE)
3. do for each edge (u, v)  E O(E)

4. do RELAX(u, v, w)
5. for each edge (u, v)  E O(E)
6. do if d[v] > d[u] + w(u, v)
7. then return FALSE
8. return TRUE
Time: O(V+VE+E)=O(VE)
Dijkstra’s Algorithm
• Cannot handle negative-weights!
– w(u, v) > 0,  (u, v)  E

• Each edge is relaxed only once!


Dijkstra’s Algorithm (cont’d)
• At each iteration, it maintains two sets of vertices:

S V-S
d[v]=δ (v) d[v]>δ (v)

(estimates have (estimates have not


converged to the shortest converged yet)
path solution)

Initially, S is empty
Dijkstra’s Algorithm (cont.)
• Vertices in V–S reside in a min-priority queue Q
– Priority of u determined by d[u]
– The “highest” priority vertex will be the one having the
smallest d[u] value.
Dijkstra (G, w, s)
S=<> Q=<s,t,x,z,y> S=<s> Q=<y,t,x,z>
Initialization
t 1 x t 1 x
  
10 
10 9 10 9
2 3 4 6 2 3 4 6
s 0 s 0
5 7 5 7
  
5 
2 2
y z y z
Example (cont.)
S=<s,y> Q=<z,t,x> S=<s,y,z> Q=<t,x>

t 1 x t 1 x
8
10 
14 8 13
14
10 9 10 9
2 3 4 6 2 3 4 6
s 0 s 0
5 7 5 7
5 
7 5 7
2 2
y z y z
Example (cont.)

S=<s,y,z,t> Q=<x> S=<s,y,z,t,x> Q=<>

t x t 1 x
1
8 13
9 8 9
10 9 10 9

2 4 2 3 4 6
s 0 3 6 s 0

7 5 7
5
5 7 5 7
2 2
y z y z

Note: use back-pointers to recover the shortest path solutions!


Dijkstra (G, w, s)
INITIALIZE-SINGLE-SOURCE(V, s)  O(V)
S← 
build priority heap
Q ← V[G]
 O(VlogV) (but can be done in O(V))
while Q    O(V) times
u ← EXTRACT-MIN(Q)  O(logV)
S ← S  {u}
for each vertex v  Adj[u]  O(Evi)
do RELAX(u, v, w) O(EvilogV)
Update Q (DECREASE_KEY)  O(logV)
Overall: O(V+2VlogV+(Ev1+Ev2+...)logV) =O(VlogV+ElgV)=O(ElogV)
Dijkstra vs Bellman-Ford
• Bellman-Ford
V2 If sparse: E=O(V)
O(VE)
V3 If dense: E=O(V2)
• Dijkstra
VlogV If sparse: E=O(V)
O(ElogV)
V2logV If dense: E=O(V2)
Improving Dijkstra’s efficiency
• Suppose the shortest path from s to w is the
following:
w
u
s x
… …
• If u is the i-th vertex in this path, it can be shown that
d[u]  δ (u) at the i-th iteration:
– move u from V-S to S
– d[u] never changes again
Add a flag for efficiency!
INITIALIZE-SINGLE-SOURCE(V, s)
S← 
Q ← V[G]
while Q  
do u ← EXTRACT-MIN(Q)
S ← S  {u};  mark u
for each vertex v  Adj[u]
If v not marked
do RELAX(u, v, w)
Update Q (DECREASE_KEY)
Example: negative weights
S=<> Q=<A,B,C>
(1) Suppose we start from A
A 1 B d[A]=0, d[B]=d[C]=max

2 -2 (2) S=<A> , mark A


Relax (A,B), (A,C)
C d[B]=1, d[C]=2
Update Q: Q=<B,C>

(3) S=<A,B>, mark B, Q=<C>


Final values:
d[A]=0 (4) S=<A,B,C>, mark C, Q=<>
d[B]=1 Relax (C,B)
d[C]=2 d[B] will not be updated!
Eliminating negative weights
• Dijkstra’s algorithm works as long as there are no
negative edge weights. Given a graph that contains
negative weights, we can eliminate negative
weights by adding a constant weight to all of the
edges. Would this work?
S 1 A S A
4
This is not going to
add 3 work well; it adds more
2 -2 1
5 “weight” to longer
paths!
B B
Revisiting BFS
• BFS can be used to solve the shortest graph
problem when the graph is weightless or when
all the weights are equal.
– Path with lowest number of edges (connections).

• Need to “mark” vertices before Enqueue! (i.e.,


no duplicates are allowed)
Exercises 19,21, p. 602
Exercises 19,21, p. 602

Using DFS/BFS, find if there is a path from


“Hawaii to Alaska“

You might also like