С++ для начинающих. Однонаправленный список FIFO

  • FIFO — это способ организации сцепки элементов в порядке обычной, привычной нам, очереди.
Когда говорят о FIFO, прежде всего подразумевают динамическую сцепку элементов, т. е. говорят о динамической структуре данных. Также FIFO могут называть очередью FIFO или списком FIFO. Всё это одна и та же организация работы программы с памятью, реализация которой сводится к тому, что,первый вошедший в очередь объект первым и обслуживается.
Ранее на странице этого сайта описывалась организация работы с памятью по принципу LIFO:

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

Пришел 1 (первый)
Пришел 2 (второй)
Пришел 3 (третий)
Пришел 4 (четвертый)
====================
При выводе, например, на экран в порядке следования будет: 1 2 3 4
Сама организация структуры FIFO предполагает, что обращением к элементу списка выбирается элемент пришедший первым. Первым пришла единица, следовательно первым будет 1, вторым 2, третьим 3..
Если создать несколько по порядку идущих чисел и поочереди записать их в список, а потом вывести список на экран, то при выводе на экран будет сохранён порядок, в LIFO же порядок переворачивается.
Я как-то долго не мог понять, чем отличается очередь от списка FIFO — ответ: ничем; это и есть список FIFO. А очередью называется потому как соблюдаются правила "Справедливых" очередей. Т. е. кто первее — того первее обслужат. Надеюсь, доступно, понятно и не сложно запомнить. Теперь перейдём к практической части.
Так как FIFO мало отличается от LIFO, то коды, организующие эти принципы, всегда будут очень сильно схожи. Итак, чтобы построить очередь FIFO, для начала имеет смысл запомнить, что для FIFO обязательные элементы адрес начала очереди и адрес конца очереди. Запихивать в очередь я буду обычные числа, т. к. так удобнее смотреть. И добавлю размер, т. е. количество размещённых в очередь элементов. Этот размер совсем не обязателен, но пусть будет.
Итак, со скелетом очереди определились: 2 адреса, значение и размер очереди. Этот скелет имеет смысл запихать в класс или структуру. Т. к. поначалу структуры попроще воспринимаются, я запихаю скелет в структуру. Тому, кто плохо знаком со структурами и классами, имеет смысл прочитать С++ для начинающих. Классы. Первое знакомство.

Да, выше я не сказал про следующий элемент, но чтобы создать очередь FIFO, нужно как-то связывать текущий элемент с вновь пришедшим. Это как в поликлиннике, когда прибежал какой-то гад, сказал, что за вами и убежал. Но связь создана. Вот чтобы создать такую связь внутри программы, используют адреса памяти, проще говоря, указатели. Тип этих указателей соответствует названию класса/структуры. В скелете я обозначил переменную *Next. Эта переменная будет хранить адрес памяти последнего поступившего в очередь элемента. Не впереди идущего, а последнего поступившего.
Теперь, когда создан скелет очереди, можно добавить функцию добавления элементов. Чтобы добавить элемент в очередь FIFO, имеет смысл запомнить простой алгоритм:

  • 1) Создать новый элемент, тип которого соответствует типу класса/структуры
  • 2) Записать в этот элемент полученное значение
  • 3) Запомнить адрес начала очереди
  • 4) Проверить очередь на наличие в ней элементов. Не новый элемент проверить, а то, что голова не пустая

    -Если Очередь пустая — то обозначить, что начало очереди одновременно и конец очереди. Для всех указателей (начало, голова, хвост) хранимый адрес на такой случай должен быть один и тот же.
    -Если очередь не пустая — то записать в хвост очереди созданный элемент и сдвинуть хвост, указав что адресом хвоста теперь будет адрес вновь созданного элемента.

Показываю описанный алгоритм на примере:


Так, добавление показал и объяснил. Теперь вопрос в том, как работать с этой очередью. Элементарное, что можно с ней сделать — это вывести всю очередь на экран. Чтобы вывести её на экран, нужно узнать адрес начала очереди. После того, как адрес очереди будет известен, можно вывести на экран значение текущего элемента очереди и сдвинуть адрес на адрес следующего запомненного элемента. (Внутри очереди это будет последний туда поступивший после только что выбранного). Т. к. нами создавалась связь адресов, то попасть в нужный адрес не будет проблемой, для этого при помощи цикла нужно постепенно обращаться к элементу Next только что выбранного элемента, выводить значение val на экран и вновь двигаться по нашей очереди цепочке узлов к следующему элементу. Цикл выполнять имеет смысл до тех пор, пока не будет достигнут конец очереди. На самом деле очередь FIFO может быть кольцевой и не кольцевой. В зависимости от закольцованности признаки определения конца очереди могут различаться. Наша очередь не кольцевая. Для некольцевой очереди обычно признак то, что изъятый элемент не последний элемент очереди (т. е. указатель Next ведёт на ненулевой адрес). Ну а в кольцевом — наиболее удобным признаком конца может послужить количество элементов, записавшихся в очередь. В самом конце я покажу кольцевой, он почти не отличается от некольцевого FIFO, а сейчас некольцевой FIFO:


Вот и всё. Теперь осталось написать функцию удаления очереди из памяти. Вместе с удалением я буду выводить информацию по удаляемому элементу:

Я не писал, что нужно в main, чтобы не отвлекать этим. Теперь мы пришли к моменту, когда всё готово, использовать созданную нами очередь довольно легко:

Вот так вот. Не так уж и сложно. Если мы задействуем конструктор, то код можно сделать чуть лучше. В настоящий момент нам после объявления очереди необходимо указывать, что Q1.Head = NULL;, это довольно неудобно. Поправим:

Всё бы ничего, но у нас на самом деле имеется весомый потенциал возникновения ошибки при работе программы: она случится, если очередь будет пустая. При наличии пустой очереди у нас будет происходить аналог вот такого:



Чтобы этого избежать, достаточно в функции вывода предусмотреть этот момент и ничего не выводить на экран, если очередь не заполнялась элементами или зачистилась до состояний "пустая":


Не хочу повторять один и тот же код ради небольшой поправки. Ничего сложного, надеюсь, не сделал. Функция вывода не изменилась почти никак, туда только добавилась одна проверка. Тут важно, что сначала надо проверить очередь на пустоту, и только потом выполнять действия с очередью. Это может касаться не только функции отображения, но и других функций, манипулирующих данными очереди.

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

static int count — переменная-счетчик, которая благодаря ключевому слову static не будет умирать и будет сохранять свое последнее значение после завершения работы Add. Вот при каждом новом вызове Add эта переменная будет увеличиваться на 1 и после этого запоминаться в переменную size структуры MyFIFO.
Т. к. до этого самым первым поступившим в очередь был NULL, который был использован в цикле для проверки окончания работы цикла (например, в функции Show()), то теперь же произведённым изменением вместо NULL используется адрес начала очереди, т. е. хранимое Head. Поскольку теперь у нас всегда Next указывает на существующий элемент, то проверка указывания на адрес с несуществующим элементом: проверка на NULL не годится. Это значит, что там где использовался обход по признаку указывания вникуда, там нужно поменять критерии выполнения цикла. Изменяем функцию Show():


Как видите, вовнутрь функции обхода теперь передаётся аргумент, т. е. вызов функции Show() слегка меняется. Теперь требуется указывать количество итераций, т. к. без этого цикл не выполняется, а чтобы их указывать, можно вовнутрь функции передавать параметр, обозначающий это количество.
Полный код кольцевого списка FIFO:


10 комментариев на «“С++ для начинающих. Однонаправленный список FIFO”»

  1. СВЕТА:

    Долго, очень долго искала информацию в интернете о программировании списков!!! И… вот, наконец-то нашла!!! Спасибо за скрупулезность и доброту! Вы мне очень помогли! Желаю удачи, здоровья, благополучия…

  2. Slavik:

    Спасибо автору за статьи, очень нужное дело для начинающих. Обязательно продолжайте писать.
    Дополню в Show утечка памяти и ненужное копирование, можно написать так:
    void List::Show() //метод отображения списка на экране
    {
    element *temp=Head; //прыгаем этим указателем к голове списка

    while (temp!=NULL) //пока что-то встречается
    {
    cout<x<Next; //и переходим к следующему элементу
    }
    }

  3. Slavik:

    редактор подвел 🙁
    void List::Show() //метод отображения списка на экране
    {
    element *temp=Head; //прыгаем этим указателем к голове списка

    while (temp!=NULL) //пока что-то встречается
    {
    cout<x<Next; //и переходим к следующему элементу
    }
    }

  4. Slavik:

    Да уж в тэги не взял код. В общем в Show()

    element *temp=Head; //прыгаем этим указателем к голове списка
    вместо

    element *temp=new element; //выделяем память указателю
    temp=Head; //прыгаем этим указателем к голове списка
    temp->x=Head->x; //присваиваем в x нашего указателя x головы

  5. Благодарю. Исправил.

  6. Руфина:

    Спасибо огромное за такую легкоусвояемую информацию)))

  7. Igor:

    а зачем делать постоянное изменение указателя в 13 строке второго примера, Next=temp->Head; там же движение по хвосту все равно только, если не кольцевать то temp->Next=null; я бы еще понял, но вот здесь то зачем

    • Низачем, да и кроме этого с кодом не всё в порядке: удаление некорректное. Так получилось, что не особо понимал, что к чему, когда писал статью. Буду на предстоящей неделе вспоминать, что к чему, и пробовать исправить свои упущения. Спасибо, что обратили внимание.

  8. Пад:

    А у вас функция удаления одного элемента есть?То есть сдвигать нго в конец

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/denis-kolisnichenko/php-i-mysql-razrabotka-web-prilozheniy-5-e-izdanie-pdf-epub-19222711/?lfrom=15589587

Последние комментарии

Яндекс.Метрика