සරල ඉංග්‍රීසියෙන් උක්කොනන්ගේ උපසර්ග ගස් ඇල්ගොරිතම


1118

මේ අවස්ථාවේදී මට ටිකක් thick න බවක් දැනේ. ගස් සෑදීම සඳහා මගේ හිස සම්පූර්ණයෙන් ඔතා ගැනීමට මම දින ගණනාවක් තිස්සේ උත්සාහ කර ඇත්තෙමි, නමුත් මට ගණිතමය පසුබිමක් නොමැති නිසා, ගණිතමය සංකේත විද්‍යාව අධික ලෙස භාවිතා කිරීමට පටන් ගන්නා විට බොහෝ පැහැදිලි කිරීම් මග හැරේ. මා සොයාගෙන ඇති හොඳ පැහැදිලි කිරීමකට ආසන්නතම දෙය නම් උපසර්ග ගස් සමඟ වේගවත් සෙවීමයි , නමුත් ඔහු විවිධ කරුණු පිළිබඳව විස්තර කරන අතර ඇල්ගොරිතමයේ සමහර අංග අපැහැදිලි ය.

ස්ටැක් පිටාර ගැලීම පිළිබඳ මෙම ඇල්ගොරිතම පිළිබඳ පියවරෙන් පියවර පැහැදිලි කිරීම මා හැර වෙනත් බොහෝ දෙනෙකුට මිල කළ නොහැකි වනු ඇතැයි මට විශ්වාසයි.

යොමුව සඳහා, ඇල්ගොරිතම පිළිබඳ යුක්කොනන්ගේ පත්‍රය මෙන්න: http://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf

මගේ මූලික අවබෝධය, මෙතෙක්:

  • දී ඇති ටී එකක එක් එක් උපසර්ගය හරහා මට නැවත යෙදීමට අවශ්‍යයි
  • මට P උපසර්ගයේ S හි එක් එක් උපසර්ගය හරහා නැවත ගසට එකතු කළ යුතුය
  • ගසට S උපසර්ගය එක් කිරීම සඳහා, S හි සෑම අක්ෂරයක් හරහාම නැවත යෙදිය යුතුය, පුනරාවර්තනයන්ගෙන් සමන්විත වන්නේ, දැනට පවතින ශාඛාවකින් පහළට ගමන් කිරීම, S හි එකම අක්ෂර මාලාවකින් ආරම්භ වන අතර මා සිටින විට දාරයක් පැවත එන නෝඩ් වලට බෙදිය හැකිය. උපසර්ගයේ වෙනස් චරිතයක් වෙත ළඟා වන්න, නැතහොත් පහළට ගමන් කිරීමට ගැලපෙන දාරයක් නොතිබුනේ නම්. සී සඳහා පහළට ගමන් කිරීමට ගැලපෙන දාරයක් සොයාගත නොහැකි වූ විට, සී සඳහා නව පත්‍ර දාරයක් සාදනු ලැබේ.

මූලික ඇල්ගොරිතම O (n 2 ) ලෙස පෙනේ , බොහෝ පැහැදිලි කිරීම්වල පෙන්වා ඇති පරිදි, අපට සියලු උපසර්ගයන් හරහා ගමන් කළ යුතු බැවින්, එක් එක් උපසර්ගය සඳහා එක් එක් උපසර්ග හරහා පියවර තැබිය යුතුය. Ukkonen ගේ ඇල්ගොරිතමය මම හිතන්නේ නමුත් ඔහු නිසා භාවිතා යන ෙපර ෙයදුම පහිටුම් දක්වනය තාක්ෂණය පෙනෙන සුවිශේෂී වේ බව මම කරදර අවබෝධයක් ඇති කරනවා මෙයයි.

මට තේරුම් ගැනීමේ අපහසුතාවයක් ද ඇත:

  • හරියටම "සක්‍රීය ලක්ෂ්‍යය" පවරා ඇත්තේ, භාවිතා කරන්නේ සහ වෙනස් කරන්නේ කවදාද සහ කෙසේද යන්නයි
  • ඇල්ගොරිතමයේ කැනොනීකරණ අංගය සමඟ සිදුවන්නේ කුමක්ද
  • මා දුටු ක්‍රියාත්මක කිරීම් වලට ඔවුන් භාවිතා කරන මායිම් විචල්‍යයන් "නිවැරදි කිරීමට" අවශ්‍ය වන්නේ ඇයි?

මෙන්න සම්පුර්ණ කරන ලද C # ප්‍රභව කේතය. එය නිවැරදිව ක්‍රියා කරනවා පමණක් නොව, ස්වයංක්‍රීය කැනනයකරණයට සහය දක්වන අතර ප්‍රතිදානයේ වඩා හොඳ පෙනුමක් ඇති පෙළ ප්‍රස්ථාරයක් සපයයි. ප්‍රභව කේතය සහ නියැදි ප්‍රතිදානය පහත පරිදි වේ:

https://gist.github.com/2373868


යාවත්කාලීන කිරීම 2017-11-04

වසර ගණනාවකට පසු මම උපසර්ග ගස් සඳහා නව භාවිතයක් සොයාගෙන ඇති අතර ජාවාස්ක්‍රිප්ට් හි ඇල්ගොරිතම ක්‍රියාත්මක කර ඇත . සාරාංශය පහතින්. එය දෝෂ රහිත විය යුතුය. npm install chalkඑකම ස්ථානයේ සිට එය js ගොනුවකට දමන්න , ඉන්පසු වර්ණවත් ප්‍රතිදානයක් බැලීමට node.js සමඟ ධාවනය කරන්න. නිදොස් කිරීමේ කේතයක් නොමැතිව එකම සාරාංශයේ ඉවත් කළ අනුවාදයක් ඇත.

https://gist.github.com/axefrog/c347bf0f5e0723cbd09b1aaed6ec6fc6


2
ඩෑන් ගස්ෆීල්ඩ්ගේ පොතේ දක්වා ඇති විස්තරය ඔබ බැලුවාද ? එය ප්‍රයෝජනවත් බව මට පෙනී ගියේය.
jogojapan

4
සාරාංශය බලපත්‍රය සඳහන් නොකරයි - මට ඔබේ කේතය වෙනස් කර MIT යටතේ නැවත ප්‍රකාශයට පත් කළ හැකිද (පැහැදිලිවම ආරෝපණ සහිතව)?
යූරික්

2
ඔව්, ඔබේ ජීවිතය සඳහා යන්න. එය පොදු වසම ලෙස සලකන්න. මෙම පිටුවේ තවත් පිළිතුරක් සඳහන් කළ පරිදි, කෙසේ හෝ නිවැරදි කිරීමට අවශ්‍ය දෝෂයක් තිබේ.
නේතන් රිඩ්ලි

1
සමහර විට මෙම ක්‍රියාත්මක කිරීම අන් අයට උපකාරී වනු ඇත, goto code.google.com/p/text-indexing
cos

2
“එය පොදු වසම ලෙස සලකන්න” යනු සමහර විට පුදුමයට කරුණක් නොවන පිළිතුරකි. හේතුව, එම කාර්යය පොදු වසමෙහි තැබීමට ඔබට සැබවින්ම නොහැකි වීමයි. එබැවින් ඔබගේ "එය සලකා බලන්න ..." ප්‍රකාශය මගින් බලපත්‍රය අපැහැදිලි බව අවධාරණය කෙරෙන අතර කෘතියේ තත්වය ඔබට සැබවින්ම පැහැදිලි දැයි සැක කිරීමට පා er කයාට හේතු සපයයි . ඔබේ කේතය භාවිතා කිරීමට මිනිසුන්ට ඔබ කැමති නම්, කරුණාකර ඒ සඳහා බලපත්‍රයක් සඳහන් කරන්න, ඔබ කැමති ඕනෑම බලපත්‍රයක් තෝරන්න (නමුත්, ඔබ නීති lawyer යෙකු නොවේ නම්, කලින් පැවති බලපත්‍රයක් තෝරන්න!)
ජේම්ස් යංමන්

Answers:


2396

පහත දැක්වෙන්නේ යුක්කොනන් ඇල්ගොරිතම විස්තර කිරීමට උත්සාහ කිරීම, එය නූල් සරල වූ විට එය කරන්නේ කුමක්ද යන්න පෙන්වීමෙනි (එනම් නැවත නැවත අක්ෂර කිසිවක් අඩංගු නොවේ), පසුව එය සම්පූර්ණ ඇල්ගොරිතම දක්වා විහිදේ.

පළමුව, මූලික ප්‍රකාශ කිහිපයක්.

  1. අප ගොඩනඟන්නේ මූලික වශයෙන් සෙවුම් ත්‍රිකෝණයකට සමානය. එබැවින් මූල නෝඩයක් ඇත, එයින් පිටතට යන දාර නව නෝඩ් වලට මඟ පාදයි, සහ තවත් දාර ඒවායින් පිටතට යයි, සහ යනාදිය

  2. නමුත් : සෙවුම් ත්‍රිත්වයේ මෙන් නොව, දාරයේ ලේබල තනි අක්ෂර නොවේ. ඒ වෙනුවට, සෑම දාරයක්ම පූර්ණ සංඛ්‍යා යුගලයක් භාවිතයෙන් ලේබල් කර ඇත [from,to]. මේවා පෙළට යොමු කරන්නන් වේ. මෙම අර්ථයෙන් ගත් කල, සෑම දාරයක්ම අත්තනෝමතික දිගකින් යුත් ලේබලයක් දරයි, නමුත් ගත වන්නේ O (1) ඉඩක් පමණි (දර්ශක දෙකක්).

මූලික මූලධර්මය

පුනරාවර්තන අක්ෂර නොමැති නූලක්, විශේෂයෙන් සරල නූලක උපසර්ග ගසක් නිර්මාණය කරන්නේ කෙසේදැයි මම පළමුව නිරූපණය කිරීමට කැමැත්තෙමි:

abc

ඇල්ගොරිතම වමේ සිට දකුණට පියවරෙන් පියවර ක්‍රියා කරයි . නූලෙහි සෑම අක්ෂරයක් සඳහාම එක් පියවරක් ඇත. සෑම පියවරකටම එක් පුද්ගල මෙහෙයුමකට වඩා වැඩි ගණනක් ඇතුළත් විය හැකි නමුත්, සමස්ත මෙහෙයුම් ගණන O (n) බව අපි දකිමු (අවසානයේ අවසාන නිරීක්ෂණ බලන්න).

ඉතින්, අපි වමේ සිට ආරම්භ කර , මුලින්ම තනි අක්‍ෂරයක් පමණක් ඇතුල් කරන්න a, මූල නෝඩයේ සිට (වමේ) [0,#]කොළයකට දාරයක් සාදා එය ලේබල් කරන්න , එයින් අදහස් වන්නේ දාරය 0 වන ස්ථානයේ සිට ආරම්භ වන උපස්ථරය නිරූපණය කරන බවයි. දී වත්මන් අවසන් . මම සංකේතය භාවිතා #කරන්නේ වත්මන් අවසානය අර්ථ දැක්වීම සඳහා වන අතර එය 1 වන ස්ථානයේ (වහාම a).

එබැවින් අපට ආරම්භක ගසක් ඇත, එය මේ ආකාරයෙන් පෙනේ:

එහි තේරුම මෙයයි:

දැන් අපි 2 වන ස්ථානයට ඉදිරියට යමු (වහාම b). සෑම පියවරකදීම අපගේ ඉලක්කය වන්නේ සියලු උපසර්ග වත්මන් ස්ථානයට ඇතුළු කිරීමයි. අපි මෙය කරන්නේ

  • පවත්නා a-එජ් දක්වා පුළුල් කිරීමab
  • සඳහා නව දාරයක් ඇතුල් කිරීම b

අපගේ නිරූපණයේ දී මෙය පෙනේ

රූප විස්තරය මෙහි ඇතුළත් කරන්න

එහි තේරුම:

අපි කරුණු දෙකක් නිරීක්ෂණය කරමු :

  • සඳහා අද්දර නියෝජනය abවන එම එය මූලික ගසක් විය කිරීම සඳහා භාවිතා ලෙස: [0,#]. අපි වර්තමාන පිහිටීම #1 සිට 2 දක්වා යාවත්කාලීන කළ නිසා එහි අර්ථය ස්වයංක්‍රීයව වෙනස් වී ඇත .
  • සෑම දාරයක්ම O (1) අවකාශය පරිභෝජනය කරයි, මන්ද එය පෙළ කොපමණ සංඛ්‍යාවක් නිරූපණය කළද එය පෙළට යොමු වන්නේ ලකුණු දෙකකින් පමණි.

ඊළඟට අපි නැවත පිහිටීම වැඩි කර ගස යාවත්කාලීන කරන්නේ cපවතින සෑම දාරයකටම එකතු කර නව උපසර්ගය සඳහා එක් නව දාරයක් ඇතුල් කරමිනි c.

අපගේ නිරූපණයේ දී මෙය පෙනේ

එහි තේරුම:

අපි නිරීක්ෂණය කරන්නේ:

  • එක් එක් පියවරෙන් පසු වත්මන් ස්ථානය දක්වා නිවැරදි උපසර්ගය ගස වේ
  • පා in යේ අක්ෂර ඇති තරම් පියවර තිබේ
  • එක් එක් පියවරේ වැඩ ප්‍රමාණය O (1) වේ, මන්ද දැනට පවතින සියලුම දාර වැඩි කිරීමෙන් ස්වයංක්‍රීයව යාවත්කාලීන වන #අතර අවසාන අක්‍ෂරය සඳහා එක් නව දාරයක් ඇතුළත් කිරීම O (1) කාලය තුළ කළ හැකිය. එබැවින් දිග n නූලක් සඳහා අවශ්‍ය වන්නේ O (n) කාලය පමණි.

පළමු දිගුව: සරල පුනරාවර්තන

ඇත්ත වශයෙන්ම මෙය ඉතා සියුම් ලෙස ක්‍රියාත්මක වන්නේ අපගේ සංගීතයට කිසිදු පුනරාවර්තනයක් නොමැති නිසාය. අපි දැන් වඩාත් යථාර්ථවාදී නූලක් දෙස බලමු:

abcabxabcd

එය ආරම්භ abcපෙර උදාහරණය ලෙස, පසුව abනැවත නැවතත් ඇති අතර පසුව x, පසුව abcඅනුගමනය නැවත නැවතත් d.

පියවර 1 සිට 3 දක්වා: පළමු පියවර 3 න් පසු අපට පෙර උදාහරණයෙන් ගස ඇත:

පියවර 4: අපි 4 වන# ස්ථානයට ගමන් කරමු . මෙය දැනට පවතින සියලුම දාරයන් ව්‍යංගයෙන් යාවත්කාලීන කරයි:

වත්මන් පියවරේ අවසාන උපසර්ගය මුලදී ඇතුළත් කළ යුතුය a.

අප මෙය කිරීමට පෙර, අපි තවත් විචල්‍යයන් දෙකක් හඳුන්වා දෙන්නෙමු (ඊට අමතරව #), ඇත්ත වශයෙන්ම ඒවා සැමවිටම පැවතුන නමුත් අපි ඒවා මෙතෙක් භාවිතා කර නොමැත:

  • මෙම ක්රියාකාරී අවස්ථාවක , ත්රිත්ව වන (active_node,active_edge,active_length)
  • මෙම remainder, අප ඇතුල් කිරීමට අවශ්ය කොපමණ නව suffixes පෙන්නුම් යනු පූර්ණ සංඛ්යාවකි වන

මේ දෙකෙහි නියම අරුත ඉතා ඉක්මනින් පැහැදිලි වනු ඇත, නමුත් දැන් අපි කියමු:

  • සරල abcඋදාහරණයේ දී, ක්‍රියාකාරී ලක්ෂ්‍යය සැමවිටම (root,'\0x',0), එනම් active_nodeමූල නෝඩය, active_edgeශුන්‍ය අක්‍ෂරය ලෙස දක්වා ඇති '\0x'අතර active_lengthඑය ශුන්‍ය විය. මෙහි ප්‍රති was ලය වූයේ සෑම පියවරකදීම අප ඇතුළු කළ එක් නව දාරයක් නැවුම් ලෙස සාදන ලද දාරයක් ලෙස මූල නෝඩයට ඇතුළත් කිරීමයි. මෙම තොරතුරු නියෝජනය කිරීම සඳහා ත්‍රිත්වයක් අවශ්‍ය වන්නේ මන්දැයි අපි ඉක්මනින් බලමු.
  • මෙම remainderසෑම විටම එක් එක් පියවර ආරම්භයේදී 1 බවට නියම කර ඇත. මෙහි අර්ථය වූයේ එක් එක් පියවර අවසානයේ අපට සක්‍රියව ඇතුළත් කළ යුතු උපසර්ග ගණන 1 (සෑම විටම අවසාන අක්‍ෂරය පමණි).

දැන් මෙය වෙනස් වනු ඇත. අපි වත්මන් අවසාන අක්‍ෂරය මුලට ඇතුළු කරන aවිට, පිටතට යන දාරයක් දැනටමත් ආරම්භ වන බව අපට පෙනේ a, විශේෂයෙන් : abca. එවැනි අවස්ථාවක අප කරන දේ මෙන්න:

  • අපි මූල නෝඩයේ නැවුම් දාරයක් ඇතුල් නොකරමු[4,#] . ඒ වෙනුවට උපසර්ගය aදැනටමත් අපගේ ගසෙහි ඇති බව අපි දකිමු. එය අවසන් වන්නේ දිගු දාරයක් මැද ය, නමුත් අපට එයින් කරදරයක් නොවේ. අපි දේවල් එලෙසම තබමු.
  • අප ක්රියාකාරී අවස්ථාවක සකස් කිරීමට (root,'a',1). ඒ කියන්නේ සක්‍රිය ලක්ෂ්‍යය දැන් මූල නෝඩයේ පිටතට යන දාරයේ කොතැනක හෝ ඇති අතර, එය ආරම්භ වන්නේ a, විශේෂයෙන්, එම දාරයේ 1 වන ස්ථානයට පසුව ය. දාරය එහි පළමු අක්ෂරයෙන් සරලව දක්වා ඇති බව අපි දකිමු a. කිසියම් චරිතයකින් ආරම්භ වන එක් දාරයක් පමණක් තිබිය හැකි බැවින් එය ප්‍රමාණවත් වේ (සම්පූර්ණ විස්තරය කියවීමෙන් පසු මෙය සත්‍ය බව තහවුරු කරන්න).
  • අපි වැඩි කරන්නෙමු remainder, එබැවින් ඊළඟ පියවර ආරම්භයේදී එය 2 ක් වනු ඇත.

නිරීක්‍ෂණය: අප ඇතුළු කළ යුතු අවසාන උපසර්ගය දැනටමත් ගසෙහි පවතින බව සොයා ගත් විට , ගස කිසිසේත් වෙනස් නොවේ (අපි සක්‍රීය ස්ථානය යාවත්කාලීන කරන්නේ සහ remainder). ගස යනු වත්මන් ස්ථානය දක්වා වූ උපසර්ග ගසෙහි නිරවද්‍ය නිරූපණයක් නොවේ , නමුත් එහි සියලු උපසර්ග අඩංගු වේ (අවසාන උපසර්ගය ව්‍යංගයෙන්a අඩංගු වන නිසා ). එබැවින්, විචල්‍යයන් යාවත්කාලීන කිරීම හැරුණු විට (ඒවා සියල්ලම ස්ථාවර දිග, එබැවින් මෙය O (1) වේ), මෙම පියවරේදී කිසිදු කාර්යයක් සිදු නොවීය .

පියවර 5: අපි වත්මන් ස්ථානය 5 දක්වා යාවත්කාලීන කරමු #. මෙය ගස ස්වයංක්‍රීයව යාවත්කාලීන කරයි:

හා නිසා remainder2 වන , අපි, වර්තමාන තත්ත්වය අවසන් suffixes දෙකක් ඇතුල් කිරීමට අවශ්ය: abහා b. මෙය මූලික වශයෙන් එයට හේතුව:

  • මෙම aපෙර පියවරෙන් සිට ෙපර ෙයදුම නිසි ලෙස ඇතුළත් කර නැහැ. එබැවින් එය ඉතිරිව ඇති අතර , අප එක් පියවරක් ඉදිරියට ගොස් ඇති බැවින්, එය දැන් සිට වර්ධනය වී aඇත ab.
  • අපි නව අවසාන දාරය ඇතුළු කළ යුතුයි b.

ප්රායෝගිකව මෙයින් අදහස් කරන්නේ අපි ක්රියාකාරී ස්ථානයට ගොස් (එය aදැන් abcabඅද්දර ඇති දෙයට පිටුපසට යොමු කරයි ), සහ වත්මන් අවසාන අක්‍ෂරය ඇතුළත් කරන්න b. නමුත්: නැවතත්, එය bදැනටමත් එම අද්දරම පවතින බව පෙනේ.

ඉතින්, නැවතත්, අපි ගස වෙනස් නොකරමු. අපි සරලවම:

  • සක්‍රීය ලක්ෂ්‍යය යාවත්කාලීන කරන්න (root,'a',2)(පෙර මෙන් එකම නෝඩ් සහ දාරය, නමුත් දැන් අපි පිටුපසින් යොමු කරමු b)
  • වැටුප් වර්ධක මෙම remainder3 අප තවමත් නිසි පෙර පියවරෙන් සිට අවසන් අද්දර ඇතුළු කර නැහැ, අපි එක්කෝ වත්මන් අවසන් අද්දර ඇතුල් නැති නිසා.

පැහැදිලි කිරීමට: අපි ඇතුල් වුණා abහා bවත්මන් පියවර නිසා නොව, abමේ වන විටත් සොයා ගෙන, අපි ක්රියාකාරී අවස්ථාවක යාවත්කාලීන සහ පවා ඇතුල් කිරීමට උත්සාහ කළේ නැත b. මන්ද? මන්ද යත්, abගස වේ, සෑම ෙපර ෙයදුම (ඇතුළු එය b) ද, ගස විය යුතුය. සමහර විට ව්‍යංගයෙන් පමණක් විය හැකි නමුත් එය එහි තිබිය යුතුය, මන්ද අප මෙතෙක් ගස ඉදිකර ඇති ආකාරය නිසාය.

අපි ඉදිරියට පියවර 6 incrementing විසින් #. ගස ස්වයංක්‍රීයව යාවත්කාලීන වන්නේ:

නිසා remainder3 වන , අපි ඇතුල් කිරීමට ඇති abx, bxහා x. සක්‍රීය ලක්ෂ්‍යය අපට පවසන්නේ කොතැනින් abකෙළවරද, එබැවින් අපට අවශ්‍ය වන්නේ එහි පැන පැන ඇතුල් කිරීමයි x. ඇත්ත වශයෙන්ම, xතවම එහි නොමැත, එබැවින් අපි abcabxදාරය බෙදී අභ්‍යන්තර නෝඩයක් ඇතුළු කරමු:

දාර නිරූපණයන් තවමත් පෙළට යොමු කර ඇති බැවින් අභ්‍යන්තර නෝඩයක් බෙදීම හා ඇතුළත් කිරීම O (1) කාලය තුළ කළ හැකිය.

එබැවින් අපි ගනුදෙනු කර 2 දක්වා abxඅඩු remainderකර ඇත්තෙමු. දැන් අපි ඊළඟ ඉතිරි උපසර්ගය ඇතුළත් කළ යුතුය bx. නමුත් අප එය කිරීමට පෙර ක්‍රියාකාරී ලක්ෂ්‍යය යාවත්කාලීන කළ යුතුය. මේ සඳහා වන රීතිය, දාරයක් බෙදීමෙන් හා ඇතුළත් කිරීමෙන් පසුව, පහත රීතිය 1 ලෙස හඳුන්වනු ලබන අතර, active_nodeඑය මූල වන සෑම විටම අදාළ වේ (තවත් අවස්ථා සඳහා අපි 3 වන රීතිය ඉගෙන ගනිමු). මෙන්න රීතිය 1:

මූලයෙන් ඇතුළු කිරීමෙන් පසුව,

  • active_node මූලයේ පවතී
  • active_edge අප ඇතුළු කළ යුතු නව උපසර්ගයේ පළමු අක්ෂරයට සකසා ඇත, එනම් b
  • active_length 1 කින් අඩු වේ

එබැවින්, නව ක්‍රියාකාරී ලක්ෂ්‍ය ත්‍රිත්වයෙන් (root,'b',1)ඇඟවෙන්නේ ඊළඟ ඇතුල් කිරීම bcabxකෙළවරේ, අක්ෂර 1 ට පිටුපසින්, එනම් පිටුපසින් කළ යුතු bබවයි. අපට O (1) වේලාවේ ඇතුළත් කිරීමේ ස්ථානය හඳුනාගත හැකි අතර xදැනටමත් තිබේද නැද්ද යන්න පරීක්ෂා කරන්න . එය තිබුනේ නම්, අපි වර්තමාන පියවර අවසන් කර සෑම දෙයක්ම ඒ ආකාරයෙන්ම තබමු. නමුත් x නොපවතී, එබැවින් අපි දාරය බෙදීමෙන් එය ඇතුල් කරමු:

නැවතත්, මේ සඳහා O (1) කාලය ගත වූ අතර අපි remainder1 ට යාවත්කාලීන කරන අතර ක්‍රියාකාරී ලක්ෂ්‍යය (root,'x',0)1 වන රීතිය ලෙස දක්වා ඇත.

නමුත් අප විසින් කළ යුතු තවත් දෙයක් තිබේ. අපි මෙම රීතිය 2 ලෙස හඳුන්වමු :

අපි දාරයක් බෙදී නව නෝඩයක් ඇතුළු කළහොත්, එය වත්මන් පියවරේදී නිර්මාණය කරන ලද පළමු නෝඩය නොවේ නම් , අපි කලින් ඇතුළත් කළ නෝඩය සහ නව නෝඩය විශේෂ දර්ශකයක් හරහා උපසර්ග සම්බන්ධකයක් හරහා සම්බන්ධ කරමු . එය ප්‍රයෝජනවත් වන්නේ මන්දැයි අපි පසුව බලමු. මෙන්න අපට ලැබෙන දේ, උපසර්ග සබැඳිය තිත් දාරයක් ලෙස නිරූපණය කෙරේ:

වත්මන් පියවරේ අවසාන උපසර්ගය අප තවමත් ඇතුළත් කළ යුතුය x. සිට active_lengthක්රියාකාරී node එකක් මතම ඊට අදාල සංරචකයක් 0 දක්වා පහත වැටී ඇතත්, අවසන් ඇතුලත් කරන්න කෙලින්ම මූල සිදු කර ඇත. ආරම්භ වන මූල නෝඩයේ පිටතට යන දාරයක් නොමැති බැවින් x, අපි නව දාරයක් ඇතුල් කරමු:

අපට පෙනෙන පරිදි, වර්තමාන පියවරේදී ඉතිරිව ඇති සියලුම ඇතුළත් කිරීම් සිදු කරන ලදී.

අපි සෑම විටම මෙන් ඊළඟ අක්ෂරයට ස්වයංක්‍රීයව එකතු කරන = 7 සැකසීමෙන් 7 වන පියවරට ඉදිරියට යමු . ඉන්පසු අපි නව අවසාන අක්‍ෂරය සක්‍රිය ස්ථානයට (මූලයට) ඇතුළු කිරීමට උත්සාහ කර එය දැනටමත් එහි ඇති බව සොයා ගනිමු. එබැවින් අපි කිසිවක් ඇතුළත් නොකර වත්මන් පියවර අවසන් කර සක්‍රිය ලක්ෂ්‍යය යාවත්කාලීන කරමු .#a(root,'a',1)

දී පියවර 8 , #= 8, අපි ඇතුලත් bපෙර දැක ලෙස, හා, අපි ක්රියාකාරී අවස්ථාවක යාවත්කාලීන මෙම එකම මාර්ගය (root,'a',2)හා වැටුප් වර්ධක remainderනිසා, වෙන කිසිවක් නොකර bමේවන විටත්. කෙසේ වෙතත්, (O (1) වේලාවේදී) සක්‍රීය ලක්ෂ්‍යය දැන් කෙළවරක ඇති බව අපි දකිමු. අපි මෙය නැවත සැකසීමෙන් පිළිබිඹු කරමු (node1,'\0x',0). මෙන්න, මා භාවිතා node1කරන්නේ abදාරය අවසන් වන අභ්‍යන්තර නෝඩය වෙතය .

එවිට, පියවර #= 9 දී , අපි 'c' ඇතුළු කළ යුතු අතර, අවසාන උපක්‍රමය තේරුම් ගැනීමට මෙය අපට උපකාරී වනු ඇත:

දෙවන දිගුව: උපසර්ග සබැඳි භාවිතා කිරීම

සෑම විටම මෙන්, #යාවත්කාලීනය cස්වයංක්‍රීයව පත්‍ර දාරවලට එකතු වන අතර අපට 'සී' ඇතුළු කළ හැකිදැයි බැලීමට අපි ක්‍රියාකාරී ස්ථානයට යමු. 'සී' දැනටමත් එම අද්දර පවතින බව පෙනේ, එබැවින් අපි සක්‍රීය ලක්ෂ්‍යය (node1,'c',1), වර්ධක remainderසහ වෙන කිසිවක් නොකරමු.

දැන් පියවර #= 10 , remainder4 වේ, එබැවින් අපි මුලින්ම සක්‍රීය ස්ථානයට abcdඇතුළු කිරීමෙන් d( පියවර 3 කට පෙර සිට ඉතිරිව තිබිය යුතුය ) .

dසක්‍රීය ස්ථානයේ ඇතුළු කිරීමට උත්සාහ කිරීම O (1) වේලාවෙහි දාරය බෙදීමට හේතු වේ:

මෙම active_node, භේදය ආරම්භ කරන ලද රතු, ඉහත ලෙස ලකුණු කර ඇත. මෙන්න අවසාන රීතිය, රීතිය 3:

active_nodeමූල නෝඩ් නොවන දාරයකින් දාරයක් බෙදීමෙන් පසුව , අපි එම නෝඩයෙන් පිටතට යන උපසර්ග සබැඳිය අනුගමනය කර, කිසියම් දෙයක් තිබේ නම්, active_nodeඑය පෙන්වා ඇති නෝඩයට නැවත සකසන්න . උපසර්ග සම්බන්ධකයක් නොමැති නම්, අපි active_nodeමූලයට සකසමු. active_edge හා active_lengthනොවෙනස්ව තිබෙනවා.

එබැවින් ක්‍රියාකාරී ලක්ෂ්‍යය දැන් ඇති (node2,'c',1)අතර node2එය රතු පැහැයෙන් සලකුණු කර ඇත:

ඇතුළත් කිරීම abcdසම්පුර්ණ බැවින්, අපි remainder3 දක්වා අඩු වන අතර වත්මන් පියවරේ ඊළඟ ඉතිරි උපසර්ගය සලකා බලමු bcd. 3 වන රීතිය මඟින් සක්‍රීය ලක්ෂ්‍යය නිවැරදි නෝඩයට හා දාරයට සකසා ඇති අතර එම නිසා bcdඑහි අවසාන අක්‍ෂරය dසක්‍රීය ස්ථානයේ ඇතුළත් කිරීමෙන් ඇතුළත් කළ හැකිය .

මෙය කිරීමෙන් තවත් දාරයක් බෙදීමට හේතු වන අතර, 2 වන රීතිය නිසා , අප කලින් ඇතුළත් කළ නෝඩයේ සිට නව එකට උපසර්ග සම්බන්ධකයක් සෑදිය යුතුය:

අපි නිරීක්ෂණය කරමු: ක්‍රියාකාරී ලක්ෂ්‍යය නැවත සැකසීමට උපසර්ග සබැඳි මඟින් අපට හැකි වන අතර එමඟින් O (1) ප්‍රයත්නයේදී ඉතිරිව ඇති ඇතුල් කිරීම සිදු කළ හැකිය . ලේබලයේ ඇත්ත වශයෙන්ම නෝඩය තහවුරු කිරීමට ඉහත ප්‍රස්ථාරය දෙස බලන්නab දී node එකක් මතම ඊට අදාල සම්බන්ධ වේ b(එහි ෙපර ෙයදුම), හා මංසල abcසම්බන්ධ වේ bc.

වත්මන් පියවර තවම අවසන් නැත. remainderදැන් 2 වන අතර, ක්‍රියාකාරී ලක්ෂ්‍යය නැවත සැකසීමට අපි 3 වන රීතිය අනුගමනය කළ යුතුය. වත්මන් active_node(ඉහළ රතු) සඳහා උපසර්ග සම්බන්ධයක් නොමැති බැවින්, අපි root වෙත නැවත සකසමු. සක්‍රීය ස්ථානය දැන් (root,'c',1).

එබැවින් ඊළඟ ඇතුල් කිරීම සිදුවන්නේ මූල නෝඩයේ පිටතට යන එක් කෙළවරක වන අතර එහි ලේබලය ආරම්භ වන්නේ c:cabxabcd , පළමු අක්ෂරයට පිටුපසින්, එනම් පිටුපසිනි c. මෙය තවත් භේදයකට හේතු වේ:

නව අභ්‍යන්තර නෝඩයක් නිර්මාණය කිරීම මෙයට සම්බන්ධ බැවින්, අපි 2 වන රීතිය අනුගමනය කර කලින් නිර්මාණය කළ අභ්‍යන්තර නෝඩයෙන් නව උපසර්ග සම්බන්ධකයක් සකසමු:

(මම භාවිතා කරමි Graphviz ඩොට් , මේ කුඩා ප්රස්තාර සඳහා නව ෙපර ෙයදුම ලින්ක් නැවත සංවිධානය පවතින දාර කිරීමට තිතක් විය. ඒ නිසා ඉහත, ඇතුළු කරන ලද එකම දෙය නම්, නව ෙපර ෙයදුම ලින්ක් බව තහවුරු හොඳින් පරීක්ෂා කර බලන්න.)

මේ සමඟ, remainder1 ට සැකසිය හැකි අතර active_node, root මූල බැවින්, සක්‍රීය ලක්ෂ්‍යය යාවත්කාලීන කිරීමට අපි රීතිය 1 භාවිතා කරමු (root,'d',0). මෙයින් අදහස් කරන්නේ වත්මන් පියවරේ අවසාන ඇතුළු කිරීම තනි එකක් ඇතුළත් කිරීමයිd මූලයේ :

එය අවසාන පියවර වූ අතර අප අවසන් කර ඇත. ගණනක් ඇතඅවසාන නිරීක්ෂණ , නමුත්:

  • සෑම පියවරකදීම අපි ගමන් කරමු # 1 ස්ථානයකින් ඉදිරියට . මෙය O (1) කාලය තුළ සියලුම පත්‍ර නෝඩ් ස්වයංක්‍රීයව යාවත්කාලීන කරයි.

  • නමුත් එය අ) පෙර පියවර වලින් ඉතිරිව ඇති කිසිදු උපසර්ගයක් හා ආ) වත්මන් පියවරේ එක් අවසාන අක්‍ෂරයක් සමඟ කටයුතු නොකරයි .

  • remainderඅප විසින් අතිරේක ඇතුළත් කිරීම් කීයක් කළ යුතුදැයි අපට කියයි. මෙම ඇතුළත් කිරීම් වත්මන් ස්ථානයෙන් අවසන් වන නූලෙහි අවසාන උපසර්ගයට අනුරූප වේ #. අපි එකින් එක සලකා බලමු. වැදගත්: සෑම ලක්ෂ්‍යයක්ම O (1) වේලාවේදී සිදු කරනුයේ සක්‍රීය ලක්ෂ්‍යය හරියටම යා යුත්තේ කොතැනටදැයි අපට පවසන හෙයින්, ක්‍රියාකාරී ස්ථානයේදී අපට එක් අක්ෂරයක් පමණක් එකතු කළ යුතුය. මන්ද? අනෙක් අක්ෂර ව්‍යංගයෙන් අඩංගු වන නිසා (එසේ නොමැතිනම් ක්‍රියාකාරී ලක්ෂ්‍යය එය පවතින තැන නොවනු ඇත).

  • එවැනි එක් එක් ඇතුළත් කිරීමෙන් පසුව, අපි අඩු remainderවී උපසර්ග සබැඳිය තිබේ නම් එය අනුගමනය කරමු . එසේ නොවේ නම් අපි මූලයට යමු (රීතිය 3). අප දැනටමත් මූලයේ සිටී නම්, අපි රීතිය 1 භාවිතා කරමින් ක්‍රියාකාරී ලක්ෂ්‍යය වෙනස් කරමු. ඕනෑම අවස්ථාවක, ගත වන්නේ O (1) කාලය පමණි.

  • මෙම එක් ඇතුළත් කිරීමක් අතරතුර, අපට ඇතුළු කිරීමට අවශ්‍ය චරිතය දැනටමත් එහි ඇති බව අපට පෙනී ගියහොත්, අපි කිසිවක් නොකර වර්තමාන පියවර අවසන් කරන්නෙමු, remainder0 වුවද. හේතුව, ඉතිරිව ඇති ඕනෑම ඇතුල් කිරීමක් අප විසින් සෑදීමට උත්සාහ කළ උපසර්ගයන් වීමයි. එබැවින් ඒවා සියල්ලම වර්තමාන ගසෙහි ගම්‍ය වේ. කාරණයremainder > 0 වර්ගයන් වග අප පසුව ඉතිරි suffixes සමග ගනුදෙනු.

  • ඇල්ගොරිතම remainder> 0 අවසානයේ කුමක් කළ යුතුද? පා of යේ අවසානය මීට පෙර කොතැනක හෝ සිදු වූ උපස්ථරයක් වන විට මෙය සිදු වේ. එවැනි අවස්ථාවකදී, මීට පෙර සිදු නොවූ නූල් අවසානයේ එක් අමතර අක්‍ෂරයක් අප විසින් එකතු කළ යුතුය. සාහිත්‍යයෙහි සාමාන්‍යයෙන් ඩොලර් ලකුණ ඒ $සඳහා සංකේතයක් ලෙස භාවිතා කරයි. එය වැදගත් වන්නේ ඇයි? -> පසුව අපි උපසර්ග සෙවීම සඳහා සම්පුර්ණ කරන ලද උපසර්ග ගස භාවිතා කරන්නේ නම්, අපි තරඟ භාර ගත යුත්තේ ඒවා පත්‍රයක අවසන් වුවහොත් පමණි . එසේ නොවුවහොත් අපට ව්‍යාජ තරග රාශියක් ලැබෙනු ඇත, මන්දයත් තිබිය යුතු බොහෝ ගසෙහි ව්‍යංගයෙන් නූල් අඩංගු වන අතර ඒවා ප්‍රධාන නූලෙහි සත්‍ය උපසර්ග නොවේ. බල කිරීමremainderඅවසානයේ 0 වීම යනු සියලු උපසර්ග පත්‍ර තුණ්ඩයකින් අවසන් වන බව සහතික කිරීමේ ක්‍රමයකි. කෙසේ වෙතත්, ප්‍රධාන උපස්ථරයේ සාමාන්‍ය උපස්ථර සෙවීම සඳහා අපට ගස භාවිතා කිරීමට අවශ්‍ය නම්, පහත දැක්වෙන OP හි අදහස අනුව මෙම අවසාන පියවර ඇත්ත වශයෙන්ම අවශ්‍ය නොවේ., උපසර්ග පමණක් නොවේ

  • ඉතින් සමස්ත ඇල්ගොරිතමයේ සංකීර්ණතාව කුමක්ද? පෙළ දිග අක්ෂර n නම්, පැහැදිලිවම පියවර n ක් ඇත (හෝ අපි ඩොලර් ලකුණ එකතු කළහොත් n + 1). සෑම පියවරකදීම අපි කිසිවක් නොකරමු (විචල්‍යයන් යාවත්කාලීන කිරීම හැර), නැතහොත් අපි remainderඇතුළත් කිරීම් සිදු කරන්නෙමු , සෑම එකක්ම O (1) කාලය ගතවේ. සිටremainderපෙර පියවරයන්හි අප කිසිවක් කර නොමැති වාර ගණනක් පෙන්නුම් කරන සහ අප දැන් කරන සෑම ඇතුළු කිරීමක් සඳහාම අඩු වී ඇති හෙයින්, අප යමක් කරන මුළු වාර ගණන හරියටම n (හෝ n + 1) වේ. එබැවින් සම්පූර්ණ සංකීර්ණතාව O (n) වේ.

  • කෙසේ වෙතත්, මා නිසියාකාරව පැහැදිලි නොකළ එක් කුඩා දෙයක් තිබේ: එය සිදුවිය හැක්කේ අප උපසර්ග සබැඳියක් අනුගමනය කිරීම, ක්‍රියාකාරී ලක්ෂ්‍යය යාවත්කාලීන කිරීම සහ එහි active_lengthසංරචකය නව සමඟ හොඳින් ක්‍රියා නොකරන බව සොයා ගැනීමෙනි active_node. උදාහරණයක් ලෙස, මෙවැනි තත්වයක් සලකා බලන්න:

(ඉරුණු රේඛා ගසේ ඉතිරි කොටස දක්වයි. තිත් රේඛාව උපසර්ග සබැඳියකි.)

දැන් සක්‍රිය ලක්ෂ්‍යය වීමට ඉඩ දෙන්න (red,'d',3), එවිට එය දාරයේ පිටුපස ස්ථානයට fයොමු කරයි defg. දැන් අපි අවශ්‍ය යාවත්කාලීනයන් කළ බව උපකල්පනය කර දැන් රීතිය 3 ට අනුව ක්‍රියාකාරී ලක්ෂ්‍යය යාවත්කාලීන කිරීම සඳහා උපසර්ග සබැඳිය අනුගමනය කරන්න. නව ක්‍රියාකාරී ලක්ෂ්‍යය (green,'d',3). කෙසේ වෙතත්, dහරිත නෝඩයෙන් පිටතට යන -edge එක de, එබැවින් එහි ඇත්තේ අක්ෂර 2 ක් පමණි. නිවැරදි ක්‍රියාකාරී ලක්ෂ්‍යය සොයා ගැනීම සඳහා, අපි පැහැදිලිවම නිල් පැහැති නෝඩයට එම දාරය අනුගමනය කර නැවත සැකසිය යුතුය (blue,'f',1).

විශේෂයෙන්ම නරක නඩුවේ, active_lengthවැනි විශාල ලෙස විය හැකි remaindern ලෙස විශාල ලෙස විය හැකි. නිවැරදි ක්‍රියාකාරී ලක්ෂ්‍යය සොයා ගැනීම සඳහා අපට අවශ්‍ය වන්නේ එක් අභ්‍යන්තර නෝඩයක් උඩින් පනින්න පමණක් නොව, බොහෝ විට නරකම අවස්ථාවක n දක්වාය. එයින් අදහස් කරන්නේ ඇල්ගොරිතමයට සැඟවුණු O (n 2 ) සංකීර්ණතාවයක් ඇති බවයි, මන්ද සෑම පියවරකදීමremainder සාමාන්‍යයෙන් O (n) වන අතර, උපසර්ග සම්බන්ධතාවයක් අනුගමනය කිරීමෙන් පසු ක්‍රියාකාරී නෝඩයට පශ්චාත් ගැලපීම් O (n) විය හැකි ද?

හේතුව, ඇත්ත වශයෙන්ම අපට සක්‍රීය ලක්ෂ්‍යය (උදා: කොළ සිට නිල් දක්වා) වෙනස් කළ යුතු නම්, එය අපව එහි උපසර්ග සම්බන්ධකයක් ඇති නව නෝඩයකට ගෙන එනු ඇති අතර active_lengthඑය අඩු වනු ඇත. අපි උපසර්ග සම්බන්ධක දාමය අනුගමනය කරන විට, ඉතිරි කිරීම් ඇතුළත් කිරීම active_lengthඅඩු කළ හැකි අතර, අපට කළ හැකි ක්‍රියාකාරී ලක්ෂ්‍ය ගැලපුම් ගණන active_lengthඕනෑම වේලාවක වඩා විශාල විය නොහැක . සිට active_lengthහැකි වඩා විශාල විය කවදාවත් remainder, සහ remainder සෑම පියවරක් පමණක් නොව සාමාන්ය (n), නමුත් මෙතෙක් සිදු වැටුප් වර්ධක කර ඇති මුළු මුදල remainderසමස්ත ක්රියාවලිය ක් පුරා ඉතා සාමාන්ය (n) වන අතර, ක්රියාකාරී අවස්ථාවක වෙනස්කම් සංඛ්යාව වේ O (n) වලින් ද සීමා වේ.


76
කණගාටුයි, මෙය මා සිතුවාට වඩා ටිකක් දිගු විය. අප කවුරුත් දන්නා සුළු සුළු කාරණා ගණනාවක් එය පැහැදිලි කරන බව මට වැටහී ඇති අතර දුෂ්කර කොටස් තවමත් පැහැදිලිව පෙනෙන්නට නැත. අපි එය එකට හැඩයට සංස්කරණය කරමු.
jogojapan

69
මෙය ඩෑන් ගුස්ෆීල්ඩ්ගේ පොතේ ඇති විස්තරය මත පදනම් නොවන බව මම එකතු කළ යුතුය . ඇල්ගොරිතම විස්තර කිරීම සඳහා නව උත්සාහයක් වන්නේ මුලින් පුනරාවර්තනයක් නොමැති නූලක් සලකා බලා පුනරාවර්තන හැසිරවිය යුතු ආකාරය සාකච්ඡා කිරීමයි. මම හිතුවා ඒක වඩාත් බුද්ධිමත් වේවි කියලා.
jogojapan

8
ස්තූතියි og ජෝගෝජපන්, ඔබේ පැහැදිලි කිරීමට ස්තූතිවන්ත වෙමින් මට පූර්ණ ක්‍රියාකාරී උදාහරණයක් ලිවීමට හැකි විය. මම මූලාශ්‍රය ප්‍රකාශයට පත් කර ඇති බැවින් වෙනත් අයෙකුට එය භාවිතයට ගත හැකි යැයි සිතමි
නේතන් රිඩ්ලි

4
Ather නාතන් රිඩ්ලි ඔව් (මාර්ගය වන විට, එම අවසාන බිට් එක වන්නේ යුක්කොනන් කැනොනිකයිස් ලෙස හඳුන්වන දෙයයි). එය අවුලුවාලීමට එක් ක්‍රමයක් නම්, උපස්ථරයක් තුන් වතාවක් දිස්වන බවටත්, වෙනත් සන්දර්භයක තවත් වරක් දර්ශනය වන නූලකින් අවසන් වන බවටත් වග බලා ගැනීමයි. උදා abcdefabxybcdmnabcdex. ආරම්භක කොටසක් abcdතුළ නැවත නැවතත් abxy(මෙම පසු අභ්යන්තර node එකක් මතම ඊට අදාල නිර්මාණය ab) සහ නැවත abcdex, සහ එය අවසන් වේ bcd, තුළ පමණක් නොව දිස්වන bcdexසන්දර්භය, පමණක් නොව, දී bcdmnසන්දර්භය. abcdexඇතුළත් කිරීමෙන් පසුව , අපි ඇතුල් කිරීම සඳහා උපසර්ග සබැඳිය අනුගමනය කරන්නෙමු bcdex, එවිට
කැනොනිකයිස් කිරීම

6
හරි මගේ කේතය මුළුමනින්ම නැවත ලියා ඇති අතර දැන් ස්වයංක්‍රීය කැනොනිකල්කරණය ඇතුළුව සියලුම අවස්ථාවන් සඳහා නිවැරදිව ක්‍රියා කරයි, ඊට අමතරව වඩා හොඳ පෙළ ප්‍රස්ථාර ප්‍රතිදානයක් ඇත. gist.github.com/2373868
නේතන් රිඩ්ලි

132

ජෝගෝජපන්ගේ පිළිතුරේ දක්වා ඇති ප්‍රවේශය සමඟ මම උපසර්ග ගස ක්‍රියාත්මක කිරීමට උත්සාහ කළෙමි, නමුත් නීති රීති සඳහා භාවිතා කරන වචන නිසා සමහර අවස්ථාවල එය ක්‍රියාත්මක නොවීය. එපමණක් නොව, මෙම ප්‍රවේශය භාවිතා කරමින් කිසිවෙකු නිවැරදි උපසර්ග ගසක් ක්‍රියාත්මක කිරීමට සමත් නොවූ බව මම සඳහන් කළෙමි. පහත දැක්වෙන්නේ නීතිරීතිවල යම් යම් වෙනස් කිරීම් සහිතව ජෝගෝජපන්ගේ පිළිතුර පිළිබඳ "දළ විශ්ලේෂණයක්" මම ලියමි. වැදගත් උපසර්ග සබැඳි නිර්මාණය කිරීමට අපට අමතක වූ විට මම නඩුව විස්තර කරමි .

භාවිතා කරන අතිරේක විචල්යයන්

  1. සක්‍රීය ලක්ෂ්‍යය - ත්‍රිත්ව (active_node; active_edge; active_length), අප නව උපසර්ගයක් ඇතුළත් කිරීම ආරම්භ කළ යුත්තේ කොතැනින්දැයි පෙන්වයි.
  2. ඉතිරිය - අප පැහැදිලිවම එකතු කළ යුතු උපසර්ග ගණන පෙන්වයි . උදාහරණයක් ලෙස, අපගේ වචනය 'අබ්කාබ්කා' සහ ඉතිරි = 3 නම්, එයින් අදහස් වන්නේ අප අවසාන උපසර්ග 3 ක් ක්‍රියාවට නැංවිය යුතු බවයි : bca , ca සහ a .

එහෙනම් අපි භාවිතයට සංකල්පයක් අභ්යන්තර node එකක් මතම ඊට අදාල - සියලු ගැටිති, හැර අන් මූල හා leafs වේ අභ්යන්තර ගැටිති .

නිරීක්ෂණ 1

අප ඇතුළු කළ යුතු අවසාන උපසර්ගය දැනටමත් ගසෙහි පවතින බව සොයා ගත් විට, ගස කිසිසේත් වෙනස් නොවේ (අපි යාවත්කාලීන කරන්නේ active pointසහ remainder).

නිරීක්ෂණ 2

යම් අවස්ථාවක active_lengthදී වත්මන් දාරයේ ( edge_length) දිගට වඩා වැඩි හෝ සමාන නම් , අපි තදින් active pointපහළට edge_lengthවඩා විශාල වන තෙක් අපගේ පහළට ගමන් කරමු active_length.

දැන් අපි නීති නැවත අර්ථ දැක්වමු:

රීතිය 1

සක්‍රීය node = root වෙතින් ඇතුළත් කිරීමෙන් පසුව , ක්‍රියාකාරී දිග 0 ට වඩා වැඩි නම්:

  1. සක්‍රීය නෝඩ් වෙනස් නොවේ
  2. සක්‍රීය දිග අඩු වේ
  3. සක්‍රීය දාරය දකුණට මාරු කරනු ලැබේ (ඊළඟ ඇතුළත් කිරීමේ පළමු අක්‍ෂරයට අප ඇතුළු කළ යුතුය)

රීතිය 2

අප නව නිර්මාණය නම් අභ්යන්තර node එකක් මතම ඊට අදාල හෝ යම් සිට inserter කරන්න අභ්යන්තර node එකක් මතම ඊට අදාල , මෙම පළමු නොවේ එවැනි අභ්යන්තර node එකක් මතම ඊට අදාල වත්මන් පියවර, ඉන් පසුව අපි පෙර සබැඳෙයි එවැනි සමග node එකක් මතම ඊට අදාල මෙම හරහා එක් ෙපර ෙයදුම ලින්ක් .

මෙහි අර්ථ දැක්වීම Rule 2ජෝගෝජපන්ට වඩා වෙනස් ය, මෙහි දී අපි අලුතින් නිර්මාණය කළ ඒවා පමණක් නොව සැලකිල්ලට ගනිමු අභ්‍යන්තර නෝඩ් පමණක් නොව, අභ්‍යන්තර නෝඩ් ද සැලකිල්ලට ගනිමු.

3 වන රීතිය

මූල නෝඩ් නොවන සක්‍රිය නෝඩයෙන් ඇතුළු කිරීමෙන් පසුව , අපි උපසර්ග සබැඳිය අනුගමනය කර සක්‍රිය නෝඩය එය පෙන්වා ඇති නෝඩයට සැකසිය යුතුය. එය ෙපර ෙයදුම ලින්ක් නැත නම්, නියම ක්රියාකාරී node එකක් මතම ඊට අදාල වෙත මූල node එකක් මතම ඊට අදාල. කෙසේ වෙතත්, ක්‍රියාකාරී දාරය සහ ක්‍රියාකාරී දිග නොවෙනස්ව පවතී.

මෙම අර්ථ දැක්වීමේදී Rule 3අපි කොළ නෝඩ් (බෙදීම්-නෝඩ් පමණක් නොව) ඇතුළු කිරීම් ද සලකා බලමු.

අවසාන වශයෙන්, නිරීක්ෂණ 3:

අපි ගස් එකතු කිරීමට අවශ්ය සංකේතය අද්දර දැනටමත් කරන විට, අපි, අනුව Observation 1, එකම යාවත්කාලීන active pointහා remainderනොවෙනස්ව ගස හැර. නමුත් උපසර්ග සබැඳියක් අවශ්‍ය යැයි සලකුණු කර ඇති අභ්‍යන්තර නෝඩයක් තිබේ නම් , අපි එම නෝඩය අපගේ ධාරාව සමඟ උපසර්ග සම්බන්ධකයක් හරහා සම්බන්ධ කළ යුතුය .active node

එවැනි අවස්ථාවකදී අපි උපසර්ග සබැඳියක් එක් කළහොත් සහ එසේ නොකළහොත් cdddcdc සඳහා උපසර්ග ගසක උදාහරණය දෙස බලමු :

  1. අපි නම් කරන්න එපා වූ ෙපර ෙයදුම ලින්ක් හරහා ගැටිති සම්බන්ධ:

    • අවසාන අකුර එකතු කිරීමට පෙර c :

    • අවසාන අක්ෂරය එකතු කිරීමෙන් පසු c :

  2. අපි නම් කරන්න ඇති ෙපර ෙයදුම ලින්ක් හරහා ගැටිති සම්බන්ධ:

    • අවසාන අකුර එකතු කිරීමට පෙර c :

    • අවසාන අක්ෂරය එකතු කිරීමෙන් පසු c :

සැලකිය යුතු වෙනසක් නොමැති බව පෙනේ: දෙවන අවස්ථාවේදී තවත් උපසර්ග සම්බන්ධක දෙකක් තිබේ. නමුත් මෙම උපසර්ග සබැඳි නිවැරදි වන අතර, ඉන් එකක් - නිල් පැහැති නෝඩයේ සිට රතු පාට දක්වා - ක්‍රියාකාරී ලක්ෂ්‍යයක් සහිත අපගේ ප්‍රවේශය සඳහා ඉතා වැදගත් වේ . ගැටළුව නම්, අපි මෙහි උපසර්ග සබැඳියක් නොතැබුවහොත්, පසුව, අපි ගසට නව අකුරු කිහිපයක් එකතු කළ විට, ගස නිසා සමහර නෝඩ් එකතු කිරීම අප විසින් මඟ හැරෙනු ඇත Rule 3, එයට අනුව, ඒ අනුව, උපසර්ග සබැඳිය, එවිට අපි active_nodeමූලයට දැමිය යුතුය .

අපි ගසට අවසාන අකුර එකතු කරන විට, අපි නිල් පැහැති නෝඩයෙන් ඇතුළු කිරීමට පෙර රතු නෝඩය පැවතුනි (දාරය 'සී' ලෙස ලේබල් කර ඇත ). නිල් පැහැති නෝඩයෙන් ඇතුළු කිරීමක් ඇති බැවින්, අපි එය උපසර්ග සම්බන්ධකයක් අවශ්‍ය යැයි සලකුණු කරමු . ඉන්පසුව, සක්‍රීය ලක්ෂ්‍ය ප්‍රවේශය මත රඳා active node, රතු නෝඩයට සකසා ඇත. 'C' අක්ෂරය දැනටමත් අද්දර ඇති බැවින් අපි රතු නෝඩයෙන් ඇතුළු කිරීමක් නොකරමු . එයින් අදහස් කරන්නේ නිල් පැහැති නෝඩය උපසර්ග සම්බන්ධකයක් නොමැතිව තිබිය යුතු බවද? නැත, අපි නිල් පැහැති නෝඩය රතු එක සමඟ උපසර්ග සම්බන්ධකයක් හරහා සම්බන්ධ කළ යුතුය. එය නිවැරදි වන්නේ ඇයි? මෙම නිසා ක්රියාකාරී අවස්ථාවකප්‍රවේශය මඟින් අපි නිවැරදි ස්ථානයකට, එනම් කෙටි උපසර්ගයක් ඇතුළු කිරීම ක්‍රියාවට නැංවිය යුතු ඊළඟ ස්ථානයට යන බවට සහතික වේ.

අවසාන වශයෙන්, උපසර්ග ගස පිළිබඳ මගේ ක්‍රියාත්මක කිරීම් මෙන්න:

  1. ජාවා
  2. සී ++

මෙම "දළ විශ්ලේෂණය" ජෝගෝජපන්ගේ සවිස්තරාත්මක පිළිතුර සමඟ සංයෝජනය වී යමෙකුට තමාගේම උපසර්ග ගසක් ක්‍රියාත්මක කිරීමට උපකාරී වනු ඇතැයි අපි බලාපොරොත්තු වෙමු.


3
බොහොම ස්තූතියි සහ ඔබේ උත්සාහයට +1. මට විශ්වාසයි ඔබ නිවැරදියි .. මට ඒ ගැන එකවරම සිතීමට වෙලාවක් නැතත්. මම පසුව පරීක්ෂා කර මගේ පිළිතුර වෙනස් කරමි.
ජෝගෝජපන්

ගොඩාක් ස්තූතියි, එය ඇත්තෙන්ම උදව් විය. නිරීක්ෂණ 3 පිළිබඳව ඔබට වඩාත් නිශ්චිත විය හැකිද? උදාහරණයක් ලෙස, නව උපසර්ග සබැඳිය හඳුන්වා දෙන පියවර 2 හි රූප සටහන් ලබා දීම. නෝඩය සක්‍රිය නෝඩය හා සම්බන්ධ වී තිබේද? (අපි ඇත්ත වශයෙන්ම 2 වන නෝඩය ඇතුල් නොකරන බැවින්)
ඩයිස්ඩීස්

c මාකගොනොව් හේයි මට ඔබේ "cdddcdc" නූලට උපසර්ග ගසක් සෑදීමට උදව් කළ හැකිද? එසේ කිරීමෙන් මම තරමක් ව්‍යාකූල වී සිටිමි (ආරම්භක පියවර).
tariq zafar

3
3 වන රීතිය සම්බන්ධයෙන් ගත් කල, ස්මාර්ට් ක්‍රමයක් නම් මූලයේ උපසර්ග සබැඳිය root කිරීමට සැකසීම සහ (පෙරනිමියෙන්) සෑම නෝඩයකම උපසර්ග සම්බන්ධකය root ලෙස සකසන්න. මේ අනුව අපට කන්ඩිෂනේෂන් වළක්වා ගත හැකි අතර උපසර්ග සබැඳිය අනුගමනය කරන්න.
වර්ග

1
aabaacaadඅමතර උපසර්ග සබැඳියක් එක් කිරීමෙන් ත්‍රිත්වය යාවත්කාලීන කිරීමේ කාලය අඩු කළ හැකි බව පෙන්වන එක් අවස්ථාවකි. ජෝගෝජපන් තනතුරේ අවසාන ඡේද දෙකේ නිගමනය වැරදිය. මෙම ලිපියේ සඳහන් උපසර්ග සබැඳි අප එකතු නොකරන්නේ නම්, සාමාන්‍ය කාල සංකීර්ණතාව O (nlong (n)) හෝ ඊට වැඩි විය යුතුය. නිවැරදි දේ සොයා ගැනීම සඳහා ගස ඇවිදීමට අමතර කාලයක් ගත වන බැවිනි active_node.
IvanaGyro

10

Og ජෝගෝජපන් විසින් හොඳින් පැහැදිලි කරන ලද නිබන්ධනයට ස්තූතියි , මම පයිතන් හි ඇල්ගොරිතම ක්‍රියාත්මක කළෙමි.

ජෝගෝජපන් විසින් සඳහන් කරන ලද සුළු ගැටළු කිහිපයක් මා බලාපොරොත්තු වූවාට වඩා නවීන බව පෙනේ, ඉතා ප්‍රවේශමෙන් ප්‍රතිකාර කළ යුතුය. මගේ ක්‍රියාත්මක කිරීම ශක්තිමත් කිරීමට දින කිහිපයක් ගත විය (මම සිතමි). ගැටළු සහ විසඳුම් පහත ලැයිස්තු ගත කර ඇත:

  1. අවසන්Remainder > 0 මෙම තත්ත්වය ද සිදු කළ හැකි හැරෙනවා දිග හැරෙන පියවරේදී , සමස්ත ඇල්ගොරිතමය පිළිබඳ පමණක් නොව අවසානයේ. එය සිදු වූ විට, අපට ඉතිරි, ඇක්ට්නෝඩ්, ඇක්ටිජ් සහ ක්‍රියාකාරීත්වය නොවෙනස්ව තැබිය හැකිය , වත්මන් දිග හැරෙන පියවර අවසන් කළ හැකිය, සහ මුල් පියවරේ ඊළඟ වරහන වත්මන් මාර්ගයේ තිබේද යන්න මත පදනම්ව නැමීම හෝ දිග හැරීම දිගටම කරගෙන යන්න. නැහැ.

  2. පිම්මේ නෝඩ්: අපි උපසර්ග සබැඳියක් අනුගමනය කරන විට, සක්‍රීය ලක්ෂ්‍යය යාවත්කාලීන කරන්න, ඉන්පසු එහි සක්‍රීය_ දිග සංරචකය නව සක්‍රීය_නෝඩය සමඟ හොඳින් ක්‍රියා නොකරන බව සොයා ගන්න. බෙදීමට හෝ පත්‍රයක් ඇතුළු කිරීමට සුදුසු ස්ථානයට අප ඉදිරියට යා යුතුය. මෙම ක්රියාවලිය විය හැකි නොවන බව, සරල වූ ද actlength හා actedge තබා සියලු ආකාරයෙන් වෙනස්, ඔබ ආපසු යාමට ඇති විට ගමන් කරමින් තුළ නිසා මූල node එකක් මතම ඊට අදාල වන actedge හා actlength විය හැකි වැරදි නිසා එම පියවර ය. එම තොරතුරු තබා ගැනීමට අපට අතිරේක විචල්‍යයන් අවශ්‍ය වේ.

    රූප විස්තරය මෙහි ඇතුළත් කරන්න

අනෙක් ගැටළු දෙක කෙසේ හෝ ඇමානගොනොව් විසින් පෙන්වා දී ඇත

  1. භේදය පිරිහීමට ලක්විය හැකිය දාරයක් බෙදීමට උත්සාහ කරන විට, යම් වේලාවකදී ඔබට පෙනෙනුයේ භේදය මෙහෙයුම හරියටම නෝඩ් එකක ඇති බවය. අපට අවශ්‍ය වන්නේ එම නෝඩයට නව පත්‍රයක් එක් කිරීම පමණි, එය සම්මත දාර බෙදීමේ මෙහෙයුමක් ලෙස ගන්න, එයින් අදහස් වන්නේ උපසර්ග සම්බන්ධතා ඇත්නම් ඒවා අනුරූපව පවත්වා ගත යුතු බවයි.

  2. සැඟවුණු උපසර්ග සබැඳි ගැටළුව 1 සහ 2 ගැටලුව නිසා ඇති වූ තවත් විශේෂ අවස්ථාවක් තිබේ. සමහර විට අපට බෙදීම සඳහා නෝඩ් කිහිපයක් නිවැරදි ලක්ෂ්‍යයට බලාපොරොත්තු විය යුතුය, ඉතිරි නූල සහ මාර්ග ලේබල සංසන්දනය කිරීමෙන් අප ගමන් කරන්නේ නම් නිවැරදි ලක්ෂ්‍යය ඉක්මවා යා හැකිය . කිසියම් අවස්ථාවකදී උපසර්ග සබැඳිය නොදැනුවත්වම නොසලකා හරිනු ඇත. ඉදිරියට යන විට නිවැරදි කරුණ මතක තබා ගැනීමෙන් මෙය වළක්වා ගත හැකිය . බෙදීම් නෝඩය දැනටමත් තිබේ නම් උපසර්ග සම්බන්ධතාවය පවත්වා ගත යුතුය, නැතහොත් දිග හැරෙන පියවරක් තුළ 1 ගැටලුව පවා සිදුවේ.

අවසාන වශයෙන්, පයිතන් හි මගේ ක්‍රියාත්මක කිරීම පහත පරිදි වේ:

ඉඟි: ඉහත කේතයේ අශෝභන ගස් මුද්‍රණ කාර්යයක් ඇතුළත් වන අතර එය නිදොස්කරණය කිරීමේදී ඉතා වැදගත් වේ. එය මට බොහෝ කාලයක් ඉතිරි කර ඇති අතර විශේෂ අවස්ථා සොයා ගැනීමට පහසුය.


10

මගේ පිළිතුර අතිරික්තයක් ලෙස පෙනේ නම් සමාව ඉල්ලන්න, නමුත් මම මෑතකදී උක්කොනන්ගේ ඇල්ගොරිතම ක්‍රියාත්මක කළ අතර දින ගණනාවක් තිස්සේ ඒ සමඟ පොරබදමින් සිටියෙමි. ඇල්ගොරිතමයේ සමහර මූලික කරුණු ඇයි සහ කෙසේද යන්න තේරුම් ගැනීමට මට මෙම විෂය පිළිබඳ බහුවිධ ලිපි කියවීමට සිදු විය.

පෙර පිළිතුරු වල 'නීති' ප්‍රවේශය යටින් පවතින හේතු තේරුම් ගැනීමට උපකාරී නොවන බව මට පෙනී ගියේය, එබැවින් මම ප්‍රායෝගිකව පමණක් අවධානය යොමු කරමින් පහත සියල්ල ලියා ඇත. මා කළාක් මෙන් ඔබ වෙනත් පැහැදිලි කිරීම් අනුගමනය කිරීමට අරගල කර ඇත්නම්, සමහර විට මගේ අතිරේක පැහැදිලි කිරීම එය ඔබ වෙනුවෙන් 'ක්ලික්' කරයි.

මම මගේ C # ක්‍රියාත්මක කිරීම මෙහි ප්‍රකාශයට පත් කළෙමි: https://github.com/baratgabor/SuffixTree

මම මෙම විෂය පිළිබඳ විශේෂ expert යෙකු නොවන බව කරුණාවෙන් සලකන්න, එබැවින් පහත දැක්වෙන කොටස් වල සාවද්‍යතා (හෝ ඊට වඩා නරක) අඩංගු විය හැකිය. ඔබට කිසියම් දෙයක් හමු වුවහොත්, සංස්කරණය කිරීමට නිදහස් වන්න.

පූර්වාවශ්යතාවයන්

පහත දැක්වෙන පැහැදිලි කිරීමේ ආරම්භක ලක්ෂ්‍යය උපකල්පනය කරන්නේ ඔබ උපසර්ග ගස්වල අන්තර්ගතය සහ භාවිතය සහ උක්කොනන්ගේ ඇල්ගොරිතමයේ ලක්ෂණ ගැන හුරුපුරුදු බවයි, උදා. මූලික වශයෙන්, මම හිතන්නේ ඔබ දැනටමත් වෙනත් පැහැදිලි කිරීම් කිහිපයක් කියවා ඇති බවයි.

(කෙසේ වෙතත්, ප්‍රවාහය සඳහා මට මූලික ආඛ්‍යානයක් එකතු කිරීමට සිදු විය, එබැවින් ආරම්භය සැබවින්ම අතිරික්තයක් ලෙස හැඟෙනු ඇත.)

වඩාත්ම සිත් ගන්නා සුළු කොටස වන්නේ උපසර්ග සබැඳි භාවිතා කිරීම සහ මූලයෙන් ගලවා ගැනීම අතර වෙනස පැහැදිලි කිරීමයි . මෙය ක්‍රියාත්මක කිරීමේදී මට බොහෝ දෝෂ සහ හිසරදය ලබා දී ඇත.

විවෘත-අවසන් පත්‍ර නෝඩ් සහ ඒවායේ සීමාවන්

වඩාත්ම මූලික 'උපක්‍රමය' යනු 'විවෘත' යන උපසර්ගවල අවසානය අපට තැබිය හැකි බව වටහා ගැනීම බව ඔබ දැනටමත් දන්නා බව මට විශ්වාසයි, එනම් අවසානය ස්ථිතික අගයකට සැකසීම වෙනුවට නූල් වල වර්තමාන දිග සඳහන් කිරීම. මේ ආකාරයෙන් අපි අතිරේක අක්ෂර එකතු කරන විට, එම අක්ෂර සියල්ලම උපසර්ග ලේබල් වලට ව්‍යංගයෙන් එකතු කරනු ලැබේ.

නමුත් මෙම විවෘත උපසර්ගය - පැහැදිලි හේතු නිසා - ක්‍රියා කරන්නේ නූල් වල අවසානය නිරූපණය කරන නෝඩ් සඳහා පමණි, එනම් ගස් ව්‍යුහයේ පත්‍ර නෝඩ් ය. අපි ගස මත ක්‍රියාත්මක කරන ශාඛා මෙහෙයුම් (නව ශාඛා නෝඩ් සහ කොළ නෝඩ් එකතු කිරීම) ඔවුන්ට අවශ්‍ය සෑම තැනකම ස්වයංක්‍රීයව ප්‍රචාරණය නොවේ.

ගසෙහි නැවත නැවත උපස්ථරයන් පැහැදිලිව නොපෙන්වන බව බොහෝ විට එය මූලික විය යුතු අතර සඳහන් කිරීම අවශ්‍ය නොවනු ඇත. කෙසේ වෙතත්, පුනරාවර්තන නොවන උපස්ථරයක් පුනරාවර්තනය නොවන අක්ෂරයකට මුහුණ දීමෙන් අවසන් වන විට, එම අවස්ථාවේ සිට අපසරනය නිරූපණය කිරීම සඳහා අපි එම අවස්ථාවේ දී අතු බෙදීමක් කළ යුතුය.

උදාහරණයක් ලෙස 'ABCXABCY' නූලට (පහත බලන්න), X සහ Y වලට අතු බෙදීම ABC , BC සහ C යන විවිධ උපසර්ග තුනකට එකතු කළ යුතුය ; එසේ නොමැතිනම් එය වලංගු උපසර්ග ගසක් නොවනු ඇති අතර, මුල සිට පහළට අක්ෂර ගැලපීමෙන් අපට සියලු උපස්ථර සොයාගත නොහැකි විය.

නැවත වරක් අවධාරණය කිරීම සඳහා - ගසෙහි උපසර්ගයක් මත අප විසින් සිදු කරනු ලබන ඕනෑම මෙහෙයුමක් එහි අඛණ්ඩ උපසර්ගයන්ගෙන් ද පිළිබිඹු විය යුතුය (උදා: ABC> BC> C), එසේ නොමැතිනම් ඒවා වලංගු උපසර්ගයන් ලෙස නතර වේ.

උපසර්ගවල අතු නැවත නැවත කිරීම

නමුත් අප විසින් මෙම අත්පොත යාවත්කාලීන කිරීම් කළ යුතු බව අප පිළිගත්තද, යාවත්කාලීන කළ යුතු උපසර්ග කොපමණ දැයි අපි දන්නේ කෙසේද? අප විසින් පුනරාවර්තී අක්ෂර A (සහ අනෙක් අක්ෂර අනුපිළිවෙලින්) එකතු කරන විට, උපසර්ගය ශාඛා දෙකකට බෙදිය යුත්තේ කවදාද / කොතැනද යන්න පිළිබඳව අපට තවමත් අවබෝධයක් නැත. බෙදීමේ අවශ්‍යතාවය නිශ්චය වන්නේ අපට පළමු පුනරාවර්තන නොවන චරිතය හමු වූ විට පමණි, මේ අවස්ථාවේ දී Y ( ගසෙහි දැනටමත් පවතින X වෙනුවට ).

අපට කළ හැකි දෙය නම් අපට කළ හැකි දීර් est තම නූල් ගැලපීම සහ පසුව යාවත්කාලීන කිරීමට අවශ්‍ය උපසර්ග ගණන ගණනය කිරීමයි. මේ මොකක්ද 'ඉතිරි' යන අර්ථයයි.

'ඉතිරි' සහ 'ගලවා ගැනීම' යන සංකල්පය

විචල්‍යය remainderඅපට අතු බෙදීමෙන් තොරව ව්‍යංගයෙන් එකතු කළ අක්ෂර කීයක් කියයිද; අපට නොගැලපෙන පළමු අක්‍ෂරය සොයාගත් පසු අතු බෙදීමේ ක්‍රියාවලිය නැවත සිදු කිරීම සඳහා අප කොපමණ උපසර්ගයන් නැරඹිය යුතුද? මෙය ගසෙහි මූලයේ සිට අප 'ගැඹුරු' අක්ෂර කීයකට සමානද යන්නට සමාන වේ.

එබැවින්, ABCXABCY නූලෙහි පෙර උදාහරණය සමඟ රැඳී සිටිමින් , අපි නැවත නැවතත් ABC කොටස 'ව්‍යංගයෙන්' ගැලපෙන අතර , remainderඑක් එක් කාලය වැඩි කරමින්, එහි ප්‍රති results ලය 3 හි ඉතිරි වේ. එවිට අපට පුනරාවර්තනය නොවන 'Y' අක්ෂරය හමු වේ. මෙන්න අපි මීට පෙර සඳහන් කළේය බෙදී ABCX බවට ABC -> X හා ABC -> Y . එවිට අපි remainder3 සිට 2 දක්වා අඩු කරන්නෙමු , මන්ද අප දැනටමත් ඒබීසී ශාඛාව ගැන සැලකිලිමත් වූ බැවිනි . දැන් අපි අවසාන අක්ෂර 2 - BC - ගැලපීමෙන් මූලය සිට අපට බෙදීමට අවශ්‍ය ස්ථානයට ලඟා වන අතර, අපි BCXබෙදන්නෙමු ක්‍රි.පූ. ->X හා ක්රි.පූ -> Y . නැවතත්, අපි remainder1 දක්වා අඩු කර, මෙහෙයුම නැවත කරන්නෙමු ; remainder0 වන තෙක්. අවසාන වශයෙන්, අපි වත්මන් අක්ෂරය ( Y ) මුලට ද එකතු කළ යුතුය .

මෙම මෙහෙයුම, අප විසින් මෙහෙයුමක් කළ යුතු ස්ථානයට ලඟාවීම සඳහා මූලයේ අඛණ්ඩ උපසර්ගයන් අනුගමනය කිරීමෙන් උක්කොනන්ගේ ඇල්ගොරිතමයේ ' ගලවා ගැනීම ' ලෙස හැඳින්වෙන අතර සාමාන්‍යයෙන් මෙය ඇල්ගොරිතමයේ වඩාත්ම මිල අධික කොටස වේ. බොහෝ නෝඩ් දුසිම් ගණනක් හරහා (අපි මෙය පසුව සාකච්ඡා කරමු) දහස් වාරයක් පුරා දිගු උපස්ථර 'නැවත සැකසීමට' අවශ්‍ය දිගු නූලක් ගැන සිතන්න.

විසඳුමක් ලෙස, අපි 'උපසර්ග සබැඳි' ලෙස හඳුන්වන දේ හඳුන්වා දෙමු .

'උපසර්ග සබැඳි' සංකල්පය

උපසර්ග සබැඳි මූලික වශයෙන් අපට සාමාන්‍යයෙන් 'නැවත පණ ගැන්විය යුතු' ස්ථාන වෙත යොමු වේ, එබැවින් මිල අධික රෙස්කන් මෙහෙයුම වෙනුවට අපට සම්බන්ධිත ස්ථානයට පැනිය හැකිය, අපගේ වැඩ කරන්න, ඊළඟ සම්බන්ධිත ස්ථානයට පනින්න, සහ නැවත නැවත කරන්න - එතෙක් යාවත්කාලීන කිරීමට තවත් ස්ථාන නොමැත.

ඇත්ත වශයෙන්ම එක් විශාල ප්‍රශ්නයක් වන්නේ මෙම සබැඳි එකතු කරන්නේ කෙසේද යන්නයි. පවත්නා පිළිතුර නම්, අපි නව ශාඛා නෝඩ් ඇතුළු කරන විට සබැඳි එක් කළ හැකි අතර, ගසෙහි සෑම දිගුවකම ශාඛා නෝඩ් ස්වභාවිකව එකින් එක නිර්මාණය වී ඇති අතර ඒවා එකට සම්බන්ධ කිරීමට අපට අවශ්‍ය වේ. . කෙසේ වෙතත්, අප විසින් අවසන් වරට සාදන ලද ශාඛා නෝඩයේ (දීර් est තම උපසර්ගය) කලින් නිර්මාණය කළ එකට සම්බන්ධ කළ යුතුය, එබැවින් අප විසින් නිර්මාණය කරන ලද අන්තිම කෑෂ් කිරීම, අප විසින් නිර්මාණය කරන ලද ඊළඟට එය සම්බන්ධ කිරීම සහ අලුතින් සාදන ලද එක හැඹිලි කිරීම අවශ්‍ය වේ.

එක් ප්‍රතිවිපාකයක් නම්, අපට බොහෝ විට අනුගමනය කිරීමට උපසර්ග සබැඳි නොමැති වීමයි, මන්ද දී ඇති ශාඛා නෝඩය දැන් නිර්මාණය කර ඇති බැවිනි. මෙම අවස්ථා වලදී අප තවමත් ඉහත සඳහන් කළ ' ගලවා ගැනීමේ ' මූලයට ආපසු යා යුතුය . ඇතුලත් කිරීමෙන් පසු, උපසර්ග සබැඳිය භාවිතා කිරීමට හෝ මූලයට පනින්නැයි ඔබට උපදෙස් දෙනුයේ මේ නිසා ය.

(හෝ විකල්පයක් ලෙස, ඔබ මව් පොයින්ටර් නෝඩ් වල ගබඩා කරන්නේ නම්, ඔබට දෙමව්පියන් අනුගමනය කිරීමට උත්සාහ කළ හැකිය, ඔවුන්ට සබැඳියක් තිබේදැයි පරීක්ෂා කර බලා එය භාවිතා කරන්න. මෙය ඉතා කලාතුරකින් සඳහන් කර ඇති බව මට පෙනී ගියේය, නමුත් උපසර්ග සම්බන්ධක භාවිතය එසේ නොවේ ගල් වලින් සකසා ඇත. හැකි ප්‍රවේශයන් කිහිපයක් ඇති අතර, යටින් පවතින යාන්ත්‍රණය ඔබ තේරුම් ගන්නේ නම්, ඔබේ අවශ්‍යතාවන්ට ගැලපෙන එකක් ක්‍රියාත්මක කළ හැකිය.)

'ක්‍රියාකාරී ලක්ෂ්‍යය' යන සංකල්පය

මේ දක්වා අපි ගස තැනීම සඳහා කාර්යක්ෂම මෙවලම් කිහිපයක් සාකච්ඡා කළ අතර, විවිධ දාර සහ නෝඩ් හරහා ගමන් කිරීම පිළිබඳව නොපැහැදිලි ලෙස සඳහන් කළ නමුත් ඊට අනුරූප ප්‍රතිවිපාක සහ සංකීර්ණතා පිළිබඳව තවමත් ගවේෂණය කර නොමැත.

කලින් විස්තර කර ඇති 'ඉතිරිය' සංකල්පය අප ගසෙහි සිටින ස්ථානය නිරීක්ෂණය කිරීමට ප්‍රයෝජනවත් වේ, නමුත් එය ප්‍රමාණවත් තොරතුරු ගබඩා නොකරන බව අප වටහා ගත යුතුය.

පළමුවෙන්ම, අපි සෑම විටම නෝඩයක නිශ්චිත දාරයක වාසය කරන්නෙමු, එබැවින් අපි දාරයේ තොරතුරු ගබඩා කළ යුතුය. අපි මෙය 'ක්‍රියාකාරී දාරය' ලෙස හඳුන්වමු .

දෙවනුව, දාරයේ තොරතුරු එකතු කිරීමෙන් පසුව වුවද, ගසෙහි දුරින් පිහිටි ස්ථානයක් හඳුනා ගැනීමට අපට තවමත් ක්‍රමයක් නොමැති අතර මූල නෝඩයට කෙලින්ම සම්බන්ධ නොවේ . ඒ නිසා අපි නෝඩ් එකත් ගබඩා කරන්න ඕන. අපි මෙම 'ක්‍රියාකාරී නෝඩ්' ලෙස හඳුන්වමු .

අවසාන වශයෙන්, මූලයට කෙලින්ම සම්බන්ධ නොවන දාරයක පිහිටීමක් හඳුනා ගැනීමට 'ඉතිරිය' ප්‍රමාණවත් නොවන බව අපට දැකගත හැකිය , මන්ද 'ඉතිරිය' යනු සමස්ත මාර්ගයේ දිග ය; පෙර දාරවල දිග මතක තබා ගැනීම හා අඩු කිරීම ගැන කරදර වීමට අපට අවශ්‍ය නොවනු ඇත. එබැවින් අපට අවශ්‍ය වන්නේ වර්තමාන දාරයේ ඉතිරි කොටසයි . මෙය අපි 'ක්‍රියාකාරී දිග' ලෙස හඳුන්වමු .

මෙය අප 'සක්‍රීය ලක්ෂ්‍යය' ලෙස හඳුන්වන දෙයට යොමු කරයි - ගසෙහි අපගේ පිහිටීම පිළිබඳව අප විසින් පවත්වා ගත යුතු සියලු තොරතුරු අඩංගු විචල්‍ය තුනක පැකේජයකි:

Active Point = (Active Node, Active Edge, Active Length)

ABCABD හි ගැලපෙන මාර්ගය AB අද්දර ( මුල සිට ) අක්ෂර 2 කින් සහ CABDABCABD අද්දර අක්ෂර 4 කින් (නෝඩ් 4 සිට) සමන්විත වන ආකාරය ඔබට පහත රූපයේ නිරීක්ෂණය කළ හැකිය - එහි ප්‍රති characters ලයක් ලෙස අක්ෂර 6 ක් ඉතිරි වේ. එබැවින් අපගේ වර්තමාන පිහිටීම සක්‍රීය නෝඩ් 4, ඇක්ටිව් එජ් සී, ඇක්ටිව් දිග 4 ලෙස හඳුනාගත හැකිය .

ඉතිරි හා ක්‍රියාකාරී ලක්ෂ්‍යය

'සක්‍රීය ලක්ෂ්‍යයේ' තවත් වැදගත් කාර්යභාරයක් නම්, එය අපගේ ඇල්ගොරිතම සඳහා වියුක්ත තට්ටුවක් සපයන අතර, එයින් අදහස් වන්නේ අපගේ ඇල්ගොරිතමයේ සමහර කොටස් 'ක්‍රියාකාරී ලක්ෂ්‍යය' මත ක්‍රියා කළ හැකි බවයි , එම ක්‍රියාකාරී ලක්ෂ්‍යය මූලයේ හෝ වෙනත් තැනක තිබේද යන්න නොසලකා. . මෙය අපගේ ඇල්ගොරිතමයේ උපසර්ග සබැඳි භාවිතය පිරිසිදු හා forward ජුව ඉදිරියට ගෙනයාම පහසු කරයි.

උපසර්ග සබැඳි භාවිතයෙන් එදිරිව ගැලවීමේ වෙනස්කම්

දැන්, උපක්‍රමශීලී කොටස, මගේ අත්දැකීම් අනුව - බොහෝ දෝෂ සහ හිසරද ඇති කළ හැකි අතර බොහෝ මූලාශ්‍රවල එය දුර්වල ලෙස විස්තර කර ඇත, රෙස්කන් නඩු වලට එදිරිව සම්බන්ධක සිද්ධි සැකසීමේ වෙනස.

'AAAABAAAABAAC' නූලට පහත උදාහරණය සලකා බලන්න :

බහු දාර හරහා ඉතිරි කරන්න

7 හි 'ඉතිරිය' මුල සිට ඇති මුළු අක්‍ෂර එකතුවට අනුරූප වන ආකාරය ඔබට ඉහළින් නිරීක්ෂණය කළ හැකි අතර, 'සක්‍රීය දිග' 4 සක්‍රීය නෝඩයේ ක්‍රියාකාරී දාරයේ සිට ගැලපෙන අක්ෂර එකතුවට අනුරූප වේ.

දැන්, සක්‍රීය ස්ථානයේ ශාඛා මෙහෙයුමක් ක්‍රියාත්මක කිරීමෙන් පසුව, අපගේ ක්‍රියාකාරී නෝඩයේ උපසර්ග සම්බන්ධකයක් තිබිය හැකිය හෝ නොතිබිය හැකිය.

උපසර්ග සම්බන්ධකයක් තිබේ නම්: අපට අවශ්‍ය වන්නේ 'ක්‍රියාකාරී දිග' කොටස පමණි. මෙම 'ඉතිරි' නිසා, අදාළ නොවන අපි යන ෙපර ෙයදුම ලින්ක් පනින්න එහිදී node එකක් මතම ඊට අදාල දැනටමත් නිසැකයෙන්ම නිවැරදි 'ඉතිරි' සංකේතවත් හුදෙක් එය කොහෙද ගස වීම පළාෙත් මහනුවර දිස්තික්කෙය් උඩුනුවර පාෙද්ශීය ෙල්කම් විසින්.

උපසර්ග සබැඳියක් නොමැති නම්: අපට ශුන්‍ය / මූලයෙන් 'නැවත ගැලවීම ' අවශ්‍ය වේ, එයින් අදහස් කරන්නේ මුල සිටම සම්පූර්ණ උපසර්ගය සැකසීමයි. මේ සඳහා අප විසින් ඉතිරි කිරීමේ ඉතිරි කොටස නැවත ලබා ගැනීමේ පදනම ලෙස භාවිතා කළ යුතුය.

උදාහරණ උපසර්ග සම්බන්ධකයක් සමඟ සහ රහිතව සැකසීම සංසන්දනය කිරීම

ඉහත උදාහරණයේ ඊළඟ පියවරේදී කුමක් සිදුවේදැයි සලකා බලන්න. එකම ප්‍රති result ලය ලබා ගන්නේ කෙසේදැයි සංසන්දනය කරමු - එනම් ක්‍රියාවලිය සඳහා ඊළඟ උපසර්ගයට මාරුවීම - උපසර්ග සම්බන්ධකයක් සමඟ සහ නැතිව.

භාවිතා 'යන ෙපර ෙයදුම ලින්ක්'

උපසර්ග සබැඳි හරහා අඛණ්ඩ උපසර්ග වෙත ළඟා වීම

අපි උපසර්ග සබැඳියක් භාවිතා කරන්නේ නම්, අපි ස්වයංක්‍රීයව 'නියම ස්ථානයේ' සිටින බව සලකන්න. 'ක්‍රියාකාරී දිග' නව තනතුර සමඟ නොගැලපෙන නිසා බොහෝ විට එය තදින්ම සත්‍ය නොවේ .

ඉහත අවස්ථාවෙහිදී, 'ක්‍රියාකාරී දිග' 4 වන බැවින්, අපි සම්බන්ධිත නෝඩ් 4 සිට ආරම්භ වන ' ඒබීඒ' උපසර්ගය සමඟ වැඩ කරන්නෙමු . නමුත් උපසර්ගයේ පළමු අක්‍ෂරයට අනුරූප වන දාරය සොයා ගැනීමෙන් පසුව ( 'ඒ' ), අපගේ 'ක්‍රියාකාරී දිග' මෙම දාරය අක්ෂර 3 කින් පිරී ඉතිරී යන බව අපට පෙනේ. ඒ නිසා අපි සම්පූර්ණ දාරයට, ඊළඟ නෝඩයට පනින අතර, පැනීම සමඟ අප පරිභෝජනය කළ අක්ෂර අනුව 'ක්‍රියාකාරී දිග' අඩු කරන්නෙමු.

'BAA ' හි අඩු වූ උපසර්ගයට අනුරූපව ඊළඟ දාරය 'B' සොයාගත් පසු , අවසානයේ අපි සටහන් කරන්නේ දාරයේ දිග ඉතිරි 'ක්‍රියාකාරී දිග' 3 ට වඩා විශාල වන බවයි , එයින් අදහස් කරන්නේ අපට නිවැරදි ස්ථානය හමු වූ බවයි.

මෙම මෙහෙයුම සාමාන්‍යයෙන් 'නැවත යථා තත්ත්වයට පත් කිරීම' ලෙස හඳුන්වනු නොලබන බව කරුණාවෙන් සලකන්න, එය කෙටි දිගක් සහ මූල නොවන ආරම්භක ලක්ෂ්‍යයක් සහිතව, එය නැවත යථා තත්ත්වයට පත් කිරීමට සමාන බව මට පෙනේ.

'රෙස්කන්' භාවිතා කිරීම

නැවත යථා තත්ත්වයට පත් කිරීම හරහා අඛණ්ඩ උපසර්ග වෙත ළඟා වීම

අපි සාම්ප්‍රදායික 'රෙස්කන්' මෙහෙයුමක් භාවිතා කරන්නේ නම් (මෙහි අපට උපසර්ග සබැඳියක් නොමැති බව මවාපාමින්), අපි ගස මුදුනේ සිට මුලදී ආරම්භ කරන අතර, අපි නැවත නිවැරදි ස්ථානයට යා යුතුය. වත්මන් උපසර්ගයේ මුළු දිග දිගේ පහත දැක්වේ.

මෙම උපසර්ගයේ දිග අප කලින් සාකච්ඡා කළ 'ඉතිරිය' වේ. මෙම ශුන්‍යයට ළඟා වන තුරු මෙම ඉතිරි කොටස අප විසින් පරිභෝජනය කළ යුතුය. මෙයට (සහ බොහෝ විට) බහු නෝඩ් හරහා පැනීම ඇතුළත් විය හැකිය, සෑම පැනීමකදීම අප පැන ඇති දාරයේ දිග අනුව ඉතිරි කොටස අඩු වේ. අවසාන වශයෙන්, අපගේ ඉතිරි 'ඉතිරි' වලට වඩා දිගු වන දාරයකට අපි ළඟා වෙමු . මෙන්න අපි සක්‍රිය දාරය දී ඇති දාරයටත්, 'ක්‍රියාකාරී දිග' ඉතිරි 'ඉතිරි ' ලෙසත් සකසා , අපි ඉවරයි.

කෙසේ වෙතත්, සත්‍ය 'ඉතිරි' විචල්‍යය ආරක්ෂා කළ යුතු බව සලකන්න, එය අඩු වන්නේ එක් එක් නෝඩ් ඇතුළත් කිරීමෙන් පසුව පමණි. එබැවින් මා ඉහත විස්තර කළ දෙය උපකල්පනය කළේ වෙනම විචල්‍යයක් ' ආරම්භක ' ලෙස ආරම්භ කිරීමයි .

උපසර්ග සබැඳි සහ ප්‍රතිසාධන පිළිබඳ සටහන්

1) ක්‍රම දෙකම එකම ප්‍රති .ලයකට තුඩු දෙන බව සැලකිල්ලට ගන්න. කෙසේ වෙතත්, උපසර්ග සම්බන්ධක පැනීම බොහෝ අවස්ථාවන්හිදී සැලකිය යුතු වේගයකින් යුක්ත වේ; උපසර්ග සබැඳි පිටුපස ඇති සමස්ත තර්කය එයයි.

2) සත්‍ය ඇල්ගොරිතම ක්‍රියාත්මක කිරීම වෙනස් විය යුතු නැත. මා ඉහත සඳහන් කළ පරිදි, උපසර්ග සබැඳිය භාවිතා කිරීමේදී පවා, 'ක්‍රියාකාරී දිග' බොහෝ විට සම්බන්ධිත ස්ථානයට නොගැලපේ, මන්ද ගසේ එම ශාඛාවේ අමතර අතු අඩංගු විය හැකි බැවිනි. එබැවින් අත්‍යවශ්‍යයෙන්ම ඔබට 'ඉතිරිය ' වෙනුවට 'ක්‍රියාකාරී දිග' භාවිතා කළ යුතු අතර , ඔබේ ඉතිරි උපසර්ග දිගට වඩා කෙටි දාරයක් සොයා ගන්නා තෙක් එකම ප්‍රතිස්ථාපන තර්කනය ක්‍රියාත්මක කරන්න.

3) කාර්ය සාධනයට අදාළ එක් වැදගත් ප්‍රකාශයක් නම්, නැවත යථා තත්ත්වයට පත් කිරීමේදී සෑම චරිතයක්ම පරික්ෂා කිරීමේ අවශ්‍යතාවක් නොමැති බවයි. වලංගු උපසර්ග ගසක් ගොඩනඟා ඇති ආකාරය නිසා, අක්ෂර ගැලපෙන බව අපට ආරක්ෂිතව උපකල්පනය කළ හැකිය. එබැවින් ඔබ බොහෝ දුරට දිග ගණනය කරන අතර, අපි නව දාරයකට පනින විට අක්ෂර සමානතා පරික්ෂා කිරීමේ එකම අවශ්‍යතාවය පැන නගින්නේ දාර ඒවායේ පළමු අක්‍ෂරයෙන් හඳුනාගෙන ඇති හෙයිනි (එය සෑම විටම ලබා දී ඇති නෝඩයක සන්දර්භය තුළ අද්විතීය වේ). මෙයින් අදහස් කරන්නේ 'නැවත ගලවා ගැනීමේ' තර්කනය සම්පූර්ණ නූල් ගැලපෙන තර්කනයට වඩා වෙනස් බවයි (එනම් ගසෙහි උපස්ථරයක් සෙවීම).

4) මෙහි විස්තර කර ඇති මුල් උපසර්ගය සම්බන්ධ කළ හැකි එක් ප්‍රවේශයක් පමණි . උදාහරණයක් ලෙස එන්.ජේ.ලාර්සන් සහ වෙනත් අය. මෙම ප්‍රවේශය Node-Oriented Top-Down ලෙස නම් කරන අතර එය Node-Oriented Bottom-Up හා Edge-Oriented ප්‍රභේද දෙකක් සමඟ සංසන්දනය කරයි . විවිධ ප්‍රවේශයන් එකිනෙකට වෙනස් හා නරකම කාර්ය සාධනයන්, අවශ්‍යතා, සීමාවන් යනාදිය ඇත, නමුත් සාමාන්‍යයෙන් පෙනෙන්නේ එජ්-දිශානත ප්‍රවේශයන් මුල් පිටපතට සමස්ත දියුණුවක් බවයි.


8

og ජෝජෝජපන් ඔබ නියම පැහැදිලි කිරීමක් සහ දෘශ්‍යකරණයක් ගෙනාවා. Ak මකගොනොව් සඳහන් කළ පරිදි, උපසර්ග සබැඳි සැකසීම සම්බන්ධයෙන් එහි නීති කිහිපයක් නොමැත. පියවරෙන් පියවර http://brenden.github.io/ukkonen-animation/ හි 'aabaaabb' යන වචනය හරහා යන විට එය හොඳ ආකාරයකින් පෙනේ. ඔබ 10 වන පියවරේ සිට 11 වන පියවර දක්වා යන විට, නෝඩ් 5 සිට නෝඩ් 2 දක්වා උපසර්ග සම්බන්ධයක් නොමැති නමුත් ක්‍රියාකාරී ලක්ෂ්‍යය හදිසියේම එහි ගමන් කරයි.

ak මකගොනොව් මම ජාවා ලෝකයේ ජීවත් වන බැවින්, ST ගොඩනැඟීමේ කාර්ය ප්‍රවාහය ග්‍රහණය කර ගැනීම සඳහා ඔබේ ක්‍රියාත්මක කිරීම අනුගමනය කිරීමට ද මම උත්සාහ කළෙමි.

  • දාර නෝඩ් සමඟ ඒකාබද්ධ කිරීම
  • යොමු කිරීම් වෙනුවට දර්ශක දර්ශක භාවිතා කිරීම
  • ප්‍රකාශ කඩ කිරීම;
  • අඛණ්ඩ ප්‍රකාශ;

එබැවින් මම ජාවාහි එවැනි ක්‍රියාවට නැංවීම අවසන් කළ අතර එමඟින් සියලු පියවර වඩාත් පැහැදිලිව පිළිබිඹු වන අතර අනෙකුත් ජාවා ජනතාව සඳහා ඉගෙනීමේ කාලය අඩු වනු ඇතැයි මම බලාපොරොත්තු වෙමි:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class ST {

  public class Node {
    private final int id;
    private final Map<Character, Edge> edges;
    private Node slink;

    public Node(final int id) {
        this.id = id;
        this.edges = new HashMap<>();
    }

    public void setSlink(final Node slink) {
        this.slink = slink;
    }

    public Map<Character, Edge> getEdges() {
        return this.edges;
    }

    public Node getSlink() {
        return this.slink;
    }

    public String toString(final String word) {
        return new StringBuilder()
                .append("{")
                .append("\"id\"")
                .append(":")
                .append(this.id)
                .append(",")
                .append("\"slink\"")
                .append(":")
                .append(this.slink != null ? this.slink.id : null)
                .append(",")
                .append("\"edges\"")
                .append(":")
                .append(edgesToString(word))
                .append("}")
                .toString();
    }

    private StringBuilder edgesToString(final String word) {
        final StringBuilder edgesStringBuilder = new StringBuilder();
        edgesStringBuilder.append("{");
        for(final Map.Entry<Character, Edge> entry : this.edges.entrySet()) {
            edgesStringBuilder.append("\"")
                    .append(entry.getKey())
                    .append("\"")
                    .append(":")
                    .append(entry.getValue().toString(word))
                    .append(",");
        }
        if(!this.edges.isEmpty()) {
            edgesStringBuilder.deleteCharAt(edgesStringBuilder.length() - 1);
        }
        edgesStringBuilder.append("}");
        return edgesStringBuilder;
    }

    public boolean contains(final String word, final String suffix) {
        return !suffix.isEmpty()
                && this.edges.containsKey(suffix.charAt(0))
                && this.edges.get(suffix.charAt(0)).contains(word, suffix);
    }
  }

  public class Edge {
    private final int from;
    private final int to;
    private final Node next;

    public Edge(final int from, final int to, final Node next) {
        this.from = from;
        this.to = to;
        this.next = next;
    }

    public int getFrom() {
        return this.from;
    }

    public int getTo() {
        return this.to;
    }

    public Node getNext() {
        return this.next;
    }

    public int getLength() {
        return this.to - this.from;
    }

    public String toString(final String word) {
        return new StringBuilder()
                .append("{")
                .append("\"content\"")
                .append(":")
                .append("\"")
                .append(word.substring(this.from, this.to))
                .append("\"")
                .append(",")
                .append("\"next\"")
                .append(":")
                .append(this.next != null ? this.next.toString(word) : null)
                .append("}")
                .toString();
    }

    public boolean contains(final String word, final String suffix) {
        if(this.next == null) {
            return word.substring(this.from, this.to).equals(suffix);
        }
        return suffix.startsWith(word.substring(this.from,
                this.to)) && this.next.contains(word, suffix.substring(this.to - this.from));
    }
  }

  public class ActivePoint {
    private final Node activeNode;
    private final Character activeEdgeFirstCharacter;
    private final int activeLength;

    public ActivePoint(final Node activeNode,
                       final Character activeEdgeFirstCharacter,
                       final int activeLength) {
        this.activeNode = activeNode;
        this.activeEdgeFirstCharacter = activeEdgeFirstCharacter;
        this.activeLength = activeLength;
    }

    private Edge getActiveEdge() {
        return this.activeNode.getEdges().get(this.activeEdgeFirstCharacter);
    }

    public boolean pointsToActiveNode() {
        return this.activeLength == 0;
    }

    public boolean activeNodeIs(final Node node) {
        return this.activeNode == node;
    }

    public boolean activeNodeHasEdgeStartingWith(final char character) {
        return this.activeNode.getEdges().containsKey(character);
    }

    public boolean activeNodeHasSlink() {
        return this.activeNode.getSlink() != null;
    }

    public boolean pointsToOnActiveEdge(final String word, final char character) {
        return word.charAt(this.getActiveEdge().getFrom() + this.activeLength) == character;
    }

    public boolean pointsToTheEndOfActiveEdge() {
        return this.getActiveEdge().getLength() == this.activeLength;
    }

    public boolean pointsAfterTheEndOfActiveEdge() {
        return this.getActiveEdge().getLength() < this.activeLength;
    }

    public ActivePoint moveToEdgeStartingWithAndByOne(final char character) {
        return new ActivePoint(this.activeNode, character, 1);
    }

    public ActivePoint moveToNextNodeOfActiveEdge() {
        return new ActivePoint(this.getActiveEdge().getNext(), null, 0);
    }

    public ActivePoint moveToSlink() {
        return new ActivePoint(this.activeNode.getSlink(),
                this.activeEdgeFirstCharacter,
                this.activeLength);
    }

    public ActivePoint moveTo(final Node node) {
        return new ActivePoint(node, this.activeEdgeFirstCharacter, this.activeLength);
    }

    public ActivePoint moveByOneCharacter() {
        return new ActivePoint(this.activeNode,
                this.activeEdgeFirstCharacter,
                this.activeLength + 1);
    }

    public ActivePoint moveToEdgeStartingWithAndByActiveLengthMinusOne(final Node node,
                                                                       final char character) {
        return new ActivePoint(node, character, this.activeLength - 1);
    }

    public ActivePoint moveToNextNodeOfActiveEdge(final String word, final int index) {
        return new ActivePoint(this.getActiveEdge().getNext(),
                word.charAt(index - this.activeLength + this.getActiveEdge().getLength()),
                this.activeLength - this.getActiveEdge().getLength());
    }

    public void addEdgeToActiveNode(final char character, final Edge edge) {
        this.activeNode.getEdges().put(character, edge);
    }

    public void splitActiveEdge(final String word,
                                final Node nodeToAdd,
                                final int index,
                                final char character) {
        final Edge activeEdgeToSplit = this.getActiveEdge();
        final Edge splittedEdge = new Edge(activeEdgeToSplit.getFrom(),
                activeEdgeToSplit.getFrom() + this.activeLength,
                nodeToAdd);
        nodeToAdd.getEdges().put(word.charAt(activeEdgeToSplit.getFrom() + this.activeLength),
                new Edge(activeEdgeToSplit.getFrom() + this.activeLength,
                        activeEdgeToSplit.getTo(),
                        activeEdgeToSplit.getNext()));
        nodeToAdd.getEdges().put(character, new Edge(index, word.length(), null));
        this.activeNode.getEdges().put(this.activeEdgeFirstCharacter, splittedEdge);
    }

    public Node setSlinkTo(final Node previouslyAddedNodeOrAddedEdgeNode,
                           final Node node) {
        if(previouslyAddedNodeOrAddedEdgeNode != null) {
            previouslyAddedNodeOrAddedEdgeNode.setSlink(node);
        }
        return node;
    }

    public Node setSlinkToActiveNode(final Node previouslyAddedNodeOrAddedEdgeNode) {
        return setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, this.activeNode);
    }
  }

  private static int idGenerator;

  private final String word;
  private final Node root;
  private ActivePoint activePoint;
  private int remainder;

  public ST(final String word) {
    this.word = word;
    this.root = new Node(idGenerator++);
    this.activePoint = new ActivePoint(this.root, null, 0);
    this.remainder = 0;
    build();
  }

  private void build() {
    for(int i = 0; i < this.word.length(); i++) {
        add(i, this.word.charAt(i));
    }
  }

  private void add(final int index, final char character) {
    this.remainder++;
    boolean characterFoundInTheTree = false;
    Node previouslyAddedNodeOrAddedEdgeNode = null;
    while(!characterFoundInTheTree && this.remainder > 0) {
        if(this.activePoint.pointsToActiveNode()) {
            if(this.activePoint.activeNodeHasEdgeStartingWith(character)) {
                activeNodeHasEdgeStartingWithCharacter(character, previouslyAddedNodeOrAddedEdgeNode);
                characterFoundInTheTree = true;
            }
            else {
                if(this.activePoint.activeNodeIs(this.root)) {
                    rootNodeHasNotEdgeStartingWithCharacter(index, character);
                }
                else {
                    previouslyAddedNodeOrAddedEdgeNode = internalNodeHasNotEdgeStartingWithCharacter(index,
                            character, previouslyAddedNodeOrAddedEdgeNode);
                }
            }
        }
        else {
            if(this.activePoint.pointsToOnActiveEdge(this.word, character)) {
                activeEdgeHasCharacter();
                characterFoundInTheTree = true;
            }
            else {
                if(this.activePoint.activeNodeIs(this.root)) {
                    previouslyAddedNodeOrAddedEdgeNode = edgeFromRootNodeHasNotCharacter(index,
                            character,
                            previouslyAddedNodeOrAddedEdgeNode);
                }
                else {
                    previouslyAddedNodeOrAddedEdgeNode = edgeFromInternalNodeHasNotCharacter(index,
                            character,
                            previouslyAddedNodeOrAddedEdgeNode);
                }
            }
        }
    }
  }

  private void activeNodeHasEdgeStartingWithCharacter(final char character,
                                                    final Node previouslyAddedNodeOrAddedEdgeNode) {
    this.activePoint.setSlinkToActiveNode(previouslyAddedNodeOrAddedEdgeNode);
    this.activePoint = this.activePoint.moveToEdgeStartingWithAndByOne(character);
    if(this.activePoint.pointsToTheEndOfActiveEdge()) {
        this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
    }
  }

  private void rootNodeHasNotEdgeStartingWithCharacter(final int index, final char character) {
    this.activePoint.addEdgeToActiveNode(character, new Edge(index, this.word.length(), null));
    this.activePoint = this.activePoint.moveTo(this.root);
    this.remainder--;
    assert this.remainder == 0;
  }

  private Node internalNodeHasNotEdgeStartingWithCharacter(final int index,
                                                         final char character,
                                                         Node previouslyAddedNodeOrAddedEdgeNode) {
    this.activePoint.addEdgeToActiveNode(character, new Edge(index, this.word.length(), null));
    previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkToActiveNode(previouslyAddedNodeOrAddedEdgeNode);
    if(this.activePoint.activeNodeHasSlink()) {
        this.activePoint = this.activePoint.moveToSlink();
    }
    else {
        this.activePoint = this.activePoint.moveTo(this.root);
    }
    this.remainder--;
    return previouslyAddedNodeOrAddedEdgeNode;
  }

  private void activeEdgeHasCharacter() {
    this.activePoint = this.activePoint.moveByOneCharacter();
    if(this.activePoint.pointsToTheEndOfActiveEdge()) {
        this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
    }
  }

  private Node edgeFromRootNodeHasNotCharacter(final int index,
                                             final char character,
                                             Node previouslyAddedNodeOrAddedEdgeNode) {
    final Node newNode = new Node(idGenerator++);
    this.activePoint.splitActiveEdge(this.word, newNode, index, character);
    previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, newNode);
    this.activePoint = this.activePoint.moveToEdgeStartingWithAndByActiveLengthMinusOne(this.root,
            this.word.charAt(index - this.remainder + 2));
    this.activePoint = walkDown(index);
    this.remainder--;
    return previouslyAddedNodeOrAddedEdgeNode;
  }

  private Node edgeFromInternalNodeHasNotCharacter(final int index,
                                                 final char character,
                                                 Node previouslyAddedNodeOrAddedEdgeNode) {
    final Node newNode = new Node(idGenerator++);
    this.activePoint.splitActiveEdge(this.word, newNode, index, character);
    previouslyAddedNodeOrAddedEdgeNode = this.activePoint.setSlinkTo(previouslyAddedNodeOrAddedEdgeNode, newNode);
    if(this.activePoint.activeNodeHasSlink()) {
        this.activePoint = this.activePoint.moveToSlink();
    }
    else {
        this.activePoint = this.activePoint.moveTo(this.root);
    }
    this.activePoint = walkDown(index);
    this.remainder--;
    return previouslyAddedNodeOrAddedEdgeNode;
  }

  private ActivePoint walkDown(final int index) {
    while(!this.activePoint.pointsToActiveNode()
            && (this.activePoint.pointsToTheEndOfActiveEdge() || this.activePoint.pointsAfterTheEndOfActiveEdge())) {
        if(this.activePoint.pointsAfterTheEndOfActiveEdge()) {
            this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge(this.word, index);
        }
        else {
            this.activePoint = this.activePoint.moveToNextNodeOfActiveEdge();
        }
    }
    return this.activePoint;
  }

  public String toString(final String word) {
    return this.root.toString(word);
  }

  public boolean contains(final String suffix) {
    return this.root.contains(this.word, suffix);
  }

  public static void main(final String[] args) {
    final String[] words = {
            "abcabcabc$",
            "abc$",
            "abcabxabcd$",
            "abcabxabda$",
            "abcabxad$",
            "aabaaabb$",
            "aababcabcd$",
            "ababcabcd$",
            "abccba$",
            "mississipi$",
            "abacabadabacabae$",
            "abcabcd$",
            "00132220$"
    };
    Arrays.stream(words).forEach(word -> {
        System.out.println("Building suffix tree for word: " + word);
        final ST suffixTree = new ST(word);
        System.out.println("Suffix tree: " + suffixTree.toString(word));
        for(int i = 0; i < word.length() - 1; i++) {
            assert suffixTree.contains(word.substring(i)) : word.substring(i);
        }
    });
  }
}

