Линейные односвязные списки

Линейные односвязные списки используют чаще других списковых структур, так как они сравнительно просты, но одновременно в отличие от одномерных массивов позволяют:

• работать с произвольным количеством элементов, добавляя и удаляя их по мере необходимости;

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

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

Рассмотрим более подробно, как выполняются основные операции с ли­нейными односвязными списками.

Исходные установки. В начале программы необходимо описать эле­мент и его тип:

Type tpel=Aelement; {тип «указатель на элемент»} element=record

num:integer; {число}

p:tpel; {указатель на следующий элемент} end;

В статической памяти описываем переменную-указатель списка и не­сколько переменных-указателей, используемых при выполнении операций со списком:

Var first, {указатель списка - адрес первого элемента списка} n,f,q:tpel; {вспомогательные указатели}

Исходное состояние «список пуст»:

first: -nil;

Добавление нового элемента к списку. Добавление элемента к списку включает запрос памяти для размещения элемента и заполнение его инфор­мационной части. Построенный таким образом элемент добавляется к уже существующей части списка.

В общем случае при добавлении элемента к списку возможны следую­щие варианты:

• список пуст, добавляемый элемент станет единственным элементом списка;

• элемент необходимо вставить перед первым элементом списка;

• элемент необходимо вставить перед заданным (не первым) элементом списка;

• элемент необходимо дописать в конец списка.

Добавление элемента к пустому списку состоит из записи адреса эле­мента в указатель списка, причем в поле «адрес следующего» добавляемого элемента необходимо поместить nil:

new(first); {запрашиваем память под элемент}

first А.пит:=5; {заносим число в информационное поле} first A.p:=nil; {записываем nil в поле «адрес следующего»}

На рис. 7.10 показана последовательность операций при добавлении элемента к пустому списку.

Добавление элемента перед первым элементом списка. При выполнении этой операции необходимо в поле «адрес следующего» переписать адрес первого элемента списка, а в указатель списка занести адрес добавляемого элемента (рис. 7.11):

new(q); {запрашиваем память под элемент} qAnum:=4; {заносим число в информационное поле} qAp:=first; {в поле «адрес следующего» переписываем адрес

первого элемента} first:=q; ... {в указатель списка заносим адрес нового элемента}

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

clip_image002

clip_image004

будут корректироваться (рис. 7.12). Пусть f- адрес предыдущего элемента, а п - адрес следующего элемента, тогда:

new(q); {запрашиваем память под элемент} дл.пит:=3; {заносим число в информационное поле} qA.p:=n; {в поле «адрес следующего» нового элемента переписы­ваем адрес следующего элемента} j*.p:=q; ... {в поле «адрес следующего» предыдущего элемента за­носим адрес нового элемента}

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

new(q); {запрашиваем память под элемент} qA.num:=3; {заносим число в информационное поле} qA.p:=f*.p; {в поле «адрес следующего» нового элемента переписы­ваем адрес следующего элемента} f*.p:=q; ... {в поле «адрес следующего» предыдущего элемента за­носим адрес нового элемента}

clip_image008

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

new(q); {запрашиваем память под элемент} qA.num:=7; {заносим число в информационное поле} qA.p:=nil; {в поле «адрес следующего» элемента записываем nil} fAp:=q;... {в поле «адрес следующего» предыдущего элемента за­носим адрес нового элемента}

clip_image010

Анализ показывает, что этот случай можно свести к предыдущему, так как адресная часть последнего элемента содержит nil:

new(q); {запрашиваем память под элемент} q\num:=7; {заносим число в информационное поле} qA.p:=fA.p; {в поле «адрес следующего» нового элемента записыва­ем nil}

fA.p:=q; ... {в поле «адрес следующего» предыдущего элемента за­носим адрес нового элемента}

Комбинируя эти фрагменты, можно организовать построение списка с любой дисциплиной добавления элементов.

Пример 7.2. Разработать программу, которая строит список по типу сте­ка из целых чисел, вводимых с клавиатуры. Количество чисел не известно, но отлично от нуля. Конец ввода - по комбинации клавиш CTRL-Z (конец файла на устройстве ввода).

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

ReadLn(a);

new(first); {запрашиваем память под элемент}

first А.пит:=а; {заносим число в информационное поле} first A.p:=nil; {записываем nil в поле, «адрес следующего»} while not EOF do begin

ReadLn(a);

new(q); {запрашиваем память под элемент} qA.num:=a; {заносим число в информационное поле} qA.p:=first; {в поле «адрес следующего» переписываем адрес

первого элемента} first:=q; {в указатель списка заносим адрес нового элемента} end; ...

Пример 7.3. Разработать программу, которая строит список по типу оче­реди из целых чисел, вводимых с клавиатуры. Количество чисел не извест­но, но отлично от нуля. Конец ввода - по комбинации CTRL-Z (конец файла на устройстве ввода).

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

ReadLn(a);

new(first); {запрашиваем память под элемент}

first Апит:=а; {заносим число в информационное поле} f:—first; {f - текущий элемент, после которого добавляется

следующий}

while not EOF do begin ReadLn(a);

new(q); {запрашиваем память под элемент} qA.num:-a; {заносим число в информационное поле} fA.p:=q; {в поле «адрес следующего» предыдущего элемента

заносим адрес нового элемента} f:-f\p; {теперь новый элемент стал последним} end;

qA.p:=nil; {в поле «адрес следующего» последнего элемента запи­сываем nil}

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

