সত্যিই এসটিএল মধ্যে একটি deque কি?


192

আমি এসটিএল পাত্রে তাকিয়ে ছিলাম এবং তারা আসলে কী তা চিত্রিত করার চেষ্টা করছিলাম (যেমন ডেটা কাঠামো ব্যবহৃত হয়েছিল), এবং ডেকি আমাকে থামিয়ে দিয়েছিল: আমি প্রথমে ভেবেছিলাম যে এটি একটি ডাবল লিঙ্কযুক্ত তালিকা, যা উভয় প্রান্ত থেকে সন্নিবেশ এবং মোছার অনুমতি দেবে ধ্রুবক সময়, তবে অপারেটর দ্বারা করা প্রতিশ্রুতিতে আমি সমস্যায় পড়েছি [] ধ্রুব সময়ে করা হবে। একটি লিঙ্কযুক্ত তালিকায়, স্বেচ্ছাসেবী অ্যাক্সেস ও (এন) হওয়া উচিত, তাই না?

এবং যদি এটি একটি গতিশীল অ্যারে হয় তবে এটি কীভাবে অবিচ্ছিন্ন সময়ে উপাদান যুক্ত করতে পারে ? এটি উল্লেখ করা উচিত যে পুনঃস্থাপন ঘটতে পারে এবং ও (1) একটি ভেক্টর এর মতো একটি অনুভূতিযুক্ত ব্যয় ।

সুতরাং আমি অবাক হই যে এই কাঠামোটি যা ধ্রুবক সময়ে নির্বিচারে অ্যাক্সেসের অনুমতি দেয় এবং একই সাথে কখনই কোনও নতুন বড় জায়গায় সরে যাওয়ার দরকার হয় না।



1
@ গ্রাহাম "ডীকু" হ'ল "ডিউক" এর আর একটি সাধারণ নাম। আমি এখনও সম্পাদনাটি অনুমোদন করেছি যেহেতু "ডীক" সাধারণত প্রচলিত নাম।
কনরাড রুডলফ

ধন্যবাদ কনরাড প্রশ্নটি বিশেষত সি ++ এসটিএল ডীক সম্পর্কে ছিল, যা সংক্ষিপ্ত বানানটি ব্যবহার করে।
গ্রাহাম বোরল্যান্ড

2
dequeদ্বিগুণ সমাপ্ত কাতারের জন্য দাঁড়িয়েছে , যদিও স্পষ্টতই মাঝারি উপাদানগুলিতে হে (1) অ্যাক্সেসের কঠোর প্রয়োজনীয়তা সি ++ এর কাছে বিশেষ
ম্যাথিউ এম।

উত্তর:


181

একটি deque কিছুটা recursively সংজ্ঞায়িত করা হয়: অভ্যন্তরীণভাবে এটি নির্দিষ্ট আকারের অংশগুলির একটি ডাবল-এন্ড সারি বজায় রাখে । প্রতিটি অংশটি একটি ভেক্টর এবং খণ্ডগুলির সারি (নীচে গ্রাফিকের "মানচিত্র") নিজেই ভেক্টর।

একটি deque এর মেমরি লেআউট পরিকল্পনা

পারফরম্যান্সের বৈশিষ্ট্যগুলির একটি দুর্দান্ত বিশ্লেষণ আছে এবং এটি কোডপ্রজেক্টেvector ওভারের সাথে কীভাবে তুলনা করে ।

জিসিসি স্ট্যান্ডার্ড লাইব্রেরি বাস্তবায়ন T**মানচিত্রের প্রতিনিধিত্ব করতে অভ্যন্তরীণভাবে একটি ব্যবহার করে । প্রতিটি ডেটা ব্লক এমন একটি T*যা কিছু নির্দিষ্ট আকারের সাথে বরাদ্দ করা হয় __deque_buf_size(যা নির্ভর করে sizeof(T))।


27
এটি যেমন একটি শিখেছে তেমনি একটি ডীকের সংজ্ঞা, তবে এইভাবে, এটি ধ্রুবক সময় অ্যাক্সেসের গ্যারান্টি দিতে পারে না, তাই কিছু অনুপস্থিত থাকতে হবে।
স্টেফানভ

14
@ স্টেফানভ, @ কনরাড: সি ++ বাস্তবায়নগুলি আমি স্থির আকারের অ্যারেগুলিতে পয়েন্টারের একটি অ্যারে ব্যবহার করে দেখেছি। এর কার্যকর অর্থ হ'ল পুশ_ফ্রন্ট এবং পুশ_ব্যাক আসলে ধ্রুবক-সময় নয়, তবে স্মার্ট ক্রমবর্ধমান কারণগুলির সাথে আপনি এখনও ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে বেড়ে ওঠেন, সুতরাং ও (1) এতটা ভুল নয়, এবং অনুশীলনে এটি ভেক্টরের চেয়ে দ্রুত কারণ আপনি অদলবদল করছেন পুরো বস্তুর চেয়ে একক পয়েন্টার (এবং বস্তুর চেয়ে কম পয়েন্টার)।
ম্যাথিউ এম।

5
অবিচ্ছিন্ন অ্যাক্সেস এখনও সম্ভব। ঠিক যদি আপনাকে সামনের দিকে একটি নতুন ব্লক বরাদ্দ করতে হয় তবে প্রধান ভেক্টরটিতে একটি নতুন পয়েন্টারটি পিছনে চাপুন এবং সমস্ত পয়েন্টার স্থানান্তর করুন।
Xeo

4
যদি মানচিত্রটি (কাতারে নিজেই) একটি ডাবল-এন্ডের তালিকা ছিল তবে আমি দেখতে পাচ্ছি না এটি কীভাবে ও (1) এলোমেলো অ্যাক্সেসের অনুমতি দিতে পারে। এটি একটি বিজ্ঞপ্তি বাফার হিসাবে প্রয়োগ করা যেতে পারে, যা বৃত্তাকার বাফারকে পুনরায় আকার দেওয়ার আরও কার্যকর করতে দেয়: কেবল সারিতে থাকা সমস্ত উপাদানের পরিবর্তে পয়েন্টারগুলি অনুলিপি করুন। তবুও এটি একটি ছোট সুবিধা বলে মনে হচ্ছে।
ওয়ার্নাইট

14
@ জেরেমি ওয়েস্ট কেন নয়? সূচকযুক্ত অ্যাক্সেসটি আই / বি-থ্রি ব্লক (বি = ব্লকের আকার) এর আই% বি-তম এলিমেন্টে যায়, এটি স্পষ্টভাবে ও (1)। আপনি মোড়কযুক্ত ও (1) এ একটি নতুন ব্লক যুক্ত করতে পারেন, অতএব উপাদানগুলি যুক্ত করার শেষে প্রান্তিক হে (1) হয়। শুরুতে একটি নতুন উপাদান যুক্ত করা ও (1) হয় যদি না কোনও নতুন ব্লক যুক্ত করার প্রয়োজন হয়। শুরুতে একটি নতুন ব্লক যুক্ত করা ও (1) নয়, সত্য, এটি ও (এন) নয় তবে বাস্তবে এটির একটি খুব সামান্য ধ্রুবক উপাদান রয়েছে কারণ আপনাকে কেবল এন উপাদানগুলির চেয়ে এন / বি পয়েন্টার সরিয়ে নেওয়া দরকার।
কনরাড রুডলফ

22

এটি ভেক্টরগুলির ভেক্টর হিসাবে কল্পনা করুন। শুধুমাত্র তারা মানক হয় না std::vector

বাইরের ভেক্টরটিতে অভ্যন্তরীণ ভেক্টরগুলির পয়েন্টার রয়েছে। যখন এর ক্ষমতাটি পুনঃনির্ধারণের মাধ্যমে পরিবর্তিত হয়, শেষের দিকে সমস্ত ফাঁকা স্থান বরাদ্দ না std::vectorকরে, শূন্য স্থানটি ভেক্টরের শুরুতে এবং শেষে সমান অংশে বিভক্ত করে। এটি অনুমতি দেয় push_frontএবং push_backএই ভেক্টরটিতে উভয়ই এমরোটাইজড ও (1) সময়ে ঘটে।

অভ্যন্তরীণ ভেক্টর আচরণটি সামনের দিকে বা পিছনের দিকের উপর নির্ভর করে পরিবর্তিত হওয়া দরকার deque। পিছনে এটি স্ট্যান্ডার্ড হিসাবে আচরণ করতে পারে std::vectorযেখানে এটি শেষে বৃদ্ধি পায় push_backএবং ও (1) সময়ে ঘটে। সামনে এটি বিপরীতে করা প্রয়োজন, প্রতিটি সঙ্গে শুরুতে বৃদ্ধি push_front। অনুশীলনে এটি সামনের উপাদানটিতে একটি পয়েন্টার এবং আকারের পাশাপাশি বর্ধনের দিক যুক্ত করে সহজেই অর্জন করা যায়। এই সাধারণ পরিবর্তন সহ push_frontও (1) সময়ও হতে পারে।

যে কোনও উপাদান অ্যাক্সেসের জন্য অফ (সেট) করা উচিত এবং যথাযথ বাহ্যিক ভেক্টর সূচক যা বিভক্ত হয় (O) (1), এবং অভ্যন্তরীণ ভেক্টরের সাথে সূচক করা যা ও (1) হয়। এটি ধরে নেওয়া হয় যে অভ্যন্তরীণ ভেক্টরগুলি শুরুতে বা শেষে থাকাগুলি ব্যতীত সমস্ত স্থির আকার deque


1
আপনি অভ্যন্তরীণ ভেক্টরগুলিকে স্থির ক্ষমতা
ক্যালাথ

18

deque = ডাবল শেষ সারি

একটি ধারক যা উভয় দিকেই বাড়তে পারে।

ডেক সাধারণত একটি হিসাবে প্রয়োগ করা vectorহয় vectors(ভেক্টরের একটি তালিকা ধ্রুবক সময় এলোমেলো অ্যাক্সেস দিতে পারে না)। মাধ্যমিক ভেক্টরগুলির আকার বাস্তবায়ন নির্ভর যখন, একটি সাধারণ অ্যালগরিদম বাইটে একটি ধ্রুবক আকার ব্যবহার করা হয়।


6
এটি অভ্যন্তরীণভাবে যথেষ্ট ভেক্টর নয় । অভ্যন্তরীণ কাঠামোগুলি শুরুতে পাশাপাশি শেষ
অবধি

@ মুভিংডাক: এটি বাস্তবায়নটিকে প্রকৃতপক্ষে সংজ্ঞায়িত করা হয়েছে t এটি অ্যারে বা ভেক্টরের ভেক্টর বা এমন কোনও কিছু হতে পারে যা স্ট্যান্ডার্ড দ্বারা বাধ্যতামূলক আচরণ এবং জটিলতা সরবরাহ করতে পারে।
অলোক

1
@Als: আমি মনে করি না arrayকিছু বা vectorকিছু amortized প্রতিজ্ঞা করতে পারেন O(1)push_front। কমপক্ষে দুটি কাঠামোর অভ্যন্তরভাগে অবশ্যই একটি O(1)পুশফ্রন্ট থাকতে সক্ষম হতে হবে , যার একটি arrayবা একটিও vectorগ্যারান্টি দিতে পারে না।
মাকিং হাঁস

4
@ মুভিংডাকের প্রয়োজনীয়তাটি সহজেই পূরণ করা হয় যদি প্রথম অংশটি নীচের অংশের চেয়ে উপরে-ডাউনে বৃদ্ধি পায়। স্পষ্টতই কোনও মানক vectorতা করে না, তবে এটি করার জন্য এটি একটি সাধারণ পর্যাপ্ত পরিবর্তন।
মার্ক রান্সম