6

මගේ ප්‍රතිභානය පහත පරිදි වේ:

ප්‍රධාන පුඩුවේ k පුනරාවර්තනයෙන් පසු ඔබ පළමු k අක්ෂර වලින් ආරම්භ වන සම්පූර්ණ නූලෙහි සියලු උපසර්ග අඩංගු උපසර්ග ගසක් සාදා ඇත.

ආරම්භයේ දී, මෙයින් අදහස් වන්නේ උපසර්ග ගසෙහි මුළු නූල නියෝජනය කරන තනි මූල නෝඩයක් අඩංගු වන බවයි (මෙය 0 සිට ආරම්භ වන එකම උපසර්ගයයි).

ලෙන් (නූල්) පුනරාවර්තනයෙන් පසු ඔබට සියලු උපසර්ග අඩංගු උපසර්ග ගසක් ඇත.

ලූපය අතරතුර යතුර ක්‍රියාකාරී ලක්ෂ්‍යයයි. මගේ අනුමානය නම්, මෙය නූලෙහි පළමු k අක්ෂරවල නිසි උපසර්ගයකට අනුරූප වන ගසේ ගැඹුරුම ලක්ෂ්‍යය නියෝජනය කරන බවයි. (මම හිතන්නේ නිසි තේරුම උපසර්ගය මුළු නූලම විය නොහැකි බවයි.)

