Вступление
Здравствуйте! |
Новости сайта
|
ТеорияИтак сегодня мы займёмся рассмотрением классического примера: связанный список. Для начала самый простой вариант - односвязный. Что же такое связаный список ? Давайте представим, что у нас есть запись.... для простоты введём только одно поле :)
И пускай нам нужно создать массив из N таких записей, причём N задаётся пользователем и может изменяться во время работы программы, как в сторону увеличения, так и в сторону уменьшения. Конечно можно создать массив таким образом:
list : array [1 .. N] of tlist;где N будет максимально возможное число для того, что бы массив поместился в сегменте данных. Однако это не есть гуд. Гораздо проще такая задача решается с использованием указателей. Чем мы сейчас и займёмся. Идея связанного списка проста как бит: в каждом элементе содержится указатель на следующий. Т.е. запись выглядит так: type plist = ^tlist; tlist = record data : integer; next : plist end;Давайте рассмотрим организацию такого списка. Проще всего это дело нарисовать, к сожалению к рассылке нельзя приаттачить картинку, поэтому я нарисую её тут с использованием текстовых символов (не судите за убогость): +------+ |---->+------+ |---->+------+ | data | | | data | | | data | +------+ | +------+ | +------+ | next | | | next | | | next | | ----------- | ----------- | -------------> nil +------+ +------+ +------+итак как мы видим в указателе next находится адрес следующего элемента в памяти. Последний же указатель указывает в никуда (т.е. в nil). Поэтому для организации списка нам потребуются 2 указателя на tlist. Один из них будет постоянно хранить адрес начала (т.е. первого элемента) списка, второй будет "суетится" по списку, т.е. бегать по нему из начала в конец. Можно было бы ввести ещё и указатель на последний элемент (т.е. 3-й указатель на tlist) , но в нём нет надобности, т.к. узнать кончился ли список мы можем, сравнив next с nil. Т.е. у нас будет 2 глобальных переменных: var list, start : plist;Start - будет указывать на начало списка, а list будет нашей рабочей лошадкой. Как видите наш список занимает в памяти всего 8 байт (под один указатель отводится 4).
Для начала напишем процедуру, которая добавляет элемент в список. Замечу, что добавляем мы всегда в конец списка! Параметр d - это то, что нам нужно добавить. list у нас указывает на последний элемент списка.
+------+ |---->...->+------+ +------+ | data | | ... | data | | data | +------+ | ... +------+ +------+ | next | | ... | next | | next | | ----------- ... | -------->nil | -------------> nil +------+ ... +------+ +------+ ^ ^ ^ | - это start | - это list | - это lВыполняя присваивание list^.next := l мы переходим к такому виду: +------+ |---->...->+------+ |---->+------+ | data | | ... | data | | | data | +------+ | ... +------+ | +------+ | next | | ... | next | | | next | | ----------- ... | ------------ | -------------> nil +------+ ... +------+ +------+ ^ ^ ^ | - это start | - это list | - это lи после list := l мы получаем такую картину: +------+ |---->...->+------+ |---->+------+ | data | | ... | data | | | data | +------+ | ... +------+ | +------+ | next | | ... | next | | | next | | ----------- ... | ------------ | -------------> nil +------+ ... +------+ +------+ ^ ^ | - это start | - это list и lЕсли же мы добавляем первай элемнт, то в начале start = nil. В конце и list и start и l будут указывать на одно и тоже. Если же освободить память, которую занимает l, то последний (или первый) элемент списка отрежется и мы ничего не добавим.
Теперь напишем процедуру, которая выводит результаты нашей деятельности, т.е. список на экран:
После выполнения программы было бы неплохо (я сказал даже очень замечательно) освободить память, которую занимает список. Это кстати может понадобится и в процессе выполнения самой программы. Процедура осовобождения очень похожа на процедуру вывода - мы так же последовательно перебираем список, только вместо вывода на экран мы освобождаем память.
...->+------+ |---->+------+ |---->+------+ | data | | | data | | | data | +------+ | +------+ | +------+ | next | | | next | | | next | | ----------- | ------------ | ------------... +------+ +------+ +------+ prev cur nxtЯ назвал условными именами элементы: cur - элемент, который удаляем; prev - элемент, который идёт перед удаляемым; nxt - элемент, идущий за удаляемым. Что бы удалить cur мы должны сделать следующие действия: prev.next = nxt (т.е. предыдущий должен указать на последующий за удаляемым): ...->+------+ +------+ |--|---->+------+ | data | | data | | | | data | +------+ +------+ | | +------+ | next | | next | | | | next | | -----------| | ---------+--- | ------------... +------+ | +------+ | +------+ prev | cur | nxt | | |--------------|Таким образом мы исключили из списка элемент cur, теперь просто освободим память и список преобразуется в такой: ...->+------+ |------>+------+ | data | | | data | +------+ | +------+ | next | | | next | | -----------| | | ------------... +------+ | | +------+ prev | | nxt | | |------|Случай удаления последнего элемента, является частным от удаления среднего, если предположить, что nxt = nil. А случай для удаления первого, если prev = nil. Процедура, которая выполняет всё описанное выше написана ниже :) При этом обратите внимание, что в момент входа в процедуру list указывает на элемент, который предшествует удаляемому (т.е. условно list = prev)
Теперь у нас есть довольно мощный арсенал для работы со списком. Мы можем заполнить список с помощью Add, очистить его Clean, найти нужный элемент Search и удалить его .... или нет удалить его сразу мы не можем. Ведь после выполнения Search list указывает на найденный элемент, а для выполнения Delete нам в list нужен указатель на предыдущий. Надо бы написать ещё процедуру, которая бы находила элемент предшествующий данному. Это и будет домашним заданием. Написать процедуру findPrior, которая ищет элемент списка, предшествующий list.... после её выполнения именно list и должен указывать на этот элемент (что бы потом проще было вызывать Delete). Так же было бы не плохо (а вернее очень хорошо) написать процедуру для добавления элемента в начало списка.Ниже я привёл пример работы со связанным списком. Это просто все процедуры описанные выше + маленькая менюшка. Естественно не хватает процедуры findPrior. Поэтому не пытайтесь что-то удалять пока не напишите её.
|
Послесловие
В следующий раз мы продолжим заниматься списками. |