Опубликован: 05.01.2015 | Уровень: для всех | Доступ: платный
Лекция 3:

Элементарные структуры данных

Выделение памяти под списки

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

Здесь очень важно представлять, как может быть реализована операция new. Например, при удалении узла из списка наша задача - оформить ссылки таким образом, чтобы узел более не был привязан к списку. Но что делает система с памятью, которую этот узел занимал? Как система утилизирует пространство, чтобы всегда иметь возможность выделять его под новый узел операцией new? За этими вопросами стоят механизмы, которые служат еще одним примером эффективности элементарной обработки списков.

Операция delete противоположна операции new. Когда блок выделенной памяти уже не нужен, мы с помощью операции delete сообщаем системе, что этот блок доступен для дальнейшего использования. Динамическое распределение памяти (dynamic memory allocation) - это процесс управления памятью и действий в ответ на вызовы new и delete из клиентских программ.

При выполнении операции new непосредственно в приложениях, таких как программы 3.9 или 3.11, как правило, запрашиваются блоки памяти одинакового размера. Поэтому метод отслеживания памяти, доступной для распределения, напрашивается сам: достаточно использовать связный список! Все узлы, которые не входят ни в один используемый список, можно совместно содержать в единственном связном списке. Этот список называется свободным (free list). Когда необходимо выделить память под узел, он извлекается - то есть удаляется - из свободного списка. При удалении узла из какого-либо списка он вставляется в свободный список.

Программа 3.14 является реализацией интерфейса, описанного в программе 3.12, включая функции распределения памяти. При совместной компиляции с программой 3.13 она дает такой же результат, что и прямая реализация, с которой мы начали в программе 3.9. Сопровождение свободного списка для узлов фиксированного размера - тривиальная задача при наличии базовых операций вставки и удаления узлов из списка.

Программа 3.14. Реализация интерфейса обработки списков

Эта программа реализует функции, объявленные в программе 3.12, а также демонстрирует стандартное распределение памяти под узлы фиксированного размера. Создается свободный список, который инициализируется максимальным количеством узлов, используемых программой. Все узлы взаимосвязаны. Когда клиентская программа выделяет память для узла, он удаляется из свободного списка. Когда клиентская программа освобождает узел, он добавляется к свободному списку.

По соглашению клиентские программы обращаются к узлам списков только путем объявления переменных типа Node и использования их в качестве аргументов функций, описанных в интерфейсе. Узлы, возвращаемые клиентским программам, имеют ссылки на самих себя. Эти соглашения служат средством защиты от ссылок неопределенными указателями и в какой-то мере гарантируют, что клиент использует интерфейс должным образом. В языке C++ эти соглашения реализуются путем использования классов совместно с конструкторами (см. "Абстрактные типы данных" ).

#include <stdlib.h>
#include "list.h"
link freelist;
void construct(int N)
  {
    freelist = new node[N+1];
    for (int i = 0; i < N; i++)
      freelist[i].next = &freelist[i+1];
    freelist[N].next = 0;
  }
  link newNode(int i)
    { link x = remove(freelist);
      x->item = i; x->next = x;
      return x;
    }
    void deleteNode(link x)
      { insert(freelist, x); }
    void insert(link x, link t)
      { t->next = x->next; x->next = t; }
    link remove(link x)
      { link t = x->next; x->next = t->next; return t; }
    link next(link x)
    Item item(link x)
      { return x->item; }
        

На рис. 3.11 показано разрастание свободного списка по мере удаления узлов в программе 3.13. Для простоты подразумевается реализация связного списка (без ведущего узла), основанная на индексах массива.