උදාහරණයක් ලෙස, ඔබ 'abcabc' අක්ෂර දැක ඇති බව සිතමු. සක්‍රීය ලක්ෂ්‍යය ගසෙහි ලක්ෂ්‍යය 'abc' උපසර්ගයට අනුරූප වේ.

සක්‍රීය ලක්ෂ්‍යය නිරූපණය කරන්නේ (සම්භවය, පළමු, අවසාන). මෙයින් අදහස් කරන්නේ ඔබ දැනට ගසෙහි ලක්ෂ්‍යයේ සිට නෝඩ් මූලාරම්භයෙන් ආරම්භ කර පසුව අක්ෂර වලින් පෝෂණය වන බවයි [පළමුව: අන්තිම]

ඔබ නව අක්ෂරයක් එකතු කළ විට, ක්‍රියාකාරී ලක්ෂ්‍යය තවමත් පවතින ගසෙහි තිබේදැයි බැලීමට ඔබ බලයි. එය එසේ නම් ඔබ ඉවරයි. එසේ නොමැතිනම් ඔබ සක්‍රීය ස්ථානයේ උපසර්ග ගසට නව නෝඩයක් එක් කළ යුතුය, ඊළඟ කෙටිම ගැලපුමට වැටීම සහ නැවත පරීක්ෂා කරන්න.

