Linked Lists in C: A Complete Guide with Examples and Practical Applications

目次

1. Introduction

C is a widely used programming language for system programming and embedded systems development. Among its many features, the “list structure” serves as a highly useful tool for data management and manipulation. In this article, we will explain list structures in C in detail, covering everything from the basic concepts to concrete implementation examples.

The Importance of List Structures

A list structure is a data structure designed to manage data while maintaining its order. It is particularly useful in situations such as:

  • Managing ordered data
  • When frequent insertion or deletion of data is required
  • Efficient use of memory

For example, in a task management application, tasks are often added and removed frequently, making a list structure more flexible than an array.

Purpose of This Article

This article will show you how to implement a list structure in C, with practical code examples to demonstrate how to manipulate it. By the end, you will understand both the basic concepts and practical applications, enabling you to integrate list structures into your own programs.

Learning Goals

  1. Understand the basics of list structures
  2. Learn how to implement linked lists in C
  3. Develop the ability to apply this knowledge by referencing practical code examples

2. What Is a List Structure?

A list structure is a type of data structure used to manage data in an ordered manner. Here, we’ll explain its basic concepts and types.

Basic Concept of List Structures

A list structure consists of data elements (nodes) connected sequentially. Each node contains two parts:

  1. Data Part: Stores the actual value
  2. Link Part: Stores a reference (pointer) to the next node

This design allows dynamic insertion and deletion of data, making it useful for handling variable-length data.

Difference Between Arrays and Lists

Arrays and list structures differ in how they manage data:

AspectArrayList Structure
SizeFixed (decided at declaration)Variable (can change dynamically)
Data AccessDirect access via indexSequential search from the head
Memory ManagementUses a contiguous memory blockUses scattered memory blocks
Insertion/DeletionHigh cost (requires shifting elements)Low cost (only pointer adjustments)

In short, list structures are more suitable when flexibility is required, whereas arrays excel when fast direct access is needed.

Overview and Characteristics of Linked Lists

A “linked list” is a representative example of a list structure. Its characteristics include:

  1. Dynamically Resizable
    You can add or remove nodes as needed, enabling efficient memory use.
  2. Easy Insertion/Deletion
    Pointer adjustments allow quick modifications, even in the middle or at the end of the list.
  3. Slower Search
    Accessing specific data requires sequential traversal from the head.

Types of Linked Lists

There are three main types of linked lists:

  1. Singly Linked List
    Each node points only to the next node.
  • Simple structure with low memory usage.
  • Traversal is possible in the forward direction only, not backward.
  1. Doubly Linked List
    Each node points to both the next and previous nodes.
  • Allows movement in both directions, enabling more flexible operations.
  • Consumes more memory than a singly linked list.
  1. Circular Linked List
    The last node points back to the first node.
  • Enables continuous traversal, making it suitable for loop-based processing.
  • Useful for specialized purposes.

In the next section, we will explain how to implement a linked list in C with practical code examples.

年収訴求

3. Implementing a Linked List in C

This section explains how to implement a linked list in C, complete with practical code examples.

Basic Structure of a Linked List

A linked list is composed of the following elements:

  1. Node Definition
  • Each node contains data and a pointer to the next node.
  1. Head Pointer
  • A pointer that points to the first node, managing the entire list.

Code Example: Basic Linked List Implementation

The following is a sample code for manipulating a linked list:

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

// Node definition
typedef struct Node {
    int data;                 // Data part
    struct Node* next;        // Pointer to the next node
} Node;

// Create a new node
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // Allocate memory
    if (newNode == NULL) {
        printf("Failed to allocate memory.\n");
        exit(1);
    }
    newNode->data = data;
    newNode->next = NULL; // Initially, next is NULL
    return newNode;
}

// Insert a node at the head
void insertAtHead(Node** head, int data) {
    Node* newNode = createNode(data); // Create new node
    newNode->next = *head;            // New node points to current head
    *head = newNode;                  // Update head to new node
}

// Print the list
void printList(Node* head) {
    Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

// Free memory
void freeList(Node* head) {
    Node* temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp); // Free memory
    }
}

int main() {
    Node* head = NULL; // Initialize empty list

    // Add data to the list
    insertAtHead(&head, 3);
    insertAtHead(&head, 2);
    insertAtHead(&head, 1);

    // Print the list
    printList(head);

    // Free memory
    freeList(head);

    return 0;
}

Code Explanation

  1. Node Creation
  • A new node is created using dynamic memory allocation.
  1. Node Insertion
  • Inserts data at the head or tail to expand the list.
  1. Node Deletion
  • Implements deletion of the head node or a node with a specific value.
  1. Memory Management
  • Frees used memory properly to prevent memory leaks.

4. Types of Linked Lists

In this section, we will explain the main types of linked lists and their characteristics. We will also outline the advantages and disadvantages of each type so you can choose the most appropriate one for your needs.

1. Singly Linked List

A singly linked list is a structure where each node refers only to the next node.

Characteristics:

  • List elements can only move in one direction.
  • Uses less memory and is relatively simple to implement.

