Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
АМ_6_LR_7.doc
Скачиваний:
11
Добавлен:
23.08.2019
Размер:
187.39 Кб
Скачать

Міністерство освіти і науки, молоді та спорту України

Прикарпатський національний університет

імені Василя Стефаника

Кафедра радіофізики і електроніки

Лабораторна робота №6

з дисципліни

"Алгоритми та методи обчислень"

Реалізація хеш-таблиці

Івано-Франківськ – 2011

Мета роботи:

- оволодіти навичками побудови функції хешування;

- оволодіти навичками побудови алгоритмів хешування даних;

- оволодіти навичками розробки алгоритмів відкритого і закритого хешування при рішенні завдань.

Теоретичні відомості

Ключові терміни:

Вторинні ключі - це ключі, що не дозволяють однозначно ідентифікувати запис у таблиці.

Закрите хешування або Метод відкритої адресації - це технологія вирішення колізій, що припускає зберігання записів у самій хеш-таблиці.

Колізія - це ситуація, коли різним ключам відповідає одне значення хеш-функції.

Коефіцієнт заповнення хеш-таблиці - це кількість збережених елементів масиву, поділена на число можливих значень хеш-функції.

Відкрите хешування або Метод ланцюжків - це технологія вирішення колізій, яка полягає в тому, що елементи множини з рівними хеш-значеннями зв'язуються в ланцюжок-список.

Первинні ключі - це ключі, що дозволяють однозначно ідентифікувати запис.

Повторне хешування - це пошук місця розташування для чергового елемента таблиці з урахуванням кроку переміщення.

Простір записів - це множина тих комірок пам'яті, які виділяються для зберігання таблиці.

Простір ключів - це множина всіх теоретично можливих значень ключів запису.

Синоніми - це співпадаючі ключі в хеш-таблиці.

Хешування - це перетворення вхідного масиву даних певного типу і довільної довжини у вихідний бітовий рядок фіксованої довжини.

Хеш-таблиця - це структура даних, що реалізує інтерфейс асоціативного масиву, тобто вона дозволяє зберігати пари вигляду "ключ-значення" і виконувати три операції: операцію додавання нової пари, операцію пошуку і операцію вилучення пари по ключі.

Хеш-таблиці із прямою адресацією - це хеш-таблиці, що використають ін’єктивні хеш-функції і не мають потреби у механізмі вирішення колізій.

Ін'єкція (ін'єктивне відображення, ін'єктивна функція) - у математиці таке співвідношення між елементами двох множин, яке одному елементу з першої множини зіставляє один і тільки один елемент з другої множини.

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

У цей час використовується широко розповсюджений метод забезпечення швидкого доступу до інформації, що зберігається в зовнішній пам'яті - хешування.

Хешування (або хешування, англ. hashіng) - це перетворення вхідного масиву даних певного типу і довільної довжини у вихідний бітовий рядок фіксованої довжини. Такі перетворення також називаються хеш-функціями або функціями згортки, а їхні результати називають хешем, хеш-кодом, хеш-таблицею або дайджестом повідомлення (англ. message dіgest).

Хеш-таблиця - це структура даних, що реалізує інтерфейс асоціативного масиву, тобто вона дозволяє зберігати пари вигляду "ключ-значення" і виконувати три операції: операцію додавання нової пари, операцію пошуку і операцію вилучення пари по ключу. Хеш-таблиця є масивом, сформованим у певному порядку хеш-функцією.

Прийнято вважати, що гарною, з погляду практичного застосування, є така хеш-функція, що задовольняє наступним умовам:

- функція повинна бути простою з обчислювальної точки зору;

- функція повинна розподіляти ключі в хеш-таблиці найбільш рівномірно;

- функція не повинна відображати який-небудь зв'язок між значеннями ключів у зв'язок між значеннями адрес;

- функція повинна мінімізувати число колізій - тобто ситуацій, коли різним ключам відповідає одне значення хеш-функції (ключі в цьому випадку називаються синонімами).

