Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pol_Grem_-_ANSI_Common_Lisp_High_tech_-_2012.pdf
Скачиваний:
28
Добавлен:
12.03.2016
Размер:
4.85 Mб
Скачать

212

 

 

 

Глава 12. Структура

 

 

 

 

 

 

(node-r prev)

(node-l bst))

 

 

root)

 

 

 

 

(progn

 

 

 

 

(setf (node-r bst)

(node-r root))

 

 

bst))))

 

 

(defun replace-node (old new)

 

 

(setf

(node-elt

old) (node-elt new)

 

 

(node-l

old) (node-l

new)

 

 

(node-r

old) (node-r

new)))

 

(defun cutmin (bst par dir)

 

 

(if (node-l bst)

 

 

 

(cutmin (node-l bst) bst :l)

 

(progn

 

 

 

 

(set-par par dir (node-r bst))

 

 

(node-elt bst))))

 

 

(defun cutmax (bst par dir)

 

 

(if (node-r bst)

 

 

 

(cutmax (node-r bst) bst :r)

 

(progn

 

 

 

 

(set-par par dir (node-l bst))

 

 

(node-elt bst))))

 

 

(defun set-par (par dir val)

 

 

(case

dir

 

 

 

(:l

(setf (node-l par) val))

 

 

(:r

(setf (node-r par) val))))

 

 

 

 

 

Рис. 12.9. Двоич­ные­ дере­вья­ поис­ка:­ дест­рук­тив­ное­ удале­ние­

12.6. Пример: двусвязные списки

Обычные­ списки­ в Лиспе­ явля­ют­ся­ одно­связ­ны­ми­. Это озна­ча­ет,­ что движе­ние­ по указа­те­лям­ проис­хо­дит­ только­ в одном­ направ­ле­нии:­ вы може­те­ перей­ти­ к следую­ще­му­ элемен­ту,­ но не може­те­ вернуть­ся­ к пре­ дыду­ще­му­. Двусвяз­ные­ списки­ имеют­ так­же и обрат­ный­ указа­тель,­ по­ этому­ можно­ пере­ме­щать­ся­ в обе сторо­ны­. В этом разде­ле­ пока­за­но,­ как созда­вать­ и исполь­зо­вать­ двусвяз­ные­ списки­.

На рис. 12.10 пока­за­на­ их возмож­ная­ реали­за­ция­. cons-ячей­ки имеют­ два поля:­ car, указы­­вающий­ на данные,­ и cdr, указы­­вающий­ на следую­­ щий элемент­. Элемент­ двусвяз­но­го­ списка­ должен­ иметь еще одно­ поле,­ указы­­вающее­ на преды­ду­щий­ элемент­. Вызов­ defstruct (рис. 12.10) соз­ дает­ объект­ из трех частей,­ назван­ный­ dl (от «doubly linked»), кото­рый­ мы будем­ исполь­зо­вать­ для созда­ния­ двусвяз­ных­ списков­. Поле­ data в dl соот­вет­ст­ву­ет­ car в cons-ячей­ке, а поле­ next соот­вет­ст­ву­ет­ cdr. Поле­

12.6. Пример: двусвязные списки

213

prev похо­же­ на cdr, но указы­­вает­ в обрат­ном­ направ­ле­нии­. (Пример­ та­ кой структу­ры­ при­веден­ на рис. 12.11.) Пусто­му­ двусвяз­но­му­ списку,­ как и обычно­му,­ соот­вет­ст­ву­ет­ nil.

Вы­зов defstruct также­ опре­де­ля­ет­ функции­ для двусвяз­ных­ списков,­ анало­гич­ные­ car, cdr и consp: dl-data, dl-next и dl-p. Функция­ печа­ти­ dl->list возвра­ща­ет­ обычный­ список­ с теми­ же значе­ния­ми,­ что и дву­ связный­.

Функция­ dl-insert похо­жа­ на cons. По крайней­ мере,­ она, как и cons, яв­ ляет­ся­ основ­ной­ функци­ей­-конст­рук­то­ром­. В отли­чие­ от cons, она изме­­ няет­ двусвяз­ный­ список,­ пере­дан­ный­ вторым­ аргу­мен­том­. В данной­ си­ туации­ это совер­шен­но­ нормаль­но­. Чтобы­ помес­тить­ новый­ объект­ в на­ чало­ обычно­го­ списка,­ вам не требу­ет­ся­ его изме­нять,­ одна­ко­ чтобы­ по­ местить­ объект­ в нача­ло­ двусвяз­но­го­ списка,­ необ­хо­ди­мо­ присво­ить­ полю­ prev указа­тель­ на новый­ объект­.

(defstruct (dl (:print-function print-dl)) prev data next)

(defun print-dl (dl stream depth) (declare (ignore depth))

(format stream "#<DL ~A>" (dl->list dl)))

(defun dl->list (lst) (if (dl-p lst)

(cons (dl-data lst) (dl->list (dl-next lst))) lst))

(defun dl-insert (x lst)

(let ((elt (make-dl :data x :next lst))) (when (dl-p lst)

(if (dl-prev lst)

(setf (dl-next (dl-prev lst)) elt (dl-prev elt) (dl-prev lst)))

(setf (dl-prev lst) elt)) elt))

(defun dl-list (&rest args) (reduce #’dl-insert args

:from-end t :initial-value nil))

