Вступление
Здравствуйте! |
Новости сайта[26.01.03] Новая статья: Процедуры и функции языка Паскаль в статье дано краткое описание большинства функций и процедур языка Паскаль(что делают, какие параметры получают, что возвращаюти), на русском. Такой своеобразный мини-help, для тех кто слабо владеет английским. |
ТеорияКак вы могли заметить односвязные списки имеют один большой недостаток, который препятствует их широкому применению: такой список нельзя просматривать в обратном направлении. Для этих целей используются списки с двойной связью. Такой список представляет из себя ту же структуру, только теперь добавляется указатель на предыдущий элемент. Т.е. вот так: +------+<---| |---->+------+<---| |---->+------+ | data | | | | data | | | | data | +------+ | | +------+ | | +------+ | next | | | | next | | | | next | | ---------+--- | ---------+--- | -------------> nil +------+ | +------+ | +------+ | prev | | | prev | | | prev | nil<--------- | |------------- | |------------- | +------+ +------+ +------+Соответственно наша структура преобразуется к следующей:
Работа с двусвязным списком не на много сложнее работы с односвязным. А в некоторых местах даже легче. Кроме двусвязных списков я бы хотел обратить внимание на ещё одну деталь. Давайте создадим указатель на указатель :) звучит не очень, но всё таки. Для нашего примера тип указателя на указатель на tlist опишется так:
p2list = ^plist;Что же это такое указатель на указатель ? Давайте вспомним что такое указатель. Указатель - это адрес. Значит указатель на указатель - это адрес указателя. Т.е. это адрес той ячейки памяти, где содержится адрес ячейки памяти, где содержится tlist. Помните: "...синица, которая ворует пшеницу, которая в чёрном чулане хранится, в доме который построил Джек." :) Мы можем создать и указатель на указатель на указатель и так далее. Но сегодня мы не будем так изощрятся и кроме двусвязных списков научимся использовать указатели на указатель.
Для начала откажемся от глобальных переменных. Давайте представим, что мы будем писать функции и процедуры для кого другого, т.е. у нас не будет глобальных переменных - всё, что нам нужно мы будем получать из параметров. Начнём с добавления нового элемента в конец списка. Это очень просто :)
Теперь давайте разберём зачем мы здесь использовали @. Опять обратимся к определению указателя: указтель - это адрес. Значит в list у нас должен содержаться адрес указателя на tlist. Такой указтель у нас есть - list^^.next - указывает на tlist. Значит мы должны присвоить list адрес list^^.next, что мы благополучно и делаем. В остальном эта процедура ничем замечательным не обладает.
Итак элемент добавлен, после работы его надо удалить.
Естественно, что нам может понадобится функция для поиска элемента. Поиск осуществляется тем же перебором от начала до конца, если поиск успешен, то функция вернёт значение true, а list будет указывать на найденный элемент. В противном случае функция вернёт false.
Внимательный читатель должен был бы уже призадуматься, а почему это мы передаём параметры обычным способом и ожидаем, что в конце что-то изменится ? (Если этот вопрос вас не гложет или вы понимаете почему произойдёт изменение переходите сразу дальше). Ведь после выполнения такого кода a и b остануться равными 0 !
Однако, то что истинно для простых переменных не всегда правда для указателей. Давайте немного изменим код:
В результате a и b изменятся ! Давайте разберём почему. Мы передаём в bla указатели (читай адреса) c и d. Потом меняем значения по этим адресам (c^ := 2). При вызове в bla передадутся адреса a и b - bla (@a, @b). Когда процедура изменит значения по этим адресам, то изменятся значения a и b.
Всё это конечно хорошо, но зачем тогда мы передаём в процедуры для работы со списком указатель на указатель, а не просто указатель! Тут всё дело в организации списка. Для односвязного списка мы использовали 2 указателя: начала и рабочий. Для двусвязного списка необходимость в указателе на начало отпадает, поэтому у нас будет один единственный указатель. С ним надо быть осторожнее! Не дай бог обратить его где-нибудь в nil! Односвязный список мы могли бы востановить по указателю начала. Поэтому например в функции поиска для перемотки используется дополнительная переменная. Однако я отвлёкся. Главная часть нашей программы будет выглядеть так:
Но вернёмся к нашим баранам. Продолжим пополнять арсенал процедур и функций для работы со списком. Теперь, когда мы можем найти элемент в списке, то нужно с ним что-то делать. Например удалить или вставить новый после него. Начнём со вставки. Что бы вставить элемент нам надо соответствующим образом изменить указатели next у предшествующего и prev у следующего. Т.е. если вначале у нас был такой список: ...---->+------+<---| |---->+------+ | data | | | | data | +------+ | | +------+ | next | | | | next | | ---------+--- | ------------->.... +------+ | +------+ | prev | | | prev | ...<--------- | |------------- | +------+ +------+тогда, изменив указтели мы получаем такую картинку: ...---->+------+ +------+ | data | | data | +------+ +------+ | next | | next | | ------| | ------------->... +------+ | +------+ | prev | | | prev | ...<--------- | | |---------- | +------+ | | +------+ ^ | \ / ^ | |->+------+ | | | data | | | +------+ | | | next | | | | ----------| | +------+ | | prev | |----------- | +------+Текст процедуры, которая описывает это приведён ниже:
Обратите внимание на "маленькую хитрость" к котороя я прибегнул: если добавляем первый элемент, то тогда вызываем AddToEnd. Попытайтесь понять почему мы так сделали. Процедуру удаления я оставляю на ваше собственное рассмотрение. Так же без комментариев останутся процедуры для добавления/удаления элемента в/из начала. Если у вас возникнут вопросы - пишите.
Вот так слово за слово мы и подошли к процедуре вывода списка на экран. Тут я хочу остановится подробнее. Вспомним примерную структуру нашей будующей программы для работы со списком:
В отличии от односвязного списка теперь у нас только один указатель - list. Он у нас и бегает по списку. При этом бегает в прямом смысле слова. Если вы где-то присвоите ему по ошибке nil, то список будет потерян. Давайте рассмотрим пример работы списка. Итак в начале список пуст, но тут мы добавляем первый элемент (* я буду показывать на что сейчас указывает list):
*123добавим элемент в начало: 321 *123добавим элемент в конец: 321 *123 456теперь добавим после текущего: 321 *123 0 456. Теперь найдём элемент 456: 321 123 0 *456Как вы видите list путешествует по списку (хотя особо не расходится :) и было бы неплохо добавить в процедуру вывода на экран именно указывать где сейчас list с помощью такой же звёздочки, что мы сейчас благополучно и сделаем:
именно для указания текущего положения мы и вводим дополнительную переменную p с помощь которой осуществляем перемотку. И когда p = list значит это текущий элемент и поэтому мы выводим звёздочку.Ну и наконец я привожу текст программы, работающей с двусвзяным списком. Как и в прошлый раз процедуры, которые я дал вам на домашнее рассмотрение не приведены.
|