- •4.9.1. Изменяемость
- •4.9.2. Классы операций
- •98 Глава 4
- •4.9.3. Полнота
- •100 Глава 4
- •4.9.5. Операции egual, similar и copy
- •102 Глава 4
- •104 Глава 4
- •5. Исключительные ситуации
- •108 Глава 5
- •110 Глава 5
- •6.2.1. Сигнализация об исключительных ситуациях
- •5.2.2. Обработка исключительных ситуаций
- •112 Глава 5
- •5.2.3. Предложение resignal
- •6.2.4. Необрабатываемые исключительные ситуации
- •114 Глава 5
- •6.2.3. Предложение resignal
- •5.2.4. Необрабатываемые исключительные ситуации
- •116 Глава 5
- •118 Глава 5
- •118 Глава 5
- •120 Глава 5
- •122 Глава 5
- •124 Глава 5
- •128 Глава 6
- •130 Глава 6
- •6.2.1. Реализация итераторов
- •6.2.2. Использование итераторов
- •132 Глава 6
- •134 Глава 6
- •136 Глава 6
- •138 Глава 6
- •140 Глава 7
- •142 Глава 7
- •7.2. Абстракция данных
- •144 Глава 7
- •146 Глава 7
- •148 Глава 7
- •150 Глава 7
- •7.4. Генераторы
- •162 Глава 7
- •154 Глава 7
- •156 Глава 7
- •162 Глава 8
- •164 Глава 8
- •166 Глава 8
- •168 Глава 8
- •170 Глава 9
- •172 Глава 9
- •174 Глава 9
- •176 Глава 9
- •9.1.3. Пример
- •178 Глава 9
- •180 Глава 9
- •182 Глава 9
- •184 Глава 9
- •186 Глава 9
- •188 Глава 9
- •190 Глава 9
- •192 Глава. 9
- •194 Глава 9
94 Глава 4
Абстракции данных
% %
Нет необходимости, чтобы представление упорядоченных списков было отсортировано. Однако эффективнее представление — это упорядоченное двоичное дерево. Каждый узел дерева содержит элемент и левое и правое поддеревья; каждое поддерево — это упорядоченный список. На языке CLU это выражается следующим образом.
node = record [val: t, right, left: olist [t]]
Как и для списков, пустой упорядоченный список рассматривается как специальный случай:
rep == variant [empty: null, some: node}
Это еще одно рекурсивное представление. Заметим, что оно использует изменяемые типы (variants и records), так как мы реализуем изменяемый тип. Теперь мы можем задать функцию абстракции
% Типичный упорядоченный список olist есть {е1, ..., еп] % Функция абстракции есть
А (г) = [ ] если r пуст = А (п. left) II [n.val] Ц А (п.right) если r не пуст,
где п = value, some (r)
Мы поддерживаем упорядочение представления, записывая все меньшие, чем значение узла, элементы в левое поддерево этого узла, а все большие — в правое поддерево. Таким образом, имеем
% Инвариант представления есть: % if is_some (r) then % все компоненты n.left < n.val % & n.val (всех компонентов n.right, % где п == value, some (r)
Если мы идентифицировали функцию абстракции и инвариант представления, реализации операций относительно определены (рис. 4.17). Реализации рекурсивны (итеративные реализации также возможны, но они более сложны).
olist = cluster [t: type] is create, addel, remel, is_in, least, empty where t has equal, It: proctype (t, t) returns (bool)
node = record [val: t, left, right; olist (t}] rep == variant [some; node, empty: null I
% Типичный упорядоченный список olist есть [el, ... en} % Функция абстракции есть
% A (r) = [] если r пуст % = A (n.left) [I [n. val ] [[ A (n.right) если r не пуст, % & где n = value_some (r) % Инвариант представления есть: % if is-some (r) then % все компоненты n.left (n.val % & n.val (всех компонентов n,right, % где n = value_some (r)
create = proc ( ) returns (cvt) return (rep$make_empty (nil)) end create
addel = proc (s: cvt, v: t) tagcase s tag some (n: node):
if v (n.val then addel (n.left, v) else addel (n.right, v) end tag empty:
rep$change_some (s, node$ {val: v, left: create (), right; create ()}) end end addel
remel = proc (s: cvt, v: t) tagcase s tag empty: return tag some (n: node):-if v == n.val then if empty (n.right) then % заменить узел на левое поддерево
rep$v .get. v (s, down (n.left))
else % сделать n.val значением из правого поддерева n.val := least (n.right) remel (n.right, n.val) end
elseit v (n.val then remel (n,left, else remel (n.right, v) end end end remel
least = proc (s: cvt) returns (t) tagcase s tag empty: tag some (n: node): if empty (n.left) then return (n.val) else return (least (n.left)) end end end least
lgJn = proc (s: cvt, v: t) returns (bool) tagcase s tag empty: return (false) tag some (n: node): if v = n.val then return (true)
elseif v (n.val then return (is_in (n.left, v)) else return (is-in (n.right, v)) end end end is_in
empty = proc (s: cvt) returns (bool) return (rep$is_empty (s)) end empty
end olist Рис. 4.17. Реализация упорядоченных списков.
9-5 Глава 4
В этой реализации скорость сортировки порядка loggn шагов для операций addel, remel, is-in и least при предположении, что дерево сбалансировано, т. е. что для каждого узла в правом и левом поддереве находится примерно одинаковое количество элементов. Если дерево несбалансировано, его можно сбалансировать, переставляя элементы случайным образом (используя благоприятный побочный эффект), однако здесь мы этого не делаем. Реализация не очень эффективна в смысле экономии пространства, так как требует хранить два указателя в каждом узле дерева.
Если бы наборы intset были реализованы с использованием упорядоченных списков, операция remove-dupis (см. ее реализацию на рис. 4.6) требовала бы порядка nioga п сравнений вслучае сбалансированного дерева. Один из случаев несбалансированного дерева — случай, в котором сортируется массив, являющийся аргументом операции remove-dupis!
4.9. Обсуждение
В этом разделе мы будем обсуждать аспекты абстракций данных, которые интересны как для того, кто определяет абстракции, так и для того, кто использует или реализует эти абстракции. Темы для обсуждения: изменяемость, полнота, анализ свойств абстрактных объектов и смысл операций equal, similar и copy.
4.9.1. Изменяемость
Абстракции данных либо изменяемы (с объектами, значения которых могут изменяться), либо неизменяемы. К определению этого аспекта типа следует относиться внимательно. В общем случае тип должен быть неизменяемым, если его объекты естественным образом имеют неизменяющиеся значения. Это, например, имеет место для таких математических объектов, как целые числа, полиномы и комплексные числа. Как правило, тип должен быть изменяемым, если моделируется что-то из реального мира, как, например, запоминающее устройство или автомобиль в имитационной системе, где значения объектов естественным образом изменяются с течением времени. Например, автомобиль может двигаться или стоять, в нем могут находиться или не находиться пассажиры. Однако мы можем все равно предпочесть использовать в этом случае неизменяемый тип, потому что он обеспечивает большую надежность. Вот почему списки неизменяемы.
Принимая решение об изменяемости, иногда необходимо учесть соотношение эффективности и надежности. Неизменяемые абстракции надежнее, чем изменяемые, так как не возникает никаких проблем при разделении объектов. Однако для неизменяемых
Абстракции данных
абстракций объекты могут создаваться и уничтожаться чаще, что означает, что чаще будет необходима сборка мусора. Например, представлять наборы intset как списки, вероятно, не слишком хорошо, если часто используются операции insert и delete.
Заметим, что в любом случае изменяемость или неизменяемость—это свойство типа, а не его реализации. Реализация должна просто поддерживать это свойство абстракции.
4.9.2. Классы операций
Операции абстракции данных распадаются на четыре класса. 1. Примитивные конструкторы. Эти операции создают объекты соответствующего им типа, не используя никаких объектов в качестве аргумента. Примерами таких операций являются операции create для наборов intset и списков.
2. Конструкторы. Эти операции используют в качестве аргументов объекты соответствующего им типа и создают другие объекты такого же типа. Например, операции add и mul — конструкторы для полиномов, а операции cons и rest — конструкторы для списков.
3. Модификаторы. Эти операции модифицируют объекты соответствующего им типа. Например, операции insert и delete — модификаторы для наборов intset. Очевидно, что только изменяемые типы могут иметь модификаторы.
4. Наблюдатели. Эти операции используют в качестве аргументов объекты соответствующего им типа и возвращают результат другого типа. Они используются для получения информации об объектах. Сюда относятся, например, операции size, member и chose для наборов intset и операции coeff, degree и equal для полиномов.
Обычно примитивные конструкторы создают не все, а только некоторые объекты. Например, операция poly$create создает только одночленные полиномы, а операция intset$create создает только пустой набор. Другие объекты создаются конструкторами или модификаторами. Например, операция poly$add может быть использована для получения полиномов, у которых количество членов больше одного, а операция intset$insert — для получения наборов с многими элементами.
Модификаторы играют ту же роль для изменяемых типов, что и конструкторы для неизменяемых. Изменяемый тип может иметь как конструкторы, так и модификаторы; например, наборы intset могут иметь операцию copy, которая создает новый набор, содержащий те же элементы, что и ее аргумент. Иногда наблюдатели комбинируются с конструкторами или модификаторами; например, операция remh модифицирует массив и возвращает? удаленный элемент.
4 Ласков Б,, Гатэг Дж.
