ডাটা স্ট্রাকচার - Data structures

ডাটা স্ট্রাকচার: স্পার্স টেবিল – O(1) টাইমে রেঞ্জ মিনিমাম ম্যাক্সিমাম কুয়েরি

Sparse table Bangla tutorial/ Sparse table bengali tutorial/ Range minimum/ maximum query in O(1)

আমরা সেগমেন্ট ট্রি নিয়ে লিখায় দেখেছিলাম কিভাবে O(log n) টাইমে আমরা রেঞ্জ ম্যাক্সিমাম, মিনিমাম কুয়েরি করতে পারি। স্পার্স টেবিল (Sparse table) নিয়ে এই লিখায় দেখবো কিভাবে O(1) টাইমে রেঞ্জ মিনিমাম,ম্যাক্সিমাম বের করতে পারিমিনিমাম এবং ম্যাক্সিমাম বের করার প্রসেস একই তাই আমরা মিনিমাম বের করার উপরে ফোকাস করবো। স্পার্স টেবিলের সাথে সেগমেন্ট ট্রি এর পার্থক্য হলো আমরা স্পার্স টেবিলে আপডেট করতে পারবো না কিন্তু সেগমেন্ট ট্রি তে আপডেট করা যাবে।

স্পার্স টেবিল (Sparse Table): সমস্যার বিবরণ

স্পার্স টেবিল (Sparse table) কেন ব্যবহার করবো তা বুঝার জন্য প্রথমে সমস্যাটি বুঝার চেষ্টা করবো। আমাদের একটি অ্যারে দেয়া আছে। আমাদেরকে যেকোনো রেঞ্জ l থেকে r এর মধ্যে মিনিমামবের করে দিতে হবে। আমরা খুব সহজেই O(n) সময়ে এর সমাধান করতে পারি। উদাহরণসরূপ নিচের কোডটি দেখি।

উপরের উদাহরণে আমাদের Q টি কুয়েরি করা হবে। প্রতিটি কুয়েরিতে রেঞ্জ l,r ইনপুট দেয়া হবে। এখানে ১২ থেকে ১৪ নং লাইনে আমরা লুপের মাধ্যমে অ্যারেটির মিনিমাম বের করেছি।

এখন উপরের কোডের টাইম কমপ্লেক্সিটি O(Q.n) । যা যথেষ্ট বড় ইনপুটের জন্য সময় বেশি নিবে। স্পার্স টেবিলের মাধ্যমে এই সমস্যার সমাধান O(Q) তে করা সম্ভব। যেখানে প্রত্যেক কুয়েরি O(1) সময় নিবে।

Sparse Table Bangla tutorial/ Sparse Table Bengali tutorial

আমরা যেকোনো অ্যারেকে কিছু ২ এর সূচকীয় সংখ্যার যোগফল আকারে লিখতে পারি। যেমন 10=8+2 । বা 10=2^3+2^1 । এটা আমরা বুঝতে পারবো যদি 10 এর বাইনারি উপস্থাপন খেয়াল করি। 1010_2 কে ডেসিমেলে রূপান্তর করার সময় 1.2^3+0.2^2+1.2^1+0.2^0=8+2=10

একই ভাবে আমরা যেকোনো রেঞ্জকে এমন কিছু রেঞ্জে ভাগ করে ফেলতে পারি। যেমন যদি l=5 এবং r=11 হয় তবে এর মধ্যে মোট ইলিমেন্ট আছে সাতটি। 7 কে আমরা উপরের মত করে 4+2+1 রূপে লিখতে পারি। তাই l\cup r=5\cup 12 কে আমরা লিখতে পারি [5,8]\cup [9,10] \cup [11,11] । এখানে সর্বোচ্চ \lceil log_2(l-r+1) \rceil টি সেগমেন্ট হতে পারে। যেখানে l-r+1 সেগমেন্ট সাইজ।

স্পার্স টেবিলের পেছনের মূল আইডিয়াটি হলো আমরা প্রথমে ২ এর সব সূচকীয় মানের (1,2,4,8,16,etc) দৈর্ঘ্যের জন্য যত সাব-অ্যারে আছে তার জন্য মিনিমাম প্রথমেই বের করে নিবো। যেমন নিচের চিত্রটি দেখি,

