Introduction to Graph Theory

Posted on July 20, 2025 by "GraphArena Team"

Introduction to Graph Theory

Graph theory is a fascinating branch of mathematics that deals with the study of graphs, which are mathematical structures used to model pairwise relationships between objects. Graphs consist of nodes (or vertices) connected by edges (or links).

Let’s visualize a simple graph:

import networkx as nx
import matplotlib.pyplot as plt

# Create a simple graph
G = nx.Graph()
G.add_edges_from([
    (1, 2), (1, 5), (2, 3), (2, 5),
    (3, 4), (4, 5), (4, 6)
])

# Visualize the graph
plt.figure(figsize=(8, 6))
pos = nx.spring_layout(G, seed=42)
nx.draw(G, pos, with_labels=True, node_color='lightblue', 
        node_size=500, edge_color='gray', 
        font_size=15, font_weight='bold')
plt.title("A Simple Graph")
plt.axis('off')
plt.show()

Basic Concepts

Let’s understand some basic concepts of graph theory:

Vertices and Edges

A graph G can be formally defined as G = (V, E), where:
- V is a set of vertices (nodes)
- E is a set of edges, which are pairs of vertices

For example, in a social network graph:
- Each person is represented by a vertex
- A friendship between two people is represented by an edge connecting their respective vertices

We can define this mathematically:

Where:
- \(V = \{v_1, v_2, …, v_n\}\) is the set of vertices
- \(E = \{(v_i, v_j) | v_i, v_j \in V\}\) is the set of edges

Types of Graphs

There are several types of graphs with different properties:

  1. Undirected Graph: Edges have no orientation (Facebook friendships)
  2. Directed Graph (Digraph): Edges have orientation (Twitter follows)
  3. Weighted Graph: Edges have weights/costs (Road networks with distances)
  4. Complete Graph: Every vertex is connected to every other vertex
  5. Bipartite Graph: Vertices can be divided into two sets where no vertices within the same set are adjacent

Let’s visualize some of these graph types:

# Create figure with subplots
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 1. Undirected Graph
G_undirected = nx.Graph()
G_undirected.add_edges_from([(1, 2), (1, 3), (2, 3), (3, 4)])
pos_undirected = nx.spring_layout(G_undirected, seed=42)
nx.draw(G_undirected, pos_undirected, ax=axes[0, 0], with_labels=True, 
        node_color='lightblue', node_size=500)
axes[0, 0].set_title("Undirected Graph")

# 2. Directed Graph
G_directed = nx.DiGraph()
G_directed.add_edges_from([(1, 2), (2, 3), (3, 1), (3, 4)])
pos_directed = nx.spring_layout(G_directed, seed=42)
nx.draw(G_directed, pos_directed, ax=axes[0, 1], with_labels=True,
        node_color='lightgreen', node_size=500, 
        arrowstyle='->', arrowsize=15)
axes[0, 1].set_title("Directed Graph")

# 3. Complete Graph
G_complete = nx.complete_graph(5)
pos_complete = nx.spring_layout(G_complete, seed=42)
nx.draw(G_complete, pos_complete, ax=axes[1, 0], with_labels=True, 
        node_color='lightcoral', node_size=500)
axes[1, 0].set_title("Complete Graph (K5)")

# 4. Bipartite Graph
G_bipartite = nx.Graph()
G_bipartite.add_nodes_from([1, 2, 3], bipartite=0)  # First group
G_bipartite.add_nodes_from([4, 5, 6, 7], bipartite=1)  # Second group
G_bipartite.add_edges_from([(1, 4), (1, 5), (2, 5), (2, 6), (3, 6), (3, 7)])
pos_bipartite = nx.bipartite_layout(G_bipartite, [1, 2, 3])
nx.draw(G_bipartite, pos_bipartite, ax=axes[1, 1], with_labels=True, 
        node_color=['lightblue', 'lightblue', 'lightblue', 'lightgreen', 'lightgreen', 'lightgreen', 'lightgreen'],
        node_size=500)
axes[1, 1].set_title("Bipartite Graph")

plt.tight_layout()
plt.show()

Why Study Graphs?

