
- •6. Абстракции данных
- •6.1 Представление рациональных чисел
- •6.1.1 Первое внутреннее представление рациональных чисел
- •6.1.2 Второе внутреннее представление рациональных чисел
- •6.1.3 Функциональный тип данных
- •6.1.4 Третье внутреннее представление рациональных чисел
- •6.2 Представления множеств
- •6.2.1 Функции над множествами. Определения
- •6.2.2 Представление множеств списками
- •6.2.2.1 Неупорядоченные множества
- •6.2.2.2 Упорядоченные множества
- •6.2.3 Представление множеств двоичными деревьями
- •6.2.4 Представление древовидных структур
6.2 Представления множеств
6.2.1 Функции над множествами. Определения
Можно определить тип «множество», описав операции, которые должны использоваться на множествах. По-крайней мере, должны быть реализованы следующие операции.
-
Название функции
Действие функции
(empty_s)
Создает пустое множество.
(singleton x)
Задает множество, состоящее из единственного элемента x.
(in? x s)
Выясняет, является ли заданный объект x элементом множества.
(join s x)
Создает множество, которое содержит все элементы множества s и добавляемый элемент x.
(union s1 s2)
Выполняет объединение двух множеств.
(intersect s1 s2)
Создает пересечение двух множеств.
Мы свободны в выборе любого представления, осуществляющие эти операции, но обязаны формально определить те правила, которым все они должны удовлетворять.
Выбор представления для множеств не столь прост, как для рациональных чисел.
Правда, сразу напрашивается способ представления множеств в виде ЛИСП’овских списков или в виде деревьев, имея в виду те же списки, но с обязательными полями для ссылок.
6.2.2 Представление множеств списками
Традиционный способ - представлять множество списком его элементов.
Но дополнительно, мы ещё должны решить, будет ли это список упорядочен или нет, а также допускаются ли повторные вхождения элементов.
6.2.2.1 Неупорядоченные множества
Рассмотрим сначала случай неупорядоченного списка без повторных вхождений.
Пустое множество представлено пустым списком.
> (define (empty_s ) (list))
Одноэлементное множество - списком из одного элемента.
> (define (singleton x) (list x))
Теперь зададим список для иллюстрации работы создаваемых функций.
> (define unordset '(2 5 1 8 7 0 3))
Для проверки принадлежности объекта множеству необходимо сравнить его со всеми элементами списка.
> (define (in? x s)
(and (not (null? s))
(or (eq? x (car s))
(in? x (cdr s)))))
> (in? 4 unordset)
#f
> (in? 1 unordset)
#t
Использование in?, позволяет определить присоединение объекта к множеству.
Если объект, который нужно добавить, уже принадлежит множеству, нужно просто возвратить множество. Иначе, добавить объект к списку, который представляет множество:
> (define (join s x)
(if (in? x s) s (cons x s)))
Заметим, ради точности, что добавление происходи спереди списка.
> (join unordset 4)
(4 2 5 1 8 7 0 3)
> (join unordset 8)
(2 5 1 8 7 0 3)
Объединение множеств получается последовательным присоединением элементов одного множества к другому.
> (define (union s1 s2)
(if (null? s2)
s1
(union (join s1 (car s2)) (cdr s2))))
> (union '(45 23 97 12) unordset)
(3 0 7 8 1 5 2 45 23 97 12)
Видно, что первое множество идёт в обратном порядке.
> (union unordset '(45 23 97 12))
(12 97 23 45 2 5 1 8 7 0 3)
Теперь – второе множество в обратном порядке, но с точки зрения неупорядоченных множеств – оба результата одинаковы.
Для вычисления пересечения множеств можно использовать рекурсивную стратегию. Если мы знаем, как найти пересечение (cdr s1) и s2, то нужно только решить, включать ли в него (car s1), что зависит от того, является ли (car s1) элементом s2.
> (define (intersect s1 s2)
(cond ((or (null? s1)
(null? s2)) '() )
((in? (car s1) s2) (cons (car s1)
(intersect (cdr s1) s2)))
(else (intersect (cdr s1) s2))))
intersect
union
join
in?
>
(intersect unordset '(1 9 2))
(2 1)
> (intersect '(1 9 2) unordset)
(
1
2)
> (intersect '() unordset)
()
Выбирая представление, мы часто руководствуемся соображениями эффективности. Попробуем оценить эффективность наших операций.
Так как все они используют in?, скорость этой функции и определяет эффективность реализации множеств в целом. Чтобы проверить, является ли объект элементом множества, вероятно, придется просматривать весь список представляющий множество. Следовательно, сложность этой процедуры растет как O(n).
Мы можем ускорить операцию join (а следовательно и union), если не будем требовать уникальности вхождения в список каждого элемента множества, т.е. определим её так:
(define (join s x) (cons x s))