සටහන 1: උපසර්ග දර්ශකයන් එක් එක් නෝඩය සඳහා ඊළඟ කෙටිම ගැලපුමට සබැඳියක් ලබා දේ.

සටහන 2: ඔබ නව නෝඩයක් සහ පසුබෑමක් එකතු කරන විට නව නෝඩය සඳහා නව උපසර්ග දර්ශකයක් එක් කරයි. මෙම උපසර්ග දර්ශකයේ ගමනාන්තය කෙටි කළ ක්‍රියාකාරී ස්ථානයේ ඇති නෝඩය වේ. මෙම නෝඩය දැනටමත් පවතිනු ඇත, නැතහොත් මෙම පසුබෑමේ ලූපයේ ඊළඟ ක්‍රියාකාරීත්වය මත නිර්මාණය වේ.

සටහන 3: කැනොනීකරණ කොටස සක්‍රීය ස්ථානය පරීක්ෂා කිරීමේදී කාලය ඉතිරි කරයි. උදාහරණයක් ලෙස, ඔබ සැමවිටම සම්භවය = 0 භාවිතා කර පළමු හා අවසාන වෙනස් කර ඇතැයි සිතමු. සක්‍රීය ලක්ෂ්‍යය පරීක්ෂා කිරීම සඳහා ඔබට සෑම අතරමැදි නෝඩ් දිගේම උපසර්ග ගස අනුගමනය කළ යුතුය. අන්තිම නෝඩයෙන් ඇති දුර පමණක් සටහන් කිරීමෙන් මෙම මාර්ගය අනුගමනය කිරීමේ ප්‍රති result ලය හැඹිලිගත කිරීම අර්ථවත් කරයි.

මායිම් විචල්‍යයන් "නිවැරදි කිරීම" යන්නෙන් ඔබ අදහස් කරන දෙයට කේත උදාහරණයක් ලබා දිය හැකිද?

සෞඛ්‍ය අනතුරු ඇඟවීම: මෙම ඇල්ගොරිතම තේරුම් ගැනීමට අපහසු බව මට පෙනී ගියේය, එබැවින් කරුණාකර මෙම අවබෝධය සියලු වැදගත් තොරතුරු වල වැරදි විය හැකි බව වටහා ගන්න ...


එක් ශාස්ත්‍රීය පත්‍රිකාවක “නිසි” යන්න අර්ථ දක්වන්නේ නූලක “නිසි උපසර්ගය” එහි පළමු අක්‍ෂරය අඩංගු නොවන බවයි. සමහර විට ඔබ සම්පූර්ණ උපස්ථරයක් "උපසර්ගයක්" ලෙස හැඳින්වුවද, ඇල්ගොරිතම නිර්වචනය කිරීමේදී "නූල්" සහ "උපස්ථරය" සහ "උපසර්ගය" යන වචන ලිබරල් ලෙස විසි වන අතර සමහර විට ඔබ "උපසර්ගය" යන්නෙන් අදහස් කරන්නේ කුමක්ද යන්න ඉතා පැහැදිලි විය යුතුය, එබැවින් “නිසි උපසර්ගය” යන යෙදුමෙන් සියල්ල උපසර්ගය ලෙස හැඳින්වීම බැහැර කරයි. එබැවින් නූලක උපසර්ගයක් ඕනෑම නීත්‍යානුකූල උපස්ථරයක් විය හැකි අතර එකම උපසර්ගය නොවන නිසි උපසර්ගයක් තිබිය හැකිය. මොකද තර්කනය.
බ්ලෙයාර් හූටන්

3

හායි මම ඉහත පැහැදිලි කළ ක්‍රියාත්මක කිරීම රූබි වලින් ක්‍රියාත්මක කිරීමට උත්සාහ කර ඇත්තෙමි. එය හොඳින් ක්‍රියාත්මක වන බව පෙනේ.

ක්‍රියාත්මක කිරීමේ එකම වෙනස නම්, මම සංකේත භාවිතා කරනවා වෙනුවට දාර වස්තුව භාවිතා කිරීමට උත්සාහ කර තිබීමයි.

එය https://gist.github.com/suchitpuri/9304856 හි ද ඇත

    require 'pry'


class Edge
    attr_accessor :data , :edges , :suffix_link
    def initialize data
        @data = data
        @edges = []
        @suffix_link = nil
    end

    def find_edge element
        self.edges.each do |edge|
            return edge if edge.data.start_with? element
        end
        return nil
    end
end

class SuffixTrees
    attr_accessor :root , :active_point , :remainder , :pending_prefixes , :last_split_edge , :remainder

    def initialize
        @root = Edge.new nil
        @active_point = { active_node: @root , active_edge: nil , active_length: 0}
        @remainder = 0
        @pending_prefixes = []
        @last_split_edge = nil
        @remainder = 1
    end

    def build string
        string.split("").each_with_index do |element , index|


            add_to_edges @root , element        

            update_pending_prefix element                           
            add_pending_elements_to_tree element
            active_length = @active_point[:active_length]

            # if(@active_point[:active_edge] && @active_point[:active_edge].data && @active_point[:active_edge].data[0..active_length-1] ==  @active_point[:active_edge].data[active_length..@active_point[:active_edge].data.length-1])
            #   @active_point[:active_edge].data = @active_point[:active_edge].data[0..active_length-1]
            #   @active_point[:active_edge].edges << Edge.new(@active_point[:active_edge].data)
            # end

            if(@active_point[:active_edge] && @active_point[:active_edge].data && @active_point[:active_edge].data.length == @active_point[:active_length]  )
                @active_point[:active_node] =  @active_point[:active_edge]
                @active_point[:active_edge] = @active_point[:active_node].find_edge(element[0])
                @active_point[:active_length] = 0
            end
        end
    end

    def add_pending_elements_to_tree element

        to_be_deleted = []
        update_active_length = false
        # binding.pry
        if( @active_point[:active_node].find_edge(element[0]) != nil)
            @active_point[:active_length] = @active_point[:active_length] + 1               
            @active_point[:active_edge] = @active_point[:active_node].find_edge(element[0]) if @active_point[:active_edge] == nil
            @remainder = @remainder + 1
            return
        end



        @pending_prefixes.each_with_index do |pending_prefix , index|

            # binding.pry           

            if @active_point[:active_edge] == nil and @active_point[:active_node].find_edge(element[0]) == nil

                @active_point[:active_node].edges << Edge.new(element)

            else

                @active_point[:active_edge] = node.find_edge(element[0]) if @active_point[:active_edge]  == nil

                data = @active_point[:active_edge].data
                data = data.split("")               

                location = @active_point[:active_length]


                # binding.pry
                if(data[0..location].join == pending_prefix or @active_point[:active_node].find_edge(element) != nil )                  


                else #tree split    
                    split_edge data , index , element
                end

            end
        end 
    end



    def update_pending_prefix element
        if @active_point[:active_edge] == nil
            @pending_prefixes = [element]
            return

        end

        @pending_prefixes = []

        length = @active_point[:active_edge].data.length
        data = @active_point[:active_edge].data
        @remainder.times do |ctr|
                @pending_prefixes << data[-(ctr+1)..data.length-1]
        end

        @pending_prefixes.reverse!

    end

    def split_edge data , index , element
        location = @active_point[:active_length]
        old_edges = []
        internal_node = (@active_point[:active_edge].edges != nil)

        if (internal_node)
            old_edges = @active_point[:active_edge].edges 
            @active_point[:active_edge].edges = []
        end

        @active_point[:active_edge].data = data[0..location-1].join                 
        @active_point[:active_edge].edges << Edge.new(data[location..data.size].join)


        if internal_node
            @active_point[:active_edge].edges << Edge.new(element)
        else
            @active_point[:active_edge].edges << Edge.new(data.last)        
        end

        if internal_node
            @active_point[:active_edge].edges[0].edges = old_edges
        end


        #setup the suffix link
        if @last_split_edge != nil and @last_split_edge.data.end_with?@active_point[:active_edge].data 

            @last_split_edge.suffix_link = @active_point[:active_edge] 
        end

        @last_split_edge = @active_point[:active_edge]

        update_active_point index

    end


    def update_active_point index
        if(@active_point[:active_node] == @root)
            @active_point[:active_length] = @active_point[:active_length] - 1
            @remainder = @remainder - 1
            @active_point[:active_edge] = @active_point[:active_node].find_edge(@pending_prefixes.first[index+1])
        else
            if @active_point[:active_node].suffix_link != nil
                @active_point[:active_node] = @active_point[:active_node].suffix_link               
            else
                @active_point[:active_node] = @root
            end 
            @active_point[:active_edge] = @active_point[:active_node].find_edge(@active_point[:active_edge].data[0])
            @remainder = @remainder - 1     
        end
    end

    def add_to_edges root , element     
        return if root == nil
        root.data = root.data + element if(root.data and root.edges.size == 0)
        root.edges.each do |edge|
            add_to_edges edge , element
        end
    end
end

suffix_tree = SuffixTrees.new
suffix_tree.build("abcabxabcd")
binding.pry
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.