3
@ মুইং হাঁস, উভয় পুশ_ফ্রন্ট এবং পুশ_ব্যাক সহজেই একক ভেক্টর কাঠামোর সাথে এমোরটাইজড ও (1) এ করা যেতে পারে। এটি একটি বিজ্ঞপ্তি বাফারের সামান্য কিছুটা বেশি হিসাবরক্ষণ, এর চেয়ে বেশি কিছুই নয়। ধরুন আপনার ধারণক্ষমতা 1000 এর নিয়মিত ভেক্টর রয়েছে যার সাথে এটি 100 থেকে 100 উপাদানগুলিতে 0 থেকে 99 পজিশনে রয়েছে Now তারপরে আপনি পুনরায় প্রকাশ করুন (ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে বেড়ে ওঠার নিশ্চয়তা বাড়ানোর জন্য) যেমন আপনি কোনও সাধারণ ভেক্টরের সাথে করেন do সুতরাং কার্যকরভাবে আপনার প্রথম এল এর জন্য কেবল একটি অতিরিক্ত পয়েন্টার প্রয়োজন।
প্লামেনকো

14

(এটি একটি উত্তর যা আমি অন্য থ্রেডে দিয়েছি। মূলত আমি যুক্তি দিচ্ছি যে vector"একটানা অ-মোড়িত ধাক্কা {{সামনে, পিছনে}" এর প্রয়োজনীয়তা অনুসারে একক ব্যবহার করে মোটামুটি নিষ্পাপ বাস্তবায়নও আপনি অবাক হতে পারেন You , এবং মনে হয় এটি অসম্ভব, তবে আমি প্রমিতের সাথে অন্যান্য প্রাসঙ্গিক উক্তিগুলি খুঁজে পেয়েছি যা প্রসঙ্গে আশ্চর্যজনক উপায়ে সংজ্ঞা দেয়। দয়া করে আমার সাথে সহ্য করুন; যদি আমি এই উত্তরে কোনও ভুল করে থাকি তবে কোন জিনিসগুলি সনাক্ত করা খুব সহায়ক হবে আমি সঠিকভাবে বলেছি এবং যেখানে আমার যুক্তিটি ভেঙে গেছে))

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

deque<T>ব্যবহার করে এটিকে সঠিকভাবে প্রয়োগ করা যেতে পারে vector<T*>। সমস্ত উপাদান হিপ এবং ভেক্টরে সঞ্চিত পয়েন্টারগুলিতে অনুলিপি করা হয়। (পরে আরও ভেক্টরের উপরে)।

এর T*বদলে কেন T? কারণ স্ট্যান্ডার্ডের এটি প্রয়োজন

"দ্বীপের উভয় প্রান্তে একটি সন্নিবেশ সমস্ত পুনরাবৃত্তিকে ডেকের কাছে অকার্যকর করে দেয়, তবে ডেকির উপাদানগুলির উল্লেখের বৈধতার উপর কোনও প্রভাব ফেলেনি। "

(আমার জোর) T*যে সন্তুষ্ট করতে সাহায্য করে। এটি আমাদের সন্তুষ্ট করতে সহায়তা করে:

"সর্বদা ডীকের শুরুতে বা শেষদিকে কোনও একক উপাদান সন্নিবেশ করা ..... টির নির্মাণকারীকে একটি কল দেয় causes "

এখন (বিতর্কিত) বিটের জন্য। কেন একজন ব্যবহার vectorসঞ্চয় করতে T*? এটি আমাদের এলোমেলো অ্যাক্সেস দেয় যা একটি ভাল শুরু। আসুন এক মুহুর্তের জন্য ভেক্টরের জটিলতাগুলি ভুলে যাব এবং সাবধানতার সাথে এটি তৈরি করুন:

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

'আমাদের জন্য deque::push_front, উপস্থিত বস্তুগুলির (টিএস) ক্রিয়াকলাপগুলি স্থির এবং ইতিমধ্যে ডের্কে থাকা বস্তুর সংখ্যার চেয়ে স্বতন্ত্র।'

অবশ্যই, অপারেটিং সংস্থার সংখ্যা T*এত ভাল আচরণ করা হবে না। যখন vector<T*>বৃদ্ধিগুলি খুব বড় হয়, এটি রিয়েল্লোসড হবে এবং অনেকগুলি T*এর চারপাশে অনুলিপি করা হবে। সুতরাং হ্যাঁ, অপারেশনগুলির সংখ্যা T*হিংস্রভাবে পরিবর্তিত হবে, তবে অপারেশনগুলির সংখ্যা Tপ্রভাবিত হবে না।

