0% found this document useful (0 votes)
24 views147 pages

Chap 8 Graph

The document provides an overview of graph data structures and algorithms. It defines key graph concepts like vertices, edges, paths, cycles, connectedness, and representations. It discusses undirected graphs, directed graphs, weighted graphs, trees, and directed acyclic graphs. Common graph representations like adjacency matrices and lists are also introduced.

Uploaded by

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

Chap 8 Graph

The document provides an overview of graph data structures and algorithms. It defines key graph concepts like vertices, edges, paths, cycles, connectedness, and representations. It discusses undirected graphs, directed graphs, weighted graphs, trees, and directed acyclic graphs. Common graph representations like adjacency matrices and lists are also introduced.

Uploaded by

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

Data structures &

Algorithms
Bin Chen
2023 Fall
Outline——Graph
A graph is an abstract data type for storing adjacency relations
• We start with definitions:
• Vertices, edges, degree and sub-graphs
• We will describe paths in graphs
• Simple paths and cycles
• Definition of connectedness
• Weighted graphs
• We will then reinterpret these in terms of directed graphs
• Directed acyclic graphs
Outline
We will define an Undirected Graph ADT as a collection of vertices
V = {v1, v2, ..., vn}
• The number of vertices is denoted by
|V| = n
• Associated with this is a collection E of unordered pairs {vi, vj} termed edges
which connect the vertices

There are a number of data structures that can be used to implement


abstract undirected graphs
• Adjacency matrices
• Adjacency lists
Graphs
Consider this collection of vertices
V = {v1, v2, ..., v9}
where |V| = n
Undirected graphs
Associated with these vertices are |E| = 5 edges
E = {{v1, v2}, {v3, v5}, {v4, v8}, {v4, v9}, {v6, v9}}
• The pair {vj , vk} indicates that both vertex vj is adjacent to vertex vk and
vertex vk is adjacent to vertex vj
Undirected graphs
We will assume in this course that a vertex is never adjacent to
itself
• For example, {v1, v1} will not define an edge

The maximum number of edges in an undirected graph is

 V  V  1
V
E 
2

 2
O V  
2
An undirected graph
Example: given the |V| = 7 vertices
V = {A, B, C, D, E, F, G}
and the |E| = 9 edges
E = {{A, B}, {A, D}, {A, E}, {B, C}, {B, D}, {B, E}, {C, E}, {C, F}, {D, E}}
Degree
The degree of a vertex is defined as the number of adjacent vertices
degree(A) = degree(D) = degree(C) = 3
degree(B) = degree(E) = 4
degree(F) = 1
degree(G) = 0

Those vertices adjacent to a given vertex are its neighbors


Sub-graphs
A sub-graph of a graph a subset of the vertices and a subset of
the edges that connected the subset of vertices in the original
graph
Vertex-induced sub-graphs
A vertex-induced sub-graph is a subset of a the vertices where
the edges are all edges in the original graph that originally
Paths
A path in an undirected graph is an ordered sequence of
vertices
(v0, v1, v2, ..., vk)
where {vj – 1, vj} is an edge for j = 1, ..., k
• Termed a path from v0 to vk
• The length of this path is k
Paths
A path of length 4:
(A, B, E, C, F)
Paths
A path of length 5:
(A, B, E, C, B, D)
Paths
A trivial path of length 0:
(A)
Simple paths
A simple path has no repetitions other than perhaps the first and
last vertices
A simple path is a path such that all vertices are distinct, except that the first and last could be the same.

A simple cycle is a simple path of at least two vertices with the


first and last vertices equal
• Note: these definitions are not universal
A simple cycle is a cycle in a Graph with no repeated vertices (except for the
beginning and ending vertex)
Connectedness
Two vertices vi, vj are said to be connected if there exists a path
from vi to vj
A graph is connected if there exists a path between any two
vertices

A connected graph An unconnected graph


Weighted graphs
A weight may be associated with each edge in a graph
• This could represent distance, energy consumption, cost, etc.
• Such a graph is called a weighted graph
Pictorially, we will represent weights by numbers next to the
edges
Weighted graphs
The length of a path within a weighted graph is the sum of all of
the edges which make up the path
• The length of the path (A, D, G) in the following graph is 5.1 + 3.7 = 8.8
Weighted graphs
Different paths may have different weights
• Another path is (A, C, F, G) with length 1.2 + 1.4 + 4.5 = 7.1
Weighted graphs
Problem: find the shortest path between two vertices
• Here, the shortest path from A to H is (A, C, F, D, E, G) with length 5.7
Trees
A graph is a tree if it is connected and there is a unique path between any two vertices
• Three trees on the same eight vertices