Реализация механизма распределения памяти общего назначения в среде C++ намного сложнее, чем подразумевают рассмотренные простые примеры, а реализация операции new в стандартной библиотеке явно не настолько проста, как показано в программе 3.14. Одно из основных различий состоит в том, что функции new приходится обрабатывать запросы на выделение памяти для узлов различного размера - от крохотных до огромных. Для этой цели разработано несколько хитроумных алгоритмов. Другой подход, используемый в некоторых современных системах, состоит в освобождении пользователя от необходимости явно удалять узлы за счет алгоритмов сборки мусора (garbage collection). Эти алгоритмы автоматически удаляют все узлы, на которые не указывает ни одна ссылка. В этой связи также разработано несколько нетривиальных алгоритмов управления памятью. Мы не будем рассматривать их подробно, поскольку их характеристики быстродействия зависят от свойств определенных компьютеров.

 Представление связного списка и свободного списка в виде массивов

Рис. 3.11. Представление связного списка и свободного списка в виде массивов

На этой версии рис. 3.6 приведен результат ведения свободного списка с узлами, удаленными из циклического списка. Слева указан индекс первого узла свободного списка. После завершения процесса свободный список представляет собой связный список, содержащий все удаленные элементы. Переходы по ссылкам, начиная с 1, дают следующий ряд элементов: 2 9 6 3 4 7 1 5 - т.е. в порядке, обратном тому, в котором элементы удалялись.

Программы, использующие конкретные сведения о приложении, часто эффективнее программ общего назначения, решающих те же задачи. Распределение памяти - не исключение из этого правила. Алгоритм обработки запросов на выделение блоков памяти различного размера не может "знать", что всегда будут запрашиваться блоки фиксированного размера, и поэтому не может использовать этот факт.

Парадоксально, но вторая причина отказа от функций библиотек общего назначения заключается в том, что это делает программу более переносимой - так можно защититься от непредвиденных изменений быстродействия при смене библиотеки либо переноса в другую систему. Многие программисты считают, что использование простого механизма распределения памяти, вроде продемонстрированного в программе 3.14 - удачный способ разработки эффективных и переносимых программ, использующих связные списки. Этот подход возможен в ряде алгоритмов, которые будут рассмотрены в данной книге, если в них применяются подобные запросы к системе управления памятью. В остальной части книги для распределения памяти будут применяться стандартные функции C++ new и delete.

Упражнения

  • 3.46. Напишите программу, которая удаляет все узлы связного списка (с помощью операции delete с указателем).
  • 3.47. Напишите программу, которая удаляет узлы связного списка, находящиеся в позициях с номерами, кратными 5 (пятый, десятый, пятнадцатый и т.д.).
  • 3.48. Напишите программу, которая удаляет узлы в четных позициях связного списка (второй, четвертый, шестой и т.д.).
  • 3.49. Реализуйте интерфейс программы 3.12 с помощью непосредственных вызовов new и delete в функциях newNode и deleteNode соответственно.
  • 3.50. Эмпирически сравните время выполнения функций распределения памяти из программы 3.14 с операторами new и delete (см. упражнение 3.49) для программы 3.13 при M = 2 и N = 103, 104, 105 и 106 .
  • 3.51. Реализуйте интерфейс программы 3.12, используя вместо указателей индексы массива (без ведущего узла) таким образом, чтобы результат работы описывался рисунком 3.11.
  • 3.52. Предположим, что имеется набор узлов без пустых указателей (каждый узел указывает на себя либо на другой узел набора). Докажите, что при переходах по ссылкам, начиная с любого узла, вы в конце концов попадете в цикл.
  • 3.53. При соблюдении условий упражнения 3.52 напишите фрагмент кода, который для заданного указателя узла подсчитывает количество различных узлов, которые будут достигнуты при переходах по ссылкам от данного узла. Модификация любых узлов не допускается. Используйте не более некоторого постоянного объема дополнительной памяти.
  • 3.54. При соблюдении условий упражнения 3.53 напишите функцию, которая определяет, достигнут ли одного и того же цикла переходы из двух заданных ссылок.
Бактыгуль Асаинова
Бактыгуль Асаинова

Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат?

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?

Александр Ефимов
Александр Ефимов
Россия, Спб, СпбГтурп
Павел Сусликов
Павел Сусликов
Россия