সরল ইংরেজিতে উকোনেনের প্রত্যয় গাছের অ্যালগোরিদম


1100

আমি এই মুহুর্তে কিছুটা ঘন অনুভব করি। আমি প্রত্যয় গাছের নির্মাণের চারপাশে পুরোপুরি আমার মাথা গুটিয়ে দেওয়ার চেষ্টা করে কাটিয়েছি, তবে আমার গাণিতিক পটভূমি না থাকায়, গাণিতিক চিহ্নের অতিরিক্ত ব্যবহার শুরু করার সাথে অনেক ব্যাখ্যা আমাকে সরিয়ে দেয়। আমি যে ভাল ব্যাখ্যা পেয়েছি তার নিকটতমটি হ'ল প্রত্যয় গাছের সাথে দ্রুত স্ট্রিং সন্ধান , তবে তিনি বিভিন্ন পয়েন্টের উপর দৃষ্টিপাত করেন এবং অ্যালগরিদমের কিছু দিক অস্পষ্ট থাকে।

স্ট্যাক ওভারফ্লোতে এই অ্যালগরিদমের একটি ধাপে ধাপে ব্যাখ্যাটি আমাকে ছাড়াও আরও অনেকের পক্ষে অমূল্য হবে, আমি নিশ্চিত।

রেফারেন্সের জন্য, এখানে অ্যালগরিদমের উপর উকোনেনের কাগজটি রয়েছে: http://www.cs.helsinki.fi/u/ukkonen/SuffixT1withFigs.pdf

আমার প্রাথমিক বোঝাপড়া, এখন পর্যন্ত:

  • আমাকে প্রদত্ত স্ট্রিং টি এর প্রতিটি উপসর্গ পি দিয়ে পুনরাবৃত্তি করতে হবে
  • আমার প্রত্যেকটি প্রত্যয় পিতে উপসর্গ পিতে পুনরাবৃত্তি করতে হবে এবং এটি গাছটিতে যুক্ত করতে হবে
  • গাছটিতে প্রত্যয় এস যুক্ত করতে, এস এর প্রতিটি চরিত্রের মাধ্যমে আমার পুনরাবৃত্তি করতে হবে, পুনরুক্তিগুলির সাথে হয় হয় একটি বিদ্যমান শাখাটি নীচে হাঁটতে হবে যা এস এর অক্ষর সি এর একই সেট দিয়ে শুরু হয় এবং সম্ভাব্যত বংশের নোডগুলিতে একটি প্রান্ত বিভক্ত করে যখন আমি প্রত্যয়টিতে একটি পৃথক চরিত্রে পৌঁছান, বা নীচে চলার মতো কোনও মিল নেই edge সি এর জন্য হাঁটার জন্য কোনও মিলের প্রান্তটি পাওয়া না গেলে, সি জন্য একটি নতুন পাতার প্রান্ত তৈরি করা হয় C.

বেসিক অ্যালগরিদমটি O (n 2 ) হিসাবে উপস্থিত হয়, বেশিরভাগ ব্যাখ্যায় উল্লেখ করা হয়েছে যেহেতু আমাদের সমস্ত উপসর্গের মধ্য দিয়ে যেতে হবে, তারপরে আমাদের প্রতিটি উপসর্গের প্রত্যয়গুলির প্রতিটি পদক্ষেপের প্রয়োজন। উকোনেনের অ্যালগরিদম তিনি যে প্রত্যয় পয়েন্টার কৌশলটি ব্যবহার করেছেন তা স্পষ্টতই অনন্য, যদিও আমি মনে করি এটিই বুঝতে সমস্যা হচ্ছে।

বুঝতে আমারও সমস্যা হচ্ছে:

  • ঠিক কখন এবং কীভাবে "সক্রিয় বিন্দু" নির্ধারিত হয়, ব্যবহৃত হয় এবং পরিবর্তিত হয়
  • অ্যালগরিদমের ক্যানোনাইজেশন দিকটি নিয়ে কী চলছে
  • আমি যে বাস্তবায়নগুলি দেখেছি তাদের ব্যবহার করার জন্য বাউন্ডিং ভেরিয়েবলগুলি "ঠিক করা" দরকার

সম্পূর্ণ সি # উত্স কোড এখানে is এটি কেবল সঠিকভাবে কাজ করে না, তবে স্বয়ংক্রিয় ক্যানোনাইজেশন সমর্থন করে এবং আউটপুটটির আরও সুন্দর দেখাচ্ছে পাঠ্য গ্রাফটি সরবরাহ করে। উত্স কোড এবং নমুনা আউটপুট এখানে:

https://gist.github.com/2373868


আপডেট 2017-11-04

বহু বছর পরে আমি প্রত্যয় গাছের জন্য একটি নতুন ব্যবহার খুঁজে পেয়েছি এবং জাভাস্ক্রিপ্টে অ্যালগরিদম প্রয়োগ করেছি । গিস্ট নীচে। এটি বাগ-মুক্ত হওয়া উচিত। এটিকে npm install chalkএকই অবস্থান থেকে একটি জেএস ফাইলে ফেলে দিন এবং কিছু রঙিন আউটপুট দেখতে নোড.জেএস দিয়ে চালান। কোনও ডিবাগিং কোড ছাড়াই একই জিস্টে স্ট্রিপ ডাউন সংস্করণ রয়েছে।

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


2
আপনি ড্যান গুসফিল্ডের বইয়ে দেওয়া বিবরণটি একবার দেখেছেন ? আমি এটি সহায়ক হতে পারে।
jogojapan

4
গিস্টটি লাইসেন্সটি নির্দিষ্ট করে না - আমি কী আপনার কোডটি পরিবর্তন করতে এবং এমআইটির অধীনে প্রকাশ করতে পারি (স্পষ্টতই গুণাবলী সহ)?
ইয়ুরিক

2
হ্যাঁ, আপনার জীবনের জন্য যান। এটি পাবলিক ডোমেন বিবেচনা করুন। এই পৃষ্ঠায় অন্য উত্তর দ্বারা উল্লিখিত হিসাবে, একটি বাগ আছে যা যাইহোক ঠিক করা প্রয়োজন।
নাথান রিডলি

1
হয়তো এই বাস্তবায়ন অন্যদের সাহায্য করবে এতে যান code.google.com/p/text-indexing
কোসাইন্

2
"এটি জনসাধারণের ডোমেন বিবেচনা করুন", সম্ভবত আশ্চর্যরকম একটি খুব অস্বাস্থ্যকর উত্তর। কারণটি হ'ল আপনার পক্ষে কাজটি সর্বজনীন ডোমেইনে স্থাপন করা অসম্ভব। অত: পর আপনার "এটা বিবেচনা ..." মন্তব্য সত্য যে লাইসেন্স স্পষ্ট নয় underlines এবং সন্দেহ পাঠক কারণ আসলে স্পষ্ট যে কাজের অবস্থা দেয় আপনি । আপনি যদি চান যে লোকেরা আপনার কোডটি ব্যবহার করতে সক্ষম হতে পারে তবে দয়া করে এটির জন্য একটি লাইসেন্স নির্দিষ্ট করুন, আপনার পছন্দের যে কোনও লাইসেন্স চয়ন করুন (তবে আপনি যদি আইনজীবী না হন, পূর্ব-বিদ্যমান লাইসেন্সটি চয়ন করুন!)
জেমস ইয়ংম্যান

উত্তর:


2376

নীচে উককোনেন অ্যালগরিদমকে বর্ণনা করার চেষ্টা করা হচ্ছে যখন প্রথমে স্ট্রিংটি সহজ হয় (যা কোনও পুনরাবৃত্তিযুক্ত অক্ষর ধারণ করে না) তখন এটি কী করে তা পূর্ণ আলগোরিদিম পর্যন্ত প্রসারিত করে what

প্রথমে কয়েকটি প্রাথমিক বক্তব্য।

  1. আমরা যা বানাচ্ছি তা মূলত অনুসন্ধান ত্রয়ের মতো। সুতরাং একটি মূল নোড আছে, প্রান্তগুলি এর বাইরে বেরিয়ে যাচ্ছে নতুন নোডগুলিতে এবং আরও প্রান্তগুলি এর বাইরে চলে যাচ্ছে, এবং আরও

  2. তবে : অনুসন্ধান অনুসন্ধানের মতো নয়, প্রান্তের লেবেলগুলি একক অক্ষর নয়। পরিবর্তে, প্রতিটি প্রান্তটি একটি জোড়া পূর্ণসংখ্যা ব্যবহার করে লেবেলযুক্ত [from,to]। এগুলি পাঠ্যের পয়েন্টার। এই অর্থে, প্রতিটি প্রান্তটি স্বেচ্ছাসেবী দৈর্ঘ্যের একটি স্ট্রিং লেবেল বহন করে তবে কেবল ও (1) স্থান (দুটি পয়েন্টার) নেয়।

মৌলিক নীতি

আমি প্রথমে প্রদর্শন করতে চাই যে কীভাবে একটি সাধারণ সরল স্ট্রিংয়ের প্রত্যয় গাছটি তৈরি করা যায়, কোনও স্ট্রিং যাতে পুনরাবৃত্তি না হয়:

abc

বাম থেকে ডানে অ্যালগরিদম পদক্ষেপে কাজ করে । নেই স্ট্রিং প্রতিটি অক্ষরের জন্য এক ধাপ । প্রতিটি পদক্ষেপে একাধিক স্বতন্ত্র অপারেশন জড়িত থাকতে পারে তবে আমরা দেখতে পাব (শেষে চূড়ান্ত পর্যবেক্ষণগুলি দেখুন) যে অপারেশনগুলির মোট সংখ্যা হ'ল (এন)।