Consequences:
• The number of edges is |E| = |V| – 1
• The graph is acyclic, that is, it does not contain any cycles
• Adding one more edge must create a cycle
• Removing any one edge creates two disjoint non-empty sub-graphs
Trees
Any tree can be converted into a rooted tree by:
• Choosing any vertex to be the root
• Defining its neighboring vertices as its children
and then recursively defining:
• All neighboring vertices other than that one designated its parent are now
defined to be that vertices children
Given this tree, here are three rooted trees associated with it
Forests
A forest is any graph that has no cycles

Consequences:
• The number of edges is |E| < |V|
• The number of trees is |V| – |E|
• Removing any one edge adds one more tree to the forest

Here is a forest with 22 vertices and 18 edges


• There are four trees
Directed graphs
In a directed graph, the edges on a graph are be associated
with a direction
• Edges are ordered pairs (vj, vk) denoting a connection from vj to vk
• The edge (vj, vk) is different from the edge (vk, vj)

Streets are directed graphs:


• In most cases, you can go two ways unless it is a one-way street
Directed graphs
Given our graph of nine vertices V = {v1, v2, …v9}
• These six pairs (vj , vk) are directed edges
E = {(v1, v2), (v3, v5), (v5, v3), (v6, v9), (v8, v4), (v9, v4)}
Directed graphs
The maximum number of directed edges in a directed graph is
V  V  1
V
E  2
2

2
 2
 
 V  V  1  O V
2
In and out degrees
The degree of a vertex must be modified to consider both
cases:
• The out-degree of a vertex is the number of vertices which are adjacent
to the given vertex
• The in-degree of a vertex is the number of vertices which this vertex is
adjacent to

In this graph:
in_degree(v1) = 0 out_degree(v1) = 2
in_degree(v5) = 2 out_degree(v5) = 3
Sources and sinks
Some definitions:
• Vertices with an in-degree of zero are described as sources
• Vertices with an out-degree of zero are described as sinks

In this graph:
• Sources: v1 , v6 , v7
• Sinks: v2, v9
Paths
A path in a directed graph is an ordered sequence of vertices
(v0, v1, v2, ..., vk)
where (vj – 1, vj) is an edge for j = 1, ..., k

A path of length 5 in this graph is


(v1, v4, v5, v3, v5, v2)

A simple cycle of length 3 is


(v8, v4, v5, v8)
Connectedness
Two vertices vj, vk are said to be connected if there exists a path from
vj to vk
• A graph is strongly connected if there exists a directed path between any two
vertices
• A graph is weakly connected there exists a path between any two vertices that
ignores the direction

In this graph:
• The sub-graph {v3, v4, v5, v8} is strongly
connected
• The sub-graph {v1, v2, v3, v4, v5, v8} is
weakly connected
Weighted directed graphs
In a weighted directed graphs, each edge is associated with a
value
Unlike weighted undirected graphs, if both (vj, vk) and (vj, vk) are
edges, it is not required that they have the same weight
6.4

7.5 7.3 6.8


6.7
5.9
4.1
3.2
4.7
5.4 4.5
Directed acyclic graphs
A directed acyclic graph is a directed graph which has no cycles
• These are commonly referred to as DAGs
• They are graphical representations of partial orders on a finite number of
elements
These two are DAGs:

This directed graph is not acyclic:


Directed acyclic graphs
Applications of directed acyclic graphs include:
• The parse tree constructed by a compiler
• A reference graph that can be garbage collected using simple
reference counting
• Dependency graphs such as those used in instruction scheduling and
makefiles
• Dependency graphs between classes formed by inheritance
relationships in object-oriented programming languages
• Information categorization systems, such as folders in a computer
• Directed acyclic word graph data structure to memory-efficiently store a
set of strings (words)
Reference: http://en.wikipedia.org/wiki/Directed_acyclic_graph
Representations
How do we store the adjacency relations?
• Binary-relation list
• Adjacency matrix
• Adjacency list
Binary-relation list
The most inefficient is a relation list:
• A container storing the edges
{(1, 2), (1, 4), (3, 5), (4, 2), (4, 5), (5, 2), (5, 3), (5, 8), (6, 9), (7, 9), (8,
4)}

• Requires Q(|E|) memory


• Determining if vj is adjacent to vk is O(|E|)
• Finding all neighbors of vj is Q(|E|)
Adjacency matrix
Requiring more memory but also faster, an adjacency matrix
• The matrix entry (j, k) is set to true if there is an edge (vj, vk)