При цьому перша властивість гарної хеш-функції залежить від характеристик комп'ютера, а друге - від значень даних.

Якби всі дані були випадковими, то хеш-функції були б дуже прості (наприклад, декілька бітів ключа). Однак на практиці випадкові дані зустрічаються досить рідко, і доводиться створювати функцію, що залежали б від усього ключа. Якщо хеш-функція розподіляє сукупність можливих ключів рівномірно по множині індексів, то хешування ефективно розбиває множину ключів. Найгірший випадок - коли всі ключі хешуються в один індекс.

При виникненні колізій необхідно знайти нове місце для зберігання ключів, що претендують на ту саму комірку хеш-таблиці. Причому, якщо колізії допускаються, то їхню кількість необхідно мінімізувати. У деяких спеціальних випадках вдається уникнути колізій взагалі. Наприклад, якщо всі ключі елементів відомі заздалегідь (або дуже рідко змінюються), то для них можна знайти деяку ін’єктивну хеш-функцію, що розподілить їх по коміркам хеш-таблиці без колізій. Хеш-таблиці, що використовують подібні хеш-функції, не мають потреби в механізмі дозволу колізій, і називаються хеш-таблицями із прямою адресацією.

Хеш-таблиці повинні відповідати наступним властивостям.

- Виконання операції в хеш-таблиці починається з обчислення хеш-функції від ключа. Хеш-значення, що отрималось, є індексом у вихідному масиві.

- Кількість збережених елементів масиву, ділена на число можливих значень хеш-функції, називається коефіцієнтом заповнення хеш-таблиці (load factor) і є важливим параметром, від якого залежить середній час виконання операцій.

- Механізм вирішення колізій є важливою складової будь-якої хеш-таблиці.

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

Колізії ускладнюють використання хеш-таблиць, тому що порушують однозначність відповідності між хеш-кодами і даними. Проте, існують способи подолання виникаючих складностей:

- метод ланцюжків (зовнішнє або відкрите хешування);

- метод відкритої адресації (закрите хешування).

Метод ланцюжків. Технологія зчеплення елементів полягає в тому, що елементи множини, яким відповідає те саме хеш-значення, зв'язуються в ланцюжок-список. У позиції номер зберігається вказівник на голову списку тих елементів, у яких хеш-значення ключа дорівнює і; якщо таких елементів у множині немає, у позиції і записаний NULL. На рис. 1 демонструється реалізація методу ланцюжків при вирішенні колізій. На ключ 002 претендують два значення, які організуються в лінійний список.

Рисунок 1.  Дозвіл колізій за допомогою ланцюжків

Кожна комірка масиву є вказівник на зв'язний список (ланцюжок) пар джерело-значення, що відповідають тому самому хеш-значенню ключа. Колізії просто приводять до того, що з'являються ланцюжки довжиною більше одного елемента.

Операції пошуку або вилучення даних вимагають перегляду всіх елементів відповідного йому ланцюжка, щоб знайти в ній елемент із заданим ключем. Для додавання даних потрібно додати елемент у кінець або початок відповідного списку, і, у випадку якщо коефіцієнт заповнення стане занадто великий, збільшити розмір масиву і перешикувати таблицю.

Метод відкритої адресації. На відміну від хешування з ланцюжками, при відкритій адресації ніяких списків немає, а всі записи зберігаються в самій хеш-таблиці. Кожна комірка таблиці містить або елемент динамічної множини, або NULL.

У цьому випадку, якщо комірка з обчисленим індексом зайнята, то можна просто переглядати наступні записи таблиці одна за одною доти, поки не буде знайдений ключ K або порожня позиція в таблиці. Для обчислення кроку можна також застосувати формулу, що і визначить спосіб зміни кроку. На рис. 2 вирішення колізій здійснюється методом відкритої адресації. Два значення претендують на ключ 002, для одного з них є перше вільне (ще не зайняте) місце в таблиці.

Рис. 2.  Вирішення колізій за допомогою відкритої адресації

При будь-якому методі вирішення колізій необхідно обмежити довжину пошуку елемента. Якщо для пошуку елемента необхідно більше 3 - 4 порівнянь, то ефективність використання такої хеш-таблиці пропадає і її треба реструктурувати (тобто знайти іншу хеш-функцію), щоб мінімізувати кількість порівнянь для пошуку елемента.

Для успішної роботи алгоритмів пошуку, послідовність проб повинна бути такою, щоб всі комірки хеш-таблиці виявилися переглянутими рівно по одному разу.

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

Існує кілька типів функцій хешування, кожна з яких має свої переваги і недоліки і заснована на поданні даних. Приведемо огляд і аналіз деяких найбільш простих із застосовуваних на практиці хеш-функцій.

Таблиця прямого доступу. Найпростішою організацією таблиці, що забезпечує ідеально швидкий пошук, є таблиця прямого доступу. У такій таблиці ключ є адресою запису в таблицю або може бути перетворений на адресу, причому таким чином, що ніякі два різних ключі не перетворяться в ту саму адресу. При створенні таблиці виділяється пам'ять для зберігання всієї таблиці і заповнюється порожніми записами. Потім записи вносяться в таблицю - кожна на своє місце, обумовлене її ключем. При пошуку ключ використовується як адреса і по цій адресі вибирається запис. Якщо обраний запис порожній, то запису з таким ключем взагалі немає в таблиці. Таблиці прямого доступу дуже ефективні у використанні, але, на жаль, область їхнього застосування досить обмежена.

Назвемо простором ключів множину всіх теоретично можливих значень ключів запису. Назвемо простором записів множину тих комірок пам'яті, які виділяються для зберігання таблиці. Таблиці прямого доступу застосовуються тільки для таких завдань, у яких розмір простору записів може дорівнювати розміру простору ключів. У більшості реальних завдань розмір простору записів набагато менше, ніж простір ключів. Так, якщо як ключ використовується прізвище, то, навіть обмеживши довжину ключа десятьма символами кирилиці, одержуємо 3310 можливих значень ключів. Навіть якщо ресурси обчислювальної системи і дозволять виділити простір записів такого розміру, то значна частина цього простору буде заповнена порожніми записами, тому що в кожному конкретному заповненні таблиці фактична множина ключів не буде повністю покривати простір ключів.

З метою економії пам'яті можна призначати розмір простору записів рівним розміру фактичної множини записів або трохи більше. У цьому випадку необхідно мати деяку функцію, що забезпечує відображення точки із простору ключів у точку в просторі записів, тобто, перетворення ключа на адресу запису: a=h(k), де a – адреса, k – ключ.

Ідеальною хеш-функцією є ін’єктивна функція, яка для будь-яких двох неоднакових ключів дає неоднакові адреси.

Метод залишків від ділення. Найпростішою хеш-функциєю є ділення по модулю числового значення ключа Key на розмір простору запису HashTableSіze. Результат інтерпретується як адреса запису. Варто мати на увазі, що така функція добре відповідає першому, але погано - останнім трьом вимогам до хеш-функції і сама по собі може бути застосована лише в дуже обмеженому діапазоні реальних завдань. Однак операція ділення по модулю звичайно застосовується як останній крок у більш складних функціях хешування, забезпечуючи приведення результату до розміру простору записів.

Якщо ключів менше, ніж елементів масиву, то як хеш-функцію можна використати ділення по модулю, тобто залишок від ділення целочисельного ключа Key на розмірність масиву HashTableSize, тобто: Key % HashTableSize

Дана функція дуже проста, хоча і не відноситься до гарних. Взагалі, можна використати будь-яку розмірність масиву, але вона повинна бути такою, щоб мінімізувати число колізій. Для цього як розмірність краще використати просте число. У більшості випадків подібний вибір цілком задовільний. Для символьного рядка ключем може бути залишок від ділення, наприклад, суми кодів символів рядка на HashTableSize.

На практиці, метод ділення - найпоширеніший.

// функція створення хеш-таблиці методом ділення по модулю

int Hash(int Key, int HashTableSize) {

//HashTableSize

return Key % HashTableSize;

}

Метод функції середини квадрата. Наступною хеш-функцією є функція середини квадрата. Значення ключа перетвориться в число, це число потім підноситься у квадрат, з нього вибираються кілька середніх цифр і інтерпретуються як адреса запису.

Метод згортки. Ще однієї хеш-функцією можна назвати функцію згортки. Цифрове подання ключа розбивається на частині, кожна з яких має довжину, рівну довжині необхідної адреси. Над частинами здійснюється певні арифметичні або порозрядні логічні операції, результат яких інтерпретується як адреса. Наприклад, для порівняно невеликих таблиць із ключами - символьними рядками непогані результати дає функція хешування, у якій адреса запису визначається у результаті додавання кодів символів, що становлять рядок-ключ.

Як хеш-функція також застосовують функцію перетворення системи числення. Ключ, записаний як число в деякій системі числення P, інтерпретується як число в системі числення Q>P. Звичайно вибирають Q=P+1. Це число переводиться із системи Q назад у систему P, приводиться до розміру простору записів і інтерпретується як адреса.

Відкрите хешування. Основна ідея базової структури при відкритому (зовнішньому) хешування полягає в тому, що потенційна множина (можливо, нескінченне) розбивається на кінцеве число класів. Для В класів, пронумерованих від 0 до В-1, будується хеш-функція h(x) така, що для будь-якого елемента х вихідної множини функція h(x) приймає цілочисельне значення з інтервалу 0,1,...,В-1, відповідне, класу, якому належить елемент х. Часто класи називають сегментами, тому будемо говорити, що елемент х належить сегменту h(x). Масив, називаний таблицею сегментів і проіндексований номерами сегментів 0,1,...,В-1, містить заголовки для B списків. Елемент х, стосовний до і-му списку - це елемент вихідної множини, для якого h(x)=i.

Якщо сегменти приблизно однакові за розміром, то в цьому випадку списки всіх сегментів повинні бути найбільш короткими при даному числі сегментів. Якщо вихідна множина складається з N елементів, тоді середня довжина списків буде N/B елементів. Якщо можна оцінити величину N і вибрати В якнайближче до цієї величини, то в кожному списку буде один або два елементи. Тоді час виконання операторів словників буде малою постійною величиною, що не залежить від N.

Закрите хешування. При закритому (внутрішньому) хешуванні в хеш-таблиці зберігаються безпосередньо самі елементи, а не заголовки списків елементів. Тому в кожному записі (сегменті) може зберігатися тільки один елемент. При закритому хешуванні застосовується методика повторного хешування. Якщо здійснюється спроба помістити елемент х в сегмент с номером h(х), який уже зайнятий іншим елементом (колізія), то відповідно до методики повторного хешування вибирається послідовність інших номерів сегментів h1(х),h2(х),..., куди можна помістити елемент х. Кожне із цих місць розташування послідовно перевіряється, поки не буде знайдене вільне. Якщо вільних сегментів ні, те, отже, таблиця заповнена, і елемент х додати не можна.

