მოძრავი ჰეში და რაბინ-კარპი

ორი სტრიქონის შედარება O(m) ღირს, ორი რიცხვისა O(1). მოძრავი ჰეში ტექსტის ყოველ ფანჯარას რიცხვად აქცევს, რომელიც მუდმივ დროში ახლდება. ამაზე დგას პლაგიატის შემმოწმებლები და ფაილების დუბლიკატების მაძიებლები.

საშუალო⏱ 12 წთ
01

ისწავლე

იდეა, მექანიზმი და ფასი.

01სტრიქონი, როგორც რიცხვი

ჰეშ-ფუნქციები ჰეშ-ცხრილის გაკვეთილში უკვე გაიცანი: გასაღები რიცხვად ვაქციოთ და შემდეგ ეს რიცხვი გამოვიყენოთ. სტრიქონებისთვის სტანდარტული არჩევანია პოლინომიალური ჰეში. სტრიქონი წავიკითხოთ, როგორც p-ფუძიანი რიცხვის ციფრები:

h(s) = s[0]·p^(m−1) + s[1]·p^(m−2) + … + s[m−1] (mod M)

როცა A=1, B=2, p = 31 და M = 97, სტრიქონი ABAB ხდება 27. ტოლ სტრიქონებს ყოველთვის ტოლი ჰეში აქვთ. განსხვავებულებს ჩვეულებრივ განსხვავებული.

მას მარცხნიდან მარჯვნივ ვითვლით: h = h·p + code(c), ყოველ ნაბიჯზე ვიღებთ mod M-ს, რომ რიცხვები პატარა დარჩეს.

ABAB"ABAB"1× 31³2× 31²1× 31¹2× 31⁰კოდიწონა29791 + 1922 + 31 + 2 = 31746mod 9727← „თითის ანაბეჭდი“

02ფანჯრის გაწევა O(1)-ში

რაბინ-კარპი შაბლონს ერთხელ ჰეშავს, შემდეგ კი ტექსტზე m სიგრძის ფანჯარას აცურებს. ყოველი ფანჯრის თავიდან დათვლა ისევ O(m) იქნებოდა. ხერხი ისაა, რომ მეზობელ ფანჯრებს m − 1 საერთო სიმბოლო აქვთ:

  • მოვხსნათ პირველი სიმბოლო: გამოვაკლოთ code(first)·p^(m−1)
  • გავწიოთ ყველაფერი ერთი თანრიგით: გავამრავლოთ p-ზე
  • დავამატოთ ახალი სიმბოლო: მივუმატოთ code(next)

ეს ყოველ ნაბიჯზე სამი არითმეტიკული ოპერაციაა, m-ის სიგრძის მიუხედავად. p^(m−1) mod M ერთხელ წინასწარ დავთვალოთ. ფრთხილად გამოკლების შემდეგ უარყოფით რიცხვებთან: ნაშთის აღებამდე დაუმატე M.

ძველი ფანჯარა: 27ახალი ფანჯარა: 80ABABCABAB− მოვხსნათ A+ დავამატოთ Cnew = (old − 1·31³) · 31 + 3 (mod 97)(27 − 12) · 31 + 3 = 468 ≡ 80

03შეჯახებები: ყოველთვის გადაამოწმე

ჰეში უამრავ შესაძლო სტრიქონს M მნიშვნელობაში ატევს, ამიტომ ორ განსხვავებულ სტრიქონს ზოგჯერ ერთი ჰეში ექნება. ესაა შეჯახება (კოლიზია). M = 97-ისას AA-საც და DE-საც ჰეში 32 აქვს.

ამიტომ, როცა ფანჯრის ჰეში შაბლონისას დაემთხვევა, რაბინ-კარპი დამთხვევის გამოცხადებამდე ნამდვილ სიმბოლოებს ადარებს. ცრუ განგაში O(m) ღირს, მაგრამ არასწორ პასუხს არასოდეს იძლევა.

რეალურ კოდში გამოიყენე დიდი მარტივი რიცხვი, მაგ. M = 10⁹ + 7, შემთხვევითი ფუძით p, ან ერთდროულად ორი მოდული. მაშინ შეჯახებები იმდენად იშვიათია, რომ მოსალოდნელი დრო O(n + m)-ია. იგივე მოძრავი იდეა პასუხობს კითხვას „ტოლია თუ არა ეს ორი ქვესტრიქონი?“ O(1)-ში, პრეფიქსული ჰეშების O(n) წინასწარი დათვლის შემდეგ.

"AA"1·31 + 1 = 32"DE"4·31 + 5 = 129 ≡ 3232ერთი და იგივე ჰეში (mod 97)შევამოწმოთ სიმბოლოები:AA ≠ DE → ცრუ დამთხვევაmod ≈ 10⁹: შეჯახება იშვიათია, მაგრამ შესაძლებელი

ფასი ერთი შეხედვით

შაბლონის ჰეშიO(m)
ფანჯრის გაწევაO(1)
რაბინ-კარპი, მოსალოდნელიO(n + m)
ქვესტრიქონების ტოლობა პრეფიქსული ჰეშებითO(1)

დაიმახსოვრე

  1. პოლინომიალური ჰეში სტრიქონს p-ფუძიან რიცხვად კითხულობს M-ის მოდულით, ამიტომ ტოლ სტრიქონებს ტოლი ანაბეჭდი აქვთ.
  2. ფანჯრის გაწევა (პირველის მოხსნა, გამრავლება, შემდეგის დამატება) ჰეშს ყოველ ძვრაზე O(1)-ში ანახლებს.
  3. ტოლი ჰეში მხოლოდ მინიშნებაა: სიმბოლოები გადაამოწმე და დიდი მოდული გამოიყენე, რომ შეჯახებები იშვიათი იყოს.
02

ითამაშე

გაიარე ალგორითმი ბიჯ-ბიჯ, მერე სცადე შენს მონაცემებზე.

👀 რას უყურო: შეადარე სათაურის ორი რიცხვი: სიმბოლოები მხოლოდ მაშინ მოწმდება, როცა ფანჯრის ჰეში შაბლონისას უდრის.

ინტერაქტიული ვიზუალიზატორიაქ დააწკაპე, მერე space ← →
✎ შენი მონაცემებიასოები A–Z; ტექსტი 16-მდე, შაბლონი 6-მდე. ჰეში: p = 31, mod 97.

რაბინ-კარპი (მოძრავი ჰეში) · შაბლონის ჰეში: 27

ABABCABAB
ავაგოთ შაბლონის ჰეში სიმბოლო-სიმბოლო: ჩავრთოთ 'A' → 1 (mod 97).

ფსევდოკოდი

 1 patHash = Σ code(pat[i]) · p^(k-1-i)  mod m 2 windowHash = hash of text[0..k-1] 3 if windowHash == patHash: verify chars (guard against collisions) 4 else: not a match here 5 roll to next window in O(1): drop leading char, shift, add next
1 / 1
03

შეამოწმე

სამი კითხვა. აირჩიე პასუხი და ნახე ახსნა.

№1

როცა A=1, B=2, p = 10 და მოდული არ გვაქვს, რა არის "BAB"-ის ჰეში?

№2

ფანჯრის ჰეში შაბლონის ჰეშს დაემთხვა. რა უნდა გააკეთოს რაბინ-კარპმა?

№3

რატომ არის შემდეგ ფანჯარაზე გადასვლა O(1) და არა O(m)?

04

ივარჯიშე

რეალური ამოცანები გასამყარებლად, მარტივიდან რთულისკენ.