6.3. Множества.

  •  
      Пусть T - некоторый тип. Существует много способов хранить (конечные) множества элементов типа T; выбор между ними определяется типом T и набором требуемых операций.

      Подмножества множества {1..n}.

      6.3.1. Используя память, пропорциональную n, хранить подмножества множества {1..n}.
      Операции Число действий

      Сделать пустым C*n
      Проверить принадлежность C
      Добавить C
      Удалить С
      Минимальный элемент C*n
      Проверка пустоты C*n

      Решение. Храним множество как array [1..n] of boolean.

      6.3.2. То же, но проверка пустоты должна выполняться за время C.

      Решение. Храним дополнительно количество элементов.

      6.3.3. То же при следующих ограничениях на число действий:
      Операции Число действий

      Сделать пустым C*n
      Проверить принадлежность C
      Добавить C
      Удалить C*n
      Минимальный элемент C
      Проверка пустоты C

      Решение. Дополнительно храним минимальный элемент множества.

      6.3.4. То же при следующих ограничениях на число действий:
      Операции Число действий

      Сделать пустым С*n
      Проверить принадлежность С
      Добавить С*n
      Удалить С
      Минимальный элемент С
      Проверка пустоты C

      Решение. Храним минимальный, а для каждого - следующий и предыдущий по величине.

      Множества целых чисел.

      В следующих задачах величина элементов множества не ограничена, но их количество не превосходит n.

      6.3.5. Память C*n.
      Операции Число действий

      Сделать пустым C
      Число элементов C
      Проверить принадлежность C*n
      Добавить новый
      (заведомо отсутствующий) C
      Удалить C*n
      Минимальный элемент C*n
      Взять какой-то элемент C

      Решение. Множество представляем с помощью переменных a:array [1..n] of integer, k: 0..n; множество содержит k элементов a[1],...,a[k]; все они различны. По существу мы храним элементы множества в стеке (без повторений). С тем же успехом можно было бы воспользоваться очередью вместо стека.

      6.3.6. Память C*n.
      Операции Число действий

      Сделать пустым C
      Проверить пустоту C
      Проверить принадлежность C*(log n)
      Добавить С*n
      Удалить C*n
      Минимальный элемент С

      Решение. См. решение предыдущей задачи с дополнительным условием a[1] < ... < a[k]. При проверке принадлежности используем двоичный поиск.

      В следующей задаче полезно комбинировать разные способы.

      6.3.7. Используя описанные способы представления множеств, найти все вершины ориентированного графа, доступные из данной по ребрам. (Вершины считаем числами 1..n.) Время не больше C * (общее число ребер, выходящих из доступных вершин).

      Решение. (Другое решение смотри в главе о рекурсии.) Пусть num[i] - число ребер, выходящих из i, out[i][1], ..., out[i][num[i]] - вершины, куда ведут рёбра из вершины i.
      procedure Доступные (i: integer);
      | {напечатать все вершины, доступные из i, включая i}
      | var X: подмножество 1..n;
      | P: подмножество 1..n;
      | q, v, w: 1..n;
      | k: integer;
      begin
      | ...сделать X, P пустыми;
      | writeln (i);
      | ...добавить i к X, P;
      | {(1) P = множество напечатанных вершин; P содержит i;
      | (2) напечатаны только доступные из i вершины;
      | (3) X - подмножество P;
      | (4) все напечатанные вершины, из которых выходит
      | ребро в ненапечатанную вершину, принадлежат X}
      | while X непусто do begin
      | | ...взять какой-нибудь элемент X в v;
      | | for k := 1 to num [v] do begin
      | | | w := out [v][k];
      | | | if w не принадлежит P then begin
      | | | | writeln (w);
      | | | | добавить w в P;
      | | | | добавить w в X;
      | | | end;
      | | end;
      | end;
      end;

      Свойство (1) не нарушается, так как печать происходит одновременно с добавлением в P. Свойства (2): раз v было в X, то v доступно, поэтому w доступно. Свойство (3) очевидно. Свойство (4): мы удалили из X элемент v, но все вершины, куда из v идут ребра, перед этим напечатаны.

      Оценка времени работы. Заметим, что изъятые из X элементы больше туда не добавляются, так как они в момент изъятия (и, следовательно, всегда позже) принадлежат P, а добавляются только элементы не из P. Поэтому тело цикла while для каждой доступной вершины выполняется не более, чем по разу, при этом тело цикла for выполняется столько раз, сколько из вершины выходит ребер.

      Для X надо использовать представление со стеком или очередью (см. выше), для P - булевский массив.

      6.3.8. Решить предыдущую задачу, если требуется, чтобы доступные вершины печатались в таком порядке: сначала заданная вершина, потом ее соседи, потом соседи соседей (еще не напечатанные) и т.д.

      Указание. Так получится, если использовать очередь для хранения множества X в приведенном выше решении: докажите индукцией по k, что существует момент, в который напечатаны все вершины на расстоянии не больше k, а в очереди находятся все вершины, удаленные ровно на k.

      Более сложные способы представления множеств будут разобраны в главах 11 (Хеширование) и 12 (Деревья).

Другие записи

10.06.2016. 6.1. Стеки.
Пусть T - некоторый тип. Рассмотрим (отсутствующий в паскале) тип "стек элементов типа T". Его значениями являются последовательности значений типа T. Операции: Сделать_пустым (var s: стек элементов…
10.06.2016. 6.4. Разные задачи.
  6.4.1. Реализовать структуру данных, которая имеет все те же операции, что массив длины n, а именно начать работу положить в i-ю ячейку число x узнать, что лежит в i-ой ячейке а…