При пошуку елемента х необхідно переглянути всі місця розташування h(x),h1(х),h2(х),..., поки не буде знайдений х або поки не зустрінеться порожній сегмент. Щоб пояснити, чому можна зупинити пошук при досягненні порожнього сегмента, припустимо, що в хеш-таблиці не допускається видалення елементів. Нехай h3(х) – перший порожній сегмент. У такій ситуації неможливе знаходження елемента х в сегментах h4(х),h5(х) і далі, тому що при вставці елемент х вставляється в перший порожній сегмент, отже, він перебуває десь до сегмента h3(х). Але якщо в хеш-таблиці допускається вилучення елементів, то при досягненні порожнього сегмента, не знайшовши елемента х, не можна бути впевненим у тому, що його взагалі немає в таблиці, тому що сегмент може стати порожнім уже після вставки елемента х. Тому, щоб збільшити ефективність даної реалізації, необхідно в сегмент, що звільнився після операції видалення елемента, помістити спеціальну константу, що назвемо, наприклад, DEL. Як альтернатива спеціальній константі можна використати додаткове поле таблиці, що показує стан елемента. Важливо розрізняти константи DEL і NULL – остання перебуває в сегментах, які ніколи не містили елементів. При такому підході виконання пошуку елемента не вимагає перегляду всієї хеш-таблиці. Крім того, при вставці елементів сегменти, позначені константою DEL, можна трактувати як вільні, таким чином, простір, звільнений після вилучення елементів, можна рано або пізно використати повторно. Але якщо неможливо безпосередньо відразу після вилучення елементів позначити сегменти, що звільнилися, то варто зволіти закритому хешуванню схему відкритого хешування.

Існує кілька методів повторного хешування, тобто визначення місць розташування h(x),h1(х),h2(х),...:

- лінійне випробування;

- квадратичне випробування;

- подвійне хешування.

Лінійне випробування зводиться до послідовного перебору сегментів таблиці з деяким фіксованим кроком:

адреса=h(x)+ci,

де i – номер спроби вирішення колізії;

c – номер спроби вирішення колізії.

При кроці, рівному одиниці, відбувається послідовний перебір всіх сегментів після поточного. Квадратичне випробування відрізняється від лінійного тим, що крок перебору сегментів нелінійно залежить від номера спроби знайти вільний сегмент:

адреса=h(x)+ci+di2,

де i – номер спроби вирішення колізії,

c і d – константи.

Завдяки нелінійності такої адресації зменшується число проб при великій кількості ключів-синонімів. Однак навіть відносно невелике число проб може швидко привести до виходу за адресний простір невеликої таблиці внаслідок квадратичної залежності адреси від номера спроби.

Ще один різновид методу відкритої адресації, що називається подвійним хешуванням, основана на нелінійній адресації, що досягається за рахунок підсумовування значень основної і додаткової хеш-функції:

адреса=h(x)+ih2(x).

Очевидно, що в міру заповнення хеш-таблиці будуть відбуватися колізії, і в результаті їхнього вирішення чергова адреса може вийти за межі адресного простору таблиці. Щоб це явище відбувалося рідше, можна піти на збільшення довжини таблиці в порівнянні з діапазоном адрес, видаваним хеш-функцією. З одного боку, це приведе до скорочення числа колізій і прискоренню роботи з хеш-таблицею, а з іншого боку - до нераціональної витрати пам'яті. Навіть при збільшенні довжини таблиці у два рази в порівнянні з областю значень хеш-функції немає гарантії того, що в результаті колізій адреса не перевищить довжину таблиці. При цьому в початковій частині таблиці може залишатися досить вільних сегментів. Тому на практиці використовують циклічний перехід до початку таблиці.

Однак у випадку багаторазового перевищення адресного простору і, відповідно, багаторазового циклічного переходу до початку буде відбуватися перегляд тих самих раніше зайнятих сегментів, тоді як між ними можуть бути ще вільні сегменти. Більш коректним буде використання зрушення адреси на 1 у випадку кожного циклічного переходу до початку таблиці. Це підвищує ймовірність знаходження вільних сегментів.

У випадку застосування схеми закритого хешування швидкість виконання вставки і інших операцій залежить не тільки від рівномірності розподілу елементів по сегментам хеш-функції, але і від обраної методики повторного хешування (випробування) для вирішення колізій, пов'язаних зі спробами вставки елементів у вже заповнені сегменти. Наприклад, методика лінійного випробування для вирішення колізій - не найкращий вибір.

Як тільки кілька послідовних сегментів будуть заповнено, утворюючи групу, будь-який новий елемент при спробі вставки в ці сегменти буде вставлений у кінець цієї групи, збільшуючи тим самим довжину групи послідовно заповнених сегментів. Інакше кажучи, для пошуку порожнього сегмента у випадку безперервного розташування заповнених сегментів необхідно переглянути більше сегментів, ніж при випадковому розподілі заповнених сегментів. Звідси також слідує очевидний висновок, що при безперервному розташуванні заповнених сегментів збільшується час виконання вставки нового елемента і інших операцій.

Дотепер розглядалися способи пошуку в таблиці по ключам, що дозволяє однозначно ідентифікувати запис. Такі ключі називаються первинними. Можливий варіант організації таблиці, при якому окремий ключ не дозволяє однозначно ідентифікувати запис. Така ситуація часто зустрічається в базах даних. Ідентифікація запису здійснюється по деякій сукупності ключів. Ключі, що не дозволяють однозначно ідентифікувати запис у таблиці, які називаються вторинними ключами. Навіть при наявності первинного ключа, для пошуку запису можуть бути використані вторинні.

Ідея хешування вперше була висловлена Г.П. Ланом при створенні внутрішнього меморандуму ІBM у січні 1953 р. із пропозицією використати для вирішення колізій метод ланцюжків. Приблизно в цей же час інший співробітник ІBM, Жині Амдал, висловила ідею використання відкритої лінійної адресації. У відкритій пресі хешування вперше було описано Арнольдом Думи (1956 рік), який вказав, що як хеш-адресу зручно використовувати залишок від ділення на просте число. А. Думи описував метод ланцюжків для вирішення колізій, але не говорив про відкриту адресацію. Підхід до хешування, відмінний від методу ланцюжків, був запропонований А.П. Єршовим (1957 рік), що розробив і описав метод лінійної відкритої адресації.

Методичні вказівки

В даній роботі пропонується написати програму обраною мовою програмування, у якій для зберігання даних необхідно реалізувати алгоритм хешування відповідно до завдання.

При виконанні лабораторної роботи для кожного завдання потрібно написати програму, яка одержує на вході дані, обчислює за хеш-функцією хеш-значення відповідно до вимог завдання, виводить результат на екран та зберігає дані у хеш-таблиці. Введення даних здійснюється із клавіатури з урахуванням вимог до вхідних даних, що міститься в постановці завдання (введення даних супроводжуйте діалогом). Обмеженнями на вхідні дані є припустимий діапазон значень використовуваних числових типів у обраній мові.

При написанні програми виконуйте коментування коду.

Література

1. Ахо А., Хопкрофт Дж., Ульман Дж. Структуры данных и алгоритмы. – М.: Издательский дом “Вильямс”, 2001. – 384 с.

2. Вирт Н. Структуры данных и алгоритмы. С примерами на Паскале. Издание 2. – СПб.: Невский диалект, 2008. – 352 с.

3. Кнут Д. Искусство программирования для ЭВМ / В 3-х томах. – М.: “Мир”, 2008.

4. Новиков Ф. А. Дискретная математика для программистов. Учебник для вузов. 2-е изд. – СПб.: Питер, 2005. – 364 с.

Завдання

  1. Вивчіть словесну постановку завдання, виділивши при цьому всі види даних;

  2. Для обробки даних необхідно реалізувати функції алгоритмів хешування даних.

  3. Сформулюйте математичну постановку завдання;

  4. Виберіть метод рішення завдання, якщо це необхідно;

  5. Запишіть розроблений алгоритм обраною мовою;

  6. Розробіть контрольний тест до програми;

  7. Налагодьте програму;

  8. Представте звіт роботі.

  9. Програму супроводьте коментарями в коді.

Вимоги до звіту

Звіт до лабораторної роботи повинен відповідати наступній структурі:

- титульний лист.

