অ্যালগরিদমিক বিল্ডিং ব্লক
আমরা স্ট্যান্ডার্ড লাইব্রেরি থেকে অ্যালগরিদমিক বিল্ডিং ব্লকগুলি একত্রিত করে শুরু করি:
#include <algorithm> // min_element, iter_swap,
// upper_bound, rotate,
// partition,
// inplace_merge,
// make_heap, sort_heap, push_heap, pop_heap,
// is_heap, is_sorted
#include <cassert> // assert
#include <functional> // less
#include <iterator> // distance, begin, end, next
- পুনরাবৃত্তকারী সরঞ্জাম যেমন অ-সদস্য
std::begin()
/ std::end()
পাশাপাশি std::next()
কেবল সি ++ 11 এবং তার বাইরেও উপলব্ধ। সি ++ 98 এর জন্য নিজের এগুলি লিখতে হবে। বুস্ট.রেঞ্জ থেকে boost::begin()
/ boost::end()
এবং বুস্ট.ইউটিলিটি ইন বিকল্পগুলি থেকে রয়েছে boost::next()
।
std::is_sorted
অ্যালগরিদম সি ++ 11 এবং তার পরেও শুধুমাত্র উপলব্ধ। সি ++ 98 এর জন্য, এটি std::adjacent_find
কোনও হাতে লিখিত ফাংশন অবজেক্টের ক্ষেত্রে প্রয়োগ করা যেতে পারে । বুস্ট.এলগোরিদম boost::algorithm::is_sorted
বিকল্প হিসাবেও সরবরাহ করে ।
std::is_heap
অ্যালগরিদম সি ++ 11 এবং তার পরেও শুধুমাত্র উপলব্ধ।
সিনট্যাকটিকাল গুডিজ
সি ++ 14 ফর্মটির স্বচ্ছ তুলনামূলক সরবরাহ std::less<>
করে যা তাদের যুক্তিগুলিতে বহিরাগতভাবে কাজ করে। এটি একটি পুনরাবৃত্তকারীর ধরণ সরবরাহ করা এড়ায়। এটি সি ++ 11 এর ডিফল্ট ফাংশন টেম্পলেট আর্গুমেন্টের সাথে মিশ্রণে ব্যবহার করা যেতে পারে যে তুলনা হিসাবে গ্রহণযোগ্য অ্যালগোরিদমগুলি বাছাই করার জন্য এবং একটি ব্যবহারকারীর সংজ্ঞায়িত তুলনা ফাংশন অবজেক্ট রয়েছে এমনগুলির জন্য একটি একক ওভারলোড তৈরি করতে <
।
template<class It, class Compare = std::less<>>
void xxx_sort(It first, It last, Compare cmp = Compare{});
সি ++ ১১-এ, একজন পুনরুক্তিযোগ্য টেম্পলেট উলামটিকে পুনরুক্তিযোগ্য টেম্প্লেট এলিফটি সংজ্ঞা দিতে পারেন যা পুনরুক্তিযোগ্য মান ধরণেরটি বের করতে পারে যা সাজানো অ্যালগরিদমের স্বাক্ষরগুলিতে সামান্য বিশৃঙ্খলা যুক্ত করে:
template<class It>
using value_type_t = typename std::iterator_traits<It>::value_type;
template<class It, class Compare = std::less<value_type_t<It>>>
void xxx_sort(It first, It last, Compare cmp = Compare{});
সি ++ 98-তে, একটিতে দুটি ওভারলোড লিখতে এবং ভার্বোস typename xxx<yyy>::type
সিনট্যাক্স ব্যবহার করা দরকার
template<class It, class Compare>
void xxx_sort(It first, It last, Compare cmp); // general implementation
template<class It>
void xxx_sort(It first, It last)
{
xxx_sort(first, last, std::less<typename std::iterator_traits<It>::value_type>());
}
- আর একটি সিনট্যাক্টিকাল নব্বইটি হ'ল সি ++ ১৪ পলিমারফিক ল্যাম্বডাসের মাধ্যমে ব্যবহারকারী-সংজ্ঞায়িত তুলনামূলক মোড়কে সহায়তা করে (
auto
ফাংশন টেম্পলেট আর্গুমেন্টের মতো পরামিতিগুলির সাথে )।
- সি ++ 11 এর মধ্যে কেবল মনোমরফিক ল্যাম্বডাস রয়েছে, যার জন্য উপরের টেমপ্লেট ওরফে ব্যবহারের প্রয়োজন
value_type_t
।
- সি ++ 98-তে, কোনও একটিকে একটি স্বতন্ত্র ফাংশন অবজেক্ট লিখতে হবে বা ভার্বোস
std::bind1st
/ std::bind2nd
/ std::not1
টাইপ সিনট্যাক্সের অবলম্বন করতে হবে ।
- বুস্ট.বাইন্ড
boost::bind
এবং _1
/ _2
স্থানধারক সিনট্যাক্সের সাহায্যে এটিকে উন্নত করে।
- সি ++ 11 এবং তার পরেও এছাড়াও আছে
std::find_if_not
যেহেতু সি ++ 98 প্রয়োজন, std::find_if
একটি সঙ্গে std::not1
একটি ফাংশন বস্তুর চারপাশে।
সি ++ স্টাইল
সাধারণভাবে গ্রহণযোগ্য সি ++ 14 স্টাইল নেই। আরও ভাল বা আরও খারাপের জন্য, আমি স্কট মায়ার্সের খসড়া কার্যকর আধুনিক সি ++ এবং হার্ব সটারের পুনর্নির্মাণ গটডাব্লু ঘনিষ্ঠভাবে অনুসরণ করি । আমি নিম্নলিখিত শৈলী সুপারিশ ব্যবহার:
- ভেষজ সুটারের "প্রায় সর্বদা অটো" এবং স্কট মেয়ার্সের "নির্দিষ্ট ধরণের ঘোষণায় অটো পছন্দ করুন" সুপারিশ, যার জন্য ব্রেভিটি নিরর্থক, যদিও এর স্পষ্টতা মাঝে মাঝে বিতর্কিত হয় ।
- স্কট মিয়ার্সের "পার্থক্য তৈরি করার সময়
()
এবং {}
অবজেক্ট তৈরি করার সময়" এবং ধারাবাহিকভাবে {}
ভাল পুরাতন প্রথম বন্ধনীযুক্ত আরম্ভের পরিবর্তে ()
(জেনেরিক কোডে সর্বাধিক ভেক্সিং-পার্স ইস্যুতে দিক-নির্দেশিত করার জন্য) পরিবর্তে ব্রেসড-আরম্ভকরণ নির্বাচন করুন choose
- স্কট মায়ার্স "টাইপডেফগুলিতে উপন্যাসের পছন্দগুলি পছন্দ করুন" । টেমপ্লেটগুলির জন্য এটি অবশ্যই একটি উপায় এবং
typedef
সময় সাশ্রয়ের পরিবর্তে সর্বত্র এটি ব্যবহার করা এবং ধারাবাহিকতা যুক্ত করে।
for (auto it = first; it != last; ++it)
ইতিমধ্যে সাজানো সাব-রেঞ্জগুলির জন্য লুপ ইনগ্রায়েন্ট চেকিংয়ের অনুমতি দেওয়ার জন্য আমি কিছু জায়গায় একটি প্যাটার্ন ব্যবহার করি । প্রোডাকশন কোডে, লুপের ভিতরে while (first != last)
এবং ++first
কোথাও ব্যবহারটি আরও ভাল হতে পারে।
বাছাই বাছাই
নির্বাচন সাজানোর কোন ভাবেই ডাটা মানিয়ে না, তাই তার রানটাইম সর্বদাO(N²)
। যাইহোক, নির্বাচন সাজানোর সম্পত্তি হয়েছে অদলবদল সংখ্যা কমানোর । অ্যাপ্লিকেশনগুলিতে যেখানে অদলবদলের আইটেমগুলির ব্যয় বেশি, নির্বাচনের সাজ্টের পছন্দ খুব পছন্দসই অ্যালগরিদম হতে পারে।
স্ট্যান্ডার্ড লাইব্রেরি ব্যবহার করে এটি প্রয়োগ করতে, বার বার ব্যবহার std::min_element
করুন অবশিষ্ট ন্যূনতম উপাদানটি অনুসন্ধান করতে এবং iter_swap
এটিকে স্থানে স্যুপ করতে:
template<class FwdIt, class Compare = std::less<>>
void selection_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
for (auto it = first; it != last; ++it) {
auto const selection = std::min_element(it, last, cmp);
std::iter_swap(selection, it);
assert(std::is_sorted(first, std::next(it), cmp));
}
}
নোট করুন যে selection_sort
ইতিমধ্যে প্রক্রিয়াজাত ব্যাপ্তিটি [first, it)
এর লুপ ইনগ্রায়েন্ট হিসাবে বাছাই করেছে। ন্যূনতম প্রয়োজনীয়তা হ'লstd::sort
র্যান্ডম অ্যাক্সেস পুনরুক্তিদের তুলনায় ফরোয়ার্ড আউটরেটর are
বিবরণ বাদ দেওয়া হয়েছে :
- প্রাথমিক বাছাই
if (std::distance(first, last) <= 1) return;
(বা ফরোয়ার্ড / দ্বি নির্দেশমূলক পুনরাবৃত্তকারীদের জন্য if (first == last || std::next(first) == last) return;
) : বাছাই বাছাই করা অনুকূলিত করা যেতে পারে ।
- দ্বি নির্দেশমূলক পুনরাবৃত্তির জন্য , উপরের পরীক্ষাটি ব্যবধানের উপর দিয়ে একটি লুপের সাথে একত্রিত করা যেতে পারে
[first, std::prev(last))
কারণ শেষ উপাদানটি ন্যূনতম অবশিষ্ট উপাদান হিসাবে গ্যারান্টিযুক্ত এবং কোনও অদলবদলের প্রয়োজন হয় না।
সন্নিবেশ সাজান
যদিও এটি O(N²)
সবচেয়ে খারাপ ক্ষেত্রে সময়ের সাথে প্রাথমিক বাছাই করা অ্যালগরিদমগুলির মধ্যে একটি, যদিও তথ্য প্রায় বাছাই করা হয় (কারণ এটি অভিযোজিত ) বা যখন সমস্যার আকার ছোট হয় (কারণ এটির ওভারহেড কম থাকে) তখন সন্নিবেশ সাজানো পছন্দের অ্যালগরিদম । এই কারণগুলির জন্য এবং এটি স্থিতিশীল হওয়ার কারণে, সন্নিবেশ বাছাইটি প্রায়শই পুনরাবৃত্ত বেস বেস হিসাবে ব্যবহৃত হয় (যখন সমস্যার আকার ছোট হয়) উচ্চতর ওভারহেড বিভাজন এবং বিজয়ী বাছাইকরণ অ্যালগরিদম যেমন মার্জ সারণি বা দ্রুত সাজানোর জন্য ব্যবহৃত হয়।
insertion_sort
স্ট্যান্ডার্ড লাইব্রেরি সহ বাস্তবায়নের জন্য , std::upper_bound
বর্তমান উপাদানটি কোথায় যেতে হবে সেই অবস্থানটি অনুসন্ধান করতে বার বার ব্যবহার করুন এবং std::rotate
বাকী উপাদানগুলিকে ইনপুট সীমাতে wardর্ধ্বমুখী স্থানান্তর করতে ব্যবহার করুন:
template<class FwdIt, class Compare = std::less<>>
void insertion_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
for (auto it = first; it != last; ++it) {
auto const insertion = std::upper_bound(first, it, *it, cmp);
std::rotate(insertion, it, std::next(it));
assert(std::is_sorted(first, std::next(it), cmp));
}
}
নোট করুন যে insertion_sort
ইতিমধ্যে প্রক্রিয়াজাত ব্যাপ্তিটি [first, it)
এর লুপ ইনগ্রায়েন্ট হিসাবে বাছাই করেছে। সন্নিবেশ সাজানোর কাজটি ফরোয়ার্ড পুনরাবৃত্তকারীগুলির সাথেও কাজ করে।
বিবরণ বাদ দেওয়া হয়েছে :
- সন্নিবেশ বাছাইটি প্রাথমিক পরীক্ষার
if (std::distance(first, last) <= 1) return;
(বা ফরোয়ার্ড / দ্বি নির্দেশমূলক if (first == last || std::next(first) == last) return;
পুনরাবৃত্তকারীদের জন্য) এবং অন্তরের মধ্যবর্তী একটি লুপ দিয়ে অনুকূলিত করা যেতে পারে [std::next(first), last)
কারণ প্রথম উপাদানটি তার জায়গায় থাকার গ্যারান্টিযুক্ত এবং একটি ঘোরানোর প্রয়োজন নেই।
- জন্য দ্বিমুখী iterators , বাইনারি অনুসন্ধান সন্নিবেশ বিন্দু খুঁজে বের করার একটি সঙ্গে প্রতিস্থাপিত হতে পারে বিপরীত রৈখিক অনুসন্ধান স্ট্যান্ডার্ড লাইব্রেরির ব্যবহার
std::find_if_not
অ্যালগরিদম।
নীচের খণ্ডটির জন্য চারটি লাইভ উদাহরণ ( সি ++ 14 , সি ++ 11 , সি ++ 98 এবং বুস্ট , সি ++ 98 ):
using RevIt = std::reverse_iterator<BiDirIt>;
auto const insertion = std::find_if_not(RevIt(it), RevIt(first),
[=](auto const& elem){ return cmp(*it, elem); }
).base();
- এলোমেলো ইনপুটগুলির জন্য এটি
O(N²)
তুলনা দেয় তবে এটি O(N)
প্রায় বাছাই করা ইনপুটগুলির তুলনায় উন্নতি করে। বাইনারি অনুসন্ধান সর্বদা O(N log N)
তুলনা ব্যবহার করে ।
- ছোট ইনপুট ব্যাপ্তির জন্য, রৈখিক অনুসন্ধানের আরও ভাল মেমরি লোকেশন (ক্যাশে, প্রিফেচিং) বাইনারি অনুসন্ধানেও প্রভাব ফেলতে পারে (অবশ্যই এটি অবশ্যই পরীক্ষা করা উচিত)।
দ্রুত বাছাই
সতর্কতার সাথে প্রয়োগ করা হলে, দ্রুত সাজানো শক্তিশালী এবং O(N log N)
প্রত্যাশিত জটিলতা থাকে, তবে O(N²)
সবচেয়ে খারাপ ক্ষেত্রে জটিলতার সাথে যা প্রতিকূলভাবে বেছে নেওয়া ইনপুট ডেটা দিয়ে ট্রিগার করা যায়। যখন একটি স্থিতিশীল বাছাই করা প্রয়োজন হয় না, দ্রুত বাছাই একটি দুর্দান্ত সাধারণ উদ্দেশ্য সারণি।
এমনকি সহজ সংস্করণগুলির জন্য, দ্রুত শ্রেণীবদ্ধ অন্যান্য ক্লাসিক বাছাই করা অ্যালগরিদমের তুলনায় স্ট্যান্ডার্ড লাইব্রেরি ব্যবহার করে প্রয়োগ করা কিছুটা জটিল। পাইপট হিসাবে ইনপুট রেঞ্জের মাঝারি উপাদানটি সনাক্ত করতে নীচের পদ্ধতির কয়েকটি আইট্রেটর ইউটিলিটি [first, last)
ব্যবহার করা হয়েছে, তারপরে ত্রি-উপাণে পার্টিশন করার জন্য দুটি কল std::partition
(যা হ'ল O(N)
) এর চেয়ে ছোট, সমান, এবং যথাক্রমে নির্বাচিত পিভট থেকে বড়। অবশেষে পিভটের চেয়ে ছোট এবং বড় উপাদানগুলির সাথে দুটি বাহ্যিক বিভাগ পুনরাবৃত্তভাবে সাজানো হয়:
template<class FwdIt, class Compare = std::less<>>
void quick_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
auto const N = std::distance(first, last);
if (N <= 1) return;
auto const pivot = *std::next(first, N / 2);
auto const middle1 = std::partition(first, last, [=](auto const& elem){
return cmp(elem, pivot);
});
auto const middle2 = std::partition(middle1, last, [=](auto const& elem){
return !cmp(pivot, elem);
});
quick_sort(first, middle1, cmp); // assert(std::is_sorted(first, middle1, cmp));
quick_sort(middle2, last, cmp); // assert(std::is_sorted(middle2, last, cmp));
}
যাইহোক, দ্রুত বাছাই সঠিক এবং দক্ষ হওয়ার জন্য কৌশলযুক্ত, কারণ উপরের প্রতিটি পদক্ষেপের প্রতিটি উত্পাদন স্তরের কোডের জন্য সাবধানতার সাথে পরীক্ষা করা এবং অপ্টিমাইজ করতে হবে। বিশেষত, O(N log N)
জটিলতার জন্য , পাইভটকে ইনপুট ডেটার ভারসাম্যপূর্ণ বিভাজনে পরিণত করতে হয়, যা সাধারণভাবে একটি O(1)
পিভটের গ্যারান্টি দেওয়া যায় না, তবে যদি কেউ O(N)
ইনপুট সীমার মধ্যম হিসাবে পাইভট সেট করে তবে গ্যারান্টি দেওয়া যেতে পারে ।
বিবরণ বাদ দেওয়া হয়েছে :
- উপরের প্রয়োগটি বিশেষত ইনপুটগুলির পক্ষে বিশেষত দুর্বল, যেমন
O(N^2)
" অর্গান পাইপ " ইনপুটটির জন্য জটিলতা রয়েছে 1, 2, 3, ..., N/2, ... 3, 2, 1
(কারণ মাঝেরটি সবসময় অন্যান্য উপাদানগুলির চেয়ে বড় থাকে)।
- ইনপুট পরিসীমাথেকে এলোমেলোভাবে নির্বাচিত উপাদানগুলি থেকে প্রায় সাজানো ইনপুটগুলি থেকে জটিলতার অন্যথায় অবনতি ঘটতে পারে রক্ষাকারীথেকে এলোমেলোভাবে নির্বাচিত উপাদানগুলির মধ্যথেকে 3 পিভট নির্বাচন
O(N^2)
।
- 3-ওয়ে বিভাজন (পিভটের চেয়ে ছোট, সমান এবং বৃহত্তর উপাদানগুলি পৃথক করা) এই কলটিঅর্জনেরজন্য দুটি কল দ্বারা দেখানো হিসাবে
std::partition
সর্বাধিক দক্ষO(N)
অ্যালগরিদমনয়।
- জন্য রেণ্ডম এক্সেস iterators , একটি নিশ্চিত
O(N log N)
জটিলতা মাধ্যমে অর্জন করা সম্ভব মধ্যমা পিভট নির্বাচন ব্যবহার std::nth_element(first, middle, last)
, এর recursive কল দ্বারা অনুসরণ quick_sort(first, middle, cmp)
এবং quick_sort(middle, last, cmp)
।
- তবে এই গ্যারান্টিটি ব্যয় করে আসে, কারণ
O(N)
জটিলতার ধ্রুবক ফ্যাক্টরটি একটি মিডিয়ান -3-পিভটের জটিলতার std::nth_element
চেয়ে আরও ব্যয়বহুল হতে পারে O(1)
তারপরে একটি O(N)
কল std::partition
(যা ক্যাশে-বান্ধব একক ফরোয়ার্ড পাসের উপর দিয়ে যায়) তথ্যটি).
বাছাই মার্জ
O(N)
অতিরিক্ত স্থান ব্যবহার করা যদি উদ্বেগের বিষয় না হয় তবে মার্জ সাজ্ট একটি দুর্দান্ত পছন্দ: এটিই কেবল স্থিতিশীল O(N log N)
বাছাই অ্যালগরিদম।
স্ট্যান্ডার্ড অ্যালগরিদম ব্যবহার করে প্রয়োগ করা সহজ: ইনপুট পরিসরের মাঝখানে অবস্থান নির্ধারণ করতে কয়েকটি [first, last)
পুনরাবৃত্তকারী ইউটিলিটিগুলি ব্যবহার করুন এবং এর সাথে দুটি পুনরাবৃত্তভাবে সাজানো বিভাগগুলি একত্রিত করুন std::inplace_merge
:
template<class BiDirIt, class Compare = std::less<>>
void merge_sort(BiDirIt first, BiDirIt last, Compare cmp = Compare{})
{
auto const N = std::distance(first, last);
if (N <= 1) return;
auto const middle = std::next(first, N / 2);
merge_sort(first, middle, cmp); // assert(std::is_sorted(first, middle, cmp));
merge_sort(middle, last, cmp); // assert(std::is_sorted(middle, last, cmp));
std::inplace_merge(first, middle, last, cmp); // assert(std::is_sorted(first, last, cmp));
}
মার্জ বাছাইয়ের জন্য দ্বি-দিকীয় পুনরুক্তি প্রয়োজন, বাধাটি হ'ল std::inplace_merge
। নোট করুন যে লিঙ্কযুক্ত তালিকাগুলি বাছাই করার সময়, মার্জ সাজানোর জন্য কেবল O(log N)
অতিরিক্ত স্থান প্রয়োজন (পুনরাবৃত্তির জন্য)। পরবর্তী অ্যালগরিদম std::list<T>::sort
স্ট্যান্ডার্ড লাইব্রেরিতে প্রয়োগ করা হয়।
গাদা সাজান
হিপ বাছাই কার্যকর করা সহজ, একটিO(N log N)
ইন-প্লেস বাছাই করে তবে স্থিতিশীল নয়।
প্রথম লুপ, O(N)
"হিপিফাই" পর্যায়ে অ্যারেটিকে হ্যাপ অর্ডারে রাখে। দ্বিতীয় লুপ, O(N log N
"" সাজ্টাউন "ধাপটি বারবার সর্বাধিক উত্তোলন করে এবং হ্যাপ ক্রম পুনরুদ্ধার করে। স্ট্যান্ডার্ড লাইব্রেরি এটিকে অত্যন্ত সোজা করে তুলেছে:
template<class RandomIt, class Compare = std::less<>>
void heap_sort(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
lib::make_heap(first, last, cmp); // assert(std::is_heap(first, last, cmp));
lib::sort_heap(first, last, cmp); // assert(std::is_sorted(first, last, cmp));
}
যদি আপনি এটি "প্রতারণামূলক" ব্যবহারের জন্য বিবেচনা করেন std::make_heap
এবং std::sort_heap
, আপনি এক স্তর আরও গভীরে যেতে পারেন std::push_heap
এবং std::pop_heap
যথাক্রমে এবং যথাযথভাবে সেই ফাংশনগুলি নিজে লিখে ফেলতে পারেন :
namespace lib {
// NOTE: is O(N log N), not O(N) as std::make_heap
template<class RandomIt, class Compare = std::less<>>
void make_heap(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
for (auto it = first; it != last;) {
std::push_heap(first, ++it, cmp);
assert(std::is_heap(first, it, cmp));
}
}
template<class RandomIt, class Compare = std::less<>>
void sort_heap(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
for (auto it = last; it != first;) {
std::pop_heap(first, it--, cmp);
assert(std::is_heap(first, it, cmp));
}
}
} // namespace lib
স্ট্যান্ডার্ড লাইব্রেরি উভয় push_heap
এবং pop_heap
জটিলতা হিসাবে নির্দিষ্ট করে O(log N)
। তবে নোট করুন যে বাহ্যিক লুপটি সীমার উপর দিয়ে জটিলতার জন্য [first, last)
ফলাফল করে , যেখানে কেবলমাত্র জটিলতা রয়েছে। সামগ্রিক জটিলতার জন্য এটি কোনও বিষয় নয়।O(N log N)
make_heap
std::make_heap
O(N)
O(N log N)
heap_sort
বিবরণ বাদ দেওয়া : O(N)
বাস্তবায়নmake_heap
পরীক্ষামূলক
এখানে চারটি লাইভ উদাহরণ রয়েছে ( সি ++ 14 , সি ++ 11 , সি ++ 98 এবং বুস্ট , সি ++ 98 ) পাঁচটি অ্যালগরিদম বিভিন্ন ধরণের ইনপুটগুলিতে পরীক্ষা করা (সম্পূর্ণ বা কঠোর নয়)। এলওসি-র বিশাল পার্থক্যগুলি কেবলমাত্র নোট করুন: সি ++ 11 / সি ++ 14 এর জন্য প্রায় 130 এলওসি, সি ++ 98 এবং বুস্ট 190 (+ 50%) এবং সি ++ 98 270 (+ 100%) এর চেয়ে বেশি প্রয়োজন।