Advantages:

  • Flexible, as the size can be changed dynamically through memory allocation.
  • Efficient insertion and deletion operations.

Disadvantages:

  • Cannot move backward, making reverse traversal inefficient.

2. Doubly Linked List

A doubly linked list is a structure where each node refers to both the next and the previous nodes.

Characteristics:

  • Each node contains a “previous link” and a “next link.”
  • Allows movement in both directions, enabling flexible operations.

Advantages:

  • Bidirectional traversal simplifies searching and deletion operations.
  • Easy to reverse the list order.

Disadvantages:

  • Requires more memory than a singly linked list (two pointers per node).
  • More complex to implement and maintain.

3. Circular Linked List

A circular linked list is a structure where the last node refers back to the first node.

Characteristics:

  • No terminating node; traversal loops infinitely.
  • Can be implemented as either singly or doubly linked.

Advantages:

  • Enables easy movement between the head and tail, making it ideal for loop processing.
  • Useful for queue or buffering operations.

Disadvantages:

  • Lacks a natural end, so deletion and search operations require careful handling.

Comparison Table of List Types

TypeCharacteristicsAdvantagesDisadvantages
Singly Linked ListMoves forward onlySimple to implement, low memory usageCannot move backward
Doubly Linked ListMoves in both directionsFlexible operations and easier searchingHigher memory usage, more complex implementation
Circular Linked ListTail points to headIdeal for loop processing, seamless head-tail movementNo termination, can be harder to manage

In the next section, we will cover Linked List Operations, including insertion, deletion, and searching for elements.

5. Linked List Operations

This section explains the basic operations for linked lists, specifically insertion, deletion, and searching, with practical code examples.

1. Inserting Elements

There are three ways to insert elements into a linked list:

(1) Insert at the Head

Adds a new node at the beginning of the list.

void insertAtHead(Node** head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = *head; // New node points to the current head
    *head = newNode;       // Head updated to new node
}

(2) Insert at the Tail

Adds a new node at the end of the list.

void insertAtTail(Node** head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;

    if (*head == NULL) { // If list is empty
        *head = newNode;
        return;
    }

    Node* temp = *head;
    while (temp->next != NULL) { // Move to the last node
        temp = temp->next;
    }
    temp->next = newNode; // Add new node at the end
}

2. Deleting Elements

There are two main ways to delete elements from a linked list:

(1) Delete the Head Node

void deleteAtHead(Node** head) {
    if (*head == NULL) {
        printf("The list is empty.\n");
        return;
    }
    Node* temp = *head;
    *head = (*head)->next; // Move head to the next node
    free(temp); // Free memory
}

(2) Delete a Node with a Specific Value

void deleteNode(Node** head, int key) {
    Node* temp = *head;
    Node* prev = NULL;

    // If the head node contains the key
    if (temp != NULL && temp->data == key) {
        *head = temp->next;
        free(temp);
        return;
    }

    // Search for the node to delete
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    if (temp == NULL) {
        printf("Value not found.\n");
        return;
    }

    prev->next = temp->next;
    free(temp);
}

3. Searching Elements

Searches for a specific value within the list.

int search(Node* head, int key) {
    Node* temp = head;
    int position = 0;

    while (temp != NULL) {
        if (temp->data == key) {
            return position; // Return found position
        }
        temp = temp->next;
        position++;
    }
    return -1; // Not found
}

4. Printing the List

Displays all the elements in the list in order.

void printList(Node* head) {
    Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

Summary

In this section, we implemented the basic operations for linked lists:

  • Insertion – Supports adding elements at the head, tail, or specific positions.
  • Deletion – Allows removing the head or elements with specific values.
  • Searching – Finds data in the list and returns its position.

6. Memory Management Tips

In this section, we will discuss memory management, a crucial aspect of linked list implementation. We will focus on dynamic memory allocation and preventing memory leaks to ensure safe and efficient program design.

1. What Is Dynamic Memory Allocation?

In C, when creating a node for a linked list, dynamic memory allocation is used. This allows you to allocate memory at runtime, enabling flexible data handling.

Example: Allocating memory for a node

Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // Allocate memory
    if (newNode == NULL) { // Check for allocation failure
        printf("Failed to allocate memory.\n");
        exit(1); // Exit program
    }
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

2. Importance of Freeing Memory

In linked lists, new nodes are allocated dynamically. If memory is not freed after use, memory leaks will occur, leading to performance issues and instability.

Example: Freeing memory

void freeList(Node* head) {
    Node* temp;
    while (head != NULL) {
        temp = head;         // Store the current node
        head = head->next;   // Move to the next node
        free(temp);          // Free memory
    }
}

3. Best Practices to Prevent Memory Leaks

  1. Always free unused memory
  • Ensure you free memory once it’s no longer needed.
  1. Use debugging tools
  • Tools like Valgrind and AddressSanitizer can help detect memory leaks.

Example: Using Valgrind (Linux)

valgrind --leak-check=full ./a.out
  1. Clearly manage pointer ownership
  • Define which part of the program “owns” each pointer to avoid conflicts.
  • Set pointers to NULL after freeing to prevent accidental access.

Summary

  • Dynamic memory allocation is essential for the flexibility of linked lists.
  • Forgetting to free memory will cause memory leaks and instability.
  • Use debugging tools and proper pointer management for safe program design.

7. Applications and Practical Use Cases

In this section, we introduce examples of data structures built using linked lists, such as stacks and queues, to highlight their flexibility and practicality.

1. Implementing a Stack

A stack is a data structure that follows the Last In, First Out (LIFO) principle — the last element added is the first one removed.

Example: Stack Implementation Using a Linked List

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

typedef struct Node {
    int data;
    struct Node* next;
} Node;

Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

void push(Node** top, int data) {
    Node* newNode = createNode(data);
    newNode->next = *top;
    *top = newNode;
}

int pop(Node** top) {
    if (*top == NULL) {
        printf("Stack is empty.\n");
        exit(1);
    }
    Node* temp = *top;
    int poppedData = temp->data;
    *top = (*top)->next;
    free(temp);
    return poppedData;
}

void printStack(Node* top) {
    while (top != NULL) {
        printf("%d -> ", top->data);
        top = top->next;
    }
    printf("NULL\n");
}

int main() {
    Node* stack = NULL;

    push(&stack, 10);
    push(&stack, 20);
    push(&stack, 30);

    printf("Stack contents: ");
    printStack(stack);

    printf("Popped value: %d\n", pop(&stack));
    printf("Popped value: %d\n", pop(&stack));

    printf("Stack contents: ");
    printStack(stack);

    return 0;
}

2. Implementing a Queue

A queue is a data structure that follows the First In, First Out (FIFO) principle — the first element added is the first one removed.

Example: Queue Implementation Using a Linked List

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

typedef struct Node {
    int data;
    struct Node* next;
} Node;

typedef struct Queue {
    Node* front;
    Node* rear;
} Queue;

Queue* createQueue() {
    Queue* q = (Queue*)malloc(sizeof(Queue));
    q->front = q->rear = NULL;
    return q;
}

void enqueue(Queue* q, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    if (q->rear == NULL) {
        q->front = q->rear = newNode;
        return;
    }
    q->rear->next = newNode;
    q->rear = newNode;
}

int dequeue(Queue* q) {
    if (q->front == NULL) {
        printf("Queue is empty.\n");
        exit(1);
    }
    Node* temp = q->front;
    int data = temp->data;
    q->front = q->front->next;

    if (q->front == NULL) {
        q->rear = NULL;
    }

    free(temp);
    return data;
}

void printQueue(Queue* q) {
    Node* temp = q->front;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

int main() {
    Queue* q = createQueue();

    enqueue(q, 1);
    enqueue(q, 2);
    enqueue(q, 3);

    printf("Queue contents: ");
    printQueue(q);

    printf("Dequeued value: %d\n", dequeue(q));
    printf("Dequeued value: %d\n", dequeue(q));

    printf("Queue contents: ");
    printQueue(q);

    return 0;
}

Summary

In this section, we explored how to implement stacks and queues using linked lists:

  • Stacks use the LIFO method, suitable for recursion and expression evaluation.
  • Queues use the FIFO method, ideal for buffering and job scheduling.

8. Conclusion

In this article, we explored linked lists in C, covering everything from the fundamental concepts to practical implementation examples and advanced applications. Let’s review the key points from each section and suggest steps for further study and application.

1. Recap of Key Points

Linked List Basics

  • A linked list is a flexible data structure that can change its size dynamically.
  • Unlike arrays, linked lists make it easy to add or remove elements, though searching for specific elements requires sequential traversal.

Implementation and Basic Operations

  • We explained how to create, insert, delete, and search nodes, along with code examples.
  • Proper memory management is essential to build safe and efficient programs.

Types and Use Cases

  • Singly Linked List – Suitable for simple, lightweight processing.
  • Doubly Linked List – Effective when bidirectional traversal is needed.
  • Circular Linked List – Useful in systems that require repeated looping.

Applications

  • We implemented stacks and queues, both of which are common data structures built on linked lists.
  • These applications are valuable for algorithm optimization and system development.

2. Skills Gained and Practical Applications

By following this article, you should have developed the following skills:

  • Understanding of Data Structures
  • Improved Programming Skills
  • Enhanced Problem-Solving Ability

3. Next Topics to Study

  1. Advanced Data Structures
  • Tree structures (Binary Tree, AVL Tree)
  • Graph algorithms (Breadth-First Search, Depth-First Search)
  1. Advanced Data Management
  • Hash tables and maps
  • Hybrid designs combining dynamic arrays and linked lists
  1. Algorithm Optimization
  • Optimizing sorting and searching algorithms
  • Analyzing time and space complexity

4. Practical Application Ideas

Project Suggestions

  • Task Management Application
  • Simulation Systems
  • Data Analysis Tools

5. Final Thoughts

Linked lists are a fundamental data structure in programming, offering a wide range of applications from simple tasks to complex system development. We hope this article has provided you with a solid understanding of both the basics and advanced uses, enabling you to confidently apply linked lists in your own projects.