ReadLn(a);

new(first); {запрашиваем память под элемент}

first Апит:=а; {заносим число в информационное поле} f:-Jirst; {f - текущий элемент, после которого добавляется

следующий}

while not EOF do begin

ReadLn(a);

new(f\p); {запрашиваем память под элемент} f:=fA>p; {теперь новый элемент стал последним} fA.num:=a; {заносим число в информационное поле} end;

f\p:-nil; {в поле «адрес следующего» последнего элемента за­писываем nil}

Пример 7.4. Разработать программу, которая строит список, сортиро­ванный по возрастанию элементов, из целых чисел, вводимых с клавиатуры. Количество чисел не известно, но отлично от нуля. Конец ввода - по комби­нации CTRL-Z (конец файла на устройстве ввода).

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

new(first); {запрашиваем память под первый элемент}

ReadLn(first Апит); {заносим число в информационное поле} firstAp:=nil; while not EOF do begin

new(q); {создаем элемент}

ReadLn(q\num); {заносим значение} if qAnum<firstAnum then {если элемент меньше первого

элемента списка, то}

begin {вставляем перед первым}

qA.p:=first; first: =q;

end

else {иначе вставляем в середину или конец}

begin

n:=first; {указатель на текущий элемент} f:=first; {указатель на предыдущий элемент} flag:=false; {"элемент не вставлен"} {цикл поиска места вставки} while (nA.ponil) and (not flag) do begin

n:=nA.p; {переходим к следующему элементу} if qAnum<nAnum then {место найдено} begin {вставляем в середину} qAp:=fAp;

flag:=true; {"элемент вставлен"} end

else f:=n; {сохраняем адрес текущего элемента} end;

if not flag then {если элемент не вставлен, то} begin {вставляем после последнего}

qA.p:-nil; fAp:=q; end;

end;

end;

Просмотр и обработка элементов списка. Просмотр и обработка эле­ментов списка выполняется последовательно с использованием дополни­тельного указателя:

f—first; while fonil do begin

<обработка элемента по адресу f> end; ...

В качестве примера рассмотрим вывод на экран элементов списка:

f:=first;

while fonil do begin

WriteLn(fA.num,' ');

f:=fA-P; end; ...

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

f:=first;

flag:-false;

while (fonil) and (not flag) do begin

if fA пит =k then flag: =not flag else f:=f A.p;

end;

if flag then <элемент найден>

else <элемент не найден>; ...

Удаление элемента из списка. При выполнении операции удаления также возможно четыре случая:

• удаление единственного элемента;

• удаление первого (не единственного) элемента списка;

• удаление элемента, следующего за данным;

• удаление последнего элемента.

Удаление единственного элемента. После удаления единственного эле­мента список становится пустым, следовательно при выполнении этой опе­рации необходимо не только освободить память, выделенную для размеще­ния элемента, но и занести nil в указатель списка first (рис. 7.14):

clip_image012

clip_image014

f:=firstAp; {сохраняем адрес следующего элемента} Dispose(first); {освобождаем память}

first: =/• ... {заносим в указатель списка адрес следующего

элемента}

Удаление единственного элемента и первого элемента в программе мож­но объединить:

q —first; first: =firstA.p; Dispose(q);...

Удаление элемента, следующего за данным (не последнего). Удаление элемента, следующего за данным, требует запоминания адреса удаляемого элемента, изменения адресной части данного элемента и освобождения па­мяти (рис. 7.16).

n:=fAp; {сохраняем адрес удаляемого элемента} fA.p:=nA.p; {заносим в адресное поле предыдущего элемента ад­рес следующего элемента} Dispose(n); ... {освобождаем память}

clip_image016

Удаление последнего элемен­та. Удаление последнего элемен­та отличается только тем, что в поле «адрес следующего» задан­ного элемента записывается кон­станта nil:

n:=fA.p;

fAp:=nil;

Dispose(п); ...

Удаление последнего эле­мента можно свести к удалению элемента, следующего за дан­ным, так как адресная часть уда­ляемого элемента равна nil.

Комбинируя приемы удале­ния, мы также можем организо­вать любую дисциплину удале­ния.

Пример 7.5. Разработать программу, которая удаляет из списка все элементы меньше за­данного значения к.

Удаляемые значения могут располагаться в списке на любом месте, следовательно, возможны все четыре варианта удаления элемента, которые сводятся к двум случаям:

• удаление единственного элемента и удаление записей из начала списка-удаление из нача­ла списка;

• удаление средних и последнего элементов - удаление не из начала списка. Для разделения этих двух случаев введем специальный признак «удаление из начала», который в начале установим равным true, а затем, как только в списке будет оставлен хотя бы один элемент - изменим на false.

п:-first;

nft:=true; {признак «удаление из начала списка»} repeat

if пА.пит<к then begin


if nft then {если «удаление из начала списка»} begin {удаление из начала списка} q:=first; first:=firstA.p; Dispose(q);

n:=first; {переходим к следующему элементу} end

else {иначе}

begin {удаление из середины и конца} q:=n;

n:=n^.р; {переходим к следующему элементу} Dispose(q); f\p:=n;

end

end

else {оставляем элемент в списке} begin

f:=n; {устанавливаем адрес предыдущего элемента}

п:-п\р; {переходим к следующему элементу} nft:=not nft {«удаление не из начала списка»} end;

until n-nil;... {до завершения списка}

Предлагаю ознакомиться с аналогичными статьями: