- •СТРУКТУРЫ ДАННЫХ И АЛГОРИТМЫ
- •Глава 1 Линейные списки
- •Понятие структуры данных
- •Тип указатель
- •Линейный список
- •Линейный однонаправленный (односвязный) список
- •Линейный двунаправленный (двусвязный) список
- •Практическое задание «Линейные списки»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 2 Структуры данных стек и очередь
- •Понятие структуры данных стек и очередь
- •Структура данных стек
- •Структура данных очередь
- •Пример решения задачи с использованием стека
- •Практическое задание «Стеки и очереди»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 3 Древовидные структуры данных
- •Понятие структуры данных дерево
- •Представление структуры бинарное дерево в памяти
- •Обход дерева
- •Основные операции над деревом
- •Ввод дерева
- •Практическое задание «Бинарные деревья»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 4 Структура данных таблица
- •Понятие структуры данных таблица
- •Представление таблицы в памяти
- •Организация хеш-таблиц
- •Выбор функции расстановки
- •Разрешение коллизии
- •Операции над таблицами
- •Практическое задание «Управление таблицами»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 5 Алгоритмы упорядочения данных
- •Понятие операции упорядочения
- •Алгоритмы упорядочения
- •Сортировка слиянием
- •Основные алгоритмы сортировки посредством сравнений
- •Улучшенные алгоритмы сортировки сравнением
- •Практическое задание «Алгоритмы сортировки»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Библиографический список
Разрешение коллизии
Разрешать коллизии можно в исходной или в дополнительной таблице. Если коллизия разрешается в исходной таблице, то такая таблица называется таблицей с перемешиванием или с открытой адресацией. В таблицах с перемешиванием применяют линейное (метод последовательных испытаний или проб), случайное или квадратичное рехеширование. Наиболее простой метод – линейное рехеширование, который заключается в том, что функция вторичной расстановки f определяется как
f (ключ, i) = (f(ключ) + i ) mod N,
здесь значение i равно 1, 2, ... , а f(ключ) – это значение, дающее коллизию.
пусто |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
элемент с |
|
|
элемент с |
NULL |
|
|
|
|
|
ключом С1 |
|
|
ключом С4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
пусто |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
||
|
|
элемент с |
|
|
элемент с |
|
|
элемент с |
NULL |
|
|
ключом С1 |
|
|
ключом С6 |
|
|
ключом С8 |
|
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
элемент с |
|
|
элемент с |
NULL |
|
|
|
|
|
ключом С1 |
|
|
ключом С6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
пусто |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 4.3.
93
Рехеширование завершается, когда очередная позиция массива с номером f (ключ, i) окажется пустой или равной снова своему начальному значению (в этом случае таблица полна). Линейное рехеширование применяют, если таблица никогда не бывает полностью заполненной (лучше, если она заполнена не более чем на 70%). В случае разрешения коллизии в дополнительной таблице удобно использовать хеширование с цепочками. В таких таблицах элементы, которым соответствует одно и то же хеш-значение, связываются в цепочку – список, а в самом элементе таблицы хранится ссылка на список (см. рис. 4.3).
Операции над таблицами
Основные операции при работе с таблицами:
а) поиск в таблице элемента с заданным ключом. Поиск элемента в таблице заключается в просмотре элементов до тех пор, пока нельзя сделать вывод: есть элемент в таблице или его нет. В неупорядоченной таблице последовательный (линейный) поиск элемента завершается, когда элемент найден или просмотрены все элементы. В последнем случае делаем вывод, что элемента в таблице нет.
Вупорядоченной таблице линейный поиск завершается, когда элемент найден или, достигнут элемент, ключ которого позволяет сделать вывод, что элемента в таблице нет, или просмотрены все элементы.
Различные способы организации таблиц принято сравнивать по числу выполнения в них основных операций и прежде всего по числу операций сравнения. Линейный поиск элемента в таблице в среднем будет выполнен за n / 2 сравнений, максимальное число сравнений n. Общая оценка сложности алгоритма линейного поиска элемента в таб-
лице O(n).
Вупорядоченной таблице кроме последовательного просмотра можно применять бинарный поиск (поиск делением пополам), максимальное число сравнений при котором 1 + log2 n. Общая оценка сложности алгоритма бинарного поиска элемента в таблице O(log n).
При бинарном поиске пространство поиска делится примерно на две равные части. Ключ серединного элемента сравнивается с заданным значением ключа и для дальнейшего поиска выбирается та половинка таблицы, где возможно находится искомый элемент. Деление
94
пополам продолжается, пока пространство поиска не сузится до одного элемента. Если ключ этого элемента совпадает с искомым, значит, поиск успешен, иначе делаем вывод, что элемента в таблице нет.
При поиске элемента в хеш-таблице вычисляется значение функции расстановки. Если позиция с вычисленным по хеш-функции номером «пуста», то элемента в таблице нет. При непустой позиции, если ключ элемента совпадает с искомым ключом, элемент найден, иначе в таблицах с открытой адресацией необходимо, выполняя рехеширование, продолжать поиск. Для продолжения поиска используется та же функция вторичной расстановки, что и при построении хеш-таблицы. Если допускается операция удаления элемента из хеш-таблицы, то в таблице с перемешиванием надо различать еще одно состояние: элемент удален из таблицы. В хеш-таблицах с цепочками при непустой позиции просматриваем список до тех пор, пока элемент не найден и не просмотрен весь список. Если список просмотрен полностью, то делаем вывод, что элемента в таблице нет.
Время поиска в хеш-таблице без коллизий (таблица с прямой адресацией) постоянно (равно 1) – это время вычисления функции расстановки. Время поиска в таблицах с перемешиванием для метода линейного рехеширования зависит от коэффициента загрузки таблицы k = n / N, среднее число сравнений в этом случае e = (1 - k / 2)(1 - k). В хеш-таблице с цепочками время поиска зависит от длины списка.
б) включение элемента в таблицу. Включение элемента в неупорядоченную таблицу заключается в добавлении элемента в таблицу (если n < N) на очередное свободное место – в конец таблицы. Включению элемента в упорядоченную таблицу предшествует поиск места k для нового элемента с учетом отношения порядка. Затем освобождение этого места (если оно занято) смещением всех последующих элементов с k-го по последний элемент вниз на одну позицию. Таким образом, включение в упорядоченную таблицу требует больше времени, чем в неупорядоченную таблицу.
При включении элемента в хеш-таблицу вычисляется значение функции расстановки. Если позиция с вычисленным по хеш-функции номером «пуста», то элемент помещается в данную позицию, иначе необходимо разрешать коллизию, используя функцию вторичной расстановки.
95
Алгоритм включения элемента в таблицу с открытой адресацией:
i = 0; b = 1;
j = f(ключi); do
{
if (T[j] == "пусто")
{
T[j] = элемент; b = 0;
}
else
{
i = i + 1;
j= (j + i) mod N;
}
} while (i != N && b);
в) исключение из таблицы элемента с заданным ключом. Исключению элемента из таблицы также предшествует поиск по ключу элемента и, если элемент в таблице есть, то исключение заключается в смещении всех последующих элементов на одну позицию вверх.
При исключении элемента из хеш-таблицы по ключу вычисляется номер его позиции в таблице. Если позиция «не пуста» и ключ элемента, находящегося в этой позиции, совпадает с ключом удаляемого элемента, то чтобы исключить элемент, достаточно сделать данную позицию «пустой» или в таблицах с перемешиванием «удаленной». Если ключи не совпадают, то поиск элемента осуществляется с учетом метода разрешения коллизии.
Примеры операций над таблицей с ключом целого типа:
struct elem
{
char name[15];
96
int m;
};
struct table
{
elem el[N]; int n;
};
1) операция поиск элемента в таблице с однозначным ключом
– линейный поиск в неупорядоченной таблице
elem *find1 (table G, int x)
{
int i = 0;
for (i < G.n && G.el[i].m != x; i++); return i == G.n ? NULL : &G.el[i];
}
– линейный поиск в упорядоченной таблице (упорядочение по воз-
растанию ключа)
int find2 (table *G, int x, elem *y)
{
int i = 0;
for ( ; i< G->n - 1 && G->el[i].m < x; i++); if (G->el[i].m == x)
{
*y = G->el[i]; return 1;
}
else
return 0;
}
97
– бинарный поиск в упорядоченной таблице (упорядочение по воз-
растанию ключа)
int find3 (table G, int x)
{
int l = 0, r = G.n – 1; while (l < r)
{
int c = (l + r) / 2; if (x G.el[c])
r = c; else
l = c + 1;
}
if (G.el[i].m == x) return i;
else
return -1;
}
2) операция включение элемента в упорядоченную таблицу с неоднозначным ключом (линейный поиск места совмещен с освобождением места в таблице):
int InsertTable (table *G, elem x)
{
int i = G->n - 1; if (G->n < N)
{
for ( ; i >= 0 && G->el[i].m > x.m; i--) G->el[i + 1] = G->el[i];
G->el[i + 1] = x; G->n++;
return 1;
}
98