স্পার্স টেবিল সাব-অ্যারে

উপরের চিত্রে 1,2,4,8=2^0,2^1,2^2,2^3 দৈর্ঘ্যের যত সাব অ্যারে আছে তার মিনিমাম বের করে নিয়েছি। আমরা 2^4=16 দৈর্ঘ্যের কোন সাব অ্যারে নিইনি। কারণ 16, অ্যারের দৈর্ঘ্য 8 এর থেকে বড়। আমরা সাব অ্যারের মিনিমাম কিভাবে নিয়েছি আশা করি বুঝা যাচ্ছে। আরেকটু ব্যাখ্যা করি।

4 দৈর্ঘ্যের সাব অ্যারের মিনিমাম বের করাকে বিবেচনা করি। আমরা শুরু করেছি l=0 ,r=3 পর্যন্ত সাব অ্যারেকে নিয়ে [ r-l+1=3-0+1=4 ]। এর মধ্যে মিনিমাম হচ্ছে -২। তাই আমরা অ্যারের 0 ইনডেক্স এ -2 রেখেছি। এরপর l=1,r=4 এর জন্যও বের করেছি। এক্ষেত্রে মিনিমাম -৫। একই ভাবে আমরা এগোতে থেকেছি যতক্ষণ না r\geq n হয়।

এবার আমরা উপরের চিত্র গুলো একসাথে করে একটা 2D অ্যারের মত টেবিল তৈরি করি।

Sparse table

এখানে বাম থেকে ডানে আমরা অ্যারের ইনডেক্স নিয়েছি এবং উপর থেকে নিচে আমরা অ্যারের সাইজ অনুযায়ী মিনিমাম বের করে রেখেছি। এই টেবিলে একটি ফিল্ড row,column আমাদের বলে দেয় যে, column থেকে শুরু করে row দৈর্ঘ্যের কোন সাব অ্যারের মিনিমাম কত। যেমন row=4,column=1 হলে আমরা বলতে পারি ইনডেক্স 1 থেকে শুরু করে 4 দৈর্ঘ্যের একটি সাব-অ্যারের মিনিমাম সংখ্যা হলো -5। এখন আমরা অ্যারের সাইজ গুলোকে 2^0,2^1,2^2,2^3 রূপে লিখতে পারি এবং 1,2,4,8 এর বদলে আমরা 0,1,2,3 সূচক গুলকে ইনডেক্স হিসেবে ব্যবহার করতে পারি নিচের মত।

স্পার্স টেবিল সাব-অ্যারে টেবিল

এখন এখানে যদি আমরা একটি ফিল্ড k,column কে পরতে চাই তাহলে বলবো column ইনডেক্স থেকে শুরু করে 2^{k} সাইজের কোন সাব অ্যারের মিনিমাম। এখানে k এর সর্বোচ্চ মান হবে Maxk=\lfloor log_2(n)\rfloor

স্পার্স টেবিল (Sparse table): লুক-আপ টেবিল প্রি-প্রসেসিং

আমদের একটি দ্রুততম রাস্তা বের করতে হবে অ্যারে হিসাব করে রাখার জন্য। শুরুতেই আমরা একটি 2D অ্যারে lookUpTable[Maxk+1][n] নিবো (Maxk+1)\times n সাইজের। যেখানে Maxk=\lfloor log_2(n) \rfloor । অর্থাৎ int lookUpTable[Maxk+1][n];

আমরা O(n \log_2(n)) সময়ে হিসাব করতে পারি। আমরা যা করবো প্রথমে k=0 থেকে k=Maxk পর্যন্ত লুপ চালাবো। তারপর আমরা column=0 থেকে যতক্ষণ না পর্যন্ত column+2^k-1<n হয় ততক্ষণ পর্যন্ত লুপ চালাবো।

