একটি ক্রমের সমস্ত সূচক উত্পন্ন করা সাধারণত একটি খারাপ ধারণা, কারণ এতে অনেক সময় লাগতে পারে, বিশেষত যদি সংখ্যার অনুপাতটি MAX
কম হয় (জটিলতা এর দ্বারা প্রাধান্য পায় O(MAX)
)। এটির সংখ্যার অনুপাত যদি MAX
একটির কাছে পৌঁছানো হয় তবে এটি আরও খারাপ হয় , তারপরে সমস্তের ক্রম থেকে নির্বাচিত সূচকগুলি অপসারণ করাও ব্যয়বহুল হয়ে যায় (আমরা কাছে যাই O(MAX^2/2)
)। তবে স্বল্প সংখ্যার জন্য, এটি সাধারণত ভালভাবে কাজ করে এবং বিশেষত ত্রুটি-প্রবণ নয়।
সংগ্রহ ব্যবহার করে উত্পন্ন সূচকগুলি ফিল্টার করাও একটি খারাপ ধারণা, কারণ সূচিগুলি অনুসারে সন্নিবেশ করানোর ক্ষেত্রে কিছুটা সময় ব্যয় করা হয়, এবং একই র্যান্ডম সংখ্যাটি কয়েকবার আঁকতে পারে বলে অগ্রগতি গ্যারান্টিযুক্ত হয় না (তবে যথেষ্ট পরিমাণে MAX
এটি অসম্ভব) )। এটি জটিলতার কাছাকাছি হতে পারে
O(k n log^2(n)/2)
, সদৃশগুলি উপেক্ষা করে এবং সংগ্রহটি দক্ষ অনুসন্ধানের জন্য একটি গাছ ব্যবহার করে (তবে k
গাছের নোডগুলিকে বরাদ্দ করার ক্ষেত্রে একটি ধ্রুবক ব্যয় এবং সম্ভবত পুনরায় ভারসাম্য বজায় রাখতে হবে) ) ধরে নেওয়া যায়।
আরেকটি বিকল্প হ'ল প্রথম থেকেই এলোমেলো মান উত্পন্ন করা, গ্যারান্টি দিয়ে অগ্রগতি হচ্ছে। এর অর্থ প্রথম রাউন্ডে, এলোমেলো সূচক [0, MAX]
তৈরি হয়:
items i0 i1 i2 i3 i4 i5 i6 (total 7 items)
idx 0 ^^ (index 2)
দ্বিতীয় রাউন্ডে, শুধুমাত্র [0, MAX - 1]
উত্পন্ন হয় (একটি আইটেম ইতিমধ্যে নির্বাচিত ছিল):
items i0 i1 i3 i4 i5 i6 (total 6 items)
idx 1 ^^ (index 2 out of these 6, but 3 out of the original 7)
সূচকের মানগুলি তখনই সামঞ্জস্য করা দরকার: দ্বিতীয় সূচকটি যদি ক্রমের দ্বিতীয়ার্ধে পড়ে (প্রথম সূচকের পরে), তবে ব্যবধানের জন্য অ্যাকাউন্টটি বর্ধিত করতে হবে। আমরা এটিকে একটি লুপ হিসাবে বাস্তবায়ন করতে পারি, আমাদের অনন্য আইটেমের স্বেচ্ছাসেবী সংখ্যার নির্বাচন করার অনুমতি দিয়ে।
সংক্ষিপ্ত ক্রমের জন্য, এটি বেশ দ্রুত O(n^2/2)
অ্যালগরিদম:
void RandomUniqueSequence(std::vector<int> &rand_num,
const size_t n_select_num, const size_t n_item_num)
{
assert(n_select_num <= n_item_num);
rand_num.clear();
for(size_t i = 0; i < n_select_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
size_t n_where = i;
for(size_t j = 0; j < i; ++ j) {
if(n + j < rand_num[j]) {
n_where = j;
break;
}
}
rand_num.insert(rand_num.begin() + n_where, 1, n + n_where);
}
}
কোথায় n_select_num
আপনার 5 এবং n_number_num
আপনার হয় MAX
। n_Rand(x)
র্যান্ডম পূর্ণসংখ্যার ফেরৎ[0, x]
(সহ)। সন্নিবেশ বিন্দুটি সন্ধান করতে বাইনারি অনুসন্ধান ব্যবহার করে প্রচুর আইটেম (যেমন 5 নয় তবে 500) বাছাই করা হলে এটি কিছুটা দ্রুত করা যায় made এটি করার জন্য, আমাদের প্রয়োজনীয়তাগুলি পূরণ করছি তা নিশ্চিত করতে হবে।
আমরা তুলনা n + j < rand_num[j]
যা একই হিসাবে বাইনারি অনুসন্ধান করব
n < rand_num[j] - j
। আমাদের এটি দেখাতে হবে যে rand_num[j] - j
এখনও বাছাই করা ক্রমের জন্য বাছাই করা ক্রম rand_num[j]
। এটি ভাগ্যক্রমে সহজেই প্রদর্শিত হয়, কারণ মূল দুটি উপাদানের মধ্যে সর্বনিম্ন দূরত্ব rand_num
এক (উত্পন্ন সংখ্যাগুলি অনন্য, তাই সর্বদা কমপক্ষে 1 এর পার্থক্য থাকে)। একই সাথে, যদি আমরা সূচকগুলি বিয়োগ করিj
সমস্ত উপাদান থেকে
rand_num[j]
পার্থক্য হুবহু 1. বাইনারি অনুসন্ধান তাই ব্যবহার করা যেতে পারে, ফলনকারী O(n log(n))
অ্যালগরিদম:
struct TNeedle {
int n;
TNeedle(int _n)
:n(_n)
{}
};
class CCompareWithOffset {
protected:
std::vector<int>::iterator m_p_begin_it;
public:
CCompareWithOffset(std::vector<int>::iterator p_begin_it)
:m_p_begin_it(p_begin_it)
{}
bool operator ()(const int &r_value, TNeedle n) const
{
size_t n_index = &r_value - &*m_p_begin_it;
return r_value < n.n + n_index;
}
bool operator ()(TNeedle n, const int &r_value) const
{
size_t n_index = &r_value - &*m_p_begin_it;
return n.n + n_index < r_value;
}
};
এবং পরিশেষে:
void RandomUniqueSequence(std::vector<int> &rand_num,
const size_t n_select_num, const size_t n_item_num)
{
assert(n_select_num <= n_item_num);
rand_num.clear();
for(size_t i = 0; i < n_select_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
std::vector<int>::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(),
TNeedle(n), CCompareWithOffset(rand_num.begin()));
rand_num.insert(p_where_it, 1, n + p_where_it - rand_num.begin());
}
}
আমি তিনটি মানদণ্ডে এটি পরীক্ষা করেছি। প্রথমত, 7 টি আইটেমের মধ্যে 3 নম্বর বেছে নেওয়া হয়েছিল এবং চয়ন করা আইটেমগুলির একটি হিস্টোগ্রাম 10,000 টিরও বেশি রান সংগ্রহ করেছিল:
4265 4229 4351 4267 4267 4364 4257
এটি দেখায় যে items টি আইটেমের প্রত্যেকটি প্রায় একই সংখ্যক বার বেছে নেওয়া হয়েছিল এবং অ্যালগরিদমের কারণে কোনও আপাত পক্ষপাত নেই। সমস্ত ক্রমগুলিও নির্ভুলতার জন্য অনুসন্ধান করা হয়েছিল (সামগ্রীর স্বতন্ত্রতা)।
দ্বিতীয় মানদণ্ডে 5000 আইটেমের মধ্যে 7 নম্বর বেছে নেওয়া জড়িত। অ্যালগরিদমের বেশ কয়েকটি সংস্করণের সময় 10,000,000 রানেরও বেশি জমে ছিল। ফলাফল হিসাবে কোড মন্তব্য করা হয়b1
। অ্যালগরিদমের সাধারণ সংস্করণটি কিছুটা দ্রুত।
তৃতীয় মানদণ্ডে 5000 আইটেমের মধ্যে 700 নম্বর বেছে নেওয়া জড়িত। অ্যালগরিদমের বেশ কয়েকটি সংস্করণের সময় আবার জমা হয়েছিল, এবার 10,000 রানেরও বেশি। ফলাফল হিসাবে কোড মন্তব্য করা হয়b2
। অ্যালগরিদমের বাইনারি অনুসন্ধান সংস্করণটি এখন সাধারণের চেয়ে দ্বিগুণের বেশি গতিযুক্ত।
আমার মেশিনে সিসিএ 75 আইটেমের চেয়ে বেশি চয়ন করার জন্য দ্বিতীয় পদ্ধতিটি দ্রুত হতে শুরু করে (নোট করুন যে কোনও একটিতে অ্যালগোরিদমের জটিলতা আইটেমের সংখ্যার উপর নির্ভর করে না, MAX
)।
এটি উল্লেখযোগ্য যে উপরের অ্যালগরিদমগুলি ক্রমবর্ধমান ক্রমে র্যান্ডম সংখ্যা উত্পন্ন করে। তবে আরও একটি অ্যারে যুক্ত করা সহজ হবে যাতে সংখ্যাটি তারা যেভাবে তৈরি করেছিল সেভাবে সংরক্ষণ করা হবে এবং পরিবর্তে এটিকে ফিরিয়ে দেওয়া হবে (নগদ অতিরিক্ত ব্যয়ে)O(n)
) । আউটপুট বদল করা প্রয়োজন হয় না: এটি অনেক ধীর হবে।
নোট করুন যে উত্সগুলি সি ++ এ রয়েছে, আমার মেশিনে জাভা নেই তবে ধারণাটি পরিষ্কার হওয়া উচিত।
সম্পাদনা :
চিত্তবিনোদন করার জন্য, আমি সেই পদ্ধতিকেও প্রয়োগ করেছি যা সমস্ত সূচকগুলির সাথে একটি তালিকা তৈরি করে
0 .. MAX
, এলোমেলোভাবে তাদের চয়ন করে এবং স্বতন্ত্রতার গ্যারান্টি হিসাবে তালিকা থেকে তাদের সরিয়ে দেয়। যেহেতু আমি বেশ উচ্চ MAX
(5000) বেছে নিয়েছি , অভিনয়টি বিপর্যয়কর:
std::vector<int> all_numbers(n_item_num);
std::iota(all_numbers.begin(), all_numbers.end(), 0);
for(size_t i = 0; i < n_number_num; ++ i) {
assert(all_numbers.size() == n_item_num - i);
int n = n_Rand(n_item_num - i - 1);
rand_num.push_back(all_numbers[n]);
all_numbers.erase(all_numbers.begin() + n);
}
আমি একটি set
(একটি সি ++ সংগ্রহ) সহ এপ্রোচটিও বাস্তবায়ন করেছি , যা b2
বাইনারি অনুসন্ধানের সাথে পদ্ধতির চেয়ে প্রায় 50% ধীর গতিতে, বেঞ্চমার্কে আসলে দ্বিতীয় স্থানে আসে । এটি বোধগম্য, যেমন set
ব্যবহারগুলি বাইনারি গাছ ব্যবহার করে, যেখানে সন্নিবেশ ব্যয় বাইনারি অনুসন্ধানের মতো। পার্থক্যটি হ'ল সদৃশ আইটেমগুলি পাওয়ার সুযোগ, যা অগ্রগতি কমিয়ে দেয়।
std::set<int> numbers;
while(numbers.size() < n_number_num)
numbers.insert(n_Rand(n_item_num - 1));
rand_num.resize(numbers.size());
std::copy(numbers.begin(), numbers.end(), rand_num.begin());
সম্পূর্ণ উত্স কোড এখানে ।