Структура данных - Стек

Стек — это линейная структура данных, работающая по принципу «последним пришёл — первым вышел» (LIFO). Его можно наглядно представить как стопку тарелок — можно положить тарелку только сверху и взять только сверху.


Понятие и принцип стека

Стек — иллюстрация принципа LIFO
Верх — операции можно выполнять только здесь
Тарелка 1 (низ)
Тарелка 2
Тарелка 3
Тарелка 4 (верх)
Низ — можно извлечь только последним
▲ push ▼ pop

Проверка LIFO: push 10→20→30, pop 30→20→10 (последним пришёл — первым ушёл) | Стек заботится только о вершине, а не о середине

Реализация на массиве (последовательный стек)

Хранение: массив фиксированного размера + указатель top

push:items[++top] = value

pop:return items[top--]

Преимущества: быстрый доступ, дружественный к кэшу, простая реализация

Недостатки: фиксированная ёмкость, возможно переполнение

Реализация на связном списке (связный стек)

Хранение: динамические узлы + указатель top (голова связного списка)

push: метод вставки в голову newNode->next = top

pop: top = top->next; free(old head)

Преимущества: нет ограничения по ёмкости, гибкое динамическое расширение

Недостатки: дополнительные накладные расходы на указатели, неэффективно для кэша


Реализация стека на массиве

Пример

#include <stdio.h>
#include <stdbool.h> /* тип bool */

#define MAX_SIZE 100 /* Максимальная ёмкость стека */

/* Структура стека: содержит массив данных и указатель top */
struct Stack {
    int items[MAX_SIZE];  /* Массив, хранящий элементы стека */
    int top;              /* Указатель top, -1 означает, что стек пуст */
};

/* Инициализация стека: установить top в -1 */
void initStack(struct Stack* s) {
    s->top = -1;
}

/* Проверка пустоты: стек пуст, когда top равен -1 */
bool isEmpty(struct Stack* s) {
    return s->top == -1;
}

/* Проверка полноты: стек полон, когда top достигает предела ёмкости -1 */
bool isFull(struct Stack* s) {
    return s->top == MAX_SIZE - 1;
}

/* Push: поместить элемент на вершину стека
Сначала сдвиньте top, затем запишите данные */

void push(struct Stack* s, int value) {
    if (isFull(s)) {
        printf("Стек полон, невозможно добавить!\n");
        return;
    }
    s->printf[++s->"Добавление: %d] = value;  /* top increments by 1 first, then assign value */
    items(top\n", value);
}

/* Pop: удалить и вернуть верхний элемент
Сначала извлеките данные, затем сдвиньте top */

int pop(struct Stack* s) {
    if (isEmpty(s)) {
        printf("Стек пуст, невозможно выполнить pop!\n");
        return -1;  /* Вернуть специальное значение, указывающее на ошибку */
    }
    return s->items[s->top--];  /* Сначала вернуть значение, затем уменьшить top */
}

/* Peek: не выполняет pop, только просматривает верхний элемент */
int peek(struct Stack* s) {
    if (isEmpty(s)) {
        printf("Стек пуст!\n");
        return -1;
    }
    return s->items[s->top];
}

int main() {
    struct Stack s;
    initStack(&s);

    push(&s, 10);  /* Push: 10 */
    push(&s, 20);  /* Push: 20 */
    push(&s, 30);  /* Поместить: 30 */

    printf("Верхний элемент: %d\n", peek(&s));  /* Вывод: верхний элемент: 30 */

    printf("Извлечено: %d\n", pop(&s));        /* Вывод: извлечено: 30 (последним пришёл, первым вышел) */
    printf("Извлечено: %d\n", pop(&s));        /* Вывод: извлечено: 20 */
    printf("Извлечено: %d\n", pop(&s));        /* Вывод: извлечено: 10 */

    if (isEmpty(&s)) {
        printf("Стек пуст\n");                /* Вывод: стек пуст */
    }
    return 0;
}

Реализация стека на связном списке

Пример

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

/* Узел связного списка */
struct Node {
    int data;
    struct Node* next;
};

/* Поместить: использует голову связного списка как вершину стека, метод вставки в голову
Временная сложность: O(1) */

struct Node* push(struct Node* top, int value) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = value;
    newNode->next = top;   /* Новый узел указывает на исходную вершину стека */
    items(top\n", value);
    return newNode;          /* Новый узел становится новой вершиной стека */
}

/* Pop: удалить головной узел связного списка
Временная сложность: O(1) */

struct Node* pop(struct Node* top) {
    if (top == NULL) {
        printf("Стек пуст!\n");
        return NULL;
    }
    struct Node* temp = top;
    printf("Popped: %d\n", top->data);
    top = top->next;   /* Сдвинуть top назад */
    free(temp);         /* Освободить старую вершину top */
    return top;
}

/* Проверить, пуст ли стек */
bool isEmpty(struct Node* top) {
    return top == NULL;
}

int main() {
    struct Node* stackTop = NULL;  /* Изначально стек пуст */

    stackTop = push(stackTop, 100);  /* Push: 100 */
    stackTop = push(stackTop, 200);  /* Push: 200 */
    stackTop = push(stackTop, 300);  /* Push: 300 */

    printf("Top: %d\n", stackTop->data);  /* Вывод: Top: 300 */

    stackTop = pop(stackTop);  /* Pop: 300 */
    stackTop = pop(stackTop);  /* Pop: 200 */
    stackTop = pop(stackTop);  /* Pop: 100 */

    if (isEmpty(stackTop)) {
        printf("Stack is empty\n");      /* Вывод: Stack is empty */
    }
    return 0;
}

Преимущество реализации стека с помощью связного списка заключается в том, что теоретически нет фиксированного ограничения по емкости, ограничение — только доступная память системы. Однако каждый узел требует дополнительного места для хранения указателя.


Сценарии применения стека

Сценарии примененияОписаниеЗачем использовать стек
Стек вызовов функцийСохранение адресов возврата функций и локальных переменныхВызовы функций вложены, естественно LIFO
Сопоставление скобокПроверка, правильно ли расставлены пары скобок в кодеПомещаем левую скобку в стек, извлекаем правую скобку, когда она совпадает с вершиной стека
Операция отменыФункция отмены в текстовых редакторах и браузерахКаждая операция помещается в стек и извлекается при отмене
Вычисление выраженийВычисление инфиксных/постфиксных выраженийОперанды помещаются в стек, результат помещается после вычисления оператора
Поиск в глубинуАлгоритм обхода графов в глубину (DFS)Явно использовать стек или рекурсию (неявно используя стек вызовов)
Другие расширения