আধুনিক সি ++ এ ক্লাসিক বাছাইকরণ অ্যালগরিদমগুলি কীভাবে বাস্তবায়ন করবেন?


331

std::sortঅ্যালগরিদম (এবং তার চাচাতো ভাই std::partial_sortএবং std::nth_elementসি ++ স্ট্যান্ডার্ড লাইব্রেরী থেকে) সবচেয়ে বাস্তবায়নের হয় আরো প্রাথমিক বাছাই আলগোরিদিম একটি জটিল এবং সংকর মিশ্রন যেমন নির্বাচন সাজানোর, সন্নিবেশ সাজানোর, দ্রুত সাজানোর, একত্রীকরণ সাজানোর, অথবা গাদা সাজানোর হিসাবে।

এখানে এবং বোনের সাইটগুলিতে অনেকগুলি প্রশ্ন রয়েছে যেমন https://codereview.stackexchange.com সম্পর্কিত এই বাগগুলি, জটিলতা এবং এই ক্লাসিক বাছাইকরণ অ্যালগরিদমের বাস্তবায়নের অন্যান্য দিকগুলি। প্রস্তাবিত বাস্তবায়নগুলির বেশিরভাগটি কাঁচা লুপগুলি সমন্বিত করে, সূচি হেরফের এবং কংক্রিটের ধরণের ব্যবহার করে এবং সাধারণত নির্ভুলতা এবং দক্ষতার দিক দিয়ে বিশ্লেষণ করতে অ-তুচ্ছ হয়।

প্রশ্ন : আধুনিক সি ++ ব্যবহার করে উল্লিখিত ক্লাসিক বাছাইকরণ অ্যালগরিদমগুলি কীভাবে প্রয়োগ করা যেতে পারে?

  • কোনও কাঁচা লুপ নেই , তবে স্ট্যান্ডার্ড লাইব্রেরির আলগোরিদিমিক বিল্ডিং ব্লকগুলি মিশ্রিত করে<algorithm>
  • পুনরুদ্ধারকারী ইন্টারফেস এবং সূচি ম্যানিপুলেশন এবং কংক্রিটের ধরণের পরিবর্তে টেম্পলেটগুলির ব্যবহার
  • সি ++ 14 স্টাইল সহ পুরো স্ট্যান্ডার্ড লাইব্রেরি, পাশাপাশি সিনট্যাকটিক শোর হ্রাসকারী যেমন auto, টেমপ্লেট অ্যালিয়াস, স্বচ্ছ তুলক এবং পলিমারফিক ল্যাম্বডাস।

দ্রষ্টব্য :

  • বাছাই করা অ্যালগরিদমগুলির বাস্তবায়নের বিষয়ে আরও রেফারেন্সের জন্য উইকিপিডিয়া , রোসেটা কোড বা http://www.sorting-algorithms.com/ দেখুন
  • অনুযায়ী শন পিতামাতাদের নিয়মাবলী (স্লাইড 39), একটি কাঁচা লুপ একটি হল for-loop একটি অপারেটর দুটি ফাংশন রচনা চেয়ে দীর্ঘতর। সুতরাং f(g(x));বা f(x); g(x);বা f(x) + g(x);কাঁচা লুপ নয় এবং লুপগুলি ভিতরে selection_sortএবং insertion_sortনীচেও নেই।
  • আমি স্কট মায়ার্সের পরিভাষাটি অনুসরণ করে বর্তমান সি ++ 1 ইটিকে ইতিমধ্যে সি ++ 14 হিসাবে চিহ্নিত করতে এবং সি ++ 98 এবং সি ++ 03 উভয়ই সি ++ 98 হিসাবে চিহ্নিত করতে, সুতরাং এর জন্য আমাকে শিখাবেন না।
  • @ মেহেরদাডের মন্তব্যে যেমন পরামর্শ দেওয়া হয়েছে, আমি উত্তরের শেষে লাইভ উদাহরণ হিসাবে চারটি বাস্তবায়ন দিচ্ছি: সি ++ 14, সি ++ 11, সি ++ 98 এবং বুস্ট এবং সি ++ 98।
  • উত্তরটি কেবলমাত্র C ++ 14 পদে উপস্থাপন করা হয়েছে। যেখানে প্রাসঙ্গিক, আমি সিন্থেটিক এবং গ্রন্থাগারের পার্থক্যগুলিকে বোঝাই যেখানে বিভিন্ন ভাষার সংস্করণ আলাদা হয়।