Graph theory has numerous applications across various fields:

  • Computer Science: Network routing, database design, compiler optimization
  • Social Sciences: Modeling social networks and relationships
  • Biology: Protein interaction networks, ecological systems
  • Transportation: Optimizing routes, traffic flow analysis
  • Telecommunications: Network design and optimization
  • Operations Research: Scheduling, resource allocation

Here’s a real-world example of analyzing a social network:

# Create a simple social network graph
social_graph = nx.Graph()

# Add friendship relationships
friendships = [
    ('Alice', 'Bob'), ('Alice', 'Carol'), ('Alice', 'David'),
    ('Bob', 'Eve'), ('Bob', 'Frank'), ('Carol', 'David'),
    ('David', 'Eve'), ('Eve', 'Frank'), ('Frank', 'Grace')
]
social_graph.add_edges_from(friendships)

# Calculate degree centrality (number of connections)
centrality = nx.degree_centrality(social_graph)

# Visualize the social network
plt.figure(figsize=(10, 8))
pos = nx.spring_layout(social_graph, seed=42)

# Size nodes by their centrality
node_sizes = [centrality[node] * 3000 + 300 for node in social_graph.nodes()]

nx.draw(social_graph, pos, with_labels=True, 
        node_color='lightblue', node_size=node_sizes,
        font_size=12, font_weight='bold')

plt.title("Simple Social Network - Node Size Represents Centrality")
plt.show()

Getting Started with Algorithms

Some fundamental algorithms in graph theory include:

  • Breadth-First Search (BFS): Explores neighbor nodes first before moving to next level
  • Depth-First Search (DFS): Explores as far as possible along each branch before backtracking
  • Dijkstra’s Algorithm: Finds the shortest path between nodes in a weighted graph
  • Minimum Spanning Tree (Kruskal’s, Prim’s): Finds a subset of edges that forms a tree including every vertex with minimum total edge weight

Let’s implement a simple Breadth-First Search:

def bfs(graph, start):
    """Simple BFS implementation"""
    visited = set([start])
    queue = [start]
    traversal_order = [start]

    while queue:
        current = queue.pop(0)
        for neighbor in graph[current]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
                traversal_order.append(neighbor)

    return traversal_order

# Create a simple graph as an adjacency list
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

# Run BFS starting from 'A'
bfs_result = bfs(graph, 'A')
print("BFS traversal starting from 'A':", ' -> '.join(bfs_result))

Output:

BFS traversal starting from 'A': A -> B -> C -> D -> E -> F

Mathematical Representation

Graphs can be represented in various ways:

Adjacency Matrix

An adjacency matrix is a square matrix where each element (i, j) represents an edge from vertex i to vertex j:

# Create a simple graph
simple_graph = nx.Graph()
simple_graph.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D'), ('C', 'D')])

# Get adjacency matrix
adj_matrix = nx.adjacency_matrix(simple_graph).todense()

# Display the matrix
print("Adjacency Matrix:")
print(adj_matrix)

# Visualize the graph
plt.figure(figsize=(6, 6))
pos = nx.spring_layout(simple_graph, seed=42)
nx.draw(simple_graph, pos, with_labels=True, node_color='lightblue', 
        node_size=500, font_size=15, font_weight='bold')
plt.title("Graph Represented by Adjacency Matrix")
plt.show()

Output:

Adjacency Matrix:
[[0 1 1 0]
 [1 0 1 1]
 [1 1 0 1]
 [0 1 1 0]]

Adjacency List

An adjacency list represents a graph as a collection of lists, one for each vertex, containing the vertices adjacent to it:

A: [B, C]
B: [A, C, D]
C: [A, B, D]
D: [B, C]

Conclusion

Graph theory provides powerful tools for modeling and analyzing interconnected systems. In future posts, we’ll explore these algorithms in detail and implement them in our interactive playground. We’ll also dive deeper into specific applications and advanced topics in graph theory.

Stay tuned for our next post on graph traversal algorithms!

References

  1. Bondy, J. A., & Murty, U. S. R. (1976). Graph theory with applications. North-Holland.
  2. Wilson, R. J. (1996). Introduction to Graph Theory. Longman.
  3. West, D. B. (2000). Introduction to Graph Theory (2nd ed.). Prentice Hall.