সুতরাং, আমরা বাম থেকে শুরু করি , এবং প্রথমে aমূল পাতায় মূল নোড (বাম দিকে) থেকে একটি প্রান্ত তৈরি করে এবং কেবলমাত্র একক অক্ষর সন্নিবেশ করান [0,#], যার অর্থ প্রান্তটি 0 পজিশনে শুরু হওয়া এবং শেষের সমাপ্তিটিকে উপস্থাপিত করে এ বর্তমান শেষ । আমি প্রতীকটি বর্তমান প্রান্তটি #বোঝাতে ব্যবহার করি , যা অবস্থান 1 এ (ডান পরে )।a

সুতরাং আমাদের কাছে একটি প্রাথমিক গাছ আছে যা দেখতে এটির মতো দেখাচ্ছে:

এবং এর অর্থ কী:

এখন আমরা অবস্থান 2 (ডান পরে b) অগ্রগতি । প্রতিটি পদে পদে আমাদের লক্ষ্য সন্নিবেশ করতে হয় বর্তমান অবস্থান সব প্রত্যয় আপ । আমরা এটি দ্বারা

  • বিদ্যমান- aবৃদ্ধিকে প্রসারিত করা হচ্ছেab
  • এর জন্য একটি নতুন প্রান্ত .োকানো হচ্ছে b

আমাদের উপস্থাপনে এটির মতো দেখাচ্ছে

এখানে চিত্র বর্ণনা লিখুন

এবং এর অর্থ কী:

আমরা দুটি বিষয় পর্যবেক্ষণ করি :

  • জন্য প্রান্ত প্রতিনিধিত্ব abহয় একই যেমন প্রাথমিক গাছে ব্যবহার করা হয়: [0,#]। এর অর্থ স্বয়ংক্রিয়ভাবে পরিবর্তিত হয়েছে কারণ আমরা বর্তমান অবস্থানটি #1 থেকে 2 তে আপডেট করেছি ।
  • প্রতিটি প্রান্ত O (1) স্পেস ব্যবহার করে, কারণ এটি কতগুলি অক্ষর উপস্থাপন করে তা পাঠ্যের মধ্যে কেবল দুটি পয়েন্টার নিয়ে গঠিত।

এরপরে আমরা আবার অবস্থানটি বৃদ্ধি করব এবং cপ্রতিটি বিদ্যমান প্রান্তে একটি যুক্ত করে এবং নতুন প্রত্যয়ের জন্য একটি নতুন প্রান্ত সন্নিবেশ করে গাছ আপডেট করব c

আমাদের উপস্থাপনে এটির মতো দেখাচ্ছে

এবং এর অর্থ কী:

আমরা পর্যবেক্ষণ:

  • গাছটি প্রতিটি পদক্ষেপের পরে বর্তমান অবস্থান পর্যন্ত সঠিক প্রত্যয় গাছ
  • পাঠ্যটিতে অক্ষর রয়েছে যতগুলি পদক্ষেপ রয়েছে
  • প্রতিটি পদক্ষেপে কাজের পরিমাণ হ'ল ও (1), কারণ সমস্ত বিদ্যমান প্রান্তগুলি ইনক্রিমেন্টিংয়ের মাধ্যমে স্বয়ংক্রিয়ভাবে আপডেট হয় #এবং চূড়ান্ত অক্ষরের জন্য একটি নতুন প্রান্ত সন্নিবেশ করা ও (1) সময়ে করা যেতে পারে। সুতরাং দৈর্ঘ্য n এর স্ট্রিংয়ের জন্য কেবল ও (এন) সময় প্রয়োজন।

প্রথম সম্প্রসারণ: সরল পুনরাবৃত্তি

অবশ্যই এটি এত সুন্দরভাবে কাজ করে কারণ আমাদের স্ট্রিংটিতে কোনও পুনরাবৃত্তি নেই। আমরা এখন আরও বাস্তবসম্মত স্ট্রিংয়ের দিকে নজর দিই:

abcabxabcd

এটি abcপূর্ববর্তী উদাহরণের মতোই শুরু হয় , তারপরে abপুনরাবৃত্তি হয় এবং তার পরে অনুসরণ করা হয় xএবং তারপরে abcপুনরাবৃত্তি হয় d

পদক্ষেপ 1 থেকে 3 পর্যন্ত: প্রথম 3 টি পদক্ষেপের পরে আমাদের পূর্ববর্তী উদাহরণ থেকে গাছটি রয়েছে:

পদক্ষেপ 4: আমরা #অবস্থান 4 এ চলেছি This এটি স্পষ্টভাবে এটিতে বিদ্যমান সমস্ত প্রান্তকে আপডেট করে:

এবং আমাদের বর্তমান ধাপের চূড়ান্ত প্রত্যয়টি aমূলে sertোকানো দরকার।

এটি করার আগে আমরা আরও দুটি ভেরিয়েবল প্রবর্তন করি (পাশাপাশি #), যা অবশ্যই সেখানে সব সময় রয়েছে তবে আমরা এখনও এগুলি ব্যবহার করি নি:

  • সক্রিয় বিন্দু , যা একটি ট্রিপল হয় (active_node,active_edge,active_length)
  • এটি remainder, যা একটি পূর্ণসংখ্যা নির্দেশ করে যে আমাদের কতগুলি নতুন প্রত্যয় inোকাতে হবে

এই দুটির সঠিক অর্থ শীঘ্রই স্পষ্ট হয়ে উঠবে, তবে আপাতত কেবল এইভাবে বলা যাক:

  • সাধারণ abcউদাহরণে, সক্রিয় বিন্দু সর্বদা ছিল (root,'\0x',0), অর্থাত্ active_nodeমূলটি নোড active_edgeছিল নাল অক্ষর হিসাবে নির্দিষ্ট '\0x', এবং active_lengthশূন্য ছিল। এর প্রভাবটি ছিল যে আমরা প্রতিটি ধাপে newোকানো একটি নতুন প্রান্তটি একটি নতুন সৃজিত প্রান্ত হিসাবে মূল নোডে .োকানো হয়েছিল। এই তথ্য উপস্থাপনের জন্য ট্রিপল কেন প্রয়োজনীয় তা আমরা শীঘ্রই দেখব।
  • remainderসবসময় প্রতিটি পদক্ষেপ শুরুতে 1 সেট করা হয়। এর অর্থ হ'ল প্রতিটি পদক্ষেপের শেষে আমাদের সক্রিয়ভাবে সন্নিবেশ করানোর প্রত্যয় সংখ্যা ছিল 1 (সর্বদা কেবল চূড়ান্ত চরিত্র)।

এখন এটি পরিবর্তন হতে চলেছে। আমরা যখন বর্তমান চূড়ান্ত অক্ষর সন্নিবেশ aরুট সময়ে, আমরা লক্ষ্য ইতিমধ্যে একটি বহির্গামী প্রান্ত দিয়ে শুরু যে a, বিশেষ করে: abca। এই জাতীয় ক্ষেত্রে আমরা যা করি তা এখানে:

  • আমরা রুট নোডে একটি নতুন প্রান্ত প্রবেশ করিনা[4,#] । পরিবর্তে আমরা কেবল লক্ষ্য করি যে প্রত্যয়টি aইতিমধ্যে আমাদের গাছে রয়েছে। এটি একটি দীর্ঘ প্রান্তের মাঝখানে শেষ হয়, তবে আমরা এটির দ্বারা বিরক্ত হই না। আমরা জিনিসগুলিকে কেবল সেভাবেই ছেড়ে দিই।
  • আমরা সক্রিয় বিন্দু নির্ধারণ করতে (root,'a',1)। এর অর্থ সক্রিয় বিন্দু এখন মূল নোডের বহির্গামী প্রান্তের মাঝখানে কোথাও যেটি শুরু হয় a, বিশেষত, সেই প্রান্তে 1 অবস্থানের পরে। আমরা লক্ষ্য করেছি যে প্রান্তটি কেবলমাত্র তার প্রথম অক্ষর দ্বারা নির্দিষ্ট করা হয়েছে a। এটি পর্যাপ্ত কারণ যে কোনও নির্দিষ্ট অক্ষর দিয়ে কেবল একটি প্রান্ত শুরু হতে পারে (সম্পূর্ণ বিবরণটি পড়ার পরে এটি সত্য কিনা তা নিশ্চিত করুন)।
  • আমরা এছাড়াও বৃদ্ধি remainder, সুতরাং পরবর্তী পদক্ষেপের শুরুতে এটি 2 হবে।

পর্যবেক্ষণ: যখন আমাদের অন্তর্ভুক্ত করতে হবে চূড়ান্ত প্রত্যয়টি ইতিমধ্যে গাছের মধ্যে উপস্থিত রয়েছে তখন গাছটি নিজেই মোটেও পরিবর্তিত হয় না (আমরা কেবল সক্রিয় বিন্দু আপডেট করি এবং remainder)। গাছ তারপর প্রত্যয় গাছের একটি সঠিক উপস্থাপনা নয় বর্তমান অবস্থান পর্যন্ত কোন, কিন্তু এটা ধারণ করে (কারণ চূড়ান্ত প্রত্যয় সব প্রত্যয় aঅন্তর্ভুক্ত করা হয় পরোক্ষভাবে )। সুতরাং, ভেরিয়েবলগুলি আপডেট করা ছাড়াও (যা সমস্ত নির্দিষ্ট দৈর্ঘ্যের, সুতরাং এটি ও (1)), এই পদক্ষেপে কোনও কাজ করা হয়নি।

পদক্ষেপ 5: আমরা বর্তমান অবস্থানটি #5 এ আপডেট করি This এটি স্বয়ংক্রিয়ভাবে গাছটিকে এতে আপডেট করে:

এবং কারণ remainder2 , আমাদের বর্তমান অবস্থার দুটি চূড়ান্ত প্রত্যয় সন্নিবেশ করা প্রয়োজন: abএবং b। এটি মূলত কারণ:

  • aপূর্ববর্তী ধাপে থেকে প্রত্যয় সঠিকভাবে কখনই ঢোকানো হয়েছে। সুতরাং এটি করেছে রয়ে , এবং যেহেতু আমরা এক ধাপ অগ্রগতির, এটি এখন থেকে উত্থিত হয়েছে aথেকে ab
  • এবং আমাদের নতুন চূড়ান্ত প্রান্তটি প্রবেশ করানো দরকার b

অনুশীলনে এর অর্থ আমরা সক্রিয় পয়েন্টে চলে যাই (যা aএখন কোন abcabপ্রান্তটি তার পিছনে নির্দেশ করে ) এবং বর্তমানের চূড়ান্ত অক্ষরটি সন্নিবেশ করি bতবে: আবারও দেখা যাচ্ছে bযে একই ধারে ইতিমধ্যে উপস্থিত রয়েছে।

সুতরাং, আবার আমরা গাছ পরিবর্তন করি না। আমরা কেবল:

  • সক্রিয় পয়েন্টটি আপডেট করুন (root,'a',2)(আগের মতো একই নোড এবং প্রান্ত, তবে এখন আমরা এর পিছনে দিকে নির্দেশ করি b)
  • remainder3 এ বৃদ্ধি করুন কারণ আমরা এখনও পূর্ববর্তী পদক্ষেপ থেকে চূড়ান্ত প্রান্তটি যথাযথভাবে প্রবেশ করিনি এবং আমরা বর্তমান চূড়ান্ত প্রান্তটি sertোকাও না।

স্পষ্ট হবে: আমরা সন্নিবেশ করতে ছিল abএবং bবর্তমান পদক্ষেপে, কিন্তু কারণ abইতিমধ্যে পাওয়া যায়নি, আমরা সক্রিয় বিন্দু আপডেট এবং এমনকি সন্নিবেশ করার প্রচেষ্টা করা হয়নি b। কেন? কারণ যদি abগাছে থাকে তবে এর প্রতিটি প্রত্যয় (সহ b) অবশ্যই গাছে থাকতে হবে। সম্ভবত কেবল অন্তর্নিহিতভাবেই , তবে এটি অবশ্যই সেখানে রয়েছে, কারণ আমরা এখনও পর্যন্ত গাছটি তৈরি করেছি।

আমরা ইনক্রিমেন্ট করে step ধাপে এগিয়ে যাই #। গাছটি স্বয়ংক্রিয়ভাবে আপডেট হয়:

remainder3 কারণ , আমাদের সন্নিবেশ করতে হবে abx, bxএবং x। সক্রিয় বিন্দুটি আমাদের বলে দেয় কোথায় abশেষ হয়, সুতরাং আমাদের কেবল সেখানে লাফিয়ে theোকাতে হবে x। সত্যই, xএখনও সেখানে নেই, তাই আমরা abcabxপ্রান্তটি বিভক্ত করে একটি অভ্যন্তরীণ নোড সন্নিবেশ করলাম :

প্রান্তের উপস্থাপনাগুলি এখনও পাঠ্যের মধ্যে পয়েন্টার রয়েছে, সুতরাং বিভক্তকরণ এবং অভ্যন্তরীণ নোড সন্নিবেশ করাতে ও (1) সময়ে করা যেতে পারে।

সুতরাং আমরা এর সাথে ডিল করেছি abxএবং হ্রাস remainderপেয়েছি 2। এখন আমাদের পরবর্তী অবশিষ্ট প্রত্যয় inোকানো দরকার bx,। তবে এটি করার আগে আমাদের সক্রিয় পয়েন্টটি আপডেট করতে হবে। এটির বিধি বিভাজন এবং একটি প্রান্ত সন্নিবেশ করার পরে, নীচের নিয়ম 1 বলা হবে , এবং এটি যখনই active_nodeমূল হয় তখনই প্রয়োগ হয় (আমরা আরও নীচে অন্যান্য মামলার জন্য নিয়ম 3 শিখব)। এখানে নিয়ম 1:

মূল থেকে একটি সন্নিবেশ পরে,

  • active_node মূল থেকে যায়
  • active_edge নতুন প্রত্যয়টির প্রথম চরিত্রে সেট করা আছে যা আমাদের sertোকাতে হবে, অর্থাত্‍ b
  • active_length 1 দ্বারা হ্রাস করা হয়

সুতরাং, নতুন সক্রিয়-পয়েন্ট ট্রিপলটি (root,'b',1)নির্দেশ করে যে পরবর্তী সন্নিবেশটি bcabx1 টি অক্ষরের পিছনে অর্থাৎ পিছনে প্রান্তে তৈরি করতে হবে b। আমরা ও (1) সময়ে সন্নিবেশ পয়েন্টটি সনাক্ত করতে পারি এবং xইতিমধ্যে উপস্থিত কিনা তা যাচাই করতে পারি। যদি এটি উপস্থিত থাকে, আমরা বর্তমান পদক্ষেপটি শেষ করব এবং সমস্ত কিছু যেমন হয় তেমন রেখে দেব। তবে x উপস্থিত নেই, তাই আমরা প্রান্তটি ভাগ করে এটি sertোকান:

আবার, এটি ও (1) সময় নিয়েছে এবং আমরা remainder1 এ আপডেট করেছি এবং (root,'x',0)নিয়ম 1 রাজ্য হিসাবে সক্রিয় পয়েন্ট ।

তবে আমাদের আরও একটি কাজ করা দরকার। আমরা এই বিধি 2 কল করব :

যদি আমরা একটি প্রান্তটি বিভক্ত করে একটি নতুন নোড সন্নিবেশ করি এবং যদি বর্তমান পদক্ষেপের সময় এটি তৈরি করা প্রথম নোড না হয় তবে আমরা পূর্বে প্রবেশ করা নোড এবং নতুন নোডটিকে একটি বিশেষ পয়েন্টার, একটি প্রত্যয় লিঙ্কের মাধ্যমে সংযুক্ত করি । কেন এটি কার্যকর তা আমরা পরে দেখব। আমরা যা পাই তা এখানে, প্রত্যয় লিঙ্কটি একটি বিন্দু প্রান্ত হিসাবে উপস্থাপিত:

আমাদের এখনও বর্তমান পদক্ষেপের চূড়ান্ত প্রত্যয় সন্নিবেশ করা প্রয়োজন x,। যেহেতু active_lengthঅ্যাক্টিভ নোডের উপাদানটি 0 তে নেমে গেছে, চূড়ান্ত সন্নিবেশটি সরাসরি রুটে তৈরি করা হয়। যেহেতু মূল নোডে কোনও বহির্গমন প্রান্ত শুরু হচ্ছে না তাই xআমরা একটি নতুন প্রান্ত সন্নিবেশ করলাম:

আমরা দেখতে পাচ্ছি, বর্তমান পদক্ষেপে সমস্ত অবশিষ্ট সন্নিবেশ তৈরি করা হয়েছিল।

আমরা = 7 সেট করে 7 ধাপে এগিয়ে #যাই, যা যথারীতি aসমস্ত পাতার প্রান্তে স্বয়ংক্রিয়ভাবে পরবর্তী অক্ষরটি সংযোজন করে । তারপরে আমরা সক্রিয় বিন্দুতে (মূল) নতুন চূড়ান্ত অক্ষরটি সন্নিবেশ করানোর চেষ্টা করি এবং এটি ইতিমধ্যে রয়েছে বলে মনে করি। সুতরাং আমরা কোনও পদক্ষেপ না সরিয়ে বর্তমান পদক্ষেপটি শেষ করি এবং সক্রিয় বিন্দুটিতে আপডেট করি (root,'a',1)

ইন পদক্ষেপ 8 , #= 8, আমরা যোগ b, এবং আগে দেখা, এই একমাত্র উপায় আমরা সক্রিয় বিন্দু আপডেট (root,'a',2)এবং বৃদ্ধি remainderঅন্য কিছু না করে, কারণ bইতিমধ্যে উপস্থিত। তবে, আমরা লক্ষ্য করি (ও (1) সময়ে) সক্রিয় পয়েন্টটি এখন একটি প্রান্তের শেষে। আমরা এটিকে আবার সেট করে প্রতিফলিত করি (node1,'\0x',0)। এখানে, আমি প্রান্তটি শেষ হওয়া node1অভ্যন্তরীণ নোডের উল্লেখ করতে ব্যবহার করি ab

তারপরে, পদক্ষেপ #= 9 এ , আমাদের 'সি' toোকানো দরকার এবং এটি আমাদের চূড়ান্ত কৌশলটি বুঝতে সহায়তা করবে:

দ্বিতীয় এক্সটেনশন: প্রত্যয় লিঙ্ক ব্যবহার করে

সর্বদা হিসাবে, #আপডেটটি cপাতার কিনারাগুলিতে স্বয়ংক্রিয়ভাবে যুক্ত হয় এবং আমরা 'সি' sertোকাতে পারি কিনা তা দেখতে আমরা সক্রিয় বিন্দুতে যাই। দেখা যাচ্ছে যে 'সি' ইতিমধ্যে সেই প্রান্তে উপস্থিত রয়েছে, তাই আমরা সক্রিয় বিন্দুতে (node1,'c',1), বৃদ্ধি বাড়িয়েছি remainderএবং অন্য কিছুই করি না।

এখন ধাপে #= 10 , remainder4 হয়, এবং তাই আমাদের প্রথমে সক্রিয় পয়েন্টে সন্নিবেশ করিয়ে abcd(যা 3 টি পদক্ষেপ আগে থেকে যায়) সন্নিবেশ dকরা দরকার।

dসক্রিয় বিন্দুতে সন্নিবেশ করানোর চেষ্টা ও (1) সময়ে একটি প্রান্ত বিভাজন ঘটায়:

active_node, যা থেকে বিভক্ত সূচনা হয়, লাল উপরে চিহ্নিত করা হয়। এখানে চূড়ান্ত বিধি, বিধি 3:

active_nodeমূল নোড নয় এমন একটি থেকে একটি প্রান্ত বিভক্ত করার পরে , আমরা সেই নোডের বাইরে চলে যাওয়া প্রত্যয় লিঙ্কটি অনুসরণ করি, যদি কোনও থাকে, এবং active_nodeএটি যে নোডটিকে নির্দেশ করে সেটি পুনরায় সেট করে । যদি প্রত্যয়টি লিঙ্ক না থাকে তবে আমরা মূলটিকে সেট করব active_nodeactive_edge এবং active_lengthঅপরিবর্তিত থাকুন।

সক্রিয় বিন্দু এখন (node2,'c',1), এবং node2নীচে লাল চিহ্নিত করা হয়েছে:

যেহেতু সন্নিবেশ abcdসম্পূর্ণ হয়েছে, আমরা remainder3 এ হ্রাস পেয়েছি এবং বর্তমান পদক্ষেপের পরবর্তী বাক্য প্রত্যয়টি বিবেচনা করি bcd,। বিধি 3 সক্রিয় পয়েন্টটি ঠিক ডান নোড এবং প্রান্তে সেট করেছে তাই সক্রিয় পয়েন্টে bcdকেবলমাত্র তার চূড়ান্ত অক্ষর সন্নিবেশ করে সন্নিবেশ করা dযায়।

এটি করার ফলে অন্য প্রান্তটি বিভক্ত হয়ে যায় এবং নিয়ম 2 এর কারণে আমাদের অবশ্যই পূর্ববর্তী nোকানো নোড থেকে নতুনটিতে একটি প্রত্যয় লিঙ্ক তৈরি করতে হবে:

আমরা পালন: প্রত্যয় সংযোগগুলি তাই আমরা পরবর্তী করতে পারেন সক্রিয় বিন্দু পুনরায় সেট করতে সক্ষম অবশিষ্ট সন্নিবেশ হে (1) প্রচেষ্টা করেন। উপরের গ্রাফটি দেখতে নিশ্চিত করুন যে লেবেলে abনোডটি নোডের b(তার প্রত্যয়) abcসাথে যুক্ত হয়েছে এবং নোডটি সংযুক্ত রয়েছে bc

বর্তমান পদক্ষেপটি এখনও শেষ হয়নি। remainderএখন 2, এবং আবার সক্রিয় পয়েন্টটি পুনরায় সেট করতে আমাদের 3 রুল অনুসরণ করতে হবে। যেহেতু বর্তমানের active_node(উপরে লাল) কোনও প্রত্যয় লিঙ্ক নেই, তাই আমরা রুটে পুনরায় সেট করি। সক্রিয় পয়েন্ট এখন (root,'c',1)

অত: পর পরবর্তী সন্নিবেশ রুট নোড যার ট্যাগ দিয়ে শুরু হয় এক বিদায়ী প্রান্ত ঘটে c: cabxabcdপ্রথম অক্ষর, অর্থাত পিছনে পিছনে c। এটি অন্য বিভক্তির কারণ:

এবং যেহেতু এটিতে একটি নতুন অভ্যন্তরীণ নোড তৈরি জড়িত, আমরা নিয়ম 2 অনুসরণ করি এবং পূর্বে নির্মিত অভ্যন্তরীণ নোড থেকে একটি নতুন প্রত্যয় লিঙ্ক সেট করি:

(আমি এই ছোট্ট গ্রাফগুলির জন্য গ্রাফভিজ ডট ব্যবহার করছি The নতুন প্রত্যয় লিঙ্কটি ডটকে বিদ্যমান প্রান্তগুলি পুনরায় সাজিয়েছে , তাই সাবধানে পরীক্ষা করে নিশ্চিত করুন যে উপরে inোকানো একমাত্র জিনিসই একটি নতুন প্রত্যয় লিঙ্ক))

এটির সাথে, remainder1 এ সেট করা যেতে পারে এবং যেহেতু active_nodeমূলটি মূল, আমরা সক্রিয় পয়েন্টটি আপডেট করতে নিয়ম 1 ব্যবহার করি (root,'d',0)। এর অর্থ বর্তমানের পদক্ষেপের চূড়ান্ত সন্নিবেশ হ'ল মূলটিতে একটি একক প্রবেশ করানো d :

এটি ছিল চূড়ান্ত পদক্ষেপ এবং আমরা সম্পন্ন করেছি। চূড়ান্ত পর্যবেক্ষণের সংখ্যা রয়েছে যদিও:

  • প্রতিটি পদক্ষেপে আমরা #1 পজিশনে এগিয়ে যাই । এটি ও (1) সময়ে সমস্ত পাত নোডগুলি স্বয়ংক্রিয়ভাবে আপডেট করে।

  • তবে এটি ক) পূর্ববর্তী পদক্ষেপগুলি থেকে বাকি কোনও প্রত্যয় এবং খ) বর্তমান পদক্ষেপের একটি চূড়ান্ত চরিত্রের সাথে মোকাবিলা করে না ।

  • remainderআমাদের আরও কত অতিরিক্ত সন্নিবেশ তৈরি করতে হবে তা আমাদের জানায়। এই সন্নিবেশগুলি স্ট্রিংয়ের চূড়ান্ত প্রত্যয়গুলির সাথে একের সাথে সামঞ্জস্য করে যা বর্তমান অবস্থানে শেষ হয় #। আমরা একের পর এক বিবেচনা করি এবং সন্নিবেশ তৈরি করি। গুরুত্বপূর্ণ: প্রতিটি সন্নিবেশ O (1) সময়ে সম্পন্ন করা হয় যেহেতু সক্রিয় বিন্দুটি ঠিক কোথায় যেতে হবে তা আমাদের জানায় এবং সক্রিয় পয়েন্টে আমাদের কেবল একটি একক অক্ষর যুক্ত করতে হবে। কেন? কারণ অন্যান্য অক্ষরগুলি স্পষ্টভাবে অন্তর্ভুক্ত রয়েছে (অন্যথায় সক্রিয় বিন্দুটি যেখানে থাকবে না সেখানে)।

  • এই জাতীয় প্রতিটি সন্নিবেশ করার পরে, আমরা remainderযদি হ'ল প্রত্যাহার লিঙ্কটি হ্রাস করি এবং অনুসরণ করি। না হলে আমরা রুটে যাই (নিয়ম 3)। যদি আমরা ইতিমধ্যে রুট থেকে থাকে তবে আমরা নিয়ম 1 ব্যবহার করে সক্রিয় বিন্দুটি সংশোধন করি any যে কোনও ক্ষেত্রে এটি কেবল ও (1) সময় নেয়।

  • যদি, এই সন্নিবেশগুলির মধ্যে একটির মধ্যে, আমরা দেখতে পাই যে আমরা যে চরিত্রটি সন্নিবেশ করতে চাইছি তা ইতিমধ্যে সেখানে রয়েছে, আমরা কিছু করি না এবং বর্তমান পদক্ষেপটি শেষ করেও remainder> 0 হয় না। কারণটি হ'ল যে কোনও সন্নিবেশ অবশিষ্ট রয়েছে তা কেবলমাত্র আমরা তৈরি করার চেষ্টা করেছি এমনটির প্রত্যয় হবে। সুতরাং এগুলি সমস্ত বর্তমান গাছে অন্তর্নিহিতremainder> 0 সত্যটি নিশ্চিত করে যে আমরা বাকী প্রত্যয়গুলি পরে করব with

  • অ্যালগরিদম remainder> 0 এর শেষে হলে কী হবে ? যখনই পাঠ্যের সমাপ্তিটি এমন একটি স্ট্রস্ট্রিং যা এর আগে কোথাও ঘটেছিল তখন এটি ঘটবে। সেক্ষেত্রে স্ট্রিংয়ের শেষে আমাদের একটি অতিরিক্ত চরিত্র যুক্ত করতে হবে যা এর আগে ঘটে নি। সাহিত্যে, সাধারণত ডলার চিহ্নটি $তার জন্য প্রতীক হিসাবে ব্যবহৃত হয়। কেন এটা ব্যাপার? -> পরে যদি আমরা প্রত্যয় অনুসন্ধানের জন্য সম্পূর্ণ প্রত্যয় গাছ ব্যবহার করি তবে আমাদের অবশ্যই ম্যাচগুলি কেবল কোনও পাতায় শেষ হলে তা গ্রহণ করতে হবে । তা না হলে আমরা কৃত্রিম মিলের একটি অনেক পাওয়া আছে কারণ হবে অনেক স্ট্রিং পরোক্ষভাবে যে প্রধান স্ট্রিং এর প্রকৃত প্রত্যয় হয় না গাছে রয়েছে। অত্যাচারremainderশেষে 0 হতে হবে এটি নিশ্চিত করার একটি উপায় যা সমস্ত প্রত্যয় একটি পাতার নোডে শেষ হয়। যাইহোক, আমরা যদি সাধারণ সাবস্ট্রিংগুলি অনুসন্ধানের জন্য গাছটি ব্যবহার করতে চাই , তবে কেবল মূল স্ট্রিংয়ের প্রত্যয়ই নয় , এই চূড়ান্ত পদক্ষেপটি অবশ্যই প্রয়োজন হয় না, নীচে ওপি'র মন্তব্যের দ্বারা প্রস্তাবিত।

  • তাহলে পুরো অ্যালগরিদমের জটিলতা কী? যদি পাঠ্যটির দৈর্ঘ্য n অক্ষর হয় তবে স্পষ্টতই n পদক্ষেপ রয়েছে (বা আমরা ডলারের চিহ্নটি যুক্ত করলে এন + 1)। প্রতিটি পদক্ষেপে আমরা হয় (ভেরিয়েবলগুলি আপডেট করা ব্যতীত) কিছুই করি না, বা আমরা remainderসন্নিবেশগুলি তৈরি করি, প্রত্যেকটি ও (1) সময় নেয়। যেহেতু remainderপূর্ববর্তী পদক্ষেপগুলিতে আমরা কতবার কিছু না করে ইঙ্গিত করে এবং আমরা এখন যে প্রতিটি সন্নিবেশ করানো হয় তার জন্য হ্রাস করা হয়, আমরা কিছু বার করার মতো মোট সংখ্যা হ'ল এন (বা এন + 1)। অতএব, মোট জটিলতা হ'ল (এন)।

  • তবে, একটি ছোট জিনিস আছে যা আমি সঠিকভাবে ব্যাখ্যা করিনি: এটি ঘটতে পারে যে আমরা প্রত্যয় লিঙ্কটি অনুসরণ করি, সক্রিয় বিন্দুটি আপডেট করি এবং তারপরে দেখতে পাই যে এর active_lengthউপাদানটি নতুনটির সাথে ভালভাবে কাজ করে না active_node। উদাহরণস্বরূপ, এই জাতীয় পরিস্থিতি বিবেচনা করুন:

(ড্যাশযুক্ত রেখাগুলি গাছের বাকী অংশ নির্দেশ করে The বিন্দুযুক্ত রেখাটি একটি প্রত্যয় লিঙ্ক)

এখন সক্রিয় বিন্দু হতে দিন (red,'d',3), তাই এটি পিছনে জায়গা পয়েন্ট fউপর defgপ্রান্ত। এখন ধরে নিন আমরা প্রয়োজনীয় আপডেটগুলি করেছি এবং এখন নিয়ম 3 অনুযায়ী সক্রিয় পয়েন্টটি আপডেট করতে প্রত্যয় লিঙ্কটি অনুসরণ করুন (green,'d',3)। নতুন সক্রিয় বিন্দুটি হ'ল । তবে, dসবুজ নোডের বাইরে চলে যাওয়া কাজটি হ'ল de, সুতরাং এতে কেবল 2 টি অক্ষর রয়েছে। সঠিক সক্রিয় বিন্দুটি সন্ধান করার জন্য, অবশ্যই আমাদের অবশ্যই সেই প্রান্তটি নীল নোডে অনুসরণ করতে হবে এবং এতে পুনরায় সেট করতে হবে (blue,'f',1)

একটি বিশেষ খারাপ ক্ষেত্রে, active_lengthমত বৃহৎ হতে পারে remainder, যা এন মত বৃহৎ হতে পারে। এবং এটি খুব ভালভাবে ঘটতে পারে যে সঠিক সক্রিয় পয়েন্টটি খুঁজতে, আমাদের কেবলমাত্র একটি অভ্যন্তরীণ নোডের উপরে ঝাঁপিয়ে পড়ার দরকার নেই, তবে সম্ভবত অনেকগুলি, সবচেয়ে খারাপ ক্ষেত্রে N পর্যন্ত। তার মানে কি অ্যালগরিদমের কোনও লুকানো ও (এন 2 ) জটিলতা রয়েছে, কারণ প্রতিটি পদক্ষেপে remainderসাধারণত ও (এন) থাকে এবং প্রত্যয় লিঙ্ক অনুসরণ করার পরে সক্রিয় নোডের পোস্ট-অ্যাডজাস্টমেন্টগুলি ও (এন )ও হতে পারে?

না। কারণ হ'ল যদি সত্যই আমাদের সক্রিয় পয়েন্টটি সমন্বিত করতে হয় (যেমন উপরে সবুজ থেকে নীল থেকে), যা আমাদের একটি নতুন নোডে নিয়ে আসে যার নিজস্ব প্রত্যয় লিঙ্ক রয়েছে, এবং active_lengthহ্রাস পাবে। আমরা যখন প্রত্যয় লিঙ্কগুলির শৃঙ্খলা অনুসরণ করি তখন আমরা বাকী সন্নিবেশগুলি তৈরি করি, active_lengthকেবল হ্রাস করতে পারে এবং পথে চলতে পারি এমন সক্রিয় বিন্দুগুলির সংখ্যা active_lengthনির্দিষ্ট সময়ের চেয়ে বড় হতে পারে না । যেহেতু active_lengthকখনই এর চেয়ে বড় হতে পারে না remainderএবং remainder ও (এন) কেবল প্রতিটি একক পদক্ষেপেই নয়, তবে remainderপুরো প্রক্রিয়া চলাকালীন সর্বমোট বর্ধিত পরিমাণের পরিমাণও ও (এন) হ'ল, সক্রিয় বিন্দুর সামঞ্জস্য সংখ্যা এছাড়াও ও (এন) দ্বারা আবদ্ধ।


74
দুঃখিত, এটি আমার প্রত্যাশার চেয়ে একটু বেশি সময় শেষ হয়েছিল। এবং আমি উপলব্ধি করেছি যে এটি আমাদের সকলের কাছে জানা কয়েকটি তুচ্ছ বিষয়গুলির ব্যাখ্যা করে, যখন কঠিন অংশগুলি এখনও পুরোপুরি পরিষ্কার হতে পারে না। আসুন এটি একসাথে আকারে সম্পাদনা করি।
জোগোজাপান

68
এবং আমার যুক্ত করা উচিত যে এটি ড্যান গুসফিল্ডের বইয়ের প্রাপ্ত বিবরণের ভিত্তিতে নয় । প্রথমে কোনও পুনরাবৃত্তি না নিয়ে স্ট্রিং বিবেচনা করে এবং তারপরে পুনরাবৃত্তিগুলি কীভাবে পরিচালনা করা হয় তা নিয়ে আলোচনা করে অ্যালগরিদম বর্ণনা করার এটি একটি নতুন প্রচেষ্টা। আমি আশা করি এটি আরও স্বজ্ঞাত হবে।
জোগোজাপান

8
ধন্যবাদ @ জোগোজাপান, আপনার ব্যাখ্যার জন্য আমি একটি সম্পূর্ণ-কার্যকর উদাহরণ লিখতে সক্ষম হয়েছি। আমি উত্সটি প্রকাশ করেছি তাই আশা করি অন্য কেউ এটির ব্যবহারটি খুঁজে পেতে পারে: gist.github.com/2373868
নাথান রিডলি

4
@ নাথানরিডলি হ্যাঁ (যাইহোক, উকোনোন ক্যানোনাইজিকে ডাকে এই চূড়ান্ত বিটটি)। এটি ট্রিগার করার একটি উপায় হ'ল এখানে একটি স্ট্রিং রয়েছে যা তিনবার প্রদর্শিত হয় এবং একটি স্ট্রিংয়ে শেষ হয় যা আরও একবারে ভিন্ন প্রসঙ্গে প্রদর্শিত হয় sure যেমন abcdefabxybcdmnabcdex। এর প্রাথমিক অংশটি abcdপুনরাবৃত্তি করা হয় abxy(এটি পরে একটি অভ্যন্তরীণ নোড তৈরি করে ab) এবং আবার ভিতরে আসে abcdexএবং এটি শেষ হয় bcd, যা কেবল bcdexপ্রসঙ্গে নয়, bcdmnপ্রসঙ্গেও প্রদর্শিত হয়। abcdexbcdex
Sertedোকানোর

6
ঠিক আছে আমার কোডটি পুরোপুরি নতুন করে লেখা হয়েছে এবং এখন স্বয়ংক্রিয়ভাবে ক্যানোনাইজেশন সহ সমস্ত ক্ষেত্রে সঠিকভাবে কাজ করে, এর সাথে আরও বেশ ভাল পাঠ্য গ্রাফ আউটপুট রয়েছে। gist.github.com/2373868
নাথান রিডলি

132

আমি জোগোজাপানের উত্তরে প্রদত্ত পদ্ধতির সাথে প্রত্যয় গাছটি বাস্তবায়নের চেষ্টা করেছি, তবে নিয়মের জন্য শব্দ ব্যবহারের কারণে এটি কিছু ক্ষেত্রে কার্যকর হয়নি। তদুপরি, আমি উল্লেখ করেছি যে কেউ এই পদ্ধতির ব্যবহার করে একেবারে সঠিক প্রত্যয় গাছ প্রয়োগ করতে পারেনি। নীচে আমি নিয়মতে কিছু সংশোধন করে জোগোজাপানের উত্তরের একটি "ওভারভিউ" লিখব। যখন আমরা গুরুত্বপূর্ণ প্রত্যয় লিঙ্ক তৈরি করতে ভুলে যাই তখন আমি কেসটিও বর্ণনা করব ।

অতিরিক্ত ভেরিয়েবল ব্যবহৃত হয়

  1. সক্রিয় বিন্দু - একটি ট্রিপল (অ্যাক্টিভ_নোড; অ্যাক্টিভ_ডেজ; অ্যাক্টিভ_লেন্থ), যেখানে আমাদের অবশ্যই একটি নতুন প্রত্যয় সন্নিবেশ করা শুরু করতে হবে তা দেখাচ্ছে।
  2. অবশিষ্ট - আমাদের স্পষ্টভাবে যুক্ত করতে হবে প্রত্যয় সংখ্যা দেখায় । উদাহরণস্বরূপ, যদি আমাদের শব্দটি 'আবকাব্বা' হয়, এবং বাকী = 3 হয় তবে এর অর্থ আমাদের অবশ্যই শেষ তিনটি প্রত্যয় প্রক্রিয়া করতে হবে: বিসিএ , সিএ এবং

আসুন একটি একটি ধারণা ব্যবহার অভ্যন্তরীণ নোড ছাড়া সমস্ত নোড, - রুট এবং পাতায় হয় অভ্যন্তরীণ নোড

পর্যবেক্ষণ ঘ

আমাদের অন্তর্ভুক্ত করতে হবে এমন চূড়ান্ত প্রত্যয়টি যখন ইতিমধ্যে গাছের মধ্যে উপস্থিত রয়েছে তখন গাছটি নিজেই একেবারেই পরিবর্তিত হয় না (আমরা কেবলমাত্র আপডেট করি active pointএবং remainder)।

পর্যবেক্ষণ 2

যদি কোনও পর্যায়ে active_lengthবর্তমান প্রান্তের দৈর্ঘ্যের ( edge_length) বৃহত্তর বা সমান হয় তবে আমরা আমাদের active pointনীচে সরাতে হবে যতক্ষণ না edge_lengthতার চেয়ে কঠোরতর হয় active_length

এখন, বিধিগুলি নতুনভাবে সংজ্ঞায়িত করা যাক:

বিধি 1

যদি সক্রিয় নোড = মূল থেকে সন্নিবেশের পরে , সক্রিয় দৈর্ঘ্য 0 এর চেয়ে বেশি হয়, তবে:

  1. সক্রিয় নোড পরিবর্তন করা হয় না
  2. সক্রিয় দৈর্ঘ্য হ্রাস করা হয়
  3. সক্রিয় প্রান্তটি ডান স্থানান্তরিত হয় (আমাদের পরবর্তী ofোকানো প্রত্যয়টির প্রথম অক্ষরে)

বিধি 2

আমরা একটি নতুন তৈরি করা হলে অভ্যন্তরীণ নোড বা একটি থেকে একটি Inserter করতে অভ্যন্তরীণ নোড , এবং এটি প্রথম নয় এত অভ্যন্তরীণ নোড বর্তমান পদে পদে, তাহলে আমরা পূর্ববর্তী লিংক এত সঙ্গে নোড এই একটি মাধ্যমে এক প্রত্যয় লিংক

এর এই সংজ্ঞাটি Rule 2জোগোজাপান থেকে আলাদা, কারণ এখানে আমরা কেবল বিবেচনা করি না সদ্য নির্মিত অভ্যন্তরীণ , অভ্যন্তরীণ নোডগুলিও বিবেচনা করি, যা থেকে আমরা একটি সন্নিবেশ তৈরি করি।

বিধি 3

থেকে একটি সন্নিবেশ করার পর সক্রিয় নোড যা নয় রুট নোড, আমরা প্রত্যয় লিঙ্কটি অনুসরণ করুন এবং সেট করতে হবে সক্রিয় নোড নোড এটি পয়েন্ট করে। যদি প্রত্যয়টির কোনও লিঙ্ক না থাকে তবে রুট নোডে সক্রিয় নোডটি সেট করুন । যে কোনও উপায়ে, সক্রিয় প্রান্ত এবং সক্রিয় দৈর্ঘ্য অপরিবর্তিত থাকে।

এই সংজ্ঞাতে Rule 3আমরা লিফ নোডগুলির সন্নিবেশগুলিও বিবেচনা করি (কেবল বিভক্ত নোডগুলি নয়)।

এবং অবশেষে, পর্যবেক্ষণ 3:

আমরা গাছটিতে যে চিহ্নটি যুক্ত করতে চাইছি তা ইতিমধ্যে প্রান্তে রয়েছে, আমরা Observation 1কেবলমাত্র আপডেট করে active pointএবং remainderগাছটিকে অপরিবর্তিত রেখে দিই । কিন্তু যদি অভ্যন্তরীণ নোড থাকে যা প্রত্যয় লিঙ্কের প্রয়োজন হিসাবে চিহ্নিত করা হয় , আমরা আমাদের বর্তমান যে নোড সংযুক্ত করতে হবে active nodeএকটি প্রত্যয় লিঙ্কের মাধ্যমে।

আসুন আমরা সিডিডিডিসিডিসি-র জন্য প্রত্যয় গাছের উদাহরণটি দেখি যদি আমরা এরকম ক্ষেত্রে প্রত্যয় লিঙ্ক যুক্ত করি এবং যদি না করি:

  1. আমরা যদি প্রত্যয় লিঙ্কের মাধ্যমে নোডগুলি সংযুক্ত না করি :

    • শেষ বর্ণটি যুক্ত করার আগে সি :

    • শেষ চিঠি যোগ করার পরে :

  2. আমরা যদি DO একটি প্রত্যয় লিঙ্কের মাধ্যমে নোড সাথে সংযোগ করুন:

    • শেষ বর্ণটি যুক্ত করার আগে সি :

    • শেষ চিঠি যোগ করার পরে :

দেখে মনে হচ্ছে কোনও উল্লেখযোগ্য পার্থক্য নেই: দ্বিতীয় ক্ষেত্রে দুটি আরও প্রত্যয় লিঙ্ক রয়েছে। তবে এই প্রত্যয় লিঙ্কগুলি সঠিক , এবং এর মধ্যে একটি - নীল নোড থেকে লাল একটি - সক্রিয় পয়েন্ট সহ আমাদের পদ্ধতির জন্য খুব গুরুত্বপূর্ণ । সমস্যাটি হ'ল আমরা যদি এখানে প্রত্যয় লিঙ্কটি না রাখি, পরে, যখন আমরা গাছটিতে কিছু নতুন অক্ষর যুক্ত করি, আমরা গাছের সাথে কিছু নোড যুক্ত করতে বাদ দিতে পারিRule 3 , কারণ এটি অনুসারে, যদি কোনও একটি থাকে না প্রত্যয় লিঙ্ক, তারপরে আমাদের অবশ্যই রুটটিতে রাখতে হবে active_node

যখন আমরা গাছটিতে শেষ চিঠিটি যুক্ত করছিলাম তখন নীল নোড থেকে একটি সন্নিবেশ করার আগে লাল নোড ইতিমধ্যে উপস্থিত ছিল (প্রান্তটি 'সি' লেবেলযুক্ত )। নীল নোড থেকে একটি সন্নিবেশ ছিল, আমরা এটি একটি প্রত্যয় লিঙ্ক প্রয়োজন হিসাবে চিহ্নিত । তারপরে, সক্রিয় বিন্দু পদ্ধতির উপর নির্ভর করে active nodeরেড নোডে সেট করা হয়েছিল। কিন্তু আমরা লাল নোড থেকে একটি সন্নিবেশ তৈরি করি না, কারণ 'সি' অক্ষরটি ইতিমধ্যে প্রান্তে রয়েছে। এর অর্থ কি এই যে নীল নোডটি অবশ্যই প্রত্যয় লিঙ্ক ছাড়াই ছেড়ে যেতে হবে? না, আমাদের অবশ্যই একটি প্রত্যয় লিঙ্কের মাধ্যমে নীল নোডটিকে লাল রঙের সাথে সংযুক্ত করতে হবে। কেন এটা সঠিক? কারণ সক্রিয় পয়েন্টপদ্ধতির গ্যারান্টি দেয় যে আমরা একটি সঠিক জায়গায় পৌঁছাতে, অর্থাৎ পরের স্থানে যেখানে আমাদের অবশ্যই একটি সংক্ষিপ্ত প্রত্যয় সন্নিবেশ প্রক্রিয়া করতে হবে ।

পরিশেষে, প্রত্যয় গাছের আমার বাস্তবায়ন এখানে দেওয়া হল:

  1. জাভা
  2. সি ++

আশা করি জোগোপনের বিস্তারিত উত্তরের সাথে মিলিত এই "ওভারভিউ "টি কাউকে তার নিজস্ব প্রত্যয় গাছ বাস্তবায়নে সহায়তা করবে।


3
আপনাকে প্রচেষ্টার জন্য অনেক ধন্যবাদ এবং +1। আমি নিশ্চিত যে আপনি ঠিক বলেছেন .. যদিও এখনই বিশদ সম্পর্কে চিন্তা করার আমার কাছে সময় নেই। আমি পরে যাচাই করব এবং সম্ভবত আমার উত্তরটিও পরে সংশোধন করব।
jogojapan

অনেক ধন্যবাদ, এটি সত্যিই সাহায্য করেছে। যদিও আপনি কি পর্যবেক্ষণ 3 সম্পর্কে আরও নির্দিষ্ট হতে পারেন? উদাহরণস্বরূপ, 2 টি পদক্ষেপের ডায়াগ্রামগুলি দেওয়া যা নতুন প্রত্যয় লিঙ্কটি প্রবর্তন করে। নোডটি কি সক্রিয় নোডের সাথে যুক্ত? (যেহেতু আমরা আসলে ২ য় নোডটি
sertোকাতে

@ ম্যাকগানোভ এই যে আপনি আপনার স্ট্রিং "সিডিডিডিসিডিসি" এর জন্য প্রত্যয় গাছ বানাতে আমাকে সহায়তা করতে পারেন আমি তা করতে পেরে কিছুটা বিভ্রান্ত হয়েছি (শুরু করার পদক্ষেপগুলি)।
তারিক জাফর

3
নিয়ম 3 হিসাবে, একটি স্মার্ট উপায় হ'ল রুটের প্রত্যয় লিঙ্কটি নিজেই রুটে সেট করা এবং (ডিফল্টরূপে) প্রতিটি নোডের প্রত্যয় লিঙ্কটি রুটে সেট করা। সুতরাং আমরা কন্ডিশনার এড়াতে পারি এবং প্রত্যয় লিঙ্কটি অনুসরণ করতে পারি।
স্কয়ার

1
aabaacaadএমন একটি ক্ষেত্রে যা অতিরিক্ত প্রত্যয় লিঙ্ক যুক্ত করে ট্রিপল আপডেট করার সময়কে হ্রাস করতে পারে shows জোগোজাপান পোস্টের শেষ দুটি অনুচ্ছেদে উপসংহারটি ভুল। যদি আমরা এই পোস্টে উল্লিখিত প্রত্যয় লিঙ্কগুলি যুক্ত না করি, গড় সময়ের জটিলতা হ'ল ও (নলং (এন)) বা আরও বেশি হওয়া উচিত। কারণ সঠিকটি পেতে গাছটি হাঁটতে অতিরিক্ত সময় লাগে active_node
ইভানাগাইরো

10

@ জোগোজাপানের সুস্পষ্ট ব্যাখ্যা টিউটোরিয়ালের জন্য ধন্যবাদ , আমি পাইথনে আলগোরিদিম প্রয়োগ করেছি।

@ জোগোজাপানের দ্বারা উল্লিখিত কয়েকটি ছোট ছোট সমস্যা আমার প্রত্যাশার চেয়ে পরিশীলিত হতে দেখা গেছে এবং খুব সাবধানতার সাথে চিকিত্সা করা দরকার। আমার বাস্তবায়ন যথেষ্ট শক্তিশালী হতে আমার বেশ কয়েক দিন ব্যয় হয়েছে (আমি মনে করি)। সমস্যা এবং সমাধান নীচে তালিকাভুক্ত করা হয়েছে:

  1. Remainder > 0এটির সাথে সমাপ্তি দেখা যাচ্ছে এই পরিস্থিতিটি পুরো অ্যালগরিদমের কেবল শেষ নয়, উত্থিত পদক্ষেপের সময়ও ঘটতে পারে । যখন এটি ঘটে তখন আমরা বাকী, অ্যাক্টনোড, অভিনয় এবং অ্যাকলেটেন্থ অপরিবর্তিত রেখে দিতে পারি , বর্তমান উদ্ঘাটন ধাপটি শেষ করতে পারি এবং মূল স্ট্রিংয়ের পরবর্তী চরটি যদি বর্তমান পথে থাকে তবে তার উপর নির্ভর করে ভাঁজ বা অনাবৃত রেখে আরও একটি পদক্ষেপ শুরু করতে পারি বা না.

  2. ওভার নোডগুলি: যখন আমরা প্রত্যয় লিঙ্কটি অনুসরণ করি, তখন সক্রিয় বিন্দুটি আপডেট করুন এবং তারপরে সন্ধান করুন যে এর সক্রিয়_ দৈর্ঘ্যের উপাদানটি নতুন অ্যাক্টিভ_নোডের সাথে ভালভাবে কাজ করে না। আমরা আছে অগ্রসর বিভক্ত করার সঠিক জায়গায়, অথবা একটি গাছের পাতা সন্নিবেশ করুন। এই প্রক্রিয়াটি এতটা সোজা নাও হতে পারে কারণ চলমান চলাকালীন অ্যাকলেথেনথ এবং অ্যাক্টেজ সমস্ত পথ পরিবর্তন করে রাখে, যখন আপনাকে রুট নোডে ফিরে যেতে হয় , সেই পদক্ষেপের কারণে অ্যাক্টেজ এবং অ্যাকটিলেথ ভুল হতে পারে । সেই তথ্য রাখতে আমাদের অতিরিক্ত ভেরিয়েবল (গুলি) দরকার।

    এখানে চিত্র বর্ণনা লিখুন

অন্য দুটি সমস্যা কোনওভাবেই @ মানাগোভ দ্বারা চিহ্নিত করা হয়েছে

  1. স্প্লিট হ্রাস পেতে পারে একটি প্রান্তকে বিভক্ত করার চেষ্টা করার সময়, আপনি কখনও কখনও স্পষ্টভাবে অপারেশনটি কোনও নোডের সাথে দেখতে পাবেন। সেক্ষেত্রে আমাদের কেবল সেই নোডে একটি নতুন পাতা যুক্ত করা দরকার, এটি একটি স্ট্যান্ডার্ড এজ স্প্লিট অপারেশন হিসাবে গ্রহণ করুন, যার অর্থ প্রত্যয় লিঙ্কগুলি যদি থাকে তবে তা যথাযথভাবে বজায় রাখা উচিত।

  2. লুকানো প্রত্যয় সংযোগগুলি আরও একটি বিশেষ কেস রয়েছে যা সমস্যা 1 এবং সমস্যা 2 দ্বারা উত্পন্ন । কখনও কখনও আমাদের বিভাজনের জন্য ডান পয়েন্টে বেশ কয়েকটি নোডের ওপরে ওঠা উচিত, আমরা যদি বাকী স্ট্রিং এবং পথের লেবেলের সাথে তুলনা করে চলি তবে আমরা ডান পয়েন্টকে ছাড়িয়ে যেতে পারি। সেক্ষেত্রে প্রত্যয় লিঙ্কটি অজান্তেই অবহেলিত হবে, যদি কিছু থাকে। এটি এগিয়ে যাওয়ার সময় সঠিক পয়েন্টটি মনে রেখে এড়ানো যেতে পারে । স্প্লিট নোড ইতিমধ্যে বিদ্যমান থাকলে, বা সমস্যাটি 1 একটি অনাবৃত পদক্ষেপের সময় ঘটলে প্রত্যয় লিঙ্কটি বজায় রাখা উচিত ।

অবশেষে পাইথনে আমার বাস্তবায়ন নিম্নরূপ:

টিপস: এটিতে উপরের কোডে একটি নিষ্পাপ ট্রি মুদ্রণ ফাংশন অন্তর্ভুক্ত রয়েছে , যা ডিবাগ করার সময় খুব গুরুত্বপূর্ণ । এটি আমার অনেক সময় সাশ্রয় করেছে এবং বিশেষ ক্ষেত্রে চিহ্নিত করার জন্য সুবিধাজনক।


10

আমার উত্তরটি যদি অপ্রয়োজনীয় মনে হয় তবে দুঃখিত, তবে আমি সম্প্রতি উকোননের অ্যালগরিদম বাস্তবায়ন করেছি এবং কয়েক দিনের জন্য নিজেকে লড়াই করে দেখতে পেয়েছি; অ্যালগরিদমের কয়েকটি মূল বিষয় কেন এবং কীভাবে তা বোঝার জন্য আমাকে বিষয়টির একাধিক কাগজপত্র পড়তে হয়েছিল।

অন্তর্নিহিত কারণগুলি বোঝার জন্য আমি পূর্ববর্তী উত্তরগুলির 'বিধিগুলি' পদ্ধতির জন্য অসহনীয় খুঁজে পেয়েছি , সুতরাং আমি কেবলমাত্র ব্যবহারিকতার উপর মনোনিবেশ করে নীচে সমস্ত কিছু লিখেছি। যদি আপনি অন্যান্য ব্যাখ্যাগুলি অনুসরণ করার সাথে লড়াই করে থাকেন, যেমনটি আমি করেছি, সম্ভবত আমার পরিপূরক ব্যাখ্যা এটি আপনার জন্য 'ক্লিক' করবে।

আমি আমার সি # বাস্তবায়ন এখানে প্রকাশ করেছি: https://github.com/baratgabor/SuffixTree

দয়া করে মনে রাখবেন যে আমি এই বিষয়ে বিশেষজ্ঞ নই, সুতরাং নিম্নলিখিত বিভাগগুলিতে ভুল (বা আরও খারাপ) থাকতে পারে। যদি আপনার কোনও মুখোমুখি হয় তবে সম্পাদনা করতে দ্বিধা বোধ করুন।

পূর্বশর্ত

নিম্নলিখিত ব্যাখ্যার প্রারম্ভিক বিন্দু ধরে নেওয়া হয়েছে যে আপনি প্রত্যয় গাছের সামগ্রী এবং ব্যবহার এবং ইউকোনেনের অ্যালগরিদমের বৈশিষ্ট্যগুলি সম্পর্কে যেমন, আপনি কীভাবে শুরু থেকে শেষ পর্যন্ত চরিত্রের সাথে প্রত্যয় গাছের চরিত্রটিকে প্রসারিত করছেন তার সাথে পরিচিত। মূলত, আমি ধরে নিই যে আপনি ইতিমধ্যে অন্যান্য কয়েকটি ব্যাখ্যা পড়েছেন।

(তবে, প্রবাহের জন্য আমাকে কিছু প্রাথমিক বিবরণ যোগ করতে হয়েছিল, যাতে শুরুতে সম্ভবত নিরর্থক বোধ হতে পারে))

সবচেয়ে আকর্ষণীয় অংশ প্রত্যয় লিঙ্ক ব্যবহার করে এবং রুট থেকে rescanning মধ্যে পার্থক্য উপর ব্যাখ্যা । এটিই আমার বাস্তবায়নে আমাকে প্রচুর বাগ এবং মাথা ব্যাথা দিয়েছিল।

খোলা-শেষ পাতার নোড এবং তাদের সীমাবদ্ধতা

আমি নিশ্চিত যে আপনি ইতিমধ্যে জানেন যে সর্বাধিক মৌলিক 'কৌতুক' অনুধাবন করা হয়েছে আমরা প্রত্যয়টির শেষে 'উন্মুক্ত' রেখে যেতে পারি, অর্থাৎ স্ট্যাটিক মানটির স্থিতিশীল মানের পরিবর্তে স্ট্রিংয়ের বর্তমান দৈর্ঘ্য উল্লেখ করে। এইভাবে যখন আমরা অতিরিক্ত অক্ষরগুলি যুক্ত করি, তখন সেই সমস্ত অক্ষরগুলি পরিদর্শন ও সেগুলির সমস্ত আপডেট না করেই সমস্ত প্রত্যয় লেবেলে স্পষ্টভাবে যুক্ত করা হবে।

কিন্তু প্রত্যয়গুলির এই উন্মুক্ত সমাপ্তি - সুস্পষ্ট কারণে - কেবল নোডের জন্য কাজ করে যা গাছের কাঠামোর মধ্যে পাতার নোডগুলি বোঝায়। আমরা গাছের উপর চালিত শাখা প্রশাখাগুলি (নতুন শাখা নোড এবং লিফ নোডের সংযোজন) তাদের যেদিকেই প্রয়োজন সেখানে স্বয়ংক্রিয়ভাবে প্রচার করবে না।

এটি সম্ভবত প্রাথমিক, এবং উল্লেখ করার প্রয়োজন হবে না, যে পুনরাবৃত্ত সাবস্ট্রিংগুলি গাছে স্পষ্টভাবে উপস্থিত হয় না, যেহেতু গাছটিতে ইতিমধ্যে তাদের পুনরাবৃত্তি হওয়ার কারণে এটি রয়েছে; যাইহোক, পুনরাবৃত্তীয় স্ট্রিং যখন একটি পুনরাবৃত্তিযোগ্য চরিত্রের মুখোমুখি হয়ে শেষ হয়, তখন আমাদের সেই বিন্দু থেকে বিচ্যুতি উপস্থাপন করার জন্য সেই বিন্দুতে একটি শাখা তৈরি করা দরকার।

উদাহরণস্বরূপ 'ABCXABCY' স্ট্রিংয়ের ক্ষেত্রে (নীচে দেখুন), এক্স এবং ওয়াইয়ের একটি শাখা তিনটি পৃথক প্রত্যয়, এবিসি , বিসি এবং সিতে যুক্ত করা দরকার ; অন্যথায় এটি একটি কার্যকর প্রত্যয় গাছ হবে না এবং মূল থেকে নীচের দিকে অক্ষরের সাথে মিল রেখে আমরা স্ট্রিংয়ের সমস্ত সাবস্ট্রিংগুলি খুঁজে পাই না।

আবারও জোর দেওয়ার জন্য - গাছের প্রত্যয়ের জন্য আমরা যে কোনও ক্রিয়াকলাপ পরিচালনা করি তার ক্রমাগত প্রত্যয়গুলিও (যেমন: এবিসি> বিসি> সি) দ্বারা প্রতিফলিত হওয়া দরকার, অন্যথায় তারা কেবল বৈধ প্রত্যয় হিসাবে বন্ধ হয়ে যায়।

প্রত্যয়গুলিতে ব্রাঞ্চিং পুনরাবৃত্তি করা হচ্ছে

তবে আমরা যদি স্বীকার করি যে আমাদের এই ম্যানুয়াল আপডেটগুলি করতে হবে তবে আমরা কীভাবে জানি যে কতগুলি প্রত্যয় আপডেট করা দরকার? যেহেতু, আমরা যখন পুনরাবৃত্ত অক্ষর (এবং ধারাবাহিকতায় পুনরাবৃত্তি করা অক্ষরগুলি) যোগ করি তখন আমাদের প্রত্যয়টি কখন / কোথায় দুটি শাখায় বিভক্ত করা দরকার তা আমাদের কোনও ধারণা নেই। বিভাজনের প্রয়োজনীয়তা কেবল তখনই সনাক্ত করা হয় যখন আমরা প্রথম অ-পুনরাবৃত্তি চরিত্রের মুখোমুখি হই, এই ক্ষেত্রে ওয়াই ( X এর পরিবর্তে গাছটিতে ইতিমধ্যে বিদ্যমান)।

আমরা যা করতে পারি তা হ'ল আমরা যা করতে পারি তার দীর্ঘতম পুনরাবৃত্ত স্ট্রিংয়ের সাথে মেলে এবং এর পরে আরও আপডেট করতে আমাদের এর কতটা প্রত্যয় প্রয়োজন তা গণনা করা। এটিই 'বাকী' বলতে বোঝায়।

'অবশিষ্ট' এবং 'পুনরুদ্ধার' ধারণা

চলকটি remainderআমাদের জানায় যে আমরা শাখা ছাড়াই কতগুলি পুনরাবৃত্তি অক্ষর স্পষ্টভাবে যুক্ত করেছি; উদাহরণস্বরূপ, আমরা যখন প্রথম অক্ষরটি আমরা মেলতে পারি না সেগুলি খুঁজে পেয়ে আমাদের ব্রাঞ্চিং অপারেশনটির পুনরাবৃত্তি করার জন্য আমাদের কতটি প্রত্যয় পরিদর্শন করতে হবে। এটি মূলত গাছের মূল থেকে আমরা কতগুলি অক্ষর 'গভীর' এর সমান।

সুতরাং, স্ট্রিং ABCXABCY এর আগের উদাহরণটি রেখে আমরা পুনরাবৃত্ত এবিসি অংশটি ' স্পষ্টতই' মিলি , remainderপ্রতি বার বৃদ্ধি করি , যার ফলস্বরূপ 3 থাকে Then এর পরে বাকী আমরা অক্ষরযুক্ত অক্ষর 'Y' এর মুখোমুখি হই । এখানে আমরা পূর্বে যুক্ত হওয়া এবিসিএক্স বিভক্ত করি মধ্যে এবিসি > - এক্স এবং এবিসি -> ওয়াই । তারপরে আমরা remainder3 থেকে 2 এ হ্রাস পেয়েছি, কারণ আমরা ইতিমধ্যে এবিসি শাখার যত্ন নিয়েছি । - এখন আমরা গত 2 অক্ষর মিলিয়ে অপারেশন পুনরাবৃত্তি বিসি - রুট থেকে বিন্দু যেখানে আমরা বিভক্ত প্রয়োজন পৌঁছানোর, এবং আমরা বিভক্ত BCX খুব মধ্যে বিসি-> এক্স এবং বিসি -> ওয়াই । আবার, আমরা হ্রাস remainder1, এবং অপারেশন পুনরাবৃত্তি; শেষ পর্যন্ত, remainderশেষ পর্যন্ত , আমাদের বর্তমান অক্ষরটি ( ওয়াই ) নিজেই রুটে যুক্ত করতে হবে।

এই অপারেশনটি মূলত পর পরের প্রত্যয় অনুসরণ করে কেবল আমাদের যে অপারেশনটি করা দরকার সেখানে পৌঁছানোর জন্য যাকে ইউকোনেনের অ্যালগরিদমে 'রিসানানিং' বলা হয় এবং সাধারণত এটি অ্যালগরিদমের সবচেয়ে ব্যয়বহুল অংশ। দীর্ঘতর স্ট্রিংয়ের কল্পনা করুন যেখানে আপনাকে কয়েক হাজার নোড জুড়ে দীর্ঘ পুনরায় সাবস্ট্রিং করতে হবে (সম্ভবত আমরা এটি পরে আলোচনা করব), কয়েক হাজার বার।

সমাধান হিসাবে, আমরা যাকে আমরা 'প্রত্যয় লিঙ্ক' বলি তা উপস্থাপন করি ।

'প্রত্যয় লিঙ্ক' ধারণা

প্রত্যয়টি লিঙ্কগুলি মূলত আমাদের যে অবস্থানগুলিতে সাধারণত 'পুনর্নবীকরণ' করতে হবে তা নির্দেশ করে, তাই ব্যয়বহুল উদ্ধার অভিযানের পরিবর্তে আমরা কেবল লিঙ্কযুক্ত অবস্থানে যেতে পারি, আমাদের কাজটি করতে পারি, পরবর্তী সংযুক্ত অবস্থানে যেতে পারি এবং পুনরাবৃত্তি করতে পারি - যতক্ষণ না সেখানে আপডেট করার জন্য আর কোনও অবস্থান নেই।

অবশ্যই একটি বড় প্রশ্ন এই লিঙ্কগুলি কীভাবে যুক্ত করবেন। বিদ্যমান উত্তরটি হ'ল আমরা লিঙ্কগুলি যুক্ত করতে পারি যখন আমরা নতুন শাখা নোডগুলি সন্নিবেশ করি এবং গাছের প্রতিটি সম্প্রসারণে, শাখা নোডগুলি প্রাকৃতিকভাবে একের পর এক যথাযথভাবে তৈরি করা হয় যাতে আমরা তাদের একসাথে লিঙ্ক করতে চাই d । যদিও, আমরা শেষের তৈরি ব্রাঞ্চ নোড (দীর্ঘতম প্রত্যয়) থেকে পূর্বের তৈরি করা একটিটির সাথে লিঙ্ক করতে হবে, সুতরাং আমাদের তৈরি করা শেষটিকে ক্যাশে করা দরকার, এটি তৈরি করা পরেরটির সাথে যুক্ত করুন এবং সদ্য নির্মিত একটিটিকে ক্যাশে করতে হবে।

একটি পরিণতি হ'ল আমাদের সাধারণত অনুসরণ করার জন্য প্রত্যয় লিঙ্ক থাকে না, কারণ প্রদত্ত শাখা নোডটি সবেমাত্র তৈরি হয়েছিল। এই ক্ষেত্রে আমাদের এখনও মূল থেকে পূর্বের উল্লিখিত 'পুনরুদ্ধার' এ ফিরে যেতে হবে । এ কারণেই, একটি সন্নিবেশের পরে, আপনাকে প্রত্যয়টি লিঙ্কটি ব্যবহার করতে, বা রুটটিতে লাফিয়ে যেতে নির্দেশ দেওয়া হচ্ছে।

(অথবা বিকল্পভাবে, আপনি যদি নোডগুলিতে প্যারেন্ট পয়েন্টারগুলি সঞ্চয় করে থাকেন তবে আপনি পিতামাতাকে অনুসরণ করার চেষ্টা করতে পারেন, তাদের কোনও লিঙ্ক আছে কিনা তা পরীক্ষা করে তা ব্যবহার করতে পারেন I আমি খুঁজে পেয়েছি যে এটি খুব কমই উল্লেখ করা হয়েছে, তবে প্রত্যয় লিঙ্কের ব্যবহারটি নয় পাথরগুলিতে স্থাপন করুন multiple

'অ্যাক্টিভ পয়েন্ট' ধারণা

এখনও অবধি আমরা গাছটি তৈরির জন্য একাধিক দক্ষ সরঞ্জাম নিয়ে আলোচনা করেছি এবং অস্পষ্টভাবে একাধিক প্রান্ত এবং নোডের উপর দিয়ে ট্র্যাভারিংয়ের কথা উল্লেখ করেছি, তবে এখনও এর পরিণতি এবং জটিলতাগুলি অনুসন্ধান করতে পারি নি।

'বাকী' এর পূর্বে বর্ণিত ধারণাটি আমরা গাছটিতে কোথায় আছি তা রক্ষার জন্য দরকারী, তবে আমাদের বুঝতে হবে যে এটি পর্যাপ্ত তথ্য সংরক্ষণ করে না।

প্রথমত, আমরা সর্বদা একটি নোডের একটি নির্দিষ্ট প্রান্তে বাস করি, তাই আমাদের প্রান্তের তথ্য সংরক্ষণ করতে হবে। আমরা এটিকে 'সক্রিয় প্রান্ত' বলব ।

দ্বিতীয়ত, প্রান্তের তথ্য যোগ করার পরেও, আমাদের এখনও গাছের আরও নিচে অবস্থিত এমন একটি অবস্থান সনাক্ত করার উপায় নেই, যা সরাসরি মূল নোডের সাথে সংযুক্ত নেই । সুতরাং আমাদের পাশাপাশি নোড সংরক্ষণ করতে হবে। আসুন কল এই 'সক্রিয় নোড'

শেষ অবধি, আমরা লক্ষ্য করতে পারি যে 'অবশিষ্টটি' কোনও মূলের সাথে সরাসরি সংযুক্ত নয় এমন একটি অবস্থান সনাক্ত করতে অপর্যাপ্ত, কারণ 'অবশিষ্ট' পুরো পথের দৈর্ঘ্য; এবং আমরা সম্ভবত পূর্ববর্তী প্রান্তগুলির দৈর্ঘ্য স্মরণ করে এবং বিয়োগ করে বিরক্ত করতে চাই না। সুতরাং আমাদের এমন একটি উপস্থাপনা দরকার যা মূলত বর্তমান প্রান্তের বাকি অংশ । এটিকে আমরা 'সক্রিয় দৈর্ঘ্য' বলি ।

এটি যাকে আমরা 'অ্যাক্টিভ পয়েন্ট' বলি তার দিকে পরিচালিত করে - তিনটি ভেরিয়েবলের একটি প্যাকেজ যা গাছের মধ্যে আমাদের অবস্থান সম্পর্কে আমাদের রাখতে প্রয়োজনীয় সমস্ত তথ্য ধারণ করে:

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

আপনি নীচের চিত্রটিতে পর্যবেক্ষণ করতে পারেন যে কীভাবে এবিসিএবিডি এর মিলিত রুটটি প্রান্তে AB ( মূল থেকে ) এর 2 টি অক্ষর, এবং CABDABCABD প্রান্তে 4 টি অক্ষর (নোড 4 থেকে) নিয়ে গঠিত - যার ফলে 6 টি অক্ষরের একটি 'অবশিষ্ট' থাকবে। সুতরাং, আমাদের বর্তমান অবস্থানটি অ্যাক্টিভ নোড 4, অ্যাক্টিভ এজ সি, অ্যাক্টিভ দৈর্ঘ্য 4 হিসাবে চিহ্নিত করা যেতে পারে ।

অনুস্মারক এবং সক্রিয় পয়েন্ট

'অ্যাক্টিভ পয়েন্ট' এর আরেকটি গুরুত্বপূর্ণ ভূমিকা হ'ল এটি আমাদের অ্যালগরিদমের জন্য একটি বিমূর্ত স্তর সরবরাহ করে, যার অর্থ আমাদের অ্যালগরিদমের অংশগুলি 'অ্যাক্টিভ পয়েন্ট' এ তাদের কাজ করতে পারে , সেই অ্যাক্টিভ পয়েন্টটি মূল বা অন্য কোথাও রয়েছে তা নির্বিশেষে ective । এটি আমাদের অ্যালগরিদমে প্রত্যয় লিঙ্কগুলির ব্যবহারকে একটি পরিষ্কার এবং সোজা-সামনের দিকে কার্যকর করতে সহজ করে তোলে।

প্রত্যয় লিঙ্ক ব্যবহার করে বনাম পুনরুদ্ধারের পার্থক্য

এখন, জটিল অংশটি - যা আমার অভিজ্ঞতায় - প্রচুর বাগ এবং মাথাব্যথার কারণ হতে পারে এবং বেশিরভাগ উত্সগুলিতে খুব কম ব্যাখ্যা করা হয়, এটি রেসান কেসগুলির বিরুদ্ধে প্রত্যয় লিঙ্কের মামলাগুলি প্রক্রিয়াকরণের পার্থক্য।

'AAAABAAAABAAC' স্ট্রিংয়ের নিম্নলিখিত উদাহরণটি বিবেচনা করুন :

একাধিক প্রান্ত জুড়ে অবশিষ্ট

আপনি উপরে পর্যবেক্ষণ করতে পারেন যে 7 এর 'অবশিষ্ট' কীভাবে মূল থেকে অক্ষরের মোট যোগফলের সাথে মিল রাখে , যখন 4 এর 'সক্রিয় দৈর্ঘ্য' সক্রিয় নোডের সক্রিয় প্রান্ত থেকে মিলিত অক্ষরের যোগের সাথে মিলে যায়।

এখন, সক্রিয় পয়েন্টে একটি ব্রাঞ্চিং অপারেশন চালানোর পরে, আমাদের সক্রিয় নোডটিতে একটি প্রত্যয় লিঙ্ক থাকতে পারে বা নাও থাকতে পারে।

যদি প্রত্যয় লিঙ্ক উপস্থিত থাকে: আমাদের কেবলমাত্র 'সক্রিয় দৈর্ঘ্য' অংশটি প্রক্রিয়া করা দরকার । 'বাকি' কারণ, অপ্রাসঙ্গিক নোড যেখানে আমরা প্রত্যয় লিঙ্কের মাধ্যমে ঝাঁপ ইতিমধ্যে সঠিক 'বাকি' পরোক্ষভাবে এনকোড , সহজভাবে গাছ এটা যেখানে হচ্ছে শক্তি কর্মদক্ষতার দ্বারা।

যদি প্রত্যয় লিঙ্কটি উপস্থিত না থাকে: আমাদের শূন্য / মূল থেকে 'রিসান' করতে হবে , যার অর্থ শুরু থেকে পুরো প্রত্যয়টি প্রক্রিয়া করা হয়। এই লক্ষ্যে আমাদের পুরো ' বাকী'টিকে পুনরুদ্ধারের ভিত্তি হিসাবে ব্যবহার করতে হবে ।

প্রত্যয় লিঙ্কের সাথে এবং ছাড়া প্রক্রিয়াজাতকরণের তুলনা উদাহরণ

উপরের উদাহরণের পরবর্তী ধাপে কী ঘটে তা বিবেচনা করুন। আসুন কীভাবে একই ফলাফলটি অর্জন করবেন - অর্থাত্ পরবর্তী প্রত্যয়টিতে প্রক্রিয়াতে চলে যাওয়া - প্রত্যয় লিঙ্কের সাথে এবং ছাড়াই।

'প্রত্যয় লিঙ্ক' ব্যবহার

প্রত্যয় লিঙ্কের মাধ্যমে ধারাবাহিক প্রত্যয় পৌঁছে দেওয়া

লক্ষ্য করুন যে আমরা যদি প্রত্যয় লিঙ্ক ব্যবহার করি তবে আমরা স্বয়ংক্রিয়ভাবে 'সঠিক জায়গায় "থাকি। যা 'সচল দৈর্ঘ্য' নতুন অবস্থানের সাথে 'বেমানান' হতে পারে তার কারণে প্রায়শই কঠোরভাবে সত্য হয় না ।

উপরের ক্ষেত্রে, যেহেতু 'সক্রিয় দৈর্ঘ্য' 4, তাই আমরা সংযুক্ত নোড 4 থেকে শুরু করে ' ABAA' প্রত্যয়টির সাথে কাজ করছি, তবে প্রত্যয়ের প্রথম অক্ষরের সাথে মিলিত প্রান্তটি খুঁজে পাওয়ার পরে ( 'এ') ), আমরা লক্ষ্য করি যে আমাদের 'সক্রিয় দৈর্ঘ্য' 3 টি অক্ষর দ্বারা এই প্রান্তটিকে উপচে ফেলেছে। সুতরাং আমরা পুরো প্রান্ত পেরিয়ে, পরবর্তী নোডে, এবং লাফের সাথে গ্রাস করা অক্ষরগুলির দ্বারা ক্রমহ্রাস 'সক্রিয় দৈর্ঘ্য'

তারপরে, আমরা পরবর্তী প্রান্ত 'বি' খুঁজে পাওয়ার পরে, হ্রাসপ্রাপ্ত প্রত্যয় 'বিএএ'র সাথে মিল রেখে , আমরা শেষ পর্যন্ত নোট করব যে প্রান্তের দৈর্ঘ্যটি 3 এর অবশিষ্ট ' সক্রিয় দৈর্ঘ্যের ' চেয়ে বড় , যার অর্থ আমরা সঠিক স্থানটি পেয়েছি।

দয়া করে মনে রাখবেন যে মনে হয় এই অপারেশনটি সাধারণত 'পুনরুদ্ধার' হিসাবে উল্লেখ করা হয় না, যদিও আমার কাছে মনে হয় এটি কেবল সংক্ষিপ্ত দৈর্ঘ্য এবং একটি মূল-বিন্দুর সূচনা বিন্দুর সাহায্যে এটি পুনরুদ্ধারের প্রত্যক্ষ সমতুল্য।

'পুনরায়' ব্যবহার করা হচ্ছে

পুনর্বার মাধ্যমে ধারাবাহিক প্রত্যয় পৌঁছে যাওয়া

লক্ষ্য করুন যে আমরা যদি একটি traditionalতিহ্যবাহী 'পুনর্বাসন' অপারেশন ব্যবহার করি (এখানে আমাদের প্রত্যয়টির যোগসূত্র নেই তা ভান করে), আমরা গাছের শীর্ষে, মূলের দিকে শুরু করি এবং আমাদের আবার নীচে সঠিক জায়গায় যেতে হবে, বর্তমান প্রত্যয় পুরো দৈর্ঘ্য বরাবর অনুসরণ।

এই প্রত্যয়ের দৈর্ঘ্যটি আমরা আগে আলোচনা করেছি 'অবশিষ্ট' । শূন্য না হওয়া পর্যন্ত আমাদের এই বাকী অংশটির পুরোপুরি গ্রাস করতে হবে। এর মধ্যে (এবং প্রায়শই ঘটে) একাধিক নোডের মধ্য দিয়ে লাফানো অন্তর্ভুক্ত থাকে, প্রতিটি লাফের সময় আমরা যে প্রান্তটি পেরিয়েছিলাম তার দৈর্ঘ্যের দ্বারা অবশিষ্ট অংশ হ্রাস করে। তারপরে অবশেষে, আমরা এমন একটি প্রান্তে পৌঁছে যা আমাদের অবশিষ্ট ' অবশিষ্ট ' এর চেয়ে দীর্ঘ ; এখানে আমরা প্রদত্ত প্রান্তে সক্রিয় প্রান্তটি সেট করে রেখেছি, 'সক্রিয় দৈর্ঘ্য' অবশেষে ' অবশিষ্ট ' থেকে সেট করুন এবং আমাদের কাজ শেষ হয়েছে।

তবে নোট করুন, প্রকৃত 'বাকী' পরিবর্তনশীল সংরক্ষণ করা দরকার এবং প্রতিটি নোড সন্নিবেশের পরে কেবল হ্রাস পেয়েছে। সুতরাং আমি উপরে বর্ণিত একটি পৃথক ভেরিয়েবল ব্যবহার করে 'অবশিষ্ট' থেকে শুরু করে ধরে নেওয়া হয়েছিল ।

প্রত্যয় লিঙ্কগুলিতে নোটস এবং পুনরুদ্ধার

1) লক্ষ্য করুন যে উভয় পদ্ধতিই একই ফলাফলের দিকে পরিচালিত করে। প্রত্যয় লিঙ্ক জাম্পিং বেশিরভাগ ক্ষেত্রে উল্লেখযোগ্যভাবে দ্রুত হয়; এটাই প্রত্যয় লিঙ্কগুলির পিছনে পুরো যুক্তি।

2) আসল অ্যালগরিদমিক বাস্তবায়নের আলাদা হওয়ার দরকার নেই। আমি উপরে উল্লিখিত হিসাবে, এমনকি প্রত্যয় লিঙ্ক ব্যবহারের ক্ষেত্রে, 'সক্রিয় দৈর্ঘ্য' প্রায়শই লিঙ্কযুক্ত অবস্থানের সাথে সামঞ্জস্যপূর্ণ না কারণ গাছের সেই শাখায় অতিরিক্ত শাখা থাকতে পারে। সুতরাং অপরিহার্যভাবে আপনাকে কেবলমাত্র 'অবশিষ্ট ' এর পরিবর্তে 'সক্রিয় দৈর্ঘ্য' ব্যবহার করতে হবে এবং আপনার অবশিষ্ট প্রত্যয়ের দৈর্ঘ্যের চেয়ে সংক্ষিপ্ত একটি প্রান্ত না পাওয়া পর্যন্ত একই পুনরায় তদন্তের যুক্তিটি কার্যকর করতে হবে।

3) পারফরম্যান্সের সাথে সম্পর্কিত একটি গুরুত্বপূর্ণ মন্তব্যটি হ'ল উদ্ধারকালে প্রতিটি চরিত্র পরীক্ষা করার দরকার নেই। বৈধ প্রত্যয় গাছ যেভাবে তৈরি হয়েছে তার কারণে আমরা নিরাপদে ধরে নিতে পারি যে অক্ষরগুলির মিল রয়েছে। সুতরাং আপনি বেশিরভাগ দৈর্ঘ্য গণনা করছেন, এবং যখন আমরা একটি নতুন প্রান্তে লাফিয়ে উঠি তখন কেবল অক্ষর সমতুল্যতা যাচাইয়ের প্রয়োজনীয়তা দেখা দেয়, যেহেতু প্রান্তগুলি তাদের প্রথম অক্ষর দ্বারা চিহ্নিত করা হয় (যা প্রদত্ত নোডের প্রসঙ্গে সর্বদা অনন্য)। এর অর্থ হ'ল 'পুনরুদ্ধার' যুক্তি পূর্ণ স্ট্রিংয়ের মিলের যুক্তির চেয়ে আলাদা (অর্থাত্ গাছটিতে একটি স্ট্রিংয়ের সন্ধান করা)।

4) এখানে বর্ণিত মূল প্রত্যয়টি লিঙ্কিং সম্ভাব্য পন্থাগুলির মধ্যে একটি । উদাহরণস্বরূপ এনজে লারসন এট আল। এই পদ্ধতির নাম নোড-ওরিয়েন্টেড টপ-ডাউন হিসাবে নামকরণ করে এবং এটিকে নোড-ওরিয়েন্টেড বটম-আপ এবং দুটি এজ-ওরিয়েন্টেড জাতের সাথে তুলনা করে । বিভিন্ন পদ্ধতির বিভিন্ন সাধারণ এবং নিকৃষ্টতম পারফরম্যান্স, প্রয়োজনীয়তা, সীমাবদ্ধতা ইত্যাদি রয়েছে তবে সাধারণত এটি মনে হয় যে এজ-ওরিয়েন্টেড পন্থাগুলি মূলটির সামগ্রিক উন্নতি।


8

@ জোগোজাপান আপনি অসাধারণ ব্যাখ্যা এবং দৃশ্যায়ন নিয়ে এসেছেন। তবে @ ম্যাকাগনভ উল্লেখ করেছেন যে এটি প্রত্যয় লিঙ্ক স্থাপনের বিষয়ে কিছু বিধি অনুপস্থিত। Http://branden.github.io/ukkonen-animation/ 'aaaaabb' শব্দের মাধ্যমে ধাপে ধাপে যাওয়ার সময় এটি দুর্দান্ত উপায়ে দৃশ্যমান । আপনি যখন পদক্ষেপ 10 থেকে 11 ধাপে যান, নোড 5 থেকে নোড 2 থেকে কোনও প্রত্যয় লিঙ্ক নেই তবে সক্রিয় বিন্দু হঠাৎ সেখানে চলে যায়।