8
প্রশ্নটিতে সি ++ ফ্যাক্স ট্যাগ যুক্ত করা দুর্দান্ত হবে যদিও এর জন্য অন্য একটিরও কমপক্ষে একটি হারাতে হবে। আমি সংস্করণগুলি অপসারণের পরামর্শ দেব (যেমন এটি একটি জেনেরিক সি ++ প্রশ্ন, কিছু অভিযোজন সহ বেশিরভাগ সংস্করণে বাস্তবায়নের ব্যবস্থা রয়েছে) with
ম্যাথিউ এম।

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

উত্তর:


388

অ্যালগরিদমিক বিল্ডিং ব্লক

আমরা স্ট্যান্ডার্ড লাইব্রেরি থেকে অ্যালগরিদমিক বিল্ডিং ব্লকগুলি একত্রিত করে শুরু করি:

#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 স্টাইল নেই। আরও ভাল বা আরও খারাপের জন্য, আমি স্কট মায়ার্সের খসড়া কার্যকর আধুনিক সি ++ এবং হার্ব সটারের পুনর্নির্মাণ গটডাব্লু ঘনিষ্ঠভাবে অনুসরণ করি । আমি নিম্নলিখিত শৈলী সুপারিশ ব্যবহার:

বাছাই বাছাই

নির্বাচন সাজানোর কোন ভাবেই ডাটা মানিয়ে না, তাই তার রানটাইম সর্বদা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_heapstd::make_heapO(N)O(N log N)heap_sort

বিবরণ বাদ দেওয়া : O(N)বাস্তবায়নmake_heap

পরীক্ষামূলক

এখানে চারটি লাইভ উদাহরণ রয়েছে ( সি ++ 14 , সি ++ 11 , সি ++ 98 এবং বুস্ট , সি ++ 98 ) পাঁচটি অ্যালগরিদম বিভিন্ন ধরণের ইনপুটগুলিতে পরীক্ষা করা (সম্পূর্ণ বা কঠোর নয়)। এলওসি-র বিশাল পার্থক্যগুলি কেবলমাত্র নোট করুন: সি ++ 11 / সি ++ 14 এর জন্য প্রায় 130 এলওসি, সি ++ 98 এবং বুস্ট 190 (+ 50%) এবং সি ++ 98 270 (+ 100%) এর চেয়ে বেশি প্রয়োজন।


13
যদিও আমি আপনার ব্যবহারের সাথে একমত নইauto (এবং অনেক লোক আমার সাথে একমত নয়), আমি স্ট্যান্ডার্ড লাইব্রেরি অ্যালগরিদমগুলি ভালভাবে ব্যবহার করা দেখে উপভোগ করেছি। শান প্যারেন্টের কথাবার্তা দেখার পরে আমি এই জাতীয় কোডের কয়েকটি উদাহরণ দেখতে চাইছি। এছাড়াও, আমি কোন ধারণা ছিল না std::iter_swapঅস্তিত্ব, যদিও এটা আমার কাছে অদ্ভুত এটি আছে বলে মনে হয় <algorithm>
জোসেফ ম্যানসফিল্ড

32
@ এসবাবিবি পুরো স্ট্যান্ডার্ড লাইব্রেরিটি নীতির উপর ভিত্তি করে তৈরি হয়েছে যে পুনরাবৃত্তিকারীরা অনুলিপি করার পক্ষে সস্তা; উদাহরণস্বরূপ এটি তাদের দ্বারা পাস করে। যদি কোনও পুনরুক্তিকারী অনুলিপি করা সস্তা না হয় তবে আপনি সর্বত্র কর্মক্ষমতাজনিত সমস্যায় ভুগছেন।
জেমস কানজে

2
দুর্দান্ত পোস্ট। [স্টাডি ::] মেক_হ্যাপের প্রতারণামূলক অংশ সম্পর্কিত। যদি std :: Make_heap কে প্রতারণা হিসাবে বিবেচনা করা হয়, তবে std :: push_heap হবে। অর্থাত্ প্রতারণা = গাদা কাঠামোর জন্য সংজ্ঞায়িত প্রকৃত আচরণ বাস্তবায়ন করছে না। আমি এটিকে শিক্ষামূলকভাবে ধাক্কা দিয়েছি বলে মনে করি।
ক্যাপ্টেন জিরাফ

3
@gnzlbg অবশ্যই যে মন্তব্যগুলি আপনি মন্তব্য করতে পারেন তা অবশ্যই। গোড়ার দিকে পরীক্ষা ট্যাগ প্রেষিত র্যান্ডম অ্যাক্সেস করার জন্য বর্তমান সংস্করণ সঙ্গে, পুনরুক্তিকারীর বিভাগ প্রতি হতে পারে, এবং if (first == last || std::next(first) == last)। আমি পরে এটি আপডেট করতে পারে। "বাদ দেওয়া বিশদ" বিভাগগুলিতে স্টাফটি বাস্তবায়ন করা প্রশ্ন, আইএমওর বাইরে নয়, কারণ এতে তাদের পুরো প্রশ্নোত্তর লিঙ্ক রয়েছে এবং নিজেরাই। রিয়েল-ওয়ার্ড বাছাইয়ের রুটিনগুলি কার্যকর করা কঠিন!
TemplateRex

3
দুর্দান্ত পোস্ট। যদিও, আপনি nth_elementআমার মতামত ব্যবহার করে আপনার quicksort সঙ্গে প্রতারণা করেছেন। nth_elementঅর্ধ কুইকোর্ট ইতিমধ্যে রয়েছে (পার্টিশনের ধাপ এবং অর্ধে একটি পুনরাবৃত্তি যাতে আপনার আগ্রহী এন-থ উপাদান রয়েছে)।
সেলিবিটজে

14

আরেকটি ছোট এবং বরং মার্জিত একটি মূলত কোড পর্যালোচনায় পাওয়া গেছে । আমি এটি ভাগ করে নেওয়ার মতো ভেবেছিলাম।

গণনা বাছাই

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

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

template<typename ForwardIterator>
void counting_sort(ForwardIterator first, ForwardIterator last)
{
    if (first == last || std::next(first) == last) return;

    auto minmax = std::minmax_element(first, last);  // avoid if possible.
    auto min = *minmax.first;
    auto max = *minmax.second;
    if (min == max) return;

    using difference_type = typename std::iterator_traits<ForwardIterator>::difference_type;
    std::vector<difference_type> counts(max - min + 1, 0);

    for (auto it = first ; it != last ; ++it) {
        ++counts[*it - min];
    }

    for (auto count: counts) {
        first = std::fill_n(first, count, min++);
    }
}

যখন এটি কেবল তখন কার্যকর হয় যখন বাছাইয়ের জন্য পূর্ণসংখ্যার পরিসর ছোট হিসাবে পরিচিত হয় (সাধারণত সংগ্রহের আকারের চেয়ে বড় আকারের নয়), গণনার ক্রমকে আরও সাধারণ তৈরি করা তার সেরা ক্ষেত্রে এটি ধীর করে দেয়। যদি পরিসীমাটি ছোট হিসাবে পরিচিত না হয় তবে এর পরিবর্তে অন্য একটি অ্যালগরিদম যেমন একটি রেডিক্স সাজান , স্কাই_সোর্ট বা স্প্রেডসোর্ট ব্যবহার করা যেতে পারে।