1 2 3 4 5 6 7 8 9
1 T T
2
3 T
4 T T
5 T T T
6 T
7 T
8 T
9

• Requires Q(|V|2) memory


• Determining if vj is adjacent to vk is O(1)

Adjacency list
Most efficient for algorithms is an adjacency list
• Each vertex is associated with a list of its neighbors
1 •→2→4
2 •
3 •→5
4 •→2→5
5 •→2→3→8
6 •→9
7 •→9
8 •→4
9 •
• Requires Q(|V| + |E|) memory
• On average: E 
O 
• Determining if vj is adjacent to vk is  V 
E 
• Finding all neighbors of vj is   V 
 
The Graph ADT
The Graph ADT describes a container storing an adjacency relation
• Queries include:
• The number of vertices
• The number of edges
• List the vertices adjacent to a given vertex
• Are two vertices adjacent?
• Are two vertices connected?

• Modifications include:
• Inserting or removing an edge
• Inserting or removing a vertex (and all edges containing that vertex)

The run-time of these operations will depend on the representation


Background
• Project 6 requires you to store a graph with a given number of vertices
numbered 0 through n – 1
• Initially, there are no edges between these n vertices
• The insert command adds edges to the graph while the number
vertices remains unchanged
Background
• In this laboratory, we will look at techniques for storing the edges of a
graph
• This laboratory will focus on weighted graphs, however, for
unweighted graphs, one can easily use bool in place of double
Background
• To demonstrate these techniques, we will look at storing the edges of
the following graph:
Adjacency Matrix
A graph of n vertices may have up to

 n  n(n  1) 2
    O(n )
edges  2 2

The first straight-forward implementation is an adjacency matrix


Adjacency Matrix
Define an n × n matrix A = (aij) and if the vertices vi and vj are connected with weight w, then set
aij = w and aji = w

That is, the matrix is symmetric, e.g.,


Adjacency Matrix
An unweighted graph may be saved as an array of Boolean values
• vertices vi and vj are connected then set
aij = aji = true
Adjacency Matrix
If the graph was directed, then the matrix would not necessarily be symmetric
Adjacency Matrix
First we must allocate memory for a two-dimensional array

C++ does not have native support for anything more than one-dimensional arrays, thus how do
we store a two-dimensional array?
• as an array of arrays
Adjacency Matrix
Suppose we require a 16 × 16 matrix of double-precision floating-point numbers

Each row of the matrix can be represented by


an array

The address of the first entry must be stored


in a pointer to a double:
double *
Adjacency Matrix
However, because we must store 16 of these pointers-to-doubles, it makes sense that we store
these in an array

What is the declaration


of this array?

Well, we must store a


pointer to a pointer to a double

That is: double **


Adjacency Matrix
Thus, the address of the first array must be declared to be:
double **matrix;
Adjacency Matrix
The next question is memory allocation

First, we must allocate the memory for the array of pointers to doubles:
matrix = new double * [16];
Adjacency Matrix
Next, to each entry of this matrix, we must assign the memory allocated for an array of doubles

for ( int i = 0; i < 16; ++i ) {


matrix[i] = new double[16];
}
Adjacency Matrix
Accessing a matrix is done through a double index, e.g., matrix[3][4]

You can interpret this as (matrix[3])[4]


Adjacency Matrix
Recall that in matrix[3][4], the variable matrix is a pointer-to-a-pointer-to-a-double:
Adjacency Matrix
Therefore, matrix[3] is a pointer-to-a-double:
Adjacency Matrix
And consequently, matrix[3][4] is a double:
C++ Notation Warning
Do not use matrix[3, 4] because:
• in C++, the comma operator evaluates the operands in order from left-to-right
• the value is the last one

Therefore, matrix[3, 4] is equivalent to calling matrix[4]

Try it:
int i = (3, 4);
cout << i << endl;
C++ Notation Warning
Many things will compile if you try to use this notation:
matrix = new double[N, N];
will allocate an array of N doubles, just like:
matrix = new double[N];

However, this is likely not to do what you really expect...


Adjacency Matrix
Now, once you’ve used the matrix, you must also delete it...
Adjacency Matrix
Recall that for each call to new[],you must have a corresponding call to delete[]

Therefore, we must use


a for-loop to delete the
arrays
• implementation up to you
Default Values
Question: what do we do about vertices which are not connected?
• the value 0
• a negative number, e.g., –1
• positive infinity: ∞

The last is the most logical, in that it makes sense that two vertices which are not connected have
an infinite distance between them
Default Values
To use infinity, you may declare a constant static member variable INF:
#include <limits>