@ ম্যাকগাগনভ যেহেতু আমি জাভা বিশ্বে বাস করি আমি এসটি বিল্ডিংয়ের ওয়ার্কফ্লো বুঝতে আপনার বাস্তবায়ন অনুসরণ করার চেষ্টাও করেছি তবে এটি আমার পক্ষে কঠিন ছিল:

  • নোড সঙ্গে প্রান্ত একত্রিত
  • সূত্রের পরিবর্তে সূচক পয়েন্টার ব্যবহার করা
  • বক্তব্য বিরতি;
  • বিবৃতি অবিরত;

তাই আমি জাভাতে এ জাতীয় বাস্তবায়ন শেষ করেছি যা আমি আশা করি যে সমস্ত পদক্ষেপ আরও সুস্পষ্টভাবে প্রতিফলিত করে এবং অন্যান্য জাভা লোকের জন্য শেখার সময় হ্রাস করবে:

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

আমার স্বজ্ঞাতটি নিম্নরূপ:

মূল লুপের কে পুনরাবৃত্তির পরে আপনি একটি প্রত্যয় গাছ তৈরি করেছেন যা সম্পূর্ণ স্ট্রিংয়ের সমস্ত প্রত্যয় যা প্রথম কে অক্ষরে শুরু হয়।

শুরুতে, এর অর্থ প্রত্যয় গাছটিতে একটি একক মূল নোড থাকে যা পুরো স্ট্রিংকে উপস্থাপন করে (এটি কেবলমাত্র প্রত্যয় যা 0 থেকে শুরু হয়)।