বিবরণ বাদ দেওয়া হয়েছে :

  • std::minmax_elementসংগ্রহের মাধ্যমে প্রথম পাসটি সম্পূর্ণরূপে পরিত্রাণ পেতে আমরা প্যারামিটার হিসাবে অ্যালগরিদম দ্বারা গৃহীত মানগুলির সীমার সীমাটি পেরিয়ে যেতে পারতাম । এটি কার্যকরভাবে-ছোট পরিসীমা সীমাটি অন্য উপায়ে পরিচিত হলে এটি আলগোরিদিমটিকে আরও ত্বরান্বিত করবে। (এটি সঠিক হতে হবে না; সত্য সীমানা 1 থেকে 95 হয় তা খুঁজে পেতে লক্ষ লক্ষ উপাদানকে অতিরিক্ত পাসের চেয়ে ধ্রুবক 0 থেকে 100 পাস করা আরও ভাল is এমনকি 0 থেকে 1000 এর মূল্যও উপযুক্ত হবে; অতিরিক্ত উপাদানগুলি একবারে শূন্যের সাথে লেখা হয় এবং একবার পড়ে থাকে)।

  • countsপৃথক প্রথম পাস এড়াতে ফ্লাইতে বেড়ে ওঠা অন্য উপায়। countsপ্রতিবার আকারটি বাড়ার জন্য দ্বিগুণ করা বাছাই করা উপাদান অনুসারে orণভুক্ত ও (1) সময় দেয় (তাত্ক্ষণিকভাবে উত্থাপক হ'ল তার প্রমাণের জন্য হ্যাশ টেবিল সন্নিবেশ ব্যয় বিশ্লেষণ দেখুন)। নতুন শূন্য উপাদান যুক্ত করা একটি নতুনের জন্য শেষে বাড়ানো maxসহজ std::vector::resizeminফ্লাইতে পরিবর্তন করা এবং সামনে নতুন শূন্য উপাদানগুলি সন্নিবেশ করা std::copy_backwardভেক্টরটি বাড়ানোর পরে করা যেতে পারে । তারপর std::fillনতুন উপাদান শূন্য।

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

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

  • যেহেতু অ্যালগরিদম কেবল পূর্ণসংখ্যার মানগুলির সাথে কাজ করে, ব্যবহারকারীদের সুস্পষ্ট প্রকারের ভুল থেকে বিরত রাখতে স্ট্যাটিক দৃser়তা ব্যবহার করা যেতে পারে। কিছু প্রসঙ্গে, একটি বিকল্প ব্যর্থতার সাথে std::enable_if_tঅগ্রাধিকার দেওয়া হতে পারে।

  • যদিও আধুনিক সি ++ দুর্দান্ত, ভবিষ্যতের সি ++ এমনকি আরও শীতল হতে পারে: কাঠামোগত বাইন্ডিংস এবং রেঞ্জের টিএসের কিছু অংশ অ্যালগরিদমকে আরও পরিষ্কার করে তুলবে।


@ টেম্পলেটআরেক্স যদি এটি একটি স্বেচ্ছাসেবী তুলনা অবজেক্ট নিতে সক্ষম হয় তবে এটি গণনা বাছাইকে তুলনা বাছাই করে তুলবে এবং তুলনার ধরণগুলি ও (এন লগ এন) এর চেয়ে ভাল খারাপ পরিস্থিতি হতে পারে না। গণনা বাছাইয়ের ক্ষেত্রে ও (এন + আর) সবচেয়ে খারাপ পরিস্থিতি রয়েছে যার অর্থ এটি কোনওভাবে তুলনা বাছাই করা যায় না। পূর্ণসংখ্যার তুলনা করা যেতে পারে তবে এই সম্পত্তিটি বাছাই করতে ব্যবহৃত হয় না (এটি std::minmax_elementকেবলমাত্র তথ্য সংগ্রহ করে এমনটিতে ব্যবহৃত হয় )। ব্যবহৃত সম্পত্তি হ'ল সত্যটি হল যে পূর্ণসংখ্যাগুলি সূচক বা অফসেট হিসাবে ব্যবহার করা যেতে পারে এবং পরবর্তী সম্পত্তি সংরক্ষণের সময় এগুলি বাড়ানো যায়।
মরউভেন

রেঞ্জের টিএস সত্যই খুব সুন্দর, যেমন চূড়ান্ত লুপটি শেষ হতে পারে counts | ranges::view::filter([](auto c) { return c != 0; })যাতে আপনাকে বারবার ভিতরে ননজারো গণনার জন্য পরীক্ষা করতে হবে না fill_n
TemplateRex

(আমি টাইপস পাওয়া small একটি rather এবং appart- আমি তাদের তিল সম্পাদন করা বিষয়ে reggae_sort রাখতে পারে?)
বৃদ্ধলোক

@ গ্রেইবার্ড আপনি যা খুশি তা করতে পারেন: পি
মরউভেন

আমি সন্দেহ করি যে ফ্লাইতে ক্রমবর্ধমান বৃদ্ধি হিস্টোগ্রামিংয়ের আগে counts[]ইনপুট ট্র্যাভার করে একটি জয় বনাম হতে পারে minmax_element। বিশেষত ব্যবহারের ক্ষেত্রে যেখানে এটি আদর্শ, ছোট পরিসরে অনেকগুলি পুনরাবৃত্তি সহ খুব বড় ইনপুট, কারণ আপনি খুব শীঘ্রই countsকয়েকটি শাখার ভুল বা আকার-দ্বিগুণে দ্রুত তার পূর্ণ আকারে বাড়বেন। (অবশ্যই একটি ছোট-যথেষ্ট ব্যাপ্তির উপর আবদ্ধ বুদ্ধিমান আপনি একটি এড়ানো দেব minmax_elementস্ক্যান এবং হিস্টোগ্রাম লুপ ভিতরে এড়ানোর সীমা-পরীক্ষণের।)
পিটার Cordes
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.