Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Зубенко, Омельчук - Програмування. Поглиблений курс

.pdf
Скачиваний:
58
Добавлен:
07.03.2016
Размер:
5 Мб
Скачать
☆

Розділ IV. АЛГОРИТМИ

{

while(start) {

printf("\n%s\t%f\t%d", start->name, start->estimationsult, start->rate);

start=start->next;

}

printf("\n");

}

/* пошук попередника елемента elem, start – голова списку*/ Itemptr getPrev(Itemptr start, Itemptr elem)

{

if(elem)

{

Itemptr prev=NULL; Itemptr p=start; while((p)&&(p!=elem)) {

prev=p; p=p->next;

}

return prev; } else

return NULL; /*елемент не знайдено*/

}

/* пошук попередника елемента за значенням name.

Результат NULL, якщо елемент не знайдено, або він перший у списку (не має попередників), start – голова списку*/

Itemptr getPrevElem(Itemptr start, char name[10])

{

Itemptr prev=NULL; while(start) {

if((strcmp(start->name, name))==0) return prev; prev=start;

start=start->next;

}

return NULL; /*елемент не знайдено*/

}

/* пошук елемента за його значенням name, start – голова списку*/ Itemptr search(Itemptr start, char name[10])

{

while(start) {

if((strcmp(start->name, name))!=0) return start; start=start->next;

}

return NULL; /*елемент не знайдено*/

531

ПРОГРАМУВАННЯ

}

/* пошук останнього елемента), start – голова списку*/ Itemptr getLast(Itemptr start)

{

while(start->next) start=start->next; return start;

}

/*додавання нового елемента itm у кінець списку,start – голова списку, результат – нова голова списку*/

Itemptr addItem (Itemptr start, Item itm)

{

Itemptr p, p1;

p=(Itemptr)malloc(sizeof(Item)); p->estimationsult=itm.estimationsult; p->rate=itm.rate;

strcpy(p->name, itm.name); p->next=NULL;

if(!start) start=p; else{

p1=getLast(start); p1->next=p;

}

return start;

}

/*наповнення списку, start – голова списку, результат – нова голова списку*/

Itemptr inputElem(Itemptr start)

{

Item itm; int num=1;

Itemptr itemList;; while(num)

{

Item st;

printf("\nInput name: "); scanf("%s", &st.name); printf("\nInput estimationsult: "); scanf("%f", &st.estimationsult); printf("\nInput rate: "); scanf("%d", &st.rate); itemList=addItem(start, st); if(!start){ start=itemList;}

532

Розділ IV. АЛГОРИТМИ

printf("Insert else? 1/0"); scanf("%d", &num);

}

return start;

}

/*видалення елемента за елементом на позиції prevItem, start – голова списку, результат – нова голова списку*/

Itemptr deleteItem(Itemptr start, Itemptr prevItem)

{/*список не порожній*/ if(start) {

Itemptr p;

/*видаляється перший елемент (якому ніщо не передує)*/

if(!prevItem) { p = start;

start=start->next; free(p);

return start;

}

else{

p=prevItem->next; /*попередник не останній*/ if(p){

prevItem->next=p->next; free(p);

return start; } else return start;

}

}

else return start;

}

/*видалення елемента зі введеним ключем, start – голова списку, результат – нова голова списку*/

Itemptr deleteElem(Itemptr start)

{

int num=1; Itemptr p;

printf("\nDeleting:"); char name[10]; while(num)

{

printf("\nInput name: "); scanf("%s", &name); p=getPrevElem(start, name); start=deleteItem(start, p);

533

ПРОГРАМУВАННЯ

display(start); printf("Delete else? 1/0"); scanf("%d", &num);

}

return start;

}

■

4.4.3. ПІДВИЩЕННЯ ЕФЕКТИВНОСТІ ПРИ ПОШУКУ В ТАБЛИЦЯХ

Для підвищення ефективності обробки таблиць їх подають у вигля- ді так званих хеш-таблиць. Середній час пошуку елемента в такій таблиці становить O(1), а в найгіршому випадку – O(n).

Якщо взяти звичайну таблицю у вигляді масиву чи списку рядків, то останні розташовані в них або випадково, або в певному порядку. У будь-якому разі, щоб знайти рядок за даним ключем, доводиться переглянути кілька або всі рядки таблиці. Ідея хешування таблиць полягає в тому, щоб за допомогою спеціальної хеш-функції пов'язати розташування рядка в таблиці зі значенням його ключа. Тоді для отримання доступу до рядка достатньо вирахувати значення хеш- функції на його ключі. Кажуть, що хеш-функція розставляє1 рядки в таблиці. Нехай K – сукупність усіх можливих ключів, m – фіксоване натуральне число. Хеш-функція h має вигляд h : K → 0..m −1, а її зна- чення називаються хеш-числами. Хеш-таблицею називається масив (хеш-масив) довжиною m , i -й елемент якого містить або рядок із ключем k таким, що h(k) = i , або покажчик на такий рядок чи зв'я-

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

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

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

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

1 Іноді хеш-функції називають функціями розташування.

534

Розділ IV. АЛГОРИТМИ

4.4.4. ПРЯМА АДРЕСАЦІЯ

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

компонентами – рядками таблиці або покажчиками на них – і нази- вається таблицею з прямою адресацією (рис. 4.3). У таблиці з прямою адресацією кожному ключу відповідає елемент T[i], де i – хеш-число

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

Ключ Додаткові дані

Хеш-масив

1

1

2

2

3 4

4

5

… m-1 m-1

Рис. 4.3

Реалізуємо основні операції для таблиць із прямою адресацією. Ко- жна з них вимагає O (1) часу.

Приклад 4.28. Хеш-таблиця з прямою адресацією й елементами- покажчиками на рядки таблиці:

Лістинг.

#define countKey 2100 /*додаткові дані*/

typedef struct information{ int iYear;

char *text; }Info;

535

ПРОГРАМУВАННЯ

typedef Info *InfoPtr;

/*Опис стpуктуpи (елемента)*/ typedef struct st{

int key; InfoPtr info;

} St;

typedef St *StPtr;

StPtr AddressT[countKey]; /*пошук інформації за ключем*/ InfoPtr addressSearch(int k)

{

InfoPtr IPtr=NULL;

if((k>0)&&(k<countKey)) IPtr=AddressT[k]->info; return IPtr;

}

/*додавання інформації*/

void addressInsert(InfoPtr info)

{

int k=addressFunction(info);

StPtr PTR=(StPtr)malloc(sizeof(St)); PTR->info=info;

PTR->key=k; AddressT[k]=PTR;

}

/*пошук інформації за ключем*/ void addressDelete(InfoPtr info)

{

int k=addressFunction(info); StPtr PTR=AddressT[k]; free(info);

free(PTR);

AddressT[k]=NULL;

}

/*Розглянемо тривіальний варіант хеш-функції. Тут ключ рівний полю iYear. Результат (-1) у випадку помилки*/

int addressFunction(InfoPtr info)

{

if(info)

return info->iYear; else return -1;

}

■

536

Розділ IV. АЛГОРИТМИ

4.4.5. ХЕШ-ТАБЛИЦІ З КОЛІЗІЯМИ

Якщо множина K усіх ключів достатньо велика, то не завжди доці- льно чи взагалі можливо зберігати хеш-масив T[n], де n =|K |. До того ж

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

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

Хеш-масив

0

h(k1) h(k2)

h(k3)=h(k4)

…

m-1

Рис. 4.4

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

K>m це неможливо. Розглянемо два способи виходу із цієї ситуації:

1)відкрите, або зовнішнє, хешування (дозволяє зберігати множини

впотенційно нескінченному просторі);

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

4.4.6. ВІДКРИТЕ ХЕШУВАННЯ

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

Бажано вибрати хеш-функцію h так, щоб вона приблизно рівно- мірно розподіляла рядки таблиці по списках. Зазвичай хеш-функція

537

ПРОГРАМУВАННЯ

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

 

Список для кож-

Хеш-масив

ного хеш-числа

0

 

k1

k2

k3

 

k4

k5

…

 

k6

 

m-1

Рис. 4.5

Приклад 4.29. Хеш-функція на рядках.

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

Лістинг.

#define countKey 701 /*довжина хеш-таблиці*/

typedef struct celltype { char *element;

struct celltype *next; }Celltype;

typedef Celltype *CelltypePtr;

CelltypePtr Dictionary[countKey];

int hаshFunction(char *x)

{

int h=0, sum=0; int len=strlen(x);

for(int i=0; i<len; i++){ sum+=x[i]; h=sum%countKey;

}

return h;

}

538

Розділ IV. АЛГОРИТМИ

void makenull(void) /*початкова ініціалізація*/

{

for(int i=0; i<countKey; i++){ Dictionary[i]=NULL;

};

}

int hаshMember(char *x) /*пошук слова x у словнику*/

{

CelltypePtr current=Dictionary[hаshFunction(x)]; /*початкове значення current дорівнює заголовку сегмента, якому належить слово x*/

while(current)

if(current->element==x) return 1; else current=current->next;

return 0; /*слово не знайдено*/

}

void hаshInsert(char *x) /*занесення слова у словник*/

{

int bucket; /*для номера сегмента*/ CelltypePtr oldheader;

if(!hаshMember(x)){ bucket=hаshFunction(x); oldheader=Dictionary[bucket];

Dictionary[bucket]=(CelltypePtr)malloc(sizeof(Celltype)); Dictionary[bucket]->element=x; Dictionary[bucket]->next=oldheader;

}

}

void hаshDelete(char *x) /*видалення слова зі словника*/

{

int bucket=hаshFunction(x); /*для хеш-числа слова*/ CelltypePtr current; /*покажчик на попередню комірку*/

if (Dictionary[bucket]){ if(Dictionary[bucket]->element==x) {

/*x у першій комірці*/

CelltypePtr delEl=Dictionary[bucket]; Dictionary[bucket]=Dictionary[bucket]->next;

/*видалення зі списку*/ free(x);

free(delEl);

}

539

ПРОГРАМУВАННЯ

else {

/*x не в першій комірці*/ current=Dictionary[bucket];

/*покажчик на попередню комірку*/ while(current->next){

if(current->next->element==x){ CelltypePtr delEl=current->next; current->next=current->next->next;

/*видалення зі списку*/ free(x);

free(delEl);

return;

}

else /*x поки не знайдено*/ current=current->next;

}

}

}

}

■

4.4.7. ЗАКРИТЕ ХЕШУВАННЯ

При закритому хешуванні немає списків із рядками – усі рядки збері- гаються безпосередньо в хеш-масиві. Кожний елемент масиву порожній або містить лише один рядок. Якщо розташувати рядок із ключем x у

елементі з індексом h (x ) неможливо (він уже зайнятий іншим рядком (колізія)), то за методикою повторного хешування будується послідов- ність інших індексів h(1) (x ), h(2) (x ), …, що задають позиції для його

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

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

Лінійне хешування. Для хеш-функції h :U → {0,1,…m −1} функція,

що задає

лінійну

послідовність

спроб,

зображується

формулою

h(i ) (k )= (h (k )+ i )%m . Роботу з

ключем k

починають

із

комірки

T (h (k )),

а потім

переходять

до

T (h (k )+1), T (h (k )+ 2),

…(після

T (m −1) – до T (0)).

 

 

 

 

 

 

540