           ჰეშირება ღია
დამისამართებით (Open
Addressing)
მარიამ გობრონიძე
ჰეშირება ღია მისამართებით
ღია მისამართებით ჰეშირებისას ყველა ჩანაწერი (ჩვენს შემთხვევაში
სიმარტივისთვის ნატურალური რიცხვები) ინახება თავად ჰეშ-
ცხრილში, რომელშიც ყოველთვის უნდა იყოს ღია, ანუ თავისუფალი
უჯრები (მისამართები). ამის გამო ჰეშ-ცხრილს ეწოდება ღია
მისამართებიანი ცხრილი, ხოლო ჰეშირების მეთოდს - ჰეშირება ღია
მისამართებით.
ღია დამისამართებით ჰეშრება წარმოადგენს კოლიზიებისაგან თავის
არიდების ერთ-ერთ მეთოდს, ნებისმიერ მომენტში ცხრილის ზომა
უნდა იყოს არანაკლებ ჩასმული ელემენტების რაოდენობისა
ღია მისამართებიანი ჰეშ-ცხრილის ყოველი უჯრა შეიცავს ან
დინამიკური სიმრავლის ელემენტს (ჩვენს შემთხვევაში
სიმარტივისთვის გასაღებს) ან NIL-ს (რაიმე საკონტროლო
მნიშვნელობა, რომელიც მიგვანიშნებს რომ უჯრა ცარიელია).
ძებნის დროს, ვიწყებთ ცხრილის ერთი უჯრიდან და, თუ იგი
დაკავებულია, შემდეგ გარკვეული წესით გრძელდება ძებნის
პროცესი ვიდრე არ ვიპოვით ღია მისამართს ცხრილში ან არ
დავრწმუნდებით მის არარსებობაში. წესით, ღია მისამართების
განსაზღვრის დროს შესანახ ელემენტთა რაოდენობა არ უნდა იყოს
ცხრილის ზომაზე მეტი.
ღია მისამართების განსაზღვრის დროს გასასინჯ უჯრათა
მიმდევრობა გამოითვლება ფორმულით, გასაღების მიხედვით.
ახალი ელემენტის ჩამატებისას ჩვენ ვსინჯავთ ღია მისამართების
ცხრილს, გადანომრილს 0-დან (m-1)-ის ჩათვლით, თავისუფალი
ადგილის პოვნამდე. თუკი ყოველ ჯერზე მოგვიწევს თავიდან
ბოლომდე მთელი ცხრილის გადასინჯვა, დაიხარჯება n–ის
პროპორციული დრო, მაგრამ მეთოდის არსი და ღირსება ისაა, რომ
ცხრილის განხილვის რიგი დამოკიდებულია გასაღებზე. კერძოდ,
ძებნა შეიძლება დაიწყოს ცხრილის ნებისმიერი ადგილიდან
გასაღების მნიშვნელობის მიხედვით და შეწყდეს რამდენიმე უჯრის
გასინჯვის შემდეგ. სხვა სიტყვებით რომ ვთქვათ, ჰეშ-ფუნქციას
ემატება მეორე არგუმენტი გასინჯული უჯრების რაოდენობა ასე
რომ ჰეშ-ფუნქციას აქვს სახე:
ჰეშ ფუნქციის სახე
H: U✕{0,1,…,m-1} → {0,1,…,m-1}
U გასაღებთა სიმრავლეა.
გამოყენებულ ადგილთა მიმდევრობას ანუ ცდათა მიმდევრობას
(probe sequence) მოცემული k გასაღებისათვის აქვს სახე:
H ფუნქცია ისეთი უნდა იყოს, რომ ამ მიმდევრობაში 0-დან (m-1)- მდე
ყველა რიცხვი ზუსტად ერთხელ შეგვხვდეს (ყოველი
გასაღებისათვის ცხრილის ნებისმიერი პოზიცია მისაწვდომი უნდა
იყოს).
მთელი ეს პროცედურა დაფუძნებულია ცდის პრინციპზე (probing).
● ჩასმა (Insert(k)): მიმდინარეობს ცდა მანამ, სანამ არ მოიძებნება ცარიელი
უჯრა. როგორც კი ასეთი უჯრა მოიძებნება, მასში ჩაიწერება k.
● ძებნა (Search(k)): მიმდინარეობს ცდა მანამ, სანამ ან ვიპოვით k-ს, ან
შევხვდებით ცარიელ უჯრას, რაც მიუთითებს, რომ ელემენტი არ არსებობს.
● წაშლა (Delete(k)): წაშლის ოპერაცია განსაკუთრებულ მიდგომას საჭიროებს.
თუ ელემენტი უბრალოდ წაიშლება, შესაძლოა ძიების პროცესი არასწორად
დასრულდეს. ამიტომ, წაშლილი ელემენტების უჯრები სპეციალურად
აღინიშნება როგორც "წაშლილი". ჩასმის ოპერაციას შეუძლია მონაცემის
ჩაწერა ასეთ უჯრაში, მაგრამ ძიების ოპერაცია მასზე არ ჩერდება
ღია დამისამართების განსხვავებული გზები
ღია მისამართების ცხრილში დაკავებული ადგილების
გამოთვლისათვის გამოიყენება სამი მეთოდი: წრფივი, კვადრატული
და ორმაგი ჰეშირება.
ღია მისამართების განსაზღვრა წრფივი
გადასინჯვის მეთოდით
წრფივი გადასინჯვის მეთოდში ჰეშ-ცხრილს ვსინჯავთ
თანმიმდევრულად, დაწყებული ჰეშ-ფუნქციით მიღებული საწყისი
მისამართიდან. თუ ამ მისამართზე უჯრა უკვე დაკავებულია, მაშინ
მოწმდება შემდეგი უჯრა ცხრილში. თავისი სიმარტივის გამო,
ზოგჯერ იქმნება დაკავებლი უჯრების გრძელი სერიები, რასაც
კლასტერები ეწოდება და რაც ანელებს ძებნას.
რეჰეშირების ფუნქცია
რეჰეშირების ფუნქცია განისაზღვრება შემდეგნაირად:
წრფივი გადასინჯვის დროს ორ წერტილს შორის დაშორება არის 1, როგორც ეს
ნაჩვენებია ქვემოთ განხილულ მაგალითში:
● თუ hash(x) % S უჯრედი დაკავებულია, მაშინ ვამოწმებთ (hash(x) + 1) %
S
● თუ (hash(x) + 1) % S უჯრედი დაკავებულია, მაშინ ვამოწმებთ (hash(x) +
2) % S
● თუ (hash(x) + 2) % S უჯრედი დაკავებულია, მაშინ ვამოწმებთ (hash(x) +
3) % S
● და ასე შემდეგ, სანამ არ ვიპოვით ცარიელ უჯრედს.
მაგალითი
მაგალითი
ვთქვათ ჰეშ-ფუნქციაა key mod 5 და ჩასასმელი ელემენტების
თანმდევრობა: 50, 70, 76, 85, 93
მაგალითი
მაგალითი
მაგალითი
მაგალითი
მაგალითი
მაგალითი
ზოგადი ფორმულა
ღია მისამართების განსაზღვრა
კვადრატული გადასინჯვის მეთოდით
თუ დავაკვირდებით, დავინახავთ, რომ წრფივი გადასინჯვისას ცდებს შორის
ინტერვალი იზრდება ჰეშ-ფუნქციის მნიშვნელობის პროპორციულად, რაც
იწვევს გადაბმის ეფექტს (clustering).
კვადრატული გადასინჯვა წარმოადგენს მეთოდს, რომელიც ამ ამოცანის
გადაჭრას უზრუნველყოფს. ეს მიდგომა ასევე ცნობილია როგორც შუა
კვადრატის მეთოდი (Mid-Square Method).
ამ ტექნიკით, i-ურ ცდაზე (iteration) ხდება i² სლოტის შემოწმება. ძიება
ყოველთვის იწყება საწყისი ჰეშ-მისამართიდან, ხოლო თუ ეს უჯრედი
დაკავებულია, ვამოწმებთ სხვა შესაძლო ადგილებს.
პრინციპი
ვთქვათ hash(x)ჰეშ-ფუნქციით გამოთვლილ სლოტის ინდექსია.
● თუ hash(x) % S დაკავებულია, ვამოწმებთ (hash(x) + 1²) % S
● თუ (hash(x) + 1²) % S დაკავებულია, ვამოწმებთ (hash(x) + 2²) % S
● თუ (hash(x) + 2²) % S დაკავებულია, ვამოწმებთ (hash(x) + 3²) % S
ვთქვათ, ჰეშ-ცხრილში, რომლის ზომაა 7, გვსურს ჩავსვათ შემდეგი
ელემენტები: 22, 30, 50.
მაგალითი
მაგალითი
მაგალითი
მაგალითი
მაგალითი
ღია მისამართების განსაზღვრა ორმაგი
ჰეშირების მეთოდით.
ორმაგი ჰეშირება არის მეთოდი, რომლის დროსაც პრობირებას შორის
ინტერვალები განისაზღვრება დამატებითი ჰეშ-ფუნქციით. ეს ტექნიკა
კლასტერიზაციის (clustering) პრობლემას ამცირებს ოპტიმალური
გზით.
ამ მეთოდში, პრობირების მიმდევრობის ნაზრდი განისაზღვრება
დამატებითი ჰეშ-ფუნქციის გამოყენებით .
ვიყენებთ მეორე ჰეშ-ფუნქციას hash2(x), რათა i-ურ ცდაზე მონაცემი
განვათავსოთ i × hash2(x) სლოტში.
პრინციპი
ვთქვათ, რომ hash(x) წარმოადგენს ძირითადი ჰეშ-ფუნქციით გამოთვლილ
სლოტის ინდექსს.
● თუ hash(x) % S დაკავებულია, ვამოწმებთ (hash(x) + 1 × hash2(x)) %
S
● თუ (hash(x) + 1 × hash2(x)) % S დაკავებულია, ვამოწმებთ (hash(x) +
2 × hash2(x)) % S
● თუ (hash(x) + 2 × hash2(x)) % S დაკავებულია, ვამოწმებთ (hash(x) +
3 × hash2(x)) % S
● და ასე შემდეგ...
მაგალითი
ვთქვათ ჰეშ-ცხრილის ზომა 7, ჩასასმელი მონაცემები: 27, 43, 692, 72 და
მოცემული გვაქვს ორი ჰეშ-ფუნქცია:
● პირველი ჰეშ-ფუნქცია : h1(k) = k mod 7
● მეორე ჰეშ-ფუნქცია : h2(k) = 1 + (k mod 5)
შეჯამება
ჰეშირების დროს ღია დამისამართება (Open Addressing) არის
კოლიზიების მართვის ტექნიკა, რომლის დროსაც, თუ ორი ან მეტი
გასაღები ერთსა და იმავე უჯრაზე მიემართება, ალგორითმი ეძებს
სხვა თავისუფალ უჯრას, რათა შეინახოს კონფლიქტური გასაღები.
შეჯამება
წრფივი გადასინჯვა (Linear Probing) გულისხმობს, რომ კოლიზიის
შემთხვევაში ალგორითმი ეძებს უშუალოდ მომდევნო ხელმისაწვდომ უჯრას
ჰეშ-ცხრილში და იქ ათავსებს გასაღებს. თუ ეს უჯრა დაკავებულია, ძიება
გრძელდება მანამ, სანამ თავისუფალი ადგილი არ მოიძებნება. წრფივი
გადასინჯვა გამოირჩევა საუკეთესო ქეშ-შესრულებით (cache performance),
თუმცა აქვს კლასტერულობის (clustering) პრობლემა. მისი მთავარი
უპირატესობა მარტივი გამოთვლებია.
შეჯამება
კვადრატული პრობირება (Quadratic Probing) უფრო ფართოდ ანაწილებს
მონაცემებს. კოლიზიის შემთხვევაში ალგორითმი ეძებს შემდეგ შესაძლო
უჯრედს იმ ფორმულის მიხედვით, რომელიც მოიცავს საწყის ჰეშ-მნიშვნელობას
და კვადრატულ ფუნქციას. თუ ეს უჯრედი დაკავებულია, კვადრატული
ფუნქცია იზრდება და ძიება გრძელდება. კვადრატული გადასინჯვის მეთოდი
გარდამავალია ხაზობრივ პრობირებასა და ორმაგ ჰეშირებას შორის – მას აქვს
ზომიერი ქეშ-შესრულება და ზომიერი კლასტერულობა.
შეჯამება
ორმაგი ჰეშირება (Double Hashing) კოლიზიის შემთხვევაში მეორე ჰეშ-
ფუნქციას იყენებს, რათა დაადგინოს მომდევნო შესამოწმებელი უჯრედი.
ალგორითმი ჯერ გამოთვლის პირველადი ჰეშ-ფუნქციის მეშვეობით მიღებულ
მნიშვნელობას, შემდეგ კი მეორე ჰეშ-ფუნქციის გამოყენებით ქმნის დამატებით
ინდექსს. საბოლოო მისამართი არის ამ ორი მნიშვნელობის ჯამი. თუ ეს უჯრედი
დაკავებულია, დამატებითი ინდექსი იზრდება და ძიება გრძელდება. ორმაგი
ჰეშირება უზრუნველყოფს მონაცემების უფრო თანაბარ განაწილებას და არ
იწვევს კლასტერულობას, თუმცა აქვს შედარებით დაბალი ქეშ-შესრულება და
მოითხოვს მეტ გამოთვლით რესურსს, რადგან ორი ჰეშ-ფუნქცია უნდა
გამოითვალოს.
შედარება
ჰეშ-ცხრილის მუშაობაზე მნიშვნელოვანი გავლენა აქვს კოლიზიების მართვის
მეთოდის სწორ შერჩევას. ხაზობრივი პრობირება მარტივი და სწრაფია, მაგრამ
შეიძლება გამოიწვიოს კლასტერულობა, რაც წარმადობას ამცირებს.
კვადრატული პრობირება მეტ დისტანციას ქმნის ჩანაწერებს შორის, თუმცა
მასაც აქვს კლასტერულობის რისკი და ზოგიერთ უჯრედზე შესაძლოა ძიება
საერთოდ არ განხორციელდეს. ორმაგი ჰეშირება უფრო რთული მეთოდია,
მაგრამ უზრუნველყოფს მონაცემების თანაბარ განაწილებას და ზოგიერთ
შემთხვევაში უკეთეს შედეგს იძლევა.
პრაქტიკული განხორციელება
● Insert(k) – ჰეშ-ფუნქციის გამოყენებით ხდება ინდექსის გამოთვლა. თუ ეს
უჯრედი დაკავებულია, ძიება გრძელდება მანამ, სანამ თავისუფალი უჯრედი
არ მოიძებნება.
● Search(k) – პრობირება მიმდინარეობს მანამ, სანამ გასაღები არ
დაემთხვევა ან ცარიელი უჯრედი არ იქნება ნაპოვნი.
● Delete(k) – გასაღების წაშლა ხდება მისი სპეციალური ნიშნით აღნიშვნით
(“deleted”). ეს არ აფერხებს ძიების პროცესს, მაგრამ საშუალებას იძლევა,
რომ ამ ადგილას ახალი ჩანაწერი შეინახოს
➡ HashMap კლასი წარმოადგენს ჰეშ-ცხრილს.
● capacity – ცხრილის მაქსიმალური ზომა.
● size – ამჟამად შენახული ელემენტების რაოდენობა.
● arr – თვითონ ცხრილი, რომელიც ინახავს HashNode ობიექტებს.
● dummy – სპეციალური ობიექტი, რომელიც გამოიყენება წაშლილი ელემენტების
აღსანიშნად.
➡ ჰეშ-ფუნქცია უზრუნველყოფს გასაღებიდან ინდექსის გამოთვლას .
● ყველაზე მარტივი და ხშირად გამოყენებული ჰეშ-ფუნქციაა ნაშთიან გაყოფაზე
დაფუძნებული ჰეშ ფუნქცია. იგი უზრუნველყოფს, რომ ინდექსი დარჩეს 0-დან
capacity-1-მდე დიაპაზონში.
➡ ეს ფუნქცია ჩასვამს (key, value) წყვილს ჰეშ-ცხრილში:
1. გამოვითვლით ჰეშ-ინდექსს .
2. თუ შესაბამის უჯრედში უკვე არსებობს ელემენტი , წრფივი გადასინჯვით (Linear Probing)
ვეძებთ მომდევნო ცარიელ უჯრას .
3. თუ ვიპოვეთ თავისუფალი უჯრა ან წაშლილი უჯრა (dummy node), ვამატებთ ახალ კვანძს.
➡ ეს ფუნქცია წაშლის ელემენტს ჰეშ-ცხრილიდან:
1. ჰეშ-ინდექსის გამოთვლა .
2. თუ ელემენტი ნაპოვნია , მას ვნიშნავთ როგორც "წაშლილს" (dummy node).
3. თუ ელემენტი ვერ ვიპოვეთ , ვაბრუნებთ None.
⚠ მნიშვნელოვანია :
ჩვენ უბრალოდ არ ვშლით ელემენტს, არამედ ვცვლით მას dummy node-ით. ეს ძიების პროცესს
არ არღვევს .
➡ ეს ფუნქცია ეძებს გასაღებს ცხრილში:
1. თუ ჰეშ-ინდექსზე არის გასაღები , აბრუნებს მის მნიშვნელობას.
2. თუ გასაღები ვერ ვიპოვეთ , ვაგრძელებთ წრფივ გადასინჯვას.
3. უსასრულო ციკლისგან თავდაცვისთვის ვიყენებთ counter ცვლადს.
გმადლობთ ყურადღებისთვის!
