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

Таблицы символов и деревья бинарного поиска

Распределяющий поиск

Предположим, что значения ключей представляют собой различные небольшие числа, как, например, в программе 12.4. В этом случае простейший алгоритм поиска основывается на хранении элементов в массиве, индексированном значениями ключей - так сделано в реализации, приведенной в программе 12.4. Ее код весьма прост: оператор new[] заносит во все элементы значение nullItem, затем можно вставить элемент со значением ключа k, просто записав его в st[k], и найти элемент со значением ключа k, выбрав его из st[k]. Чтобы удалить элемент со значением ключа k, в st[k] записывается значение nullItem. Реализации операций выбрать, сортировать и подсчитать в программе 12.4 используют линейный просмотр массива с пропуском пустых элементов. Данная реализация оставляет клиенту решение задачи обработки элементов с повторяющимися ключами и проверку таких условий, как выполнение операции удалить для ключа, отсутствующего в таблице. Эта реализация служит отправной точкой для всех реализаций таблиц символов, которые рассматриваются в этой главе и лекциях 13-15.

Программа 12.4. Таблица символов, основанная на индексируемом значениями ключей массиве

В данной реализации предполагается, что значения ключей - положительные целые числа, меньшие сигнального значения M - используются в качестве индексов массива. Конструктор Item создает элементы со значениями ключей, равными сигнальному значению, чтобы конструктор ST мог найти в пустом элементе значение M. Основные затраты этого метода - объем памяти, необходимый при большом размере сигнального значения, и время, необходимое конструктору ST, когда значение N мало по сравнению с M.

template <class Item, class Key>
class ST
  { private:
      Item nullItem, *st;
      int M;
    public:
      ST(int maxN)
        { M = nullItem.key(); st = new Item[M]; }
      int count()
        { int N = 0; 
for (int i = 0; i < M; i++)
  if (!st[i].null()) N+ + ;
return N;
        }
      void insert(Item x)
        { st[x.key()] = x; }
      Item search(Key v)
        { return st[v]; }
      void remove(Item x)
        { st[x.key()] = nullItem; }
      Item select(int k)
        { for (int i = 0; i < M; i++)
  if (!st[i].null())
    if (k- == 0) return st[i];
return nullItem;
        }
      void show(ostream& os)
        { for (int i = 0; i < M; i++)
if (!st[i].null()) st[i].show(os); }
  };
      

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

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

Если элементы вообще отсутствуют (имеются только ключи), можно использовать битовую таблицу. В этом случае таблица символов называется таблицей существования (existence table), поскольку ее к-й разряд можно рассматривать как признак существования значения к в множестве ключей таблицы. Например, используя на 32-разрядном компьютере таблицу из 313 слов, этот метод позволяет быстро выяснить, используется ли уже конкретный 4-значный номер телефонного коммутатора (см. упражнение 12.14).

Лемма 12.1. Если значения ключей - положительные целые числа, меньшие M, и элементы имеют различные ключи, то тип данных таблицы символов может быть реализован с помощью индексированных значениями ключей массивов так, что для выполнения операций вставить, найти и удалить потребуется постоянное время; а время выполнения операций инициализировать, выбрать и сортировать будет пропорционально M - для любой из операций в таблице, содержащей N элементов.

Это свойство очевидно после ознакомления с кодом. Обратите внимание, что ключи должны удовлетворять условию N < M. $\blacksquare$

Программа 12.4 не обрабатывает повторяющиеся ключи, и в ней предполагается, что значения ключей лежат в пределах между 0 и M-1. Для хранения элементов с одинаковыми ключами можно использовать связные списки или один из подходов, перечисленных в разделе 12.1, а перед использованием ключей в качестве индексов можно выполнять их простые преобразования (см. упражнение 12.13). Но мы отложим подробное рассмотрение таких случаев до "Хеширование" , посвященной хешированию, где для реализации таблиц символов для любых ключей используется этот подход - преобразование ключей из потенциально широкого диапазона в узкий и выполнение специальной обработки элементов с повторяющимися ключами. Пока будем считать, что старый элемент с ключом, равным ключу вставляемого элемента, может быть либо молча проигнорирован (как в программе 12.4), либо считаться ошибкой (см. упражнение 12.10).

Реализация операции подсчитать в программе 12.4 - пример " ленивого " подхода, когда действия выполняются только при вызове функции count. Альтернативный ( " энергичный " ) подход заключается в использовании локальной переменной для счетчика непустых позиций таблицы с увеличением значения этой переменной при вставке в позицию таблицы, содержащую nullItem, и с уменьшением счетчика при удалении из позиции таблицы, не содержащей nullItem (см. упражнение 12.11). " Ленивый " подход предпочтительнее, если операция подсчитать используется редко (или вообще не используется), а количество возможных значений ключей мало; в остальных случаях предпочтительнее " энергичный " подход. Для подпрограммы библиотеки общего назначения лучше использовать " энергичный " подход, поскольку он обеспечивает оптимальную производительность в худшем случае при небольшом постоянном коэффициенте увеличения затрат на выполнение операций вставить и удалить. Для внутреннего цикла в приложении с очень большим количеством операций вставить и удалить, но незначительным количеством операций подсчитать " ленивый " подход удобнее, поскольку обеспечивает наиболее быструю реализацию часто выполняемых операций. Как мы уже неоднократно убеждались, подобная дилемма типична для разработки АТД, которые должны поддерживать различные наборы операций.

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

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

Упражнения

12.10. Реализуйте АТД таблицы символов первого класса (см. упражнение 12.6), используя динамически размещаемые массивы с индексированием по ключам.

12.11. Измените реализацию в программе 12.4, чтобы обеспечить " энергичную " реализацию функции count (с помощью отслеживания количества непустых записей).

12.12. Измените реализацию из упражнения 12.10, чтобы обеспечить " энергичную " реализацию функции count (см. упражнение 12.11).

12.13. Разработайте версию программы 12.4, в которой используется функция h(Key), преобразующая ключи в неотрицательные целые числа, меньшие M - так, чтобы никакие два ключа не отображались одним и тем же целым числом. (Это усовершенствование делает реализацию полезной, если ключи лежат в узком диапазоне (не обязательно начинающемся с 0) и в других простых случаях.)

12.14. Разработайте версию программы 12.4 для случая, если элементы представляют собой ключи, являющиеся положительными целыми числами, меньшими M (без какой-либо связанной информации). В этой реализации используйте динамически размещаемый массив, состоящий приблизительно из M/bitword слов, где bitword - количество битов в одном слове в используемой компьютерной системе.

12.15. Используйте реализацию из упражнения 12.14 для экспериментального определения среднего значения и среднеквадратичного отклонения количества различных целых чисел в случайной последовательности N неотрицательных целых чисел, меньших N, для N, близкого к объему памяти, который доступен программе в используемом компьютере, и выраженному в количестве битов (см. программу 12.3)

Бактыгуль Асаинова
Бактыгуль Асаинова

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

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

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

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

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