किसी अनुक्रम के सभी सूचकांकों को बनाना आम तौर पर एक बुरा विचार है, क्योंकि इसमें बहुत समय लग सकता है, खासकर अगर संख्याओं का अनुपात चुना जाना 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) का चयन करते समय इसे थोड़ा तेज किया जा सकता है। ऐसा करने के लिए, हमें यह सुनिश्चित करने की आवश्यकता है कि हम आवश्यकताओं को पूरा करते हैं।
हम तुलना के साथ द्विआधारी खोज करेंगे 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
इससे पता चलता है कि 7 वस्तुओं में से प्रत्येक को लगभग एक ही समय में चुना गया था, और एल्गोरिथ्म के कारण कोई स्पष्ट पूर्वाग्रह नहीं है। सभी अनुक्रमों की शुद्धता (सामग्री की विशिष्टता) के लिए भी जाँच की गई थी।
दूसरे बेंचमार्क में 5000 वस्तुओं में से 7 नंबर चुनना शामिल था। एल्गोरिथ्म के कई संस्करणों का समय 10,000,000 रनों से अधिक जमा हुआ था। परिणामों को कोड में टिप्पणियों के रूप में दर्शाया जाता है b1
। एल्गोरिथ्म का सरल संस्करण थोड़ा तेज है।
तीसरे बेंचमार्क में 5000 वस्तुओं में से 700 नंबर को चुनना शामिल था। एल्गोरिथ्म के कई संस्करणों का समय फिर से जमा हुआ, इस बार 10,000 से अधिक रन। परिणामों को कोड में टिप्पणियों के रूप में दर्शाया जाता है b2
। एल्गोरिथ्म का द्विआधारी खोज संस्करण अब साधारण की तुलना में दो गुना अधिक तेज है।
मेरी मशीन पर cca 75 से अधिक आइटम चुनने के लिए दूसरी विधि तेजी से शुरू हो रही है (ध्यान दें कि दोनों एल्गोरिथ्म की जटिलता वस्तुओं की संख्या पर निर्भर नहीं करती है MAX
)।
यह उल्लेखनीय है कि उपरोक्त एल्गोरिदम आरोही क्रम में यादृच्छिक संख्या उत्पन्न करते हैं। लेकिन एक और सरणी जोड़ना सरल होगा, जिसमें संख्याओं को उस क्रम में सहेजा जाएगा, जिसमें वे उत्पन्न हुए थे, और बदले में (नगण्य अतिरिक्त लागत पर O(n)
)। आउटपुट को फेरबदल करना आवश्यक नहीं है: यह बहुत धीमा होगा।
ध्यान दें कि स्रोत C ++ में हैं, मेरे पास मेरी मशीन पर जावा नहीं है, लेकिन अवधारणा स्पष्ट होनी चाहिए।
संपादित करें :
मनोरंजन के लिए, मैंने दृष्टिकोण भी लागू किया है जो सभी सूचकांकों के साथ एक सूची बनाता है
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());
पूर्ण स्रोत कोड यहाँ है ।