2.8. Lecture 7: Graphs

Before this class you should:

  • Read Think Complexity, Chapter 2

  • Read the Wikipedia page about graphs at https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) and answer the following questions:

    1. What is a simple graph? For our discussion today, we will assume that all graphs are simple graphs. This is a common assumption for many graph algorithms – so common it is often unstated.

    2. What is a regular graph? What is a complete graph? Prove that a complete graph is regular.

    3. What is a path? What is a cycle?

    4. What is a forest? What is a tree? Note: a graph is connected if there is a path from every node to every other node.

Before next class you should:

Note taker: Joseph Talon

Today’s class was an introduction to Graphs and working with graphs in Python.

2.8.1. Graph Types

Graphs are a representation of a system with discrete interconnected elements. The elements of the system are represented by nodes or vertices (usually as a circle) and the connections between nodes are represented by edges.

Simple Graphs are undirected graphs that do not contain multiple edges or loops

  • Multiple edges are two or more edges that connect the same two nodes

  • A loop is an edge than connects a node back to itself

Regular Graphs are graphs where every node has the same number of neighbours. The number of neighbours is also known as the degree. If the degree is k, the graph is called a “k regular graph” or a “regular graph of degree k”.

Complete Graphs are graphs where each node is joined to every other node by an edge. To be complete, each node must be connected to \(N-1\) other nodes. Since all nodes will have the same number of neighbours, complete graphs will always also be regular graphs, but not vice versa.

2.8.2. Graph Traversal

Path

A sequence of nodes with an edge between each consecutive pair.

In graph theory, a path is not allowed to repeat vertices, with the exception of the start and end vertices if the path is a cycle.

Cycle

A path that is closed, which requires repeating the start and end vertices.

Walk

Similar to a path, but allows the repetition of vertices and edges.

Tree

A connected graph with no cycles.

A graph is considered to be connected if there is a path from one node to all other nodes in the graph.

Forest

A graph with no cycles, but not connected.

A forest is made of multiple trees that do not connect to each other.

2.8.3. Graphs in Python

The NetworkX library provides data structures for graphs, standard graph algorithms, graph generators, and other features for working with graphs in Python. In this course, we will be using version 3.

The next few lectures will use the following imports taken from homework02 when dealing with graphs:

# Embed matplotlib plots in the notebook instead of
%matplotlib inline
%config InlineBackend.figure_format = 'svg'

import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import seaborn as sns

from utils import decorate, savefig

# I set the random seed so the notebook
# produces the same results every time.
np.random.seed(17)
To create a graph with NetworkX:
  • G = nx.Graph() will create an empty undirected graph

  • G = nx.DiGraph() will create an empty directed Graph object and assign it to the variable G

  • G.add_node("NodeX") can be used to add nodes to the graph, where NodeX is whatever you want the node to represent

  • G.add_edge("Node1", "Node2") will add an edge from Node1 to Node2,

    i.e., (Node1 Node2) in a directed graph

  • G.add_nodes_from() can be used to add multiple nodes from structures such as lists, dicts, or tuples

  • The nodes and edges of the graph can be returned from the G.nodes() and G.edges() commands

  • The graph can be plotted with the nodes positioned in a circle using nx.draw_circular()

For example, on social media where you can follow someone without them following back:

G = nx.DiGraph()
# Create three nodes
G.add_node("Jim")
G.add_node("Jon")
G.add_node("Jacques")

# Add edges representing following
G.add_edge("Jim", "Jon") # Jim follows both Jon and Jacques
G.add_edge("Jim", "Jacques")
G.add_edge("Jon", "Jacques") # Jon follows only Jacques

# Draw the graph
nx.draw_circular(G, node_color="<colour>", node_size=2000, with_labels=True)
Directed Graph Social Network Example

Directed Graph social network example

2.8.3.1. Complete Graphs

To draw a complete graph, we must iterate through all nodes and pair each node with every other node.

# Find all possible pairs for a list of nodes and return as a generator
def all_pairs(nodes):
    for i, u in enumerate(nodes):
        for j, v in enumerate(nodes):
            if i < j:
                yield u, v

# Create a graph of n nodes and add edges between all pairs of nodes
def make_complete_graph(n):
    G = nx.Graph()
    nodes = range(n)
    G.add_nodes_from(nodes)
    G.add_edges_from(all_pairs(nodes))
    return G

# Create a complete graph of 10 nodes
complete = make_complete_graph(10)
nx.draw_circular(complete,
    node_color='C2',
    node_size=1000,
    with_labels=True)
../_images/complete_graph_example.svg

Complete Graph example with 10 nodes

2.8.3.2. Random Graphs

To create a random graph, a helper function can be used that acts similarly to a coin flip, but with probably p to increase or decrease the odds.

def flip(p):
    return np.random.random() < p

# Finds all possible pairs of nodes and adds an edge
# depending on output of flip(p)
def random_pairs(nodes, p):
    if 0 < p < 1:
        for edge in all_pairs(nodes):
            if flip(p):
                yield edge
    else:
        raise ValueError("Probability p must be in range [0, 1]")

# Makes an Erdos-Renyi graph using above function
def make_random_graph(n, p):
    G = nx.Graph()
    nodes = range(n)
    G.add_nodes_from(nodes)
    # probablity of an edge between each node is p
    G.add_edges_from(random_pairs(nodes, p))
    return G


# Make random graph of 10 nodes with probability of 0.3 of an edge
# between each pair of nodes
random_graph = make_random_graph(10, 0.3)
nx.draw_circular(random_graph,
    node_color="C3",
    node_size=1000,
    with_labels=True)
../_images/random_graph_example.svg

Random Graph example with 10 nodes and probability 0.3

If we were to increase the probability to 0.9, most of the pairs of nodes should have an edge between them, while decreasing to 0.1 will have fewer edges.

../_images/random_graph_example_high_p.svg

Random Graph example with 10 nodes and probability 0.9

../_images/random_graph_example_low_p.svg

Random Graph example with 10 nodes and probability 0.1

2.8.3.3. Connectivity

To check if a graph is connected, a helper function can be used that traverses the graph from a starting node and adds each node to a set as it is traversed. Then, the length of that returned set can be compared to the length of the graph. If they are equal, then the graph is complete.

def reachable_nodes(G, start):
    seen = set()
    stack = [start]
    while stack:
        node = stack.pop()
        if node not in seen:
            seen.add(node)
            stack.extend(G.neighbors(node))
    return seen

def is_connected(G):
    if len(G) == 0:
        raise ValueError("Graph must not be empty.")
    start = next(iter(G))
    reachable = reachable_nodes(G, start)
    return len(reachable) == len(G)