Как создать и связать 100 элементов в двусвязном списке на C++?

Ответ

Для создания двусвязного списка из 100 элементов нужно последовательно создавать узлы и устанавливать связи next (следующий) и prev (предыдущий) между ними. Вот практическая реализация на C++ с использованием сырых указателей и с учетом управления памятью.

Полный пример:

#include <iostream>

struct ListNode {
    int data;
    ListNode* prev; // Указатель на предыдущий узел
    ListNode* next; // Указатель на следующий узел

    ListNode(int val) : data(val), prev(nullptr), next(nullptr) {}
};

class DoublyLinkedList {
    ListNode* head;
    ListNode* tail;
    size_t count;
public:
    DoublyLinkedList() : head(nullptr), tail(nullptr), count(0) {}

    ~DoublyLinkedList() { // Деструктор для очистки памяти
        clear();
    }

    void push_back(int value) {
        ListNode* newNode = new ListNode(value);
        if (!head) { // Если список пуст
            head = tail = newNode;
        } else { // Добавление в конец
            tail->next = newNode;
            newNode->prev = tail;
            tail = newNode;
        }
        ++count;
    }

    void createListOf100() {
        for (int i = 1; i <= 100; ++i) {
            push_back(i); // Создаем узлы со значениями от 1 до 100
        }
    }

    void traverseForward() const {
        for (ListNode* curr = head; curr != nullptr; curr = curr->next) {
            std::cout << curr->data << " ";
        }
        std::cout << std::endl;
    }

    void traverseBackward() const {
        for (ListNode* curr = tail; curr != nullptr; curr = curr->prev) {
            std::cout << curr->data << " ";
        }
        std::cout << std::endl;
    }

    void clear() {
        ListNode* curr = head;
        while (curr) {
            ListNode* next = curr->next;
            delete curr;
            curr = next;
        }
        head = tail = nullptr;
        count = 0;
    }

    size_t size() const { return count; }
};

int main() {
    DoublyLinkedList list;
    list.createListOf100();

    std::cout << "List size: " << list.size() << std::endl; // 100
    std::cout << "Forward traversal (first 5): ";
    // Для демонстрации выведем только начало
    DoublyLinkedList tempList;
    for (int i = 1; i <= 5; ++i) tempList.push_back(i);
    tempList.traverseForward(); // 1 2 3 4 5

    // Деструктор `list` автоматически освободит всю память
    return 0;
}

Ключевые моменты реализации:

  1. Структура узла: Содержит данные и два указателя (prev, next).
  2. Добавление элемента:
    • Создается новый узел.
    • Если список пуст, новый узел становится и head, и tail.
    • Иначе, tail->next связывается с новым узлом, newNode->prev связывается с tail, и tail обновляется.
  3. Управление памятью: Критически важно освобождать выделенную память в деструкторе, чтобы избежать утечек. В реальном коде предпочтительнее использовать std::list или умные указатели.

Ответ 18+ 🔞

Давай разберём эту задачку про двусвязный список, но без занудства, как будто я тебе на кухне объясняю, пока чайник кипит.

Смотри, представь себе цепочку из ста человек, которые держатся за руки. Каждый знает, кто перед ним, и кто за ним. Вот это и есть наш двусвязный список. Задача — собрать такую цепочку из ста узлов, чтобы они все были связаны. Звучит просто, но если накосячить с указателями, получится не цепочка, а какая-то пиздопроебибна, где все друг друга потеряли.

Вот скелет нашего «человечка», то есть узла:

struct ListNode {
    int data;        // Ну, допустим, у него в кармане лежит цифра
    ListNode* prev;  // Он тянется левой рукой к предыдущему
    ListNode* next;  // А правой — к следующему

    ListNode(int val) : data(val), prev(nullptr), next(nullptr) {}
};

nullptr — это когда рука висит в воздухе, никого не держит. Сначала все так и будут стоять, одиночки, ёпта.

А теперь самая мякотка — создание ста штук. Делаем класс, который будет всем этим безобразием управлять. У него есть голова (head) — первый в цепочке, и хвост (tail) — последний. Когда добавляем нового, мы его цепляем к хвосту.

Вот как это выглядит в коде, если не выёбываться:

void push_back(int value) {
    ListNode* newNode = new ListNode(value); // Родили нового человечка
    if (!head) { // Если цепочка пустая (головы нет)
        head = tail = newNode; // Он и голова, и хвост, сам с собой тусуется
    } else { // А если уже кто-то есть
        tail->next = newNode; // Старый хвост правой рукой хватает новичка
        newNode->prev = tail; // А новичок левой — старого хвоста
        tail = newNode;       // И теперь новичок сам становится хвостом
    }
    ++count; // Считаем, сколько нас уже
}

И чтобы создать ровно сто штук, просто вызываем эту функцию в цикле сто раз. Ядрёна вошь, и всё!

Но самое главное, чувак, о чём все вечно забывают — это уборка за собой. Мы же new использовали, память выделяли. Если её не почистить, будет утечка, а это, блядь, как прийти в гости, насрать посреди ковра и смыться. Не комильфо. Поэтому пишем деструктор, который всё подчистит:

~DoublyLinkedList() {
    clear();
}

void clear() {
    ListNode* curr = head;
    while (curr) {
        ListNode* next = curr->next; // Запоминаем, кто следующий
        delete curr;                 // Убиваем текущего
        curr = next;                 // Переходим к запомненному
    }
    head = tail = nullptr;
    count = 0;
}

Вот теперь всё чётко. Создали, потыкали, а когда объект списка умрёт (выйдет из области видимости), деструктор вызовется и подчистит всю память. Красота.

Полный код, который делает ровно то, что просили, выглядит так. Я его уже привёл выше, но ты посмотри на него теперь другими глазами — это же просто цепочка людей, которых правильно познакомили. Главное, не перепутать, кого за какую руку держать, а то будет хитрая жопа с доступом к памяти, от которой потом волосы дыбом встанут. Удачи, не облажайся.