- словесна постановка завдання. У цьому підрозділі проводиться повний опис завдання. Описується суть завдання, аналіз вхідних даних, область їх припустимих значень, одиниці їхнього виміру, можливі обмеження, аналіз умов при яких завдання має рішення (не має рішення), аналіз очікуваних результатів.

- математична модель. У цьому підрозділі вводяться математичні описи даних і математичний опис їхньої взаємодій. Ціль підрозділу - подати розв'язуване завдання у математичному формулюванні.

- алгоритм рішення завдання. У підрозділі описується розробка структури алгоритму, обґрунтовується абстракція даних, завдання розбивається на підзадачі.

- лістинг програми. Підрозділ повинен містити текст програми обраною мовою програмування.

- контрольний тест. Підрозділ містить набори вихідних даних і отримані в ході виконання програми результати.

- висновки по лабораторній роботі.

- відповіді на контрольні питання.

Приклад виконання роботи

Приклад 1. Програмна реалізація відкритого хешування.

#include "stdafx.h"

#include <iostream>

#include <fstream>

using namespace std;

typedef int T; // тип елементів

typedef int hashTableIndex; // індекс в хеш-таблиці

#define compEQ(a,b) (a == b)

typedef struct Node_ {

T data;// дані, що зберігаються у вершині

struct Node_ *next; // наступна вершина

} Node;

Node **hashTable;

int hashTableSize;

hashTableIndex myhash(T data);

Node *insertNode(T data);

void deleteNode(T data);

Node *findNode (T data);

int _tmain(int argc, _TCHAR* argv[]){

int i, *a, maxnum;

cout << "Введіть кількість елементів maxnum : ";

cin >> maxnum;

cout << "Введіть розмір хеш-таблиці HashTableSize : ";

cin >> hashTableSize;

a = new int[maxnum];

hashTable = new Node*[hashTableSize];

for (i = 0; i < hashTableSize; i++)

hashTable[i] = NULL;

// генерація масиву

for (i = 0; i < maxnum; i++)

a[i] = rand();

// заповнення хеш-таблиці елементами масиву

for (i = 0; i < maxnum; i++) {

insertNode(a[i]);

}

// пошук елементів масиву по хеш-таблиці

for (i = maxnum-1; i >= 0; i--) {

findNode(a[i]);

}

// виведення елементів масиву в файл List.txt

ofstream out("List.txt");

for (i = 0; i < maxnum; i++){

out << a[i];

if ( i < maxnum - 1 ) out << "\t";

}

out.close();

// збереження хеш-таблиці в файл HashTable.txt

out.open("HashTable.txt");

for (i = 0; i < hashTableSize; i++){

out << i << " : ";

Node *Temp = hashTable[i];

while ( Temp ){

out << Temp->data << " -> ";

Temp = Temp->next;

}

out << endl;

}

out.close();

// очистка хеш-таблиці

for (i = maxnum-1; i >= 0; i--) {

deleteNode(a[i]);

}

system("pause");

return 0;

}

// хеш-функція розміщення вершини

hashTableIndex myhash(T data) {

return (data % hashTableSize);

}

// функція пошуку місця розташування і вставки вершини в таблицю

Node *insertNode(T data) {

Node *p, *p0;

hashTableIndex bucket;

// вставка вершини в початок списку

bucket = myhash(data);

if ((p = new Node) == 0) {

fprintf (stderr, " Недостача пам'яті (insertNode)\n");

exit(1);

}

p0 = hashTable[bucket];

hashTable[bucket] = p;

p->next = p0;

p->data = data;

return p;

}

// функція вилучення вершини з таблиці

void deleteNode(T data) {

Node *p0, *p;

hashTableIndex bucket;

p0 = 0;

bucket = myhash(data);

p = hashTable[bucket];

while (p && !compEQ(p->data, data)) {

p0 = p;

p = p->next;

}

if (!p) return;

if (p0)

p0->next = p->next;

else

hashTable[bucket] = p->next;

free (p);

}

