يرجى بايدو لبعض المفاهيم الأساسية لصفائف لاحقة. ببساطة ، مجموعة لاحقة هي مجموعة من جميع أحجام لاحقة من السلسلة. ثم يمكننا تحقيق الاحتياجات المختلفة بناءً على بعض خصائص مجموعة لاحقة.
الطبقة العامة MySuffixArrayTest {public char [] // string الأصلي public int n ؛ // طول السلسلة public int [] // ترتيب اللاحقة [i] في جميع اللاحقة العامة int [] sa ؛ int للجمهور [] الارتفاع ؛ // يشير إلى لاحقة [sa [i]] واللاحقة [sa [i - 1] ، أي أطول بادئة عامة من اللاحقة المجاورة ، int العامة [] h ؛ صفيف رتبة الكلمة الرئيسية الثانية العامة []تأخذ التفسيرات التالية السلسلة "aabaaaab" كمثال. دعونا أولاً نعرض النتائج. يرجى الرجوع إلى هذه النتيجة إلى الفهم والتحليل (قمت بنسخ صورة شخص آخر لهذه النتيجة. يرجى الانتشار 1 بشكل افتراضي ، لأن صفيف بلدي يبدأ بـ Substrict 0)
لاحقة: تفترض صفيف السلسلة الأصلي أن السلسلة الأصلية هي "aabaaaab" ، ثم يجب أن تكون القيمة المقابلة لهذا الصفيف {'a' ، 'a' ، 'b' ، 'a' ، 'a' ، 'a' ، 'b'}
N: طول السلسلة هنا N هو 8
المرتبة: صفيف تصنيف صفيف اللاحقة يعادل الترتيب المقابل للاحتياجات I-Th. على سبيل المثال ، يشير Rank [0] إلى ترتيب لاحقة "Aabaaaab" رتبة [1] تشير إلى ترتيب اللاحقة "Abaaab"
SA: هذا صفيف يعكس صفيف الرتبة. هل يقوم X-Donged بتخزين اللاحقة؟ أو لإعطاء مثال لتوضيح أن SA [0] يشير إلى صفيف لاحقة المرتبة الأولى ، أي 3. أي أن المرتبة المقابلة [3] للمصفوفة هي 0. يرجى التأكد من فهم الصيغة SA [I [I]] = i. إذا فهمت العلاقة بين SA والرتبة ، فيجب عليك أيضًا فهمها.
الارتفاع: الارتفاع [i] هو طول أكبر بادئة مشتركة لمصفوقة SA [i] وارتفاع صفيف اللاحقة SA [I-1] [1] يشير إلى أكبر وأول بادئة شائعة SA [1] و SA [0] أي ، بوقت أكبر شائع لـ "AAAB"
H: H [i] يشير إلى لاحقة I-th وأكبر بادئة عامة للبادئة السابقة H [0] يشير إلى صفيف اللاحقة الأولى ، أي "Aabaaaab" وأكبر بادئة عامة للبادئة السابقة ، وهي "AAB" ، وهي الارتفاع [المرتبة [0]] = 3] = 3 هذا أمر يصعب فهمه. لا يمكنك أن تفهم في الوقت الحالي ومواصلة القراءة.
WS: لا شيء يمكن قوله ، عد مجموعة الفرز المساعدة
Y: النوع الرئيسي الثاني هو صفيف SA مع الكلمة الرئيسية الثانية المكافئة للكلمة الرئيسية الثانية
X: يمكنك فهمها كنسخة احتياطية من صفيف الرتبة. يستخدم في البداية نسخة احتياطية من صفيف الرتبة ، ثم يسجل صفيف الرتبة بعد كل حلقة
أولاً ، دعونا نلقي نظرة على رمز صفيف SA. سأشرح وظيفة الكود واحد تلو الآخر وأرفق الكود الكلي إلى ما يلي
رتبة = جديدة [n] ؛ sa = int new [n] ؛ WS = New Int [255] ؛ y = new int [n] ؛ x = new int [n] ؛ // حلقة السلسلة الأصلية لتحويل قيمة int إلى صفيف الترتيب لـ (int i = 0 ؛ i <n ؛ i ++) {rank [i] = (int) fuckix [i] ؛ }تتمثل وظيفة الكود أعلاه في تهيئة الصفيف وتنفيذ أول حساب وفرز. الحلقة الأولى هي تعيين القيمة الأولية إلى صفيف الرتبة. بعد التنفيذ ، تكون القيمة المقابلة لصفيف الترتيب {97 ، 97 ، 98 ، 97 ، 97 ، 97 ، 97 ، 98}. يجب أن ترى أن القيمة الأولية لمصفوفة الترتيب هي رمز ASCII المقابل للحرف.
الدورات الثلاث التالية هي أول فرز العد. إذا كنت لا تفهم حساب الفرز ، فيرجى Baidu. اسمحوا لي أن أتحدث عن عملية هذه الدورات الثلاث
لـ (int i = 0 ؛ i <n ؛ i ++) {ws [rank [i]] ++ ؛ x [i] = Rank [i] ؛ } لـ (int i = 1 ؛ i <ws.length ؛ i ++) {ws [i]+= ws [i - 1] ؛ }ما تفعله هاتان الحلقتان هو حساب كل قيم الحدوث والنسخ الاحتياطي لصفيف الرتبة إلى مجموعة X. بعد تشغيل الحلقة الأولى ، WS [97] = 6 ، WS [98] = 2 ، وبعد تشغيل الحلقة الثانية ، WS [97] = 6 ، WS [98] = 8
لـ (int i = n-1 ؛ i> = 0 ؛ i--) {sa [-ws [rank [i]]] = i ؛ }الفقرة أعلاه هي الرمز المحدد للعد والفرز للعثور على صفيف SA. يجب أن يكون الجميع قد أسيء فهمه في المرة الأولى التي يقرؤونها فيها. لماذا وجدوا SA؟ كنت مرتبكًا أيضًا لأول مرة ، ولكن يرجى التحلي بالصبر وفهم هذا الرمز بعناية. هل ما زلت تتذكر الصيغة المذكورة أعلاه sa [رتبة [i]] = i على سبيل المثال ، لللاحقة "B" ، نسأل SA له ، أي ، SA [7]] = SA [98] = 7. من الواضح ، SA [98] غير موجود ، لكننا سجلنا عدد المرات التي تظهر فيها 98 في صفيف WS ، لذلك يجب أن يكون WS [98] الترتيب المقابل لـ "B". من فضلك لا تنسى طرح 1 لتصبح SA [-WS [RASE [I]]] = i. بالنسبة إلى سبب حاجة إلى اجتياز من الخلف إلى الأمام ، فأنت بحاجة إلى فهمها بعناية هنا ، وإلا فستكون بالتأكيد عمياء تمامًا عن طريق تصنيفها وفقًا للكلمة الرئيسية الثانية. كيف يمكنك فرزها إذا كانت هناك قيمتان رتبة متماثلتان؟ يجب أن يظهر أولاً أمام صفيف SA. إذا كنت تفكر في هذه الحلقة والتغييرات في قيمة صفيف WS ، فسوف تفهم أن ترتيب الحلقة For يمثل بالفعل ترتيب الترتيب عندما تكون قيمة الرتبة هي نفسها. يعني اجتياز الظهر إلى الأمام أن ترتيب اللاحقة أقل أيضًا عندما تكون قيمة الترتيب هي نفسها.
ما سبق هو مجرد فرز العد الأول ، وهو ما يعادل فقط مقارنة الحرف الأول من كل صفيف لاحقة للعثور على SA. النتيجة المقابلة كما هو موضح في الشكل أدناه.
// loop combination sort for (int j = 1 ، p = 0 ؛ j <= n ؛ j = j << 1) {// إذا كنت بحاجة إلى ملءه ، أضف صفيف الفرز أولاً yp = 0 ؛ لـ (int i = n - j ؛ i <n ؛ i ++) {y [p ++] = i ؛ } // النطاق الكلمة الرئيسية الثانية وفقًا للكلمة الرئيسية الأولى SA لـ (int i = 0 ؛ i <n ؛ i ++) {if (sa [i]> = j) {y [p ++] = sa [i] - j ؛ }} // فرز الكلمة الرئيسية لـ (int i = 0 ؛ i <ws.length ؛ i ++) {ws [i] = 0 ؛ } لـ (int i: x) {ws [i] ++ ؛ } لـ (int i: x) {ws [i] ++ ؛ } لـ (int i = 1 ؛ i <ws.length ؛ i ++) {ws [i]+= ws [i - 1] ؛ } لـ (int i = n-1 ؛ i> = 0 ؛ i--) {sa [-ws [x [y [i]]] = y [i] ؛ y [i] = 0 ؛ } // حساب صفيف الترتيب من SA int xb [] = new int [n] ؛ // x array backup for (int i = 0 ؛ i <n ؛ i ++) {xb [i] = x [i] ؛ } رقم int = 1 ؛ x [sa [0]] = 1 ؛ لـ (int i = 1 ؛ i <n ؛ i ++) {if (xb [sa [i]]! = xb [sa [i - 1]]) {x [sa [i]] = ++ number ؛ } آخر إذا (sa [i] + j> = n && sa [i - 1] + j> = n) {x [sa [i]] = number ؛ } آخر إذا (sa [i] + j <n && sa [i - 1] + j> = n) {x [sa [i]] = ++ number ؛ } آخر إذا (xb [sa [i] + j]! = xb [sa [i - 1] + j]) {x [sa [i]] = ++ رقم ؛ } آخر {x [sa [i]] = number ؛ } if (number> = n) break ؛ }}هذا هو أصعب جزء من الكود لفهم عند العثور على صفيف SA. بادئ ذي بدء ، تحتاج إلى فهم فكرة خوارزمية الضرب. بعد أمر العد الأول ، هل نعرف بالفعل فرز الحرف الأولي الأول لجميع صفائف لاحقة؟ نظرًا لأننا نعرف أن فرز الرسالة الأولية الأولى يعادل ترتيب رسالته الثانية (لاحظ الفرق بين الفرز والترتيب. الفرز هو أننا نعرف أي واحد تم إصلاحه. الترتيب هو أننا نعرف فقط الترتيب الذي يظهر فيه ، لكننا لا نعرف أي واحد يحتل المرتبة على وجه التحديد). هذا بالطبع ، لأنها في الأصل من سلسلة ، ولكل لاحقة ، يمكن أيضًا استخدامها كلاحقة للاحتفال السابق. الحديث عنها ، على سبيل المثال ، لـ "Baaaab" يتوافق ترتيب رسالته الأولى مع ترتيب الكلمة الرئيسية الثانية لـ "Abaaaab". مع ترتيب الكلمة الرئيسية الأولى ونوع الكلمة الرئيسية الثانية ، يمكننا العثور على النوع المدمج للكلمة الرئيسية. وفقًا لنتيجة نوع المجموعة ، لا يزال بإمكاننا استخدام الفكرة السابقة. بعد أول مجموعة من "Baaaab" ، نقوم بتصنيف ترتيب الأحرف الأولى "BA" ، حتى يتمكن أيضًا من استخدام ترتيب الكلمة الرئيسية الثانية لـ "Aabaaab". يتم الإشارة إلى منطق النوع بأكمله أدناه
ثم سنقوم بتحليل الكود في الأجزاء
لـ (int i = n - j ؛ i <n ؛ i ++) {y [p ++] = i ؛ } // حدد الكلمة الرئيسية الثانية وفقًا للكلمة الرئيسية الأولى SA لـ (int i = 0 ؛ i <n ؛ i ++) {if (sa [i]> = j) {y [p ++] = sa [i] - j ؛ }}الكود أعلاه هو العثور على SA ، أي صفيف Y من الكلمة الرئيسية الثانية ، مع القيمة الأولية لـ P 0 ، والحلقة الأولى هي تصنيف اللاحقة التي يجب ملؤها في مقدمة الصفيف.
تحتاج إلى فهم منطق الحلقة الثانية مع الرسم البياني المنطقي السابق. نحن نعبر نتيجة الفرز للكلمة الرئيسية الأولى SA. إذا حدد (SA [i]> = j) ما إذا كان يمكن استخدام اللاحقة ككلمة رئيسية ثانية للاحقات الأخرى. أخذ الحلقة الأولى J = 1 كمثال ، عندما يمثل SA [i] = 0 صفيف اللاحقة "Aabaaaab" ، من الواضح أنه لا يمكن استخدامه ككلمة رئيسية ثانية لللواحق الأخرى. بالنسبة للكلمة الرئيسية الثانية التي يمكن استخدامها كلواحق أخرى ، فإن ترتيب SA هو الكلمة الرئيسية الثانية المقابلة. Sa [i] - J يجد لاحقة له ككلمة رئيسية ثانية ويضعها في صفيف y ، و p ++. تحتاج إلى فهم هنا ببطء.
// دمج نوع كلمتين رئيسيتين لـ (int i = 0 ؛ i <ws.length ؛ i ++) {ws [i] = 0 ؛ } لـ (int i: x) {ws [i] ++ ؛ } لـ (int i = 1 ؛ i <ws.length ؛ i ++) {ws [i]+= ws [i - 1] ؛ } لـ (int i = n-1 ؛ i> = 0 ؛ i--) {sa [-ws [x [y [i]]]] = y [i] ؛ y [i] = 0 ؛ }ما سبق هو العثور على فرز المجموعة بناءً على الكلمات الرئيسية الأولى SA وفرز الكلمة الرئيسية الثانية. هذا الرمز غامض للغاية. لا يمكننا أولاً أن نفهم الكود ، ولكن نفهم فكرة. بالنسبة لفرز كلمتين رئيسيتين ، تتشابه القواعد الفعلية مع فرز رقمين. على سبيل المثال ، يقارن 11 و 12 الحجم ، و 10 بت هي الكلمة الرئيسية الأولى ، والجملة الفردية هي الكلمة الرئيسية الثانية. بعد مقارنة 10 بت ، نجد 11 = 12 ، ثم نقارن البتات المفردة ، نعلم أن 11 <12. إذا كانت البتات العشرة هي نفسها ، فإن ترتيب البتات المفردة هو ترتيب الحجم. قلت في المرة الأولى التي أحسب فيها الفرز أعلاه أن ترتيب فرز العد للحلقة يمثل فعليًا ترتيب الترتيب عندما تكون قيم الترتيب هي نفسها. فكيف نجد الترتيب بعد دمج الكلمة الرئيسية في فرز إحصائي واحد؟ دعني أخبرك ما فهمي. يحتوي نوع واحد على نوع واحد على نوعين ، أحدهما هو نوع من القيم العددية ، والآخر هو نوع الحدوث. القواعد تعادل المثال السابق للمقارنة بين 11 و 12. هذا النوع من القيم العددية هو 10 بت ، ونوع ترتيب الحدوث هو بت واحد. في هذه المرحلة ، لدينا فكرة. يتم فرز فرز القيم بواسطة الكلمة الرئيسية الأولى ، ويتم فرز الفرز من خلال الكلمة الرئيسية الثانية ، حتى نتمكن من الاعتماد والفرز في وقت واحد للعثور على الفرز بعد الجمع بين الكلمة الرئيسية. الرمز أعلاه هو تنفيذ هذه الفكرة. مجموعة X هي صفيف رتبة الكلمة الرئيسية الأولى ، ونحن نحسبها.
لـ (int i = n-1 ؛ i> = 0 ؛ i--) {sa [-ws [x [y [i]]] = y [i] ؛ y [i] = 0 ؛ }هذه الحلقة هي تنفيذ جميع الأفكار المذكورة أعلاه. نعبر مجموعة الكلمات الرئيسية الثانية من الخلف. بالنسبة لـ y [i] ، نقوم بحساب الترتيب العد لكلماته الرئيسية الأولى. هذا الترتيب العد هو ترتيب y [i] ، ويتم تخفيض العدد النهائي بمقدار 1. تم العثور على نوع الكلمة الرئيسية المدمجة بنجاح.
أعتقد أنه إذا فهمت كل الرموز المذكورة أعلاه ، فستندهش بالتأكيد. كنت متحمسًا أيضًا عندما فكرت في هذا الرمز مرارًا وتكرارًا ، وكنت مقتنعًا ببساطة. هذا هو سحر الخوارزميات.
مع صفيف SA ، يمكننا العثور على صفيف الرتبة. هذا ليس بالأمر الصعب ، لذلك لن نشرح ذلك. يتم إرفاق جميع الرموز للعثور على SA أدناه.
public static void main (string [] args) {string str = "aabaaaab" ؛ mysuffixArrayTest arrayTest = جديد mysuffixArrayTest (str.toString ()) ؛ arraytest.initsa () ؛ // find sa array} public void initsa () {rank = new int [n] ؛ sa = int new [n] ؛ WS = New Int [255] ؛ y = new int [n] ؛ x = new int [n] ؛ // حلقة السلسلة الأصلية لتحويل قيمة int إلى صفيف الترتيب لـ (int i = 0 ؛ i <n ؛ i ++) {rank [i] = (int) fuckix [i] ؛ } // First Count Sort for (int i = 0 ؛ i <n ؛ i ++) {ws [rank [i]] ++ ؛ x [i] = Rank [i] ؛ } لـ (int i = 1 ؛ i <ws.length ؛ i ++) {ws [i]+= ws [i - 1] ؛ } لـ (int i = n-1 ؛ i> = 0 ؛ i--) {sa [-ws [rease [i]]] = i ؛ } // مجموعة حلقة الفرز لـ (int j = 1 ، p = 0 ؛ j <= n ؛ j = j << 1) {// إذا كنت بحاجة إلى ملء ، إضافة الصفيف المرتبة أولاً yp = 0 ؛ لـ (int i = n - j ؛ i <n ؛ i ++) {y [p ++] = i ؛ } // النطاق الكلمة الرئيسية الثانية وفقًا للكلمة الرئيسية الأولى SA لـ (int i = 0 ؛ i <n ؛ i ++) {if (sa [i]> = j) {y [p ++] = sa [i] - j ؛ }} // فرز الكلمة الرئيسية لـ (int i = 0 ؛ i <ws.length ؛ i ++) {ws [i] = 0 ؛ } لـ (int i: x) {ws [i] ++ ؛ } لـ (int i = 1 ؛ i <ws.length ؛ i ++) {ws [i]+= ws [i - 1] ؛ } لـ (int i = n-1 ؛ i> = 0 ؛ i--) {sa [-ws [x [y [i]]] = y [i] ؛ y [i] = 0 ؛ }. } رقم int = 1 ؛ x [sa [0]] = 1 ؛ لـ (int i = 1 ؛ i <n ؛ i ++) {if (xb [sa [i]]! = xb [sa [i - 1]]) {x [sa [i]] = ++ number ؛ } آخر إذا (sa [i] + j> = n && sa [i - 1] + j> = n) {x [sa [i]] = number ؛ } آخر إذا (sa [i] + j <n && sa [i - 1] + j> = n) {x [sa [i]] = ++ number ؛ } آخر إذا (xb [sa [i] + j]! = xb [sa [i - 1] + j]) {x [sa [i]] = ++ رقم ؛ } آخر {x [sa [i]] = number ؛ } if (number> = n) break ؛ }}}}لخص
ما ورد أعلاه هو رمز المثال لصفائف SAC من صفائف اللاحقة Java التي قدمت لك. آمل أن يكون ذلك مفيدًا لك. إذا كان لديك أي أسئلة ، فيرجى ترك رسالة لي وسوف يرد المحرر إليك في الوقت المناسب. شكرا جزيلا لدعمكم لموقع wulin.com!