Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
_Delphi_1курс лекции / Тема Динамические структуры данных.doc
Скачиваний:
69
Добавлен:
23.03.2015
Размер:
330.24 Кб
Скачать

Динамические структуры данных Связные списки, стеки и очереди

Как и массивы, связные списки представляют собой универсальную струк­туру данных, широко используемую многими программистами. Однако, в отличие от массивов, связные списки не входят в состав стандартного языка Turbo/ Object Pascal. Тем не менее, вTurbo/ Object Pascal создать связный список достаточно просто. Все что для этого нужно - наличие в составе языка указателя, хотя фак­тически могут использоваться и классы или объекты.

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

Начнем наше рассмотрение со связного списка и операций, которые такой спи­сок должен поддерживать.

Односвязные (однонаправленные)списки

По своей сути связный список (linked list) представляет собой цепочку элементов или объектов с некоторыми описаниями (обычно называемых узлами). При этом каждый элемент содержит указатель, указывающий на следующий элемент в списке. Такая структура данных называется односвязным списком (singly linked list) - каждый элемент имеет только одну ссылку или указатель на следующий элемент. Сам список начинается с первого узла, от которого путем последователь­ных переходов по ссылкам можно обойти все остальные узлы. Обратите внимание, что определение связного списка отличается от определения массива, для которо-

Рисунок 3.1. Односвязный список

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

А каким образом помечается конец списка? Самый простой способ - устано­вить указатель ссылки в последнем элементе списка равным nil. Это будет озна­чать, что следующий элемент отсутствует. Второй способ - ввести специальный узел, называемый конечным узлом, и установить так, чтобы ссылка последнего узла указывала на этот узел. И третий способ - установить так, чтобы ссылка последнего узла указывала на первый элемент. В этом случае мы получим круго­вой связный список. Наиболее часто используется первый способ определения конца списка.

Различают :

  1. линейные односвязные (однонаправленные) списки без заглавного звена;

  2. линейные односвязные (однонаправленные) списки с заглавным звеном;

  3. циклические односвязные (однонаправленные) списки без заглавного звена;

  4. циклические односвязные (однонаправленные) списки с заглавным звеном

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

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

Хорошо. Если связный список настолько удобен, почему бы его не использовать вместо массива? В чем состоят его недостатки? Первый, хотя и незначительный, со­стоит в том, что каждый элемент связного списка должен содержать указатель на сле­дующий элемент. Таким образом, чтобы вставить элемент в список, его реальный раз­мер необходимо увеличить на размер указателя (в настоящее время это 4 байта).

Хуже то, что память под каждый узел распределяется отдельно. Сравним эту ситуацию с аналогичной ситуацией для массива. Распределение памяти под nэле­ментов массива, фактически, представляет собой операцию класса O(l): все эле­менты должны находится в одном непрерывном блоке памяти, поэтому одновре­менно распределяется целый блок. (Нужно помнить, что память для элементов массивов не обязательно должна распределяться из кучи. Массивы могут пред­ставлять собой, например, локальные переменные в стеке.) Для связного списка память под узлы распределяется отдельно, следовательно, это операция класса О(n). Даже если не учитывать быстродействие, подобное поведение может привес­ти к фрагментации кучи.

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