Implementing Graphs with Adjacency Matrix and Adjacency List in C

Implementing Graphs with Adjacency Matrix and Adjacency List in C

In data structures, a graph is a very important model that can represent various relationships, such as social networks, maps, and transportation networks. Graphs can mainly be represented in two ways: the adjacency matrix and the adjacency list. This article will detail these two representation methods and provide C language code examples to help you understand.

1. Basic Concepts of Graphs

Here, we first define some concepts:

  • Graph: A collection of vertices (Vertex) and edges (Edge).
  • Undirected Graph: Edges have no direction, meaning the connection between one vertex and another is bidirectional.
  • Directed Graph: Edges have direction, pointing from one vertex to another.

2. Adjacency Matrix

An adjacency matrix is a way to represent a graph using a two-dimensional array. If there are n vertices, we use an n x n matrix, where <span>matrix[i][j]</span> equals 1 indicates that there is an edge from vertex i to vertex j, while 0 indicates no edge. For undirected graphs, since edges are bidirectional, <span>matrix[i][j]</span> equals <span>matrix[j][i]</span>.

Example Code – Adjacency Matrix

The following is a method to implement a simple undirected graph using an adjacency matrix in C:

#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100

typedef struct {
    int matrix[MAX_VERTICES][MAX_VERTICES];
    int numVertices;
} Graph;

// Initialize an empty graph
void initGraph(Graph *g, int vertices) {
    g->numVertices = vertices;
    for (int i = 0; i < vertices; i++) {
        for (int j = 0; j < vertices; j++) {
            g->matrix[i][j] = 0; // Initialize no edges to 0
        }
    }
}

// Add edge
void addEdge(Graph *g, int start, int end) {
    g->matrix[start][end] = 1;
    g->matrix[end][start] = 1; // Undirected edge
}

// Print adjacency matrix
void printGraph(Graph *g) {
    for (int i = 0; i < g->numVertices; i++) {
        for (int j = 0; j < g->numVertices; j++) {
            printf("%d ", g->matrix[i][j]);
        }
        printf("\n");
    }
}

int main() {
    Graph g;
    initGraph(&g, 5); // Create an empty graph with 5 vertices
    addEdge(&g, 0, 1);
    addEdge(&g, 0, 4);
    addEdge(&g, 1, 2);
    addEdge(&g, 1, 3);
    printf("Neighbor adjacency matrix:\n");
    printGraph(&g);
    return EXIT_SUCCESS;
}

Code Explanation

  • We first define the maximum number of supported nodes as 100, and then use a structure to store the corresponding data.
  • A two-dimensional array simulates an <span>numVertices x numVertices</span> adjacency matrix.
  • The initialization function sets all values to zero, indicating that initially, no nodes are connected.
  • When adding an edge, we only need to modify two positions to reflect the undirected nature (i.e., symmetry).

Disadvantages:

Although effective, the memory overhead increases as the number of nodes increases, making it unsuitable for sparse graphs with a large number of vertices.

Adjacency List

In certain cases, the adjacency list can be more efficient than the adjacency matrix: it uses linked lists or dynamic arrays, where each node only retains the other nodes it is connected to. This is known as the adjacency list representation.

Example Code – Adjacency List

The following is a C implementation of a simple undirected graph using the linked list method:

#include <stdio.h>
#include <stdlib.h>

// Define linked list item structure
typedef struct Node {
    int vertex;
    struct Node* next;
} Node;

typedef struct {
    Node* head[MAX_VERTICES];
    int numVertices;
} Graph;

// Initialize an empty graph
void initGraph(Graph* graph, int vertices) {
    graph->numVertices = vertices;
    for (int v = 0; v < vertices; v++)
        graph->head[v] = NULL;
}

// Add edge
void addEdge(Graph* graph, int src, int dest) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->vertex = dest;
    newNode->next = graph->head[src];
    graph->head[src] = newNode;

    newNode = (Node*)malloc(sizeof(Node));
    newNode->vertex = src;
    newNode->next = graph->head[dest];
    graph->head[dest] = newNode;
}

// Print graph
void printGraph(Graph* graph) {
    for (int src = 0; src < graph->numVertices; src++) {
        Node* temp = graph->head[src];
        printf("Vertex %d: ", src);
        while (temp) {
            printf("%d -> ", temp->vertex);
            temp = temp->next;
        }
        printf("NULL\n");
    }
}

int main() {
    Graph g;
    initGraph(&g, 5); // Create an empty graph with 5 vertices
    addEdge(&g, 0, 1);
    addEdge(&g, 0, 4);
    addEdge(&g, 1, 2);
    addEdge(&g, 1, 3);
    printf("Adjacency list:\n");
    printGraph(&g);
    return EXIT_SUCCESS;
}

Code Explanation – Linked List Implementation

The above code initializes a graph using linked lists, where each vertex points to a list of its adjacent vertices. This method is more memory-efficient for sparse graphs.

Overall, for sparse storage in large-scale random scenarios, the adjacency list provides outstanding convenience and efficiency.

We hope this site can help you learn and understand the C programming language effectively.

Leave a Comment