class Weighted_graph {
private:
static const double INF;
// ...
// ...
};

const double Weighted_graph::INF =


std::numeric_limits<double>::infinity();
Default Values
As defined in the IEEE 754 standard, the representation of the double-precision floating-point
infinity eight bytes:
0x 7F F0 00 00 00 00 00 00

Incidentally, negative infinity is stored as:


0x FF F0 00 00 00 00 00 00
Default Values
In this case, you can initialize your array as follows:

for ( int i = 0; i < N; ++i ) {


for ( int j = 0; j < N; ++j ) {
matrix[i][j] = INF;
}

matrix[i][i] = 0;
}

It makes intuitive sense that the distance from a node to itself is 0


Default Values
If we are representing an unweighted graph, use Boolean values:

for ( int i = 0; i < N; ++i ) {


for ( int j = 0; j < N; ++j ) {
matrix[i][j] = false;
}

matrix[i][i] = true;
}

It makes intuitive sense that a vertex is connected to itself


Adjacency Matrix
Let us look at the representation of our example graph
Initially none of the edges are recorded:
Adjacency Matrix
To insert the edge between 0 and 1 with weight 0.83, we set
matrix[0][1] = matrix[1][0] = 0.83;
Adjacency Matrix
The final result is shown as follows

Note, however, that these six arrays could be anywhere in memory...


Adjacency Matrix
We have now looked at how we can store an adjacency graph in C++

Next, we will look at:


• Two improvements for the array-of-arrays implementations, including:
• allocating the memory for the matrix in a single contiguous block of code, and
• a lower-triangular representation; and
• A sparse linked-list implementation
Adjacency Matrix Improvement
To begin, we will look at the first improvement:
• allocating all of the memory of the arrays in a single array with n2 entries
Adjacency Matrix Improvement
For those of you who would like to reduce the number of calls to new, consider the following
idea:
• allocate an array of 16 pointers to doubles
• allocate an array of 162 = 256 doubles

Then, assign to the 16 pointers in the first array the addresses of entries
0, 16, 32, 48, 64, ..., 240
Adjacency Matrix Improvement
First, we allocate memory:
matrix = new double * [16];
double * tmp = new double[256];
Adjacency Matrix Improvement
Next, we allocate the addresses:
matrix = new double * [16];
double * tmp = new double[256];

for ( int i = 0; i < 16; ++i ) {


matrix[i] = &( tmp[16*i] );
}
This assigns:
matrix[ 0] = &( tmp[ 0] );
matrix[ 1] = &( tmp[ 16] );
matrix[ 2] = &( tmp[ 32] );
.
.
.

matrix[15] = &( tmp[240] );


Adjacency Matrix Improvement
Deleting this array is easier:
delete [] matrix[0];
delete [] matrix;
Adjacency Matrix Improvement
Our sample graph would be represented as follows:
Lower-triangular adjacency matrix
Next we will look at another improvement which can be used for undirected graphs

We will store only half of the entries


• To do this, we must also learn about pointer arithmetic
Lower-triangular adjacency matrix
Note also that we are not storing a directed graph: therefore, we really need only store half of
the matrix

Thus, instead of 256 entries, we really only require 120 entries


Lower-triangular adjacency matrix
The memory allocation for this would be straight-forward, too:
matrix = new double * [16];
matrix[0] = 0;
matrix[1] = new double[120];

for( int i = 2; i < 16; ++i ) {


matrix[i] = matrix[i – 1] + i - 1;
}
Lower-triangular adjacency matrix
• What we are using here is pointer arithmetic:
• in C/C++, you can add values to a pointer
• the question is, what does it mean to set:
ptr = ptr + 1;
or
ptr = ptr + 2;
Lower-triangular adjacency matrix
• Suppose we have a pointer-to-a-double:
double * ptr = new double( 3.14 );
where:
• the pointer has a value of 0x53A1D780, and
• the representation of 3.14 is 0x40091Eb851EB851F
Lower-triangular adjacency matrix
• If we just added one to the address, then this would give us the value
0x53A1D781, but this contains no useful information...
Lower-triangular adjacency matrix
• The only logical interpretation of ptr + 1 is to go to the next
location a different double could exist, i.e., 0x53A1D788
Lower-triangular adjacency matrix
Therefore, if we define:
double * array = new double[4];
then the following are all equivalent:
array[0] *array
array[1] *(array + 1)
array[2] *(array + 2)
array[3] *(array + 3)
Lower-triangular adjacency matrix
• Thus, the following code simply adds appropriate amounts to the
pointer:

matrix = new double *[N];


matrix[0] = nullptr;
matrix[1] = new double[N*(N – 1)/2];

for( int i = 2; i < N; ++i ) {


matrix[i] = matrix[i – 1] + i - 1;
}
Lower-triangular adjacency matrix
Visually, we have, for N = 16, the following:
matrix[0] = nullptr;
matrix[1] = &( tmp[0] );
matrix[2] = &( tmp[1] );
matrix[3] = &( tmp[3] );
matrix[4] = &( tmp[6] );
matrix[5] = &( tmp[10] );
matrix[6] = &( tmp[15] );
matrix[7] = &( tmp[21] );
matrix[7] = &( tmp[28] );
.
.
.
matrix[15] = &( tmp[105] );
Lower-triangular adjacency matrix
The only thing that we would have to do is ensure that we always put
the larger number first:
void insert( int i, int j, double w ) {
if ( j < i ) {
matrix[i][j] = w;
} else {
matrix[j][i] = w;
}
}
Lower-triangular adjacency matrix
• A slightly less efficient way of writing this would be:

void insert( int i, int j, double w ) {


matrix[max(i,j)][min(i,j)] = w;
}

• The benefits (from the point-of-view of clarity) are much more


significant...
Lower-triangular adjacency matrix
• Our example graph is stored
using this representation as
shown here
• Notice that we do not store
any 0’s, nor do we store
any duplicate entries
• The second array has only
15 entries, versus 36
Lower-triangular adjacency matrix
• To determine the weight of the
edge connecting vertices 1 and
4, we must look up the entry
matrix[4][1]
Beyond Array Bounds
• Until now, some of you may have gone beyond array bounds
accidentally
• Recall that
int * array = new int[10];
allocates 40 bytes (4 bytes/int) and the entries are accessed with
array[0] through array[9]
Beyond Array Bounds
• If you try to access either array[10] or array[-1], you are
accessing memory which has not been allocated for this array
Beyond Array Bounds
• This memory may be used:
• for different local variables, or
• by some other process
• In the first case, you will have a bug which is very difficult to track
down
• e.g., a variable will appear to change its value without an explicit assignment
• In the second case, the OS will terminate your process (segmentation
fault)
Beyond Array Bounds
• Now we have a very explicit example of what happens if you go
outside your expected array bounds
• Notice that the value stored
at matrix[4][1] is 0.46
• We can also access it using
either:
matrix[3][4]
matrix[5][-3]
Beyond Array Bounds
• Thus, if you wanted to find the distance between vertices 3 and 4, if
you access
matrix[4][3], you get is 0.24
• If, however, you access
matrix[3][4], you get 0.46
Beyond Array Bounds
• Similarly, if you wanted to find the distance between vertices 2 and 3,
if you access
matrix[3][2], you get is 0.39
• If, however, you access
matrix[2][3], you get 0.72
Sparse Matrices
• Finally we will consider the problem with sparse matrices and we will
look at one implementation using linked lists
Sparse Matrices
• The memory required for creating an n × n matrix using an array-of-
arrays is:
4 bytes + 4n bytes + 8n2 bytes = Q(n2) bytes
• This could potentially waste a significant amount of memory:
• consider all intersections in Canada as vertices and streets as edges
• how could we estimate the number of intersections in Canada?
Sparse Matrices
• The population of Canada is ~33 million
• Suppose we have one intersection per 10 houses and four occupants
per house
• Therefore, there are roughly
33 million / 10 / 4 ≈ 800 000
intersections in Canada which would require 4.66 TiB of memory
Sparse Matrices
• Assume that each intersection connects, on average, four other
intersections
• Therefore, less than 0.0005% of the entries of the matrix are used to
store connections
• the rest are storing the value infinity
Sparse Matrices
• Matrices where less than 5% of the entries are not the default value
(either infinity or 0, or perhaps some other default value) are said to be
sparse
• Matrices where most entries (25% or more) are not the default value
are said to be dense
• Clearly, these are not hard limits
Sparse Matrices
• We will look at a very efficient sparse-matrix implementation with the
last topic
• Here, we will consider a simpler implementation:
• use an array of linked lists to store edges
• Note, however, that each node in a linked list must store two items of
information:
• the connecting vertex and the weight
Sparse Matrices
• One possible solution:
• modify the SingleNode data structure to store both an integer and a double:
class SingleNode {
private:
int adacent_vertex;
double edge_weight;
SingleNode * next_node;
public:
SingleNode( int, double SingleNode = 0 );
double weight() const;
int vertex() const;
SingleNode * next() const;
};
• exceptionally stupid and inefficient
Sparse Matrices
A better solution is to create a new class which stores a vertex-edge pair