আমরা ধরে নিই 2 (k=1) সাইজের সকল সাব-অ্যারের জন্য মিনিমাম আমরা বের করে নিয়েছি। এবার 4 সাইজের (k=2) সকল সাব অ্যারের মিনিমাম বের করার সময় আমরা 2 সাইজের সাব-অ্যারে গুলোকে কাজে লাগাতে পারি নাকি একটু দেখি। 4 সাইজের সাব অ্যারেকে আমরা 2 সাইজের 2 টি সাব অ্যারেতে ভেঙ্গে ফেলতে পারি।

যেমন l=3,r=6 হলে আমরা একে 3,4 এবং 5,6 এই দুই ভাগে ভাগ করতে পারি। যেহেতু আমরা আগেই 2 সাইজের সব সাব অ্যারের মিনিমাম হিসাব করেছিলাম তাই এই রেঞ্জের মিনিমাম হবে 3,4 রেঞ্জের মিনিমাম এবং 5,6 রেঞ্জের মিনিমাম এর মধ্যে ছোটটি।

আমরা প্রথমে 1 দৈর্ঘ্যের সব সাব অ্যারের মিনিমাম বের করে নিবো (ওই ইলিমেনটের মান)। তারপর আমরা 2 দৈর্ঘ্যের সাব অ্যারের মিনিমাম, তারপর 4 এভাবে এগোতে থাকবো।

কোডে যাবার আগে আমাদেরকে কয়কটি বিষয় নিয়ে পরিষ্কার ধারনা অর্জন করতে হবে।

lookUpTable[k][column] কে পড়বো কিভাবে?

lookUpTable[k][column] এর মানে হলো মূল অ্যারের column ইনডেক্স থেকে 2^{k} সাইজের কোন সাব অ্যারের মিনিমাম এখানে জমা করা আছে।

2^{k} সাইজের একটি অ্যারেকে কিভাবে দুইটি সাব-অ্যারেতে ভাঙতে পারি?

স্পার্স টেবিল সাব-অ্যারে: Breaking into half

ধরা যাক মূল অ্যারের column=1 থেকে শুরু করে 2^k=2^3 সাইজের একটি অ্যারেকে আমরা দুই ভাগে ভাগ করবো। আমরা দেখতে পাই 2^3 এর অর্ধেক হলো 2^2 বা 4 । তাই আমরা প্রথম সেগমেন্ট নিবো 4 দৈর্ঘ্যের একটি সাব-অ্যারে যা 1 থেকে শুরু হয়ে 1+2^2-1 বা ইনডেক্স 4 এ শেষ। পরের সেগমেন্ট নিবো 1+2^2=5 থেকে 5+2^2-1=8 পর্যন্ত।

এখন lookUpTable[k][column] কে আমরা পরি column থেকে শুরু করে 2^{k} সাইজের কোন সাব অ্যারের মিনিমাম। উপরে ইনডেক্স 1 থেকে 4 পর্যন্ত আসলে একটি সাব অ্যারে যার সাইজ 4, ইনডেক্স 5 থেকে 8 পর্যন্ত একটি সাব অ্যারে যার সাইজ 4।

এবার যেহেতু আমরা 4 দৈর্ঘ্যের সকল সাব অ্যারের মিনিমাম আগেই বের করে নিবো, তাই আমরা সাধারণ ভাবে লিখতে পারি,

lookUpTable[k][column]=min(lookUpTable[k-1][column],lookUpTable[k-1][column+2^{k-1}])

এখানে আমরা আগের সারি বা k-1 কে নিয়ে কাজ করেছি কারণ k-1, 2^{k-1} সাইজের একটি সাব অ্যারেকে উপস্থাপন করে যা 2^{k} এর অর্ধেক।

স্পার্স টেবিলে কুয়েরি করা (Query in sparse table Bangla tutorial)

স্পার্স টেবিলে কুয়েরি করা সহজ। শুধু একটি কনসেপ্ট বুঝতে হবে। ধরা যাক আমাদেরকে একটি রেঞ্জ l,r এর মিনিমাম বের করতে দিলো। তো আমরা যা করবো প্রথমে k=\lfloor \log_2(r-l+1)\rfloor বের করবো বা মোট যতগুলো ইলিমেন্ট আছে এই লেন্থে তার 2 ভিত্তিক লগ বের করে floor করে নিবো। এবার বুঝার চেষ্টা করি, 2^k এমন একটি সাব অ্যারের দৈর্ঘ্য যার মিনিমাম আমরা আগেই হিসাব করে নিয়েছি (যেহেতু r-l+1<=n) এবং r-l+1\geq 2^k \geq \frac{r-l+1}{2}

এখানে যেহেতু 2^k, \frac{r-l+1}{2} এর থেকে সমান অথবা বড় হবে এবং r-l+1 এর সমান বা ছোট হবে তাই বলতে পারি 2^k লেন্থের দুইটি সাব-অ্যারে নিয়ে আমরা কুয়েরি করলে রেঞ্জের সকল ইলিমেন্ট কভার করা যাবে। নিচের চিত্রটি দেখি।

স্পার্স টেবিল: Query

এখানে l=2,r=7 হলে k=\lfloor\log_2(r-l+1)\rfloor=\lfloor \log_2(7-2+1)\rfloor=\lfloor\log_2(6)\rfloor=2 । এখানে k=2 মানে আমরা 2^k=2^2=4 দৈর্ঘ্যের সাব অ্যারের মিনিমাম বের করবো যা (১) l থেকে শুরু হয়েছে এবং যা (২) r-2^k+1 থেকে শুরু হয়েছে। এবার প্রথম সাব অ্যারে যা l থেকে শুরু হয়েছে তা তো বুঝলাম। কিন্তু r- 2^k+1 থেকে 2^k দৈর্ঘ্যের সাব অ্যারে কেন নিবো? লক্ষ করি, আমরা কিন্তু r এর বাইরের কোন ইলিমেন্ট বিবেচনা করবো না। তাই আমাদেরকে দ্বিতীয় সাব অ্যারের এমন শুরুর ইনডেক্স বের করতে হবে যাতে সেখান থেকে 2^k দৈর্ঘ্যের সাব অ্যারে নিলে তা r এ গিয়ে শেষ হয়, কারণ মিনিমাইজেশন প্রবলেমের ক্ষেত্রে অভারলেপিং ইলিমেন্ট নিলে সমস্যা নেই কিন্তু বাইরের ইলিমেন্ট নিলে সমস্যা রয়েছে।

আমরা এক লাইনেই কুয়েরি করতে পারি। কুয়েরি করার জন্য l,r কে ইনপুট নিতে হবে।

নিচের কোডটি দিয়ে l,r রেঞ্জে আমরা কুয়েরি করতে পারবো। [1<<k যেই কথা 2^k একই কথা]

এটাই আমাদের সম্পূর্ণ স্পার্স টেবিলের টিউটোরিয়াল। আমরা খুব সহজেই কুয়েরি করে নিয়েছি সূত্র মেনে। প্রতিটি কুয়েরি সময় নিবে O(1) টাইম। যদিও r-l+1 বের করতে log(n) সময় লাগবে। আমরা ইচ্ছে করলে কুয়েরি করার লুপের বাইরে নিচের কোড দিয়ে 1 থেকে n পর্যন্ত সকল সংখ্যার log এর মান O(n) এ হিসেব করে নিতে পারি।

তারপর আমরা int k=__lg(r-l+1);কে শুধু int k=log[r-l+1];দ্বারা O(1) এ বের করতে পারবো।

সম্পূর্ণ স্পার্স টেবিলের C++ কোড (Sparse table C++ implementation)

আজকের লিখা এ পর্যন্তই সমাপ্ত রাখলাম। স্পার্স টেবিল আমার নিজের কাছে একটু জটিল লাগে। তাই আমি সাজেস্ট করবো লিখাটিতে আরও কয়েকবার চোখ বুলিয়ে নিতে। এতে করে অনেক ছোট ছোট বিষয়ে ধারনা পরিষ্কার হয়ে যাবে। #happy_coding.

স্পার্স টেবিল অনুশীলনের জন্য কিছু প্রবলেম (Credit: cp-algorithms.com)

লেখাটি কেমন লেগেছে আপনার?

রেটিং দিতে হার্টের উপর ক্লিক করুন।

গড় রেটিং / 5. মোট ভোট:

আপনি প্রথম ভোটদাতা.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button