Hash table’da collision’ni bartaraf qilish. Separate chaining va Linear probing

Collision deb hash funksiyaning turli xil key’lar uchun bir xil hash generatsiya qilishiga aytiladi. Amaliy tajribalar shuni ko’rsatadiki, hattoki eng yaxshi kriptografik algoritmlar yordamida generatsiya qilingan hash’da ham collision bo’lish ehtimoli kichik bo’lsada mavjud.

Agar biz o’zimiz hash funksiya yozadigan bo’lsak, albatta collision’da nima qilish kerakligini ham aniqlab olishimiz kerak. Collision’ning yechimi sifatida ikki usul keltirilgan: Separate chaining va Linear probing.

Separate chaining

Image credit:  algs4.cs.princeton.edu

Separate chaining (alohida zanjirlar) – key’larni index’ga to’g’ridan to’g’ri bog’lamay, linked list yordamida bog’lab qo’yish nazarda tutiladi. Masalan, separate chaining usulida biz hash table‘da ko’rib o’tganimiz

hashmap[hash] = key

emas,

hashmap[hash] = Node {
key: key,
next: null
}

ko’rinishida ifodalanadi. Hash table’ga key qo’shish vaqtida collision paydo bo’lsa, ya’ni generatsiya qilingan hash kod bo’sh bo’lmasa, qo’shilayotgan key, mavjud key’ga bog’lanadi.

hashmap[hash] = Node {
key: key1,
next: Node {
key: key2,
next: null
}
}

Qidirishda ham hashmap[hash] dagi linked list ichidan key qidiriladi. Oson va samarali usul!

Linear probing

Collision muammosining yechimi uchun yana bir usul – Linear probing (chiziqli tekshirish).

Image credit:  algs4.cs.princeton.edu

Bunda agar hashmap[hash] bo’sh bo’lmasa, key hashmap[hash] dan keyingi slotga qo’yiladi – hashmap[hash + 1]. hashmap[hash + 1] ham bo’sh bo’lmasa, u holda hashmap[hash + 2] ga. Shu tariqa collision bo’lganda, key hashmap[hash] dan keyingi bo’sh indeksda saqlanadi. Qidirishda ham hashmap[hash] da berilgan key qidirilayotgan key’ga to’g’ri kelmasa hashmap[hash + 1] va hk tekshiriladi. Agar hashmap[hash + 1] bo’sh bo’lsa, demak key hash table’da mavjud emas – qidiruv tugaydi.

Linear probing ‘ni kamchiligi – key’ni hash table’dan o’chirgandan keyin paydo bo’ladigan muammosida. Agar key hashmap[hash + 2] da turgan bo’lsayu, hashmap[hash] o’chirilsa, keyin hashmap[hash + 1] va hashmap[hash + 2] ni topish mumkin bo’lmay qoladi. Shu sababli key o’chirilgach, undan keyingi elementlarni joyini surib chiqish kerak bo’ladi.

Separate chaining vs Linear probing

Image credit:  algs4.cs.princeton.edu

Kod yozish. Separate chaining da qo’shish (insert) va o’chirish (delete) ni amalga oshirish osonroq

Klastering. Separate chaining da yomon hash funksiya bilan ham kamroq muammo chiqadi.

Joy. Linear probing xotirada kamroq joy oladi, sababi Linear probing da Linked list yo’q, keyingi node’ga pointer yo’q – bular xotirani tejaydi.

Keshlash. Linear probing da keshlash samaraliroq. Sababi Linked list’ni o’qib chiqish protsessor keshini samarasiz.

O’qish (N/M). Barchasi kiritilayotgan key’larga va hash funksiyaga bog’liq.
Separate chaining da, agar key’lar uzunligi N ni hash indekslar soni M ga bo’lganimizda son kattalashib ketadigan bo’lsa (ya’ni bir xil hash kodli key’lar ko’p bo’lsa), u holda linked list’lar uzunlashib ketadi va key’ni o’qib olish ko’proq vaqt oladi. Odatda o’qish tezligi uchun N/M ~ 5 samarali deb baholanadi.
Linear probing da klasterlar – bir xil hash kodli key’lar ketma-ketligi uzun bo’lib ketsa, o’qish sekinlashadi.

Hash table vs Binary search tree

Hash table

  • Kod yozishga osonroq
  • Key’larning tartiblangan bo’lishi shartmas
  • Oddiy key’lar uchun o’qish va yozish tez ishlaydi – O(N)
  • Dasturlash tillarida tayyor yechimlar mavjud. Masalan hash table uchun Javascript’da Object yoki Map() dan foydalanish mumkin.

Binary search tree

  • Kafolatlangan ishlash tezligi – O(log N)
  • Tartiblash amallari (min, max, k-th min key) uchun qulay