class Pair {
private:
double edge_weight;
int adacent_vertex;
public:
Pair( int, double );
double weight() const;
int vertex() const;
};

Now create an array of linked-lists storing these pairs


Sparse Matrices
Thus, we define and create the array:
SingleList<Pair> * array;

array = new SingleList<Pair>[16];


Sparse Matrices
As before, to reduce redundancy, we would only insert the entry into the entry corresponding
with the larger vertex

void insert( int i, int j, double w ) {


if ( i < j ) {
array[j].push_front( Pair(i, w) );
} else {
array[i].push_front( Pair(j, w) );
}
}
Sparse Matrices
For example, the graph shown below would be stored as
Sparse Matrices
Later, we will see an even more efficient implementation
• The old an new Yale sparse matrix formats
Outline
We will look at traversals of graphs
• Breadth-first or depth-first traversals
• Must avoid cycles
• Depth-first traversals can be recursive or iterative
• Problems that can be solved using traversals
Strategies
Traversals of graphs are also called searches

We can use either breadth-first or depth-first traversals


• Breadth-first requires a queue
• Depth-first requires a stack

We each case, we will have to track which vertices have been visited requiring Q(|V|)
memory
• One option is a hash table
• If we can use a bit array, this requires only |V|/8 bytes

The time complexity cannot be better than and should not be worse than Q(|V| + |E|)
• Connected graphs simplify this to Q(|E|)
• Worst case: Q(|V|2)
Breadth-first traversal
Consider implementing a breadth-first traversal on a graph:
• Choose any vertex, mark it as visited and push it onto queue
• While the queue is not empty:
• Pop to top vertex v from the queue
• For each vertex adjacent to v that has not been visited:
• Mark it visited, and
• Push it onto the queue

This continues until the queue is empty


• Note: if there are no unvisited vertices, the graph is connected,
Iterative depth-first traversal
An implementation can use a queue
void Graph::depth_first_traversal( Vertex *first ) const {
unordered_map<Vertex *, int> hash;
hash.insert( first );
std::queue<Vertex *> queue;
queue.push( first );

while ( !queue.empty() ) {
Vertex *v = queue.front();
queue.pop();
// Perform an operation on v

for ( Vertex *w : v->adjacent_vertices() ) {


if ( !hash.member( w ) ) {
hash.insert( w );
queue.push( w );
}
}
}
}
Breadth-first traversal
The size of the queue is O(|V|)
• The size depends both on:
• The number of edges, and
• The out-degree of the vertices
Depth-first traversal
Consider implementing a depth-first traversal on a graph:
• Choose any vertex, mark it as visited
• From that vertex:
• If there is another adjacent vertex not yet visited, go to it
• Otherwise, go back to the most previous vertex that has not yet had all of its
adjacent vertices visited and continue from there
• Continue until no visited vertices have unvisited adjacent vertices

Two implementations:
• Recursive
• Iterative
Recursive depth-first traversal
A recursive implementation uses the call stack for memory:
void Graph::depth_first_traversal( Vertex *first ) const {
std::unordered_map<Vertex *, int> hash;
hash.insert( first );

first->depth_first_traversal( hash );
}

void Vertex::depth_first_traversal( unordered_map<Vertex *, int> &hash ) const {


// Perform an operation on this

for ( Vertex *v : adjacent_vertices() ) {


if ( !hash.member( v ) ) {
hash.insert( v );
v->depth_first_traversal( hash );
}
}
}
Iterative depth-first traversal
An iterative implementation can use a stack
void Graph::depth_first_traversal( Vertex *first ) const {
unordered_map<Vertex *, int> hash;
hash.insert( first );
std::stack<Vertex *> stack;
stack.push( first );

while ( !stack.empty() ) {
Vertex *v = stack.top();
stack.pop();
// Perform an operation on v

for ( Vertex *w : v->adjacent_vertices() ) {


if ( !hash.member( w ) ) {
hash.insert( w );
stack.push( w );
}
}
}
}
Iterative depth-first traversal
If memory is an issue, we can reduce the stack
size:
• For the vertex:
• Mark it as visited
• Perform an operation on that vertex
• Place it onto an empty stack
• While the stack is not empty:
• If the vertex on the top of the stack has an unvisited adjacent
vertex,
• Mark it as visited
• Perform an operation on that vertex
• Place it onto the top of the stack
• Otherwise, pop the top of the stack
Standard Template Library (STL)
approach
An object-oriented STL approach would be
create a iterator class:
• The hash table and stack/queue are private member
variables created in the constructor
• Internally, it would store the current node
• The auto-increment operator would pop the top of
the stack and place any unvisited adjacent vertices
onto the stack/queue
• The auto-decrement operator would not be
implemented
• You can’t go back…
Example
Consider this graph
Minimum Cost Spanning Tree
• The cost of a spanning tree of a weighted undirected graph is the sum
of the costs of the edges in the spanning tree
• A minimum cost spanning tree is a spanning
tree of least cost
• Three different algorithms can be used
• Kruskal
• Prim
• Sollin Select n-1 edges from a weighted graph
of n vertices with minimum cost.
Greedy Strategy
• An optimal solution is constructed in stages
• At each stage, the best decision is made at this time
• Since this decision cannot be changed later,
we make sure that the decision will result in a feasible solution
• Typically, the selection of an item at each
stage is based on a least cost or a highest profit criterion
Kruskal’s algorithm for finding MST

