Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 2178 / 0 | Длительность: 63:16:00
Лекция 4:

Абстрактные типы данных

Повторяющиеся и индексные элементы

Во многих приложениях обрабатываемые абстрактные элементы должны быть уникальными. Это качество подводит нас к мысли пересмотреть представления о том, как должны функционировать стеки, очереди FIFO и другие обобщенные АТД. В частности, в данном разделе рассматриваются такие изменения спецификаций стеков, очередей FIFO и обобщенных очередей, которые запрещают в этих структурах данных наличие повторяющихся элементов.

Например, компании, ведущей список рассылки по адресам покупателей, может потребоваться расширить этот список информацией из других списков, собранных из различных источников. Это выполняется с помощью соответствующих операций вставить. При этом не требуется заносить информацию о покупателях, адреса которых уже присутствуют в списке. Далее мы увидим, что этот же принцип применим в самых разнообразных приложениях. В качестве еще одного примера рассмотрим задачу маршрутизации сообщений в сложной сети передачи данных. Можно передавать сообщение одновременно по нескольким маршрутам ("наперегонки"), однако во внутренних структурах данных каждого отдельно взятого узла сети должна находиться не более чем одна копия этого сообщения.

Один из подходов к решению данной проблемы - поручить программам-клиентам следить за тем, чтобы в АТД не было повторяющихся элементов. Наверно, программы-клиенты могли бы выполнять эту задачу, используя какой-нибудь другой АТД. Но поскольку цель создания любого АТД - обеспечить клиенты четкими решениями прикладных задач, то похоже, что обнаружение и устранение дубликатов - это часть задачи, относящаяся к компетенции АТД.

Запрет присутствия повторяющихся элементов приводит к изменению абстракции: интерфейс, имена операций и прочее для такого АТД будут такими же, как и для соответствующего АТД без такого запрета, но поведение реализации изменится кардинально. Вообще говоря, при изменении спецификации какого-нибудь АТД получается совершенно новый АТД - АТД, обладающий совсем другими свойствами. Эта ситуация демонстрирует также ненадежную природу спецификации АТД: обеспечить, чтобы клиентские программы и реализации придерживались спецификации интерфейса, довольно трудно, однако обеспечить применение такого высокоуровневого принципа - совсем другое дело. Тем не менее, в подобных алгоритмах есть необходимость, поскольку клиенты могут использовать такие свойства для решения задач новыми способами, а реализации могут использовать преимущества подобных ограничений для обеспечения более эффективных решений.

На рис. 4.9 проиллюстрирована работа модифицированного АТД стека без дубликатов для случая, показанного на рис. 4.1; на рис. 4.10 приведен результат аналогичных изменений для очереди FIFO.

 Стек магазинного типа без дубликатов

Рис. 4.9. Стек магазинного типа без дубликатов

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

 Очередь FIFO без дубликатов, с правилом игнорирования нового элемента

Рис. 4.10. Очередь FIFO без дубликатов, с правилом игнорирования нового элемента

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

В общем случае нужно принять решение о том, что делать, когда клиент выдает запрос на занесение элемента, уже имеющегося в структуре данных. Продолжать работу так, как будто запроса вообще не было? Или удалить старый элемент и занести новый? Это решение влияет на порядок, в котором, в конечном счете, будут обрабатываться элементы в АТД наподобие стеков и очередей FIFO (см. рис. 4.11 рис. 4.11); такое различие может оказаться очень существенным для клиентских программ. Например, компания, использующая подобный АТД для списка рассылки, скорее предпочтет замену старого элемента новым (поскольку он, возможно, содержит более свежую информацию о клиенте); а коммутационный центр, использующий такой АТД, наверно, проигнорирует новый элемент (поскольку действия по отправке этого сообщения, видимо, уже выполнены). Более того, выбор конкретного принципа влияет на реализации: как правило, принцип "удалить старый элемент" более труден в реализации, нежели принцип "игнорировать новый элемент", поскольку связан с модификацией структуры данных.


Рис. 4.11.

Очередь FIFO без дубликатов, с правилом удаления старого элемента Здесь показан результат выполнения тех же операций, что и на рис. 4.10. Однако тут используется другой, более сложный дляреа-лизации принцип: новый элемент всегда добавляется в конец очереди. Если в очереди уже имеется такой же элемент, он удаляется.

Для реализации обобщенных очередей без дубликатов необходима абстрактная операция для проверки элементов на равенство (как было сказано в разделе 4.1). Помимо такой операции необходимо иметь возможность определять, существует ли уже в структуре данных новый элемент. Этот общий случай предполагает необходимость реализации АТД таблицы символов, поэтому он рассматривается в контексте реализаций, приведенных в лекциях 12 - 15.

Имеется важный частный случай, для которого существует простое решение, приведенное в программе 4.16 для АТД стека магазинного типа. В этой реализации предполагается, что элементы являются целыми числами в диапазоне от 0 до М - 1. Для определения, имеется ли уже в стеке некоторый элемент, в реализации используется второй массив, индексами которого являются сами элементы стека. Когда в стек заносится элемент i, в i-й элемент второго массива заносится 1, а при удалении из стека элемента i туда заносится 0. Во всем остальном для вставки и удаления элементов применяется тот же код, но перед вставкой элемента выполняется проверка, нет ли в стеке такого элемента. Если есть, операция занесения элемента игнорируется. Это решение не зависит от используемого представления стека - на базе массива, связного списка или чего-нибудь еще. Реализация принципа "удалять старый элемент" требует большего объема работы (см. упражнение 4.57).

