Cвязные списки

Как мы видели в одной из предыдущий глав, в структуре могут содержаться указатели на различные типы данных, и ничто не мешает объявлять в структурах указатели на другие структуры. Особенно интересен случай, когда структура содержит указатель на структуру такого же типа. Это позволяет создавать цепочки структур, или деревья (когда таких указателей в структуре два или больше).

Начнём с цепочек:

// Стуктура определяет одну ячейку цепочки
struct cell {
    int value; // значение ячейки
    struct cell *next; // указатель на следующую ячейку
};

Конструировать цепочки можно следующим образом:

Цепочка = Пустая_Цепочка
или
Цепочка = Ячейка(Значение, Цепочка)

В принципе, видно, что таким способом (он называется алгебраическим построением типа) можно построить из пустой цепочки цепочку любой длинны, просто добавляя новые ячейки к уже полученным цепочкам. В языке си мы будем создавать новую ячейку и указывать на ячейку, находящуюся в начале предыдущего списка. А пустой список в Си логичнее всего обозначить константой NULL.

TODO: картинка с построением списка.

Давайте воспользуемся такой структурой данных, чтобы реализовать стек. Стек (англ. stack – стопка) – это не только область памяти, применяемая для расположения данных вызванных функций, но и любая структура данных, которая поддерживает только две операции: добавление нового элемента и извлечение элемента, который лежит на вершине стека.

Реализация стека выглядит следующим образом:

Операция PUSH:

temp/PUSH.jpg

Операция POP:

temp/POP.jpg

Обе эти операции уже реализованы в коде этой программы:

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

typedef struct IntStack {
    int value;
    struct IntStack *next;
} IntStack;

int main()
{
    int a, value;
    IntStack *stack, *new_head, *old_head, *current, *previous;

    stack = NULL;               // Пустой стек.

    while (1) {
        printf("1 Добавить значение в стек\n"
               "2 Распечатать значение из головы стека\n"
               "3 Распечатать все значения стека\n"
               "4 Удалить вершину стека\n"
               "5 Выход\n\n" "Выберите пункт меню: \n");
        // Считываем число у пользователя, до тех пор пока оно не будет
        // от 1 до 5 включительно.
        while (scanf("%d", &a) != 1 && (a < 1 || a > 5))
        {} // вся работа цикла заключена в условие, тело пустое.
        switch (a) { // оператор множественного выбора.
        case 1: // если пользователь ввёл 1
            while (scanf("%d", &value) != 1);
            new_head = (IntStack *) malloc(sizeof(IntStack));
            new_head->value = value;
            new_head->next = stack;
            stack = new_head;
            break;
        case 2:
            if (stack != NULL) { 
                printf("Вершина стека: %d\n", stack->value);
            } else {
                printf("Ошибка! Стек пусть!\n");
            }
            break;
        case 3:
            for (current = stack; current != NULL; current = current->next) {
                printf("%d ", current->value);
            }
            printf("\n");
            break;
        case 4:
            old_head = stack;
            stack = stack->next;
            free(old_head);
            break;
        case 5:
            if (NULL == stack) { // память из-под стека уже освобождена, можем выйти.
                return 0;
            }
            // Освобождаем память.
            previous = stack;
            for (current = stack->next; current != NULL; current = current->next) {
                free(previous);
                previous = current;
            }
            free(previous);
            return 0;
            break;
        default:
            printf
                ("Странно. Этот кусок кода не должен выполняться, "
                 "значит где-то в программе ошибка. Завершаем работу.\n");
            return 1;
            break;
        }
    }
}

Если же пользваться связным списоком именно не как стеком, а как структурой с доступом к произвольным дынным, которые есть в списке, нам понядобятся дополнительно функции INSERT и REMOVE.

INSERT:

temp/INSERT.jpg

REMOVE:

temp/REMOVE.jpg

TODO: перерисовать картинки