Step 1: Sort all edges into nondecreasing order.


Step 2: Add the next smallest weight edge to the forest
if it will not cause a cycle.
Step 3: Stop if n-1 edges. Otherwise, go to Step2.

Time complexity: O(|E| log |E|)


Example
Kruskal’s Algorithm
T= {};
while (T contains less than n-1 edges
&& E is not empty) {
choose a least cost edge (v,w) from E;
delete (v,w) from E;
if ((v,w) does not create a cycle in T)
add (v,w) to T
else discard (v,w);
}
if (T contains fewer than n-1 edges)
printf(“No spanning tree\n”);
Prim’s Algorithm
Step 1: x  V, Let A = {x}, B = V - {x}.
Step 2: Select (u, v)  E, u  A, v  B such that (u, v) has the smallest
weight between A and B.
Step 3: Put (u, v) in the tree. A = A  {v}, B = B - {v}
Step 4: If B = , stop; otherwise, go to Step 2.

• Time complexity : O(|V|2)


Example
Prim’s Algorithm

T={};
TV={0};
while (T contains fewer than n-1 edges)
{
let (u,v) be a least cost edge such
u  TV v  TV
that and
if (there is no such edge ) break;
add v to TV;
add (u,v) to T;
}
if (T contains fewer than n-1 edges)
printf(“No spanning tree\n”);
Shortest Paths
• Given a directed graph G=(V,E), a weighted function, w(e).
• How to find the shortest path from u to v?
Example:

• shortest paths from v0 to all destinations


Shortest Paths
• Single source all destinations:
• Nonnegative edge costs: Dijkstra’s algorithm
• General weights: Bellman-Ford’s algorithm
• All pairs shortest path
• Floyd’s algorithm
Single Source All Destinations: Nonnegative Edge
Costs
• Given a directed graph G=(V,E), a weighted function, w(e), and a
source vertex v0
• We wish to determine a shortest path from v0 to each of the remaining
vertices of G
Dijkstra’s algorithm
• Let S denotes the set of vertices, including v 0, whose shortest paths
have been found.
• For w not in S, let distance[w] be the length of the shortest path
starting from v0, going through vertices only in S, and ending in w.
• We can find a vertex u not in S and distance[u] is minimum, and add u
into S.
• Maintain distance properly
• Complexity: (|V|2)
Dijkstra’s algorithm

Cost adjacency matrix.


All entries not shown
are +.
1 2 3 4 5 6 7 8
1 0
2 300 0

3 1000 800 0
4 1200 0
5 1500 0 250
6 1000 0 900 1400
7 0 1000
8 1700 0
Vertex
Iteration S Selected (1) (2) (3) (4) (5) (6) (7) (8)
Initial ----
1 5 6 + + + 1500 0 250 + +
2 5,6 7 + + + 1250 0 250 1150 1650
3 5,6,7 4 + + + 1250 0 250 1150 1650
4 5,6,7,4 8 + + 2450 1250 0 250 1150 1650
5 5,6,7,4,8 3 3350 + 2450 1250 0 250 1150 1650
6 5,6,7,4,8,3 2 3350 3250 2450 1250 0 250 1150 1650
5,6,7,4,8,3,2 3350 3250 2450 1250 0 250 1150 1650
int[][] graph = new int[][]{
{0 , 2, ∞, 6}
Dijkstra Algorithm {2 , 0, 3, 2}
{∞ , 3, 0, 2}
{6 , 2, 2, 0}};

•result : Vertices has been solved in terms of minimal path


