CodeGym /Kurslar /Python SELF AZ /Koliziyalar problemi və onların həlli üsulları

Koliziyalar problemi və onların həlli üsulları

Python SELF AZ
Səviyyə , Dərs
Mövcuddur

7.1 Hash cədvəlində kolliziyaların müəyyən edilməsi

Hash cədvəlində kolliziya o zaman baş verir ki, iki müxtəlif açar eyni array-in indeksinə hash-lənir. Bu, eyni hücrəni hash cədvəlində birdən artıq elementin tutmağa çalışmasına gətirib çıxarır.

Kolliziya nümunəsi:

Təsəvvür edin, bizdə sadə bir hash-funksiya var, hansı ki, açarın cədvəlin ölçüsünə bölünməsindən alınan qalığı qaytarır. 5 və 15 açarları üçün, əgər cədvəl ölçüsü 10-dursa, hər iki açar eyni indeks verəcək: 5 % 10 = 515 % 10 = 5. Bu kolliziyadır.

Daha bir misal:

Fərz edin ki, siz adlardan ibarət bir sözlük (dictionary) qurursunuz və sadə bir hash-funksiya var, hansı ki, adın ilk hərfini qaytarır. Amma məlum olur ki, adların 80%-i «A» hərfi ilə başlayır. Bu təkcə kolliziya deyil, bu problemdir.

7.2 Toqquşma həlli metodları – zəncirlər

Hash-table-larda toqquşmaların həlli üçün bir neçə metod mövcuddur. Ən çox istifadə olunan iki metod zəncirlər (chaining) və açıq ünvanlama (open addressing) metodlarıdır.

Zəncirlər (Chaining)

Üstünlüklər:

  • İcrası sadədir.
  • Az sayda toqquşma olduqda effektivdir.
  • Hash-table-ın dolma sıxlığından asılılığı azdır.

Mənfi cəhətlər:

  • Bağlı siyahılarda göstəricilər üçün əlavə yaddaş tələb edir.
  • Uzun zəncirlər səbəbindən çox sayda toqquşma olduqda performans azala bilər.

Zəncir metodunun tətbiq nümunəsi:

insert funksiyasının iş xüsusiyyətini öyrənə bilərsiniz, amma mən rahatlıq üçün tam kodu qeyd etmək qərarına gəldim.


class HashTableChaining:
    def __init__(self, size):
        self.size = size
        self.table = [None] * size

    def hash_function(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        index = self.hash_function(key)
        if self.table[index] is None:
            self.table[index] = [(key, value)]
        else:
            for i, kv in enumerate(self.table[index]):
                k, v = kv
                if k == key:
                    self.table[index][i] = (key, value)
                    return
            self.table[index].append((key, value))

    def search(self, key):
        index = self.hash_function(key)
        if self.table[index] is None:
            return None
        for k, v in self.table[index]:
            if k == key:
                return v
        return None

    def delete(self, key):
        index = self.hash_function(key)
        if self.table[index] is None:
            return
        for i, kv in enumerate(self.table[index]):
            k, v = kv
            if k == key:
                del self.table[index][i]
                return

# İstifadə nümunəsi:
hash_table = HashTableChaining(10)
hash_table.insert("apple", 1)
hash_table.insert("banana", 2)
hash_table.insert("cherry", 3)
print(hash_table.search("banana"))  # Giriş: 2
hash_table.delete("banana")
print(hash_table.search("banana"))  # Giriş: None

7.3 İkinci metod – açıq adresasiya

Açıq adresasiya metodu koliziya baş verdikdə massivdə növbəti mövcud hüceyrəni axtarmaqdan ibarətdir. Yeni hüceyrənin axtarışı üçün müxtəlif strategiyalar mövcuddur: xətti probasiya, kvadratik probasiya və ikiqat hashing.

Üstünlüklər:

  • Pointer-ləri saxlamaq üçün əlavə yaddaş tələb etmir.
  • Dataların daha kompakt təqdimatı.

Mənfi cəhətlər:

  • Cədvəl doluluq sıxlığı yüksək olduqda performans əhəmiyyətli dərəcədə azala bilər.
  • Koliziya həlli üçün daha çox hesablama resursları tələb edir.

Açıq adresasiya strategiyalarına nümunələr

Xətti probasiya (Linear Probing):

  • Addım 1 ilə probasiya: index = (index + 1) % size.
  • Sadədir, amma "klasterizasiya"ya səbəb ola bilər.

Kvadratik probasiya (Quadratic Probing):

  • Addım kvadrata artır: index = (index + i^2) % size.
  • Klasterizasiya problemini azaldır, amma reallaşdırması çətindir.

İkiqat hashing (Double Hashing):

  • Addımı hesablamaq üçün ikinci hash-funksiya istifadə olunur: index = (index + i * hash2(key)) % size.
  • Klasterizasiya problemi daha azdır, amma reallaşdırması çətindir.

Açıq adresasiya metodunun reallaşdırmasına nümunə (xətti probasiya): Mən tam işlək kod təqdim edəcəyəm, sadəlik üçün insert funksiyasının işini analiz edə bilərsiniz


class HashTableOpenAddressing:
    def __init__(self, size):
        self.size = size
        self.table = [None] * size

    def hash_function(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        index = self.hash_function(key)
        original_index = index
        while self.table[index] is not None:
            if self.table[index][0] == key:
                self.table[index] = (key, value)
                return
            index = (index + 1) % self.size
            if index == original_index:
                raise Exception("Hash cədvəli doludur")
        self.table[index] = (key, value)

    def search(self, key):
        index = self.hash_function(key)
        original_index = index
        while self.table[index] is not None:
            if self.table[index][0] == key:
                return self.table[index][1]
            index = (index + 1) % self.size
            if index == original_index:
                break
        return None

    def delete(self, key):
        index = self.hash_function(key)
        original_index = index
        while self.table[index] is not None:
            if self.table[index][0] == key:
                self.table[index] = None
                return
            index = (index + 1) % self.size
            if index == original_index:
                break

# İstifadə nümunəsi:
hash_table = HashTableOpenAddressing(10)
hash_table.insert("apple", 1)
hash_table.insert("banana", 2)
hash_table.insert("cherry", 3)
print(hash_table.search("banana"))  # Çıxış: 2
hash_table.delete("banana")
print(hash_table.search("banana"))  # Çıxış: None

7.4 Toqquşmaların hash-cədvələ təsiri

Toqquşmaların performansa təsiri.

Orta müraciət zamanı:

  • İdeal şərtlərdə (toqquşma olmadıqda) hash-cədvəldəki elementlərə müraciət üçün orta zaman O(1) olur.
  • Toqquşmalar müraciət zamanını artırır, çünki zəncirlər və ya hüceyrələr üzrə probing emalı tələb olunur və bu da əməliyyatların sayını artırır.

Zəncirlərin uzunluğunun artması:

  • Zəncir metodu zamanı çoxlu toqquşmalar zamanı zəncirlərin uzunluğu artır və bu da axtarış, əlavə etmə və çıxarma əməliyyatları üçün xətti müraciət zamanına O(n) səbəb ola bilər.
  • Uzun zəncirlər elementin axtarışı və ya əlavə edilməsi üçün tələb olunan müqayisələrin sayını artırır.

Yaddaşın mürəkkəbliyi:

  • Zəncir metodu zamanı qoşulan siyahılar üçün göstəriciləri saxlamaq üçün əlavə yaddaş tələb olunur.
  • Açıq adresasiya metodunda cədvəlin müəyyən səviyyədən üstün (adətən təxminən 70-80%) doldurulması toqquşmaların sayını və buna bağlı olaraq müraciət zamanı əhəmiyyətli dərəcədə artırır.

Klasterləşmə: Açıq adresasiya metodunda (xüsusilə xətti probing istifadə edilən zaman) klasterləşmə baş verir, bir neçə ardıcıl hüceyrə dolur, bu isə toqquşmaların ehtimalını artırır və performansı pisləşdirir.

Toqquşmaların performansa təsirinin analiz nümunəsi:

Bu, məhz daha dərindən araşdırmaq istəyənlər üçündür


import time
import random

# Xətti probing
class HashTableOpenAddressing:
    def __init__(self, size):
        self.size = size
        self.table = [None] * size

    def hash_function(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        index = self.hash_function(key)
        original_index = index
        while self.table[index] is not None:
            if self.table[index][0] == key:
                self.table[index] = (key, value)
                return
            index = (index + 1) % self.size
            if index == original_index:
                raise Exception("Hash table is full")
        self.table[index] = (key, value)

    def search(self, key):
        index = self.hash_function(key)
        original_index = index
        while self.table[index] is not None:
            if self.table[index][0] == key:
                return self.table[index][1]
            index = (index + 1) % self.size
            if index == original_index:
                break
        return None

# Əlavə üçün çoxlu məlumatların yaradılması
hash_table = HashTableOpenAddressing(1000)
keys = [random.randint(1, 10000) for _ in range(800)]

# Məlumatların daxil edilməsi və zamanın ölçülməsi
start_time = time.time()
for key in keys:
    hash_table.insert(key, key)
print(f"Əlavə vaxtı: {time.time() - start_time:.6f} saniyə")

# Məlumatların axtarışı və zamanın ölçülməsi
start_time = time.time()
for key in keys:
    hash_table.search(key)
print(f"Axtarış vaxtı: {time.time() - start_time:.6f} saniyə")

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION