Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
прога_билеты.docx
Скачиваний:
2
Добавлен:
01.03.2025
Размер:
2.98 Mб
Скачать

Ассоциативный массив. Интерфейс, варианты реализации.

Ассоциативный массив (словарь) — абстрактный тип данных (интерфейс к хранилищу данных), позволяющий хранить пары вида «(ключ, значение)» и поддерживающий операции добавления пары, а также поиска и удаления пары по ключу.

Реализация:

Существует множество различных реализаций ассоциативного массива.

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

Наиболее популярны реализации, основанные на различных деревьях поиска. Так, например, в стандартной библиотеке STL языка С++ контейнер map реализован на основе красно-чёрного дерева.

У каждой реализации есть свои достоинства и недостатки. Важно, чтобы все три операции выполнялись как в среднем, так и в худшем случае за время  , где   — текущее количество хранимых пар. Для сбалансированных деревьев поиска (в том числе для красно-чёрных деревьев) это условие выполнено.

В реализациях, основанных на хэш-таблицах, среднее время оценивается как  , что лучше, чем в реализациях, основанных на деревьях поиска. Но при этом не гарантируется высокая скорость выполнения отдельной операции: время операции INSERT в худшем случае оценивается как  . Операция INSERT выполняется долго, когда коэффициент заполнения становится высоким и необходимо перестроить индекс хэш-таблицы.

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

Интерфейс:

template<Key, Value>

class IMap {

public:

virtual ~IMap() =0; //virtual destructor

virtual bool Insert( Key, Value ) =0; //returns true if a new element was inserted or false if an element with the same key existed (new element wasn't inserted)

virtual bool Exist( Key ) const =0; //returns true if element with specified key exists

virtual void Delete( Key ) =0;

virtual void DeleteAll() =0;

virtual int Size() const =0; //returns number of elements.

virtual const Value* Find( Key ) const =0; //returns 0 if there is no element with specified key in container or pointer to element otherwise

virtual Value* Find( Key ) =0;

virtual const Value& operator[]( Key ) const =0; //returns reference to element that was in container in this moment or throw exception otherwise

virtual Value& operator[]( Key ) =0; //returns reference to element that was in container in this moment or newly created element with specified key otherwise

};

Хеш-табли́ца (unordered map) — это структура данных, реализующая интерфейс ассоциативного массива, а именно, она позволяет хранить пары (ключ, значение) и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу.

Хеш-функцией называется односторонняя функция, предназначенная для получения дайджеста или "отпечатков пальцев" файла, сообщения или некоторого блока данных. По хеш-функции вычисляем значение, куда вставить/откуда удалить элемент из хеш-таблицы.

Хеш-таблица – вектор из пары ключ-значение, или из списков, или что-то еще

A=N/M – коэффициент заполнения. Где M – размер таблицы, а N – количество элементов в ней.

Если А>1 то одному значению из 0…M соответствует несколько ключей – коллизия (увеличиваем M вдвое и создаем новую хеш-таблицу. Минус – придется пересчитать все значения. Для этого существуют способы разрешения коллизий). Можно хранить в виде списков (Vector <list<par<Key,Value>>>)

Хеш-функция всегда дает один и тот же ответ!

Хеш-функция должна удовлетворять целому ряду условий:

  • Хеш-функция должна быть чувствительна к всевозможным изменениям в тексте M, таким как вставки, выбросы, перестановки и т.п.;

  • На одном входе один и тот же результат;

  • Равномерное распределение по хеш-таблицу

  • Хеш-функция должна обладать свойством необратимости, то есть задача подбора документа M', который обладал бы требуемым значением хеш-функции, должна быть вычислительно неразрешима;

  • Вероятность того, что значения хеш-функций двух различных документов (вне зависимости от их длин) совпадут, должна быть ничтожно мала.

Существует множество алгоритмов хеширования с различными характеристиками (разрядность, вычислительная сложность, криптостойкость и т. п.).

Выбор той или иной хеш-функции определяется спецификой решаемой задачи.

Пример:

class CHash {

public:

int Max;

CHash( int _max = 10 ): Max( _max ) {};

int Hash( int );

int Hash( double );

int Hash( CString );

};

int CHash::Hash( int n ) {

return n % (Max-1); // -1, т.к. если M – степень двойки, то будут одинаковые ключи

}

int CHash::Hash( double n ) {

if( n < 1 && n > 0 ) return int( n * Max ); else return int( n ) % Max;

}

int CHash::Hash( CString str ) {

int result = 0, i = 0;

int k = ( str.Length() - 1 ) % Max;

while( i < k ) {

result += ( int )str[i];

i++;

}

return result % Max; // или например цикл от первой буквы до последней h=((a^h)*’V’)%M, где V –i-ая буква, h увеличиваем с 0 в каждой итерации на 1, а а – простое число. Тогда “abc” при a=256: ‘a’*256^2+’b’*256+’c’

}

Хорошо брать простое M, ибо тогда уменьшаем вероятность одинаковых значений разных хеш-функций

Разрешение коллизий

Метод цепочек - Каждая ячейка массива H является указателем на связный список (цепочку) пар ключ-значение, соответствующих одному и тому же хеш-значению ключа. Коллизии просто приводят к тому, что появляются цепочки длиной более одного элемента.

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

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

При предположении, что каждый элемент может попасть в любую позицию таблицы H с равной вероятностью и независимо от того, куда попал любой другой элемент, среднее время работы операции поиска элемента составляетΘ(1 + α), где α — коэффициент заполнения таблицы.

Открытая адресация (зондирование)

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

Последовательность, в которой просматриваются ячейки хеш-таблицы, называется последовательностью проб. В общем случае, она зависит только от ключа элемента, то есть это последовательность h0(x), h1(x), …, hn — 1(x), где x — ключ элемента, а hi(x) — произвольные функции, сопоставляющие каждому ключу ячейку в хеш-таблице.

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

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

Линейное зондирование - Алгоритм поиска просматривает ячейки хеш-таблицы в том же самом порядке, что и при вставке, до тех пор, пока не найдется либо элемент с искомым ключом, либо свободная ячейка (что означает отсутствие элемента в хеш-таблице).

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

Можно применить двойное зондирование – в первый раз считаем по h1(x), если коллизия, то по h2(x). Так мы избегаем многочисленных переходов на следующую пару, но проигрываем в памяти.

Универсальное хеширование – то же самое, но в каждой ячейке своя хеш-таблица со своей хеш-функцией.

Если хеш-таблица заполнена – удваиваем M и создаем новую.

Алгоритм кукушки

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

Опасность – можем зациклиться (если попали в ячейку, откуда началось), тогда меняем хеш-функции. Минус – увеличиваем память в 2 раза.

Красно-черные деревья. Основные операции.

Красно-чёрное дерево (Red-Black-Tree, RB-Tree) — это одно из самобалансирующихся двоичных деревьев поиска, гарантирующих логарифмический рост высоты дерева от числа узлов и быстро выполняющее основные операции дерева поиска: добавление, удаление и поиск узла.

Сбалансированность достигается за счёт введения дополнительного атрибута узла дерева — «цвета». Этот атрибут может принимать одно из двух возможных значений — «чёрный» или «красный».

Сбалансированность дерева – высоты вершины и потомка отличаются не более чем в 2 раза. Избегаем вырождения в цепочку.

Свойства:

  1. Узел либо красный, либо чёрный.

  2. Корень — чёрный. (В других определениях это правило иногда опускается. Это правило слабо влияет на анализ, так как корень всегда может быть изменен с красного на чёрный, но не обязательно наоборот).

  3. Все листья — черные.

  4. Оба потомка каждого красного узла — черные.

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

Bh(x) Черная высота – количество вершин от любого листа к зафиксированной вершине.

Реализации в языке – unordered_map, set, map