•notFound : Vertices has not been solved in terms of minimal
path
public static int[] dijkstra(int[][] graph,int startVertex){ result[minIndex] = min;
// 初始化 以求出最短路径的点 result[] notFound[minIndex] = -1;
int length = graph.length; //2. 刷新 「未求出最短距离的点」 notFound[] 中的距离
int[] result = new int[length]; //2.1 遍历刚刚找到最短距离的点 (B) 的出度
for (int i = 0; i < length; i++) { // (BA 、 BB 、 BC 、 BD)
result[i] = -1; for (int j = 0; j < length; j++) {
} // 出度可通行 ( 例如 BD:graph[1][3] > 0)
result[startVertex] = 0 ; // 出度点不能已经在结果集 result 中 ( 例如 D: result[3] == -
// 初始化 未求出最短路径的点 notFound[] 1)
int[] notFound = new int[length]; if (graph[minIndex][j] > 0&& result[j] == -1){
for (int i = 0; i < length; i++) { int newDistance = result[minIndex] + graph[minIndex][j];
notFound[i] = graph[startVertex][i]; // 通过 B 为桥梁,刷新距离
} // (比如 `AD = 6 < AB + BD = 4` 就刷新距离)( -1 代表无限
notFound[startVertex] = -1; 大)
// 开始 Dijkstra 算法 if (newDistance < notFound[j] || notFound[j]==-1){
for (int i = 1; i < length; i++) { notFound[j] = newDistance;
//1. 从「未求出最短路径的点」 notFound 中取出 最 }
短路径的点 }
//1.1 找到最短距离的点 }
int min = Integer.MAX_VALUE;
int minIndex = 0; }
for (int j = 0; j < length; j++) { return result;
if (notFound[j] > 0 && notFound[j] < min){ }
min = notFound[j];
minIndex = j;
char[] vertices = new char[]{'A','B','C','D'};
int[][] graph = new int[][]{
{0, 2, -1, 6}
, {2, 0, 3, 2}
, {-1, 3, 0, 2}
, {6, 2, 2, 0}};
int[] dijkstra = dijkstra(graph, 0);
All Pairs Shortest Paths
• Given a directed graph G=(V,E), a weighted function, w(e).
• How to find every shortest path from u to v for all u,v in V?
Floyd’s Algorithm
• Represent the graph G by its length adjacency matrix with length[i][j]
• If the edge <i, j> is not in G, the Represent the graph G by its length
adjacency matrix with length[i][j] is set to some sufficiently large
number
• Ak[i][j] is the Represent the graph G by its length adjacency matrix
with length of the shortest path form i to j, using only those
intermediate vertices with an index  k
• Complexity: O(|V|3)
Ak[i][j]
• A-1[i][j] = length[i][j]
• Ak[i][j] = the length (or cost) of the shortest path from i to j going
through
no intermediate vertex of index greater than k
= min{ Ak-1[i][j], Ak-1[i][k]+Ak-1[k][j] } k≧0
Example
6
A-1 0 1 2
v0 v1 0 0 4 11
4
11
1 6 0 2
3 2
2 3  0
v2
6

v0 v1 A0 0 1 2
4 0 0 4 11
11 1 6 0 2
3 2
v2 2 3 7 0
Example cont.
6
A1 0 1 2
v0 v1
0 0 4 6
4
11 1 6 0 2
3 2
2 3 7 0
v2

6
A2 0 1 2
v0 v1
0 0 4 6
4
11 1 5 0 2
3 2
2 3 7 0
v2
void floyd(int[][] graph) {
distance = graph; // 初始化距离矩阵 distance
// 初始化路径
path = new int[graph.length][graph.length];
for (int i = 0; i < graph.length; i++) {
for (int j = 0; j < graph[i].length; j++) {
path[i][j] = j;
}
}
for (int i = 0; i < graph.length; i++) {// 开始 Floyd 算法 // 每个点为中转 // 所有入度
for (int j = 0; j < graph.length; j++) { // 所有出度
for (int k = 0; k < graph[j].length; k++) {
// 以每个点为「中转」,刷新所有出度和入度之间的距离 例如 AB + BC < AC 就刷新距离
if (graph[j][i] != -1 && graph[i][k] != -1) {
int newDistance = graph[j][i] + graph[i][k];
if (newDistance < graph[j][k] || graph[j][k] == -1) {
// 刷新距离
graph[j][k] = newDistance;
// 刷新路径
path[j][k] = i;
}
}
}
}
}
}
https://www.cnblogs.com/skywang12345/p/
3711526.html

You might also like