// функція пошуку вершини зі значенням data

Node *findNode (T data) {

Node *p;

p = hashTable[myhash(data)];

while (p && !compEQ(p->data, data))

p = p->next;

return p;

}

Приклад 2. Програмна реалізація закритого хешування.

#include "stdafx.h"

#include <iostream>

#include <fstream>

using namespace std;

typedef int T; // тип елемента

typedef int hashTableIndex;// індекс в хеш-таблиці

int hashTableSize;

T *hashTable;

bool *used;

hashTableIndex myhash(T data);

void insertData(T data);

void deleteData(T data);

bool findData (T data);

int dist (hashTableIndex a,hashTableIndex b);

int _tmain(int argc, _TCHAR* argv[]){

int i, *a, maxnum;

cout << " Введіть кількість елементів maxnum : ";

cin >> maxnum;

cout << "Введите размер хеш-таблицы hashTableSize : ";

cin >> hashTableSize;

a = new int[maxnum];

hashTable = new T[hashTableSize];

used = new bool[hashTableSize];

for (i = 0; i < hashTableSize; i++){

hashTable[i] = 0;

used[i] = false;

}

// генерація масиву

for (i = 0; i < maxnum; i++)

a[i] = rand();

// заповнення хеш-таблиці елементами масиву

for (i = 0; i < maxnum; i++)

insertData(a[i]);

// пошук елементів масиву у хеш-таблиці

for (i = maxnum-1; i >= 0; i--)

findData(a[i]);

// виведення елементів масиву в файл List.txt

ofstream out("List.txt");

for (i = 0; i < maxnum; i++){

out << a[i];

if ( i < maxnum - 1 ) out << "\t";

}

out.close();

// збереження хеш-таблиці в файл HashTable.txt

out.open("HashTable.txt");

for (i = 0; i < hashTableSize; i++){

out << i << " : " << used[i] << " : " << hashTable[i] << endl;

}

out.close();

// очищення хеш-таблиці

for (i = maxnum-1; i >= 0; i--) {

deleteData(a[i]);

}

system("pause");

return 0;

}

// хеш-функція розміщення величини

hashTableIndex myhash(T data) {

return (data % hashTableSize);

}

// функція пошуку місця розташування і вставки величини в таблицю

void insertData(T data) {

hashTableIndex bucket;

bucket = myhash(data);

while ( used[bucket] && hashTable[bucket] != data)

bucket = (bucket + 1) % hashTableSize;

if ( !used[bucket] ) {

used[bucket] = true;

hashTable[bucket] = data;

}

}

// функція пошуку величини, рівної data

bool findData (T data) {

hashTableIndex bucket;

bucket = myhash(data);

while ( used[bucket] && hashTable[bucket] != data )

bucket = (bucket + 1) % hashTableSize;

return used[bucket] && hashTable[bucket] == data;

}

// функція вилучення величини з таблиці

void deleteData(T data){

int bucket, gap;

bucket = myhash(data);

while ( used[bucket] && hashTable[bucket] != data )

bucket = (bucket + 1) % hashTableSize;

if ( used[bucket] && hashTable[bucket] == data ){

used[bucket] = false;

gap = bucket;

bucket = (bucket + 1) % hashTableSize;

while ( used[bucket] ){

if ( bucket == myhash(hashTable[bucket]) )

bucket = (bucket + 1) % hashTableSize;

else if ( dist(myhash(hashTable[bucket]),bucket) < dist(gap,bucket) )

bucket = (bucket + 1) % hashTableSize;

else {

used[gap] = true;

hashTable[gap] = hashTable[bucket];

used[bucket] = false;

gap = bucket;

bucket++;

}

}

}

}

// функція обчислення відстань від a до b (по годинній стрілці, зліва направо)

int dist (hashTableIndex a,hashTableIndex b){

return (b - a + hashTableSize) % hashTableSize;

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]