(defun dl-remove (lst) (if (dl-prev lst)

(setf (dl-next (dl-prev lst)) (dl-next lst))) (if (dl-next lst)

(setf (dl-prev (dl-next lst)) (dl-prev lst))) (dl-next lst))

Рис. 12.10. Построе­ние­ двусвяз­ных­ списков­

214

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Глава 12. Структура

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a

b

 

 

 

c

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 12.11. Двусвяз­ный­ список­

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

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

Функция­ dl-list явля­ет­ся­ dl-анало­гом­ list. Она полу­ча­ет­ произ­воль­ное­ коли­че­ст­во­ аргу­мен­тов­ и возвра­ща­ет­ состоя­щий­ из них dl:

> (dl-list ’a ’b ’c) #<DL (A B C)>

В ней исполь­зу­ет­ся­ reduce с пара­мет­ра­ми:­ :from-end, уста­нов­лен­­ным в t, и :initial-value, уста­нов­лен­­ным в nil, что дела­ет­ приве­ден­ный­ выше­ вызов­ экви­ва­лент­ным­ следую­щей­ после­до­ва­тель­но­сти:­

(dl-insert ’a (dl-insert ’b (dl-insert ’c nil)))

Заме­нив­ #’dl-insert на #’cons в опре­де­ле­нии­ dl-list, эта функция­ будет­ вести­ себя­ анало­гич­но­ list:

>(setf dl (dl-list ’a ’b)) #<DL (A B)>

>(setf dl (dl-insert ’c dl)) #<DL (C A B)>

>(dl-insert ’r (dl-next dl)) #<DL (R A B)>

>dl

#<DL (C R A B)>

Нако­нец,­ для удале­ния­ элемен­та­ из двусвяз­но­го­ списка­ опре­де­ле­на­ dl-remove. Как и dl-insert, она сдела­на­ дест­рук­тив­ной­.

12.7. Циклическая структура

215

12.7. Циклическая структура

Изме­няя­ структу­ру­ списков,­ можно­ созда­вать­ цикли­че­ские­ списки­. Они быва­ют­ двух видов­. Наибо­лее­ полез­ны­ми­ явля­ют­ся­ те, кото­рые­ имеют­ замкну­тую­ структу­ру­ верхне­го­ уровня­. Такие­ списки­ назы­­вают­ся­ цик­ личе­ски­ми­ по хво­сту (cdr-circular), так как цикл созда­ет­ся­ cdr-частя­ми­ ячеек­.

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

>(setf x (list ’a))

(A)

>(progn (setf (cdr x) x) nil) NIL

Теперь­ x – цикли­че­ский­ список­. Его структу­ра­ изобра­же­на­ на рис. 12.12.

x =

y =

nil

a

Рис. 12.12. Цикли­че­ские­ списки­

При попыт­ке­ напе­ча­тать­ такой­ список­ символ­ a будет­ выво­дить­ся­ до беско­неч­но­сти­. Этого­ мож­но избе­жать,­ уста­но­вив­ значе­ние­ *print-circ­ le* в t:

>(setf *print-circle* t)

T

>x

#1=(A . #1#)

При необ­хо­ди­мо­сти­ можно­ исполь­зо­вать­ макро­сы­ чтения­ #n= и #n# для представ­ле­ния­ такой­ структу­ры­.

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

(defun circular (lst)

(setf (cdr (last lst)) lst))

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

Прим. ред.

216

Глава 12. Структура

Другой­ тип цикли­че­ских­ списков ­– цикли­че­ские­ по голо­ве­ (car-circular). Список­ тако­го­ типа­ мож­но пони­мать­ как дере­во,­ являю­щее­ся­ подде­ре­­ вом само­го­ себя­. Его назва­ние­ обуслов­ле­но­ тем, что в нем содер­жит­ся­ цикл, замкну­тый­ на car ячейки­. Ниже­ мы созда­дим­ цикли­че­ский­ по голо­ве­ список,­ второй­ элемент­ кото­ро­го­ явля­ет­ся­ им самим:­

> (let ((y (list ’a))) (setf (car y) y) y)

#1=(#1#)

Резуль­тат­ изобра­жен­ на рис. 12.12. Несмот­ря­ на циклич­ность,­ этот цик­ личе­ский­ по голо­ве­ список­ (car-circular) по-прежне­му­ явля­ет­ся­ пра­ вильным­ списком,­ в отли­чие­ от цикли­че­ских­ по хвосту­ (cdr-circular),­ кото­рые­ правиль­ны­ми­ быть не могут­.

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

> (let ((c (cons 1 1))) (setf (car c) c

(cdr c) c)

c)

#1=(#1# . #1#)

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

Цикли­че­ская­ структу­ра­ может­ быть пробле­мой­ не только­ для списков,­ но и для других­ типов­ объек­тов,­ напри­мер­ для масси­вов:­

>(setf *print-array* t)

T

>(let ((a (make-array 1))) (setf (aref a 0) a)

a)

#1=#(#1#)

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

Разу­ме­ет­ся,­ структу­ры,­ созда­вае­мые­ defstruct, также­ могут­ быть цик­ личе­ски­ми­. Напри­мер,­ структу­ра­ c, представ­ляю­щая­ элемент­ дере­ва,­ может­ иметь поле­ parent, содер­жа­щее­ другую­ структу­ру­ p, чье поле­ child ссыла­ет­ся­ обрат­но­ на c:

> (progn (defstruct elt

(parent nil) (child nil)) (let ((c (make-elt))

(p (make-elt)))

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]