Итак, один из способов реализации стека без повторяющихся элементов, функционирующего по принципу "игнорировать новый элемент", состоит в использовании двух структур данных. Первая, как и прежде, содержит элементы стека и позволяет отслеживать порядок, в котором были вставлены элементы стека. Вторая структура является массивом, позволяющим определять, какие элементы находятся в стеке; индексами этого массива являются элементы стека. Такое использование массива является частным случаем реализации таблицы символов, которая будет рассмотрена в разделе 12.2 "Таблицы символов и деревья бинарного поиска" . Если известно, что элементы представляют собой целые числа в диапазоне от 0 до М - 1, эту технику можно применять по отношению к любой обобщенной очереди.

Этот частный случай встречается довольно часто. Его наиболее важный пример - когда элементы структуры данных сами являются индексами массива, поэтому такие элементы называются индексными элементами (index item). Обычно имеется набор из М объектов, хранимых в каком-то другом массиве, и в каком-то более сложном алгоритме необходимо передавать эти объекты через структуру обобщенной очереди. Объекты заносятся в очередь по индексам и обрабатываются при удалении, причем каждый объект должен обрабатываться только один раз. Очередь без дубликатов, в которой используются индексы массива, позволяет непосредственно достичь этой цели.

Каждый из таких вариантов (запрещать или нет повторяющиеся элементы; использовать или нет новый элемент) приводит к новому АТД. Различия могут показаться незначительными, однако они заметно влияют на динамическую характеристику АТД с точки зрения клиентских программ, а также на выбор алгоритма и структуры данных для реализации различных операций. Поэтому приходится считать все эти АТД разными. Бывает необходимо учитывать и другие варианты: например, может понадобиться изменить интерфейс, чтобы сообщать клиентской программе о том, что она пытается вставить дубликат уже имеющегося элемента, либо предоставить клиенту возможность выбора: игнорировать новый элемент или удалять старый.

Программа 4.16. Стек индексных элементов без дубликатов

В этой реализации стека магазинного типа предполагается, что класс Item приводим к типу int, который возвращает целые числа в диапазоне от 0 до maxN-1. Поэтому в нем используется массив t, где каждому элементу стека соответствует отличное от нуля значение. Этот массив дает возможность функции push быстро проверять наличие в стеке ее аргумента, и не выполнять занесение, если он там есть. Для каждого элемента массива t достаточно одного бита, поэтому при желании можно сэкономить память, используя вместо целых чисел символы или биты (см. упр. 12.12).

template <class Item>
class STACK
  {
    private:
      Item *s, *t; int N;
    public:
      STACK(int maxN)
        {
s = new Item[maxN]; N = 0;
t = new Item[maxN];
for (int i = 0; i < maxN; i++) t[i] = 0;
        }
        int empty() const
{ return N == 0; }
        void push(Item item)
{
  if (t[item] == 1) return;
  s[N++] = item; t[item] = 1;
}
Item pop()
  { t[s[--N]] = 0; return s[N]; }
  };
        

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

Упражнения

  • 4.53. Нарисуйте рисунок, аналогичный рис. 4.9, для стека без дубликатов, работающего по правилу "удалять старый элемент".
  • 4.54. Измените стандартную реализацию стека на базе массива из раздела 4.4 (программа 4.7), чтобы в ней были запрещены дубликаты по правилу "игнорировать новый элемент". Используйте простейший метод, выполняющий просмотр всего стека.
  • 4.55. Измените стандартную реализацию стека на базе массива из раздела 4.4 (программа 4.7), чтобы в ней были запрещены дубликаты по правилу "удалять старый элемент". Используйте простейший метод, выполняющий просмотр всего стека и перемещение его элементов.
  • 4.56. Выполните упражнения 4.54 и 4.55 для реализации стека на базе связного списка из раздела 4.4 (программа 4.8).
  • 4.57. Разработайте реализацию стека магазинного типа с запретом дубликатов по правилу "удалять старый элемент". Элементами стека являются целые числа в диапазоне от 0 до М - 1, а операции втолкнуть и вытолкнуть должны иметь постоянное время выполнения. Подсказка: возьмите представление стека на базе двухсвязного списка и храните в массиве индексных значений указатели на узлы этого списка, а не значения 0-1.
  • 4.58. Выполните упражнения 4.54 и 4.55 для очереди FIFO.
  • 4.59. Выполните упражнение 4.56 для очереди FIFO.
  • 4.60. Выполните упражнение 4.57 для очереди FIFO.
  • 4.61. Выполните упражнения 4.54 и 4.55 для рандомизированной очереди.
  • 4.62. Напишите клиентскую программу для АТД, полученного в упражнении 4.61, в которой используется рандомизированная очередь без повторяющихся элементов.
Бактыгуль Асаинова
Бактыгуль Асаинова

Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат?

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?