গণনা কার্যক্রম Tএবং গণনা অপারেশনগুলির মধ্যে কেন আমরা এই পার্থক্যটি যত্ন করি T*? স্ট্যান্ডার্ড বলে কারণ এটি:

এই ধারাটিতে সমস্ত জটিলতার প্রয়োজনীয়তা কেবলমাত্র অন্তর্ভুক্ত বস্তুগুলির ক্রিয়াকলাপের সংখ্যার ভিত্তিতে বর্ণিত হয়েছে।

এর জন্য dequeঅন্তর্ভুক্ত বস্তুগুলি হ'ল T, নয় T*, অর্থ আমরা কোনও ক্রিয়াকলাপ অনুলিপি করতে পারি যা কপি করে (বা রিলোকস) ক T*

কোনও ভেক্টর কীভাবে কোনও deque এর সাথে আচরণ করবে সে সম্পর্কে আমি বেশি কিছু বলিনি। সম্ভবত আমরা এটিকে একটি বিজ্ঞপ্তিযুক্ত বাফার হিসাবে ব্যাখ্যা করব (ভেক্টর সর্বদা এটির সর্বোচ্চটি গ্রহণ করে capacity()এবং তারপরে ভেক্টর পূর্ণ হয়ে গেলে সমস্ত কিছু আবার বড় ব্যাফারে পরিণত করে all বিশদটি কোনও বিষয় নয়।

শেষের কয়েকটি অনুচ্ছেদে আমরা বিশ্লেষণ করেছি deque::push_frontএবং ইতিমধ্যে ডেকের মধ্যে বস্তুর সংখ্যা এবং ধারণিত-বিষয়গুলিতে পুশ_ফ্রন্ট দ্বারা পরিচালিত ক্রিয়াকলাপগুলির মধ্যে Tসম্পর্ক। এবং আমরা পেয়েছি তারা একে অপরের থেকে স্বাধীন ছিল। যেহেতু স্ট্যান্ডার্ড আদেশ দেয় যে জটিলতা অপারেশন-শর্তাদির ক্ষেত্রে T, তাই আমরা বলতে পারি এটির স্থির জটিলতা রয়েছে।

হ্যাঁ, অপারেশন-অন-টি * -কম্প্লেসিটিটি amorised হয়েছে (এর কারণে vector), তবে আমরা কেবল অপারেশন-অন-টি-জটিলতায় আগ্রহী এবং এটি ধ্রুবক (অ-বিহীন)।

ভেক্টরের জটিলতা :: পুশ_ব্যাক বা ভেক্টর :: পুশ_ফ্রন্ট এই বাস্তবায়নে অপ্রাসঙ্গিক; এই বিবেচনাগুলি অপারেশন জড়িত T*এবং তাই অপ্রাসঙ্গিক। যদি মানটি জটিলতার 'প্রচলিত' তাত্ত্বিক ধারণার কথা উল্লেখ করে, তবে তারা স্পষ্টভাবে তাদের "অন্তর্ভুক্ত বস্তুগুলির ক্রিয়াকলাপের সংখ্যায়" সীমাবদ্ধ রাখত না। আমি কি এই বাক্যটিকে খুব বেশি ব্যাখ্যা করছি?


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

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

আমি নিশ্চিত নিশ্চিত O(n)যে অপারেশন সংখ্যা উপাদানগুলির সংখ্যার সাথে সংক্ষিপ্তভাবে আনুপাতিক। আইই, মেটা-অপারেশন গণনা। অন্যথায় এটি দেখার সীমাবদ্ধ করার কোনও মানে নেই O(1)। তবে, লিঙ্কযুক্ত-তালিকা যোগ্যতা অর্জন করে না।
মাকিং হাঁস

8
এটি একটি খুব আকর্ষণীয় ব্যাখ্যা, কিন্তু এই যুক্তি দ্বারা একটি খুব পয়েন্টার listহিসাবে প্রয়োগ করা যেতে পারে vector(মাঝের মধ্যে সন্নিবেশ ফলাফলের আকার নির্বিশেষে একক অনুলিপি নির্মাতা অনুরোধের ফলস্বরূপ , এবং O(N)পয়েন্টারগুলির স্থানচ্যুতি এড়ানো যাবে কারণ) তারা টি-তে অপারেশন করে না।
মনকরসে

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

13

ওভারভিউ থেকে আপনি এ dequeহিসাবে ভাবতে পারেনdouble-ended queue

ডেক ওভারভিউ

Datas মধ্যে dequeসংশোধন করা হয়েছে আকার ভেক্টরের chuncks দ্বারা সঞ্চিত হয়, যা

একটি দ্বারা নির্দেশিত map(যা ভেক্টরের একটি অংশও তবে এর আকার পরিবর্তন হতে পারে)

সুন্দর অভ্যন্তরীণ কাঠামো

এর মূল অংশের কোডটি deque iteratorনীচে রয়েছে:

/*
buff_size is the length of the chunk
*/
template <class T, size_t buff_size>
struct __deque_iterator{
    typedef __deque_iterator<T, buff_size>              iterator;
    typedef T**                                         map_pointer;

    // pointer to the chunk
    T* cur;       
    T* first;     // the begin of the chunk
    T* last;      // the end of the chunk

    //because the pointer may skip to other chunk
    //so this pointer to the map
    map_pointer node;    // pointer to the map
}

এর মূল অংশের কোডটি dequeনীচে রয়েছে:

/*
buff_size is the length of the chunk
*/
template<typename T, size_t buff_size = 0>
class deque{
    public:
        typedef T              value_type;
        typedef T&            reference;
        typedef T*            pointer;
        typedef __deque_iterator<T, buff_size> iterator;

        typedef size_t        size_type;
        typedef ptrdiff_t     difference_type;

    protected:
        typedef pointer*      map_pointer;

        // allocate memory for the chunk 
        typedef allocator<value_type> dataAllocator;

        // allocate memory for map 
        typedef allocator<pointer>    mapAllocator;

    private:
        //data members

        iterator start;
        iterator finish;

        map_pointer map;
        size_type   map_size;
}

নীচে আমি আপনাকে dequeমূলত তিনটি অংশের মূল কোড দেব :

  1. পুনরুক্তিকারীর

  2. কীভাবে নির্মাণ করবেন deque

1. পুনরুক্তি ( __deque_iterator)

পুনরুদ্ধারের প্রধান সমস্যাটি হ'ল, যখন ++, - পুনরুক্তিকারী, এটি অন্যান্য অংশে এড়িয়ে যেতে পারে (যদি এটি খণ্ডের প্রান্তে নির্দেশ করে)। উদাহরণস্বরূপ, সেখানে তিন ডেটা খন্ডে আছেন: chunk 1, chunk 2, chunk 3

pointer1পয়েন্টার শুরু chunk 2, যখন অপারেটর --pointerএটা শেষে পয়েন্টার হবে chunk 1তাই হিসাবে, pointer2

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

নীচে আমি এর মূল কাজটি দেব __deque_iterator:

প্রথমত, যে কোনও অংশে এড়িয়ে যান:

void set_node(map_pointer new_node){
    node = new_node;
    first = *new_node;
    last = first + chunk_size();
}

দ্রষ্টব্য, chunk_size()ফাংশন যা খণ্ড আকার গণনা করে, আপনি ভাবতে পারেন এটি এখানে সরলকরণের জন্য 8 প্রদান করে।

operator* খণ্ডে তথ্য পেতে

reference operator*()const{
    return *cur;
}

operator++, --

// ইনক্রিমেন্ট উপসর্গ ফর্ম

self& operator++(){
    ++cur;
    if (cur == last){      //if it reach the end of the chunk
        set_node(node + 1);//skip to the next chunk
        cur = first;
    }
    return *this;
}

// postfix forms of increment
self operator++(int){
    self tmp = *this;
    ++*this;//invoke prefix ++
    return tmp;
}
self& operator--(){
    if(cur == first){      // if it pointer to the begin of the chunk
        set_node(node - 1);//skip to the prev chunk
        cur = last;
    }
    --cur;
    return *this;
}

self operator--(int){
    self tmp = *this;
    --*this;
    return tmp;
}
পুনরুদ্ধারকারী এন পদক্ষেপ / এলোমেলো অ্যাক্সেস এড়িয়ে যান
self& operator+=(difference_type n){ // n can be postive or negative
    difference_type offset = n + (cur - first);
    if(offset >=0 && offset < difference_type(buffer_size())){
        // in the same chunk
        cur += n;
    }else{//not in the same chunk
        difference_type node_offset;
        if (offset > 0){
            node_offset = offset / difference_type(chunk_size());
        }else{
            node_offset = -((-offset - 1) / difference_type(chunk_size())) - 1 ;
        }
        // skip to the new chunk
        set_node(node + node_offset);
        // set new cur
        cur = first + (offset - node_offset * chunk_size());
    }

    return *this;
}

// skip n steps
self operator+(difference_type n)const{
    self tmp = *this;
    return tmp+= n; //reuse  operator +=
}

self& operator-=(difference_type n){
    return *this += -n; //reuse operator +=
}

self operator-(difference_type n)const{
    self tmp = *this;
    return tmp -= n; //reuse operator +=
}

// random access (iterator can skip n steps)
// invoke operator + ,operator *
reference operator[](difference_type n)const{
    return *(*this + n);
}

2. কীভাবে নির্মাণ করবেন deque

সাধারণ ফাংশন deque

iterator begin(){return start;}
iterator end(){return finish;}

reference front(){
    //invoke __deque_iterator operator*
    // return start's member *cur
    return *start;
}

reference back(){
    // cna't use *finish
    iterator tmp = finish;
    --tmp; 
    return *tmp; //return finish's  *cur
}

reference operator[](size_type n){
    //random access, use __deque_iterator operator[]
    return start[n];
}


template<typename T, size_t buff_size>
deque<T, buff_size>::deque(size_t n, const value_type& value){
    fill_initialize(n, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::fill_initialize(size_t n, const value_type& value){
    // allocate memory for map and chunk
    // initialize pointer
    create_map_and_nodes(n);

    // initialize value for the chunks
    for (map_pointer cur = start.node; cur < finish.node; ++cur) {
        initialized_fill_n(*cur, chunk_size(), value);
    }

    // the end chunk may have space node, which don't need have initialize value
    initialized_fill_n(finish.first, finish.cur - finish.first, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::create_map_and_nodes(size_t num_elements){
    // the needed map node = (elements nums / chunk length) + 1
    size_type num_nodes = num_elements / chunk_size() + 1;

    // map node num。min num is  8 ,max num is "needed size + 2"
    map_size = std::max(8, num_nodes + 2);
    // allocate map array
    map = mapAllocator::allocate(map_size);

    // tmp_start,tmp_finish poniters to the center range of map
    map_pointer tmp_start  = map + (map_size - num_nodes) / 2;
    map_pointer tmp_finish = tmp_start + num_nodes - 1;

    // allocate memory for the chunk pointered by map node
    for (map_pointer cur = tmp_start; cur <= tmp_finish; ++cur) {
        *cur = dataAllocator::allocate(chunk_size());
    }

    // set start and end iterator
    start.set_node(tmp_start);
    start.cur = start.first;

    finish.set_node(tmp_finish);
    finish.cur = finish.first + num_elements % chunk_size();
}

ধরা যাক এর i_deque20 টি উপাদান রয়েছে 0~19যার খণ্ড আকার 8 এবং এখন ধাক্কা ব্যাক 3 উপাদান (0, 1, 2) থেকে i_deque:

i_deque.push_back(0);
i_deque.push_back(1);
i_deque.push_back(2);

এটি নীচের মত অভ্যন্তরীণ কাঠামো:

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

তারপরে আবার পুশ_ব্যাক করুন, এটি নতুন অংশ বরাদ্দ দেবে:

push_back(3)

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

যদি আমরা push_front, এটি পূর্বের আগে নতুন অংশ বরাদ্দ করা হবেstart

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

দ্রষ্টব্য যখন push_backউপাদানটিকে ডীক হিসাবে তৈরি করা হয়েছে, যদি সমস্ত মানচিত্র এবং খণ্ডগুলি পূরণ করা হয় তবে এটি নতুন মানচিত্র বরাদ্দ করতে এবং খণ্ডগুলিকে সামঞ্জস্য করবে B তবে উপরের কোডটি বুঝতে আপনার পক্ষে যথেষ্ট deque


আপনি উল্লেখ করেছেন, "পুশ_ব্যাক্ট উপাদানটি যখন দেবে পরিণত করুন, সমস্ত মানচিত্র এবং খণ্ডগুলি পূরণ করা হলে তা নতুন মানচিত্র বরাদ্দ করবে এবং খণ্ডগুলি সামঞ্জস্য করবে" mentioned আমি আশ্চর্য হয়েছি কেন সি ++ স্ট্যান্ডার্ডটি "[২.3.৩. ].৪.৩] একটি ডিউকের শুরুতে বা শেষদিকে কোনও উপাদান সন্নিবেশ করাতে সবসময় ধ্রুব সময় লাগে" এন 4713 এ। একগাদা ডেটা বরাদ্দ করা ধ্রুবক সময়ের চেয়ে বেশি সময় নেয়। কোন?
এইচসিএসএফ

7

আমি অ্যাডাম দ্রোজডেকের দ্বারা "ডেটা স্ট্রাকচারস এবং সি ++ এ অ্যালগরিদমগুলি" পড়ছিলাম এবং এটি দরকারী বলে মনে করি। আছে HTH।

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

আপনি মাঝখানে লক্ষ্য করতে পারেন ডেটারে পয়েন্টারগুলির অ্যারে (ডানদিকে অংশ), এবং আপনি লক্ষ্য করতে পারেন যে মাঝখানে অ্যারেটি পরিবর্তনশীলভাবে পরিবর্তিত হচ্ছে।

একটি চিত্র হাজার শব্দের মূল্যবান।

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


1
একটি বই উল্লেখ করার জন্য আপনাকে ধন্যবাদ। আমি dequeঅংশটি পড়েছি এবং এটি বেশ ভাল।
রিক

@ এটি শুনে খুশি হোন। আমি মনে করি কোন এক সময় আপনি ডীকটি খনন করেছেন কারণ আমি বুঝতে পারি না কীভাবে আপনি ও (1) এ এলোমেলো অ্যাক্সেস ([] অপারেটর) রাখতে পারবেন। এছাড়াও প্রমাণ করে যে (ধাক্কা / পপ) _ (পিছনে / সামনের) ও (1) মোড়কে করেছে জটিলতা একটি আকর্ষণীয় 'আহা মুহুর্ত' is
কেলু

6

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


4
বা মাঝখানে মুছে ফেলার জন্য প্রচুর স্থানান্তর প্রয়োজন
মার্ক হেন্ড্রিকসন

যদি insertপ্রচুর স্থানান্তর প্রয়োজন হয় তবে এখানে পরীক্ষা 4 কীভাবে এবং এর মধ্যে বিস্ময়কর পার্থক্য দেখায় ? vector::insert()deque::insert()
বুলা

1
@ বুলা: সম্ভবত বিশদ বিবরণীর কারণে? ডেক সন্নিবেশকরণের জটিলতা হ'ল "elementsোকানো উপাদানের সংখ্যার সাথে লিনিয়ার এবং ডিকের শুরু এবং শেষের দূরত্ব কম of" এই ব্যয়টি অনুভব করার জন্য, আপনাকে বর্তমান মাঝের মধ্যে সন্নিবেশ করা প্রয়োজন; আপনার বেঞ্চমার্ক কি এটি করছে?
কেরেরেক এসবি

@ কেরেকএসবি: উপরের কনরাড উত্তরে মাপদণ্ডের সাথে নিবন্ধটি উল্লেখ করা হয়েছিল। আসলে আমি নীচের নিবন্ধটির মন্তব্য বিভাগ লক্ষ্য করিনি। থ্রেডে 'তবে ডিকির লিনিয়ার সন্নিবেশের সময় আছে?' লেখক উল্লেখ করেছিলেন যে তিনি সমস্ত পরীক্ষার মাধ্যমে 100 পদে সন্নিবেশ ব্যবহার করেছিলেন, যা ফলাফলকে আরও কিছুটা বোধগম্য করে তোলে।
বুলা
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.