লেন (স্ট্রিং) পুনরাবৃত্তির পরে আপনার একটি প্রত্যয় গাছ রয়েছে যাতে সমস্ত প্রত্যয় থাকে।

লুপ চলাকালীন কীটি সক্রিয় বিন্দু। আমার অনুমান যে এটি প্রত্যয় গাছের গভীরতম পয়েন্টটি উপস্থাপন করে যা স্ট্রিংয়ের প্রথম কে অক্ষরের যথাযথ প্রত্যয়টির সাথে মিলে যায়। (আমি মনে করি যথাযথ অর্থ হ'ল প্রত্যয়টি পুরো স্ট্রিং হতে পারে না))

উদাহরণস্বরূপ, ধরুন আপনি 'abcabc' অক্ষর দেখেছেন। সক্রিয় বিন্দু 'abc' প্রত্যয় অনুরূপ গাছের বিন্দু প্রতিনিধিত্ব করবে।

সক্রিয় পয়েন্টটি (উত্স, প্রথম, শেষ) দ্বারা প্রতিনিধিত্ব করা হয়। এর অর্থ আপনি বর্তমানে গাছের যে বিন্দুতে পৌঁছেছেন নোডের উত্স থেকে শুরু করে এবং তারপরে অক্ষরে খাওয়ানো [প্রথম: শেষ]

আপনি যখন একটি নতুন চরিত্র যুক্ত করবেন আপনি সক্রিয় পয়েন্টটি বিদ্যমান গাছটিতে এখনও আছে কিনা তা দেখতে সন্ধান করবেন। যদি তা হয় তবে আপনার কাজ শেষ। অন্যথায় আপনাকে সক্রিয় পয়েন্টে প্রত্যয় গাছটিতে একটি নতুন নোড যুক্ত করতে হবে, পরবর্তী সংক্ষিপ্ততম ম্যাচে ফ্যালব্যাক করুন এবং আবার পরীক্ষা করুন।

দ্রষ্টব্য 1: প্রত্যয় পয়েন্টার প্রতিটি নোডের জন্য পরবর্তী সংক্ষিপ্ত মিলের লিঙ্ক দেয়।

দ্রষ্টব্য 2: আপনি যখন একটি নতুন নোড এবং ফালব্যাক যুক্ত করবেন আপনি নতুন নোডের জন্য একটি নতুন প্রত্যয় পয়েন্টার যুক্ত করবেন। এই প্রত্যয় পয়েন্টারের জন্য গন্তব্য হ'ল সংক্ষিপ্ত সক্রিয় বিন্দুর নোড। এই নোডটি ইতিমধ্যে উপস্থিত রয়েছে, বা এই ফ্যালব্যাক লুপের পরবর্তী পুনরাবৃত্তিতে তৈরি করা হবে।

দ্রষ্টব্য 3: ক্যানোনাইজেশন অংশটি সক্রিয় পয়েন্টটি পরীক্ষা করতে কেবল সময় সাশ্রয় করে। উদাহরণস্বরূপ, ধরুন আপনি সর্বদা উৎপত্তি = 0 ব্যবহার করেছেন এবং কেবল প্রথম এবং শেষ পরিবর্তন করেছেন। সক্রিয় বিন্দুটি পরীক্ষা করতে আপনাকে প্রতিটি মধ্যবর্তী নোডের পাশাপাশি প্রতিবার প্রত্যয় গাছটি অনুসরণ করতে হবে। শেষ নোড থেকে কেবল দূরত্ব রেকর্ড করে এই পথটি অনুসরণের ফলাফলটি ক্যাশে করা বোধগম্য।

"ফিক্স" বাউন্ডিং ভেরিয়েবল বলতে কী বোঝায় তার একটি কোড উদাহরণ দিতে পারেন?

স্বাস্থ্য সতর্কতা: আমি এই অ্যালগরিদমটি বিশেষভাবে বুঝতে পারাও তাই অনুগ্রহ করে বুঝতে পারি যে এই অন্তর্দৃষ্টিটি সমস্ত গুরুত্বপূর্ণ বিবরণে ভুল হতে পারে ...


একাডেমিক গবেষণাপত্রে "যথাযথ" সংজ্ঞা দেওয়া হয় যার অর্থ একটি স্ট্রিংয়ের "যথাযথ প্রত্যয়" এর প্রথম অক্ষর থাকে না। কখনও কখনও আপনি একটি পুরো সাবস্ট্রিংকে "প্রত্যয়" বলে থাকেন, তবে "স্ট্রিং" এবং "সাবস্ট্রিং" এবং "প্রত্যয়" শব্দের সংজ্ঞাটি উদারপন্থীভাবে ছড়িয়ে দেওয়া হয় এবং কখনও কখনও আপনাকে "প্রত্যয়" বলতে কী বোঝায় তা খুব পরিষ্কার হওয়া দরকার। "যথাযথ প্রত্যয়" শব্দটি পুরো জিনিসটিকে প্রত্যয় বলা বাদ দেয়। সুতরাং একটি স্ট্রিংয়ের প্রত্যয় স্ট্রিং কোনও বৈধ সাবস্ট্রিং হতে পারে এবং একটি উপযুক্ত প্রত্যয় থাকতে পারে যা একই প্রত্যয় নয়। কারণ যুক্তি।
ব্লেয়ার হাউটন

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
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.