Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Stack and Queue Data Structures in C

Tech 1

Stack: A LIFO Structure

A stack is a linear data structure that restricts ensertion and deletion to one end—the top. This end is known as the top, while the opposite end is the bottom. Inserting an element into a stack is called pushing, which places the new item above the current top. Removing an element is referred to as popping, which removes the topmost item, promoting the next item to become the new top.

Stacks follow the Last In, First Out (LIFO) principle. They can be implemented using either static arrays (static stack) or dynamic linked lists (linked stack). Here, we focus on implementing a dynamic array-based stack.

Stack Structure Definition

#define StackDataType int

typedef struct Stack {
    StackDataType* data;
    int top;         // Points to the next available position
    int capacity;
} Stack;

The top field starts at 0, indicating the first free slot—this allows it to double as a size counter. The data pointer manages dynamically allocated memory for elements.

Initialization

void StackInit(Stack* pST) {
    assert(pST);
    pST->data = NULL;
    pST->top = pST->capacity = 0;
}

Push Operation

Before inserting, insure there's space:

static void ResizeIfNecessary(Stack* pST) {
    assert(pST);
    if (pST->top == pST->capacity) {
        int new_capacity = (pST->capacity == 0) ? 5 : pST->capacity * 2;
        StackDataType* tmp = (StackDataType*)realloc(pST->data, sizeof(StackDataType) * new_capacity);
        if (!tmp) {
            printf("Memory reallocation failed\n");
            exit(-1);
        }
        pST->data = tmp;
        pST->capacity = new_capacity;
    }
}

void StackPush(Stack* pST, const StackDataType val) {
    assert(pST);
    ResizeIfNecessary(pST);
    pST->data[pST->top] = val;
    pST->top++;
}

Pop Operation

void StackPop(Stack* pST) {
    assert(pST);
    assert(pST->top > 0);  // Ensure stack is not empty
    pST->top--;
}

Access Top Element

StackDataType StackTop(const Stack* pST) {
    assert(pST);
    assert(!StackEmpty(pST));
    return pST->data[pST->top - 1];
}

Since top points to the next position, subtracting 1 gives the actual top value.

Check for Empty Stack

bool StackEmpty(const Stack* pST) {
    assert(pST);
    return pST->top == 0;
}

Get Stack Size

int StackSize(const Stack* pST) {
    assert(pST);
    return pST->top;
}

Destroy Stack

void StackDestroy(Stack* pST) {
    assert(pST);
    free(pST->data);
    pST->data = NULL;
    pST->top = pST->capacity = 0;
}

Queue: A FIFO Structure

A queue is a linear structure where insertions ocurr at the rear and deletions happen at the front. It follows the First In, First Out (FIFO) model. The front is the head, and the rear is the tail.

We implement a linked queue without a sentinel node.

Queue Node and Structure

typedef int QueueDataType;

typedef struct QueueNode {
    QueueDataType data;
    struct QueueNode* next;
} QueueNode;

typedef struct Queue {
    QueueNode* head;
    QueueNode* tail;
} Queue;

Tracking both head and tail simplifies enqueue and dequeue operations.

Initialization

void QueueInit(Queue* pQue) {
    assert(pQue);
    pQue->head = pQue->tail = NULL;
}

Enqueue (Insert at Rear)

static QueueNode* CreateNode(const QueueDataType val) {
    QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));
    if (!new_node) {
        printf("Allocation failed\n");
        exit(-1);
    }
    new_node->data = val;
    new_node->next = NULL;
    return new_node;
}

void QueuePush(Queue* pQue, const QueueDataType val) {
    assert(pQue);
    QueueNode* new_node = CreateNode(val);
    if (!pQue->head) {
        pQue->head = pQue->tail = new_node;
    } else {
        pQue->tail->next = new_node;
        pQue->tail = new_node;
    }
}

Dequeue (Remove from Front)

void QueuePop(Queue* pQue) {
    assert(pQue);
    assert(!QueueEmpty(pQue));
    QueueNode* temp = pQue->head;
    pQue->head = pQue->head->next;
    free(temp);
    if (!pQue->head) {
        pQue->tail = NULL;  // Reset tail if queue becomes empty
    }
}

Always update tail when removing the last node to prevent dangling pointers.

Check if Queue is Empty

bool QueueEmpty(const Queue* pQue) {
    assert(pQue);
    return pQue->head == NULL;
}

Retrieve Front and Rear Values

QueueDataType QueueFront(const Queue* pQue) {
    assert(pQue);
    assert(!QueueEmpty(pQue));
    return pQue->head->data;
}

QueueDataType QueueBack(const Queue* pQue) {
    assert(pQue);
    assert(!QueueEmpty(pQue));
    return pQue->tail->data;
}

Count Elements

size_t QueueSize(const Queue* pQue) {
    assert(pQue);
    size_t count = 0;
    QueueNode* curr = pQue->head;
    while (curr) {
        count++;
        curr = curr->next;
    }
    return count;
}

Destroy Queue

void QueueDestroy(Queue* pQue) {
    assert(pQue);
    QueueNode* curr = pQue->head;
    while (curr) {
        QueueNode* next = curr->next;
        free(curr);
        curr = next;
    }
    pQue->head = pQue->tail = NULL;
}

Practice Challenges

  • Implement a queue using two stacks (LeetCode 232)
  • Implement a stack using a queue (LeetCode 225)

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.