मेरा मानना है कि O (n) में लंबाई n के एक अनियोजित सरणी में kth सबसे बड़े तत्व को खोजने का एक तरीका है। या शायद यह "उम्मीद" ओ (एन) या कुछ है। हम ऐसा कैसे कर सकते हैं?
मेरा मानना है कि O (n) में लंबाई n के एक अनियोजित सरणी में kth सबसे बड़े तत्व को खोजने का एक तरीका है। या शायद यह "उम्मीद" ओ (एन) या कुछ है। हम ऐसा कैसे कर सकते हैं?
जवाबों:
इसे k-th ऑर्डर स्टेटिस्टिक ढूंढना कहा जाता है । वहाँ एक बहुत ही सरल यादृच्छिक एल्गोरिथ्म (कहा जाता है quickselect ) लेने O(n)
औसत समय, O(n^2)
सबसे खराब स्थिति समय है, और एक बहुत जटिल गैर-यादृच्छिकृत एल्गोरिथ्म (बुलाया introselect ) ले जा रहा O(n)
सबसे खराब स्थिति समय। विकिपीडिया पर कुछ जानकारी है , लेकिन यह बहुत अच्छी नहीं है।
आपको जो कुछ भी चाहिए वह इन पावरपॉइंट स्लाइड्स में है । बस O(n)
सबसे खराब स्थिति एल्गोरिथ्म (इंट्रोसेलेक्ट) के बुनियादी एल्गोरिथ्म को निकालने के लिए :
Select(A,n,i):
Divide input into ⌈n/5⌉ groups of size 5.
/* Partition on median-of-medians */
medians = array of each group’s median.
pivot = Select(medians, ⌈n/5⌉, ⌈n/10⌉)
Left Array L and Right Array G = partition(A, pivot)
/* Find ith element in L, pivot, or G */
k = |L| + 1
If i = k, return pivot
If i < k, return Select(L, k-1, i)
If i > k, return Select(G, n-k, i-k)
यह कॉर्मेन एट अल द्वारा इंट्रोडक्शन टू अल्गोरिद्म बुक में भी बहुत अच्छी तरह से विस्तृत है।
यदि आप एक सच्चा O(n)
एल्गोरिथ्म चाहते हैं , जैसा कि विरोध किया जाता है O(kn)
या ऐसा कुछ होता है, तो आपको एस्केलेक्ट का उपयोग करना चाहिए (यह मूल रूप से क्विकॉर्ट है जहां आप उस विभाजन को बाहर फेंक देते हैं जिसमें आप रुचि नहीं रखते हैं)। मेरे प्रोफेसर के पास एक महान राइटअप है, रनटाइम विश्लेषण के साथ: ( संदर्भ )
QuickSelect एल्गोरिथ्म जल्दी से तत्वों के एक अनसुलझे सरणी के k-th सबसे छोटे तत्व को ढूंढता है n
। यह एक रैंडमाइज्ड एल्गोरिथ्म है , इसलिए हम सबसे खराब स्थिति की उम्मीद कर रहे हैं।
यहाँ एल्गोरिथ्म है।
QuickSelect(A, k)
let r be chosen uniformly at random in the range 1 to length(A)
let pivot = A[r]
let A1, A2 be new arrays
# split into a pile A1 of small elements and A2 of big elements
for i = 1 to n
if A[i] < pivot then
append A[i] to A1
else if A[i] > pivot then
append A[i] to A2
else
# do nothing
end for
if k <= length(A1):
# it's in the pile of small elements
return QuickSelect(A1, k)
else if k > length(A) - length(A2)
# it's in the pile of big elements
return QuickSelect(A2, k - (length(A) - length(A2))
else
# it's equal to the pivot
return pivot
इस एल्गोरिथम का रनिंग टाइम क्या है? अगर विरोधी हमारे लिए सिक्के उछालता है, तो हम पा सकते हैं कि धुरी हमेशा सबसे बड़ा तत्व होती है और k
हमेशा 1 होती है, एक समय का प्रवाह देती है
T(n) = Theta(n) + T(n-1) = Theta(n2)
लेकिन अगर विकल्प वास्तव में यादृच्छिक हैं, तो अपेक्षित समय चल रहा है
T(n) <= Theta(n) + (1/n) ∑i=1 to nT(max(i, n-i-1))
जहां हम पूरी तरह से उचित धारणा नहीं बना रहे हैं कि पुनरावृत्ति हमेशा बड़े A1
या में भूमि A2
।
T(n) <= an
कुछ के लिए लगता है कि चलो a
। तब हमें मिलता है
T(n)
<= cn + (1/n) ∑i=1 to nT(max(i-1, n-i))
= cn + (1/n) ∑i=1 to floor(n/2) T(n-i) + (1/n) ∑i=floor(n/2)+1 to n T(i)
<= cn + 2 (1/n) ∑i=floor(n/2) to n T(i)
<= cn + 2 (1/n) ∑i=floor(n/2) to n ai
और अब किसी तरह हमें बाईं ओर अवशोषित करने के लिए प्लस चिन्ह के दाईं ओर भयावह राशि प्राप्त करनी होगी cn
। यदि हम इसे बस के रूप में बाध्य करते हैं , तो हम मोटे तौर पर प्राप्त करते हैं । लेकिन यह बहुत बड़ा है - अतिरिक्त में निचोड़ने के लिए कोई जगह नहीं है । तो आइए अंकगणितीय श्रृंखला सूत्र का उपयोग करते हुए योग का विस्तार करें:2(1/n) ∑i=n/2 to n an
2(1/n)(n/2)an = an
cn
∑i=floor(n/2) to n i
= ∑i=1 to n i - ∑i=1 to floor(n/2) i
= n(n+1)/2 - floor(n/2)(floor(n/2)+1)/2
<= n2/2 - (n/4)2/2
= (15/32)n2
जहाँ हम floor(n/2)
बहुत साफ (और छोटे) के साथ बदसूरत कारकों को बदलने के लिए "पर्याप्त रूप से बड़े" होने का लाभ उठाते हैं n/4
। अब हम जारी रख सकते हैं
cn + 2 (1/n) ∑i=floor(n/2) to n ai,
<= cn + (2a/n) (15/32) n2
= n (c + (15/16)a)
<= an
प्रदान किया गया a > 16c
।
यह देता है T(n) = O(n)
। यह स्पष्ट रूप से है Omega(n)
, इसलिए हम प्राप्त करते हैं T(n) = Theta(n)
।
k > length(A) - length(A2)
?
A
में A1
और A2
उसके आस - पास विभाजित थे , हम जानते हैं कि length(A) == length(A1)+length(A2)+1
। तो, k > length(A)-length(A2)
के बराबर है k > length(A1)+1
, जो सच है जब k
कहीं में है A2
।
उस पर एक त्वरित Google ('kth सबसे बड़ा तत्व सरणी') इसे लौटाया गया: http://discuss.joelonsoftware.com/default.asp?interview.11.509587.17
"Make one pass through tracking the three largest values so far."
(यह विशेष रूप से 3 डी के लिए सबसे बड़ा था)
और यह उत्तर:
Build a heap/priority queue. O(n)
Pop top element. O(log n)
Pop top element. O(log n)
Pop top element. O(log n)
Total = O(n) + 3 O(log n) = O(n)
आपको क्विकसर्ट पसंद है। यादृच्छिक पर एक तत्व उठाओ और सब कुछ या तो उच्च या निम्न को हिलाओ। इस बिंदु पर आपको पता चल जाएगा कि आपने वास्तव में कौन सा तत्व उठाया है, और यदि यह आपके द्वारा किया गया kth तत्व है, अन्यथा आप बिन (उच्च या निम्न) के साथ दोहराते हैं, कि kth तत्व गिर जाएगा। सांख्यिकीय रूप से, समय। यह पता लगाने के लिए kth तत्व n, O (n) के साथ बढ़ता है।
एल्गोरिदम विश्लेषण के लिए एक प्रोग्रामर का साथी एक संस्करण देता है जो है ओ (एन) , हालांकि लेखक का कहना है कि स्थिर कारक इतना अधिक है, आप शायद भोले सॉर्ट-ऑफ-द-लिस्ट-मेथड विधि को पसंद करेंगे।
मैंने आपके प्रश्न के पत्र का उत्तर दिया :)
C ++ मानक लाइब्रेरी में लगभग फ़ंक्शन कॉल होता है nth_element
, हालांकि यह आपके डेटा को संशोधित करता है। इसने लीनियर रन-टाइम, O (N) की अपेक्षा की है, और यह एक आंशिक प्रकार भी करता है।
const int N = ...;
double a[N];
// ...
const int m = ...; // m < N
nth_element (a, a + m, a + N);
// a[m] contains the mth element in a
यद्यपि O (n) जटिलता के बारे में बहुत निश्चित नहीं है, लेकिन यह O (n) और nLog (n) के बीच होना सुनिश्चित होगा। इसके अलावा nLog (n) की तुलना में O (n) के करीब होना सुनिश्चित करें। कार्य जावा में लिखा गया है
public int quickSelect(ArrayList<Integer>list, int nthSmallest){
//Choose random number in range of 0 to array length
Random random = new Random();
//This will give random number which is not greater than length - 1
int pivotIndex = random.nextInt(list.size() - 1);
int pivot = list.get(pivotIndex);
ArrayList<Integer> smallerNumberList = new ArrayList<Integer>();
ArrayList<Integer> greaterNumberList = new ArrayList<Integer>();
//Split list into two.
//Value smaller than pivot should go to smallerNumberList
//Value greater than pivot should go to greaterNumberList
//Do nothing for value which is equal to pivot
for(int i=0; i<list.size(); i++){
if(list.get(i)<pivot){
smallerNumberList.add(list.get(i));
}
else if(list.get(i)>pivot){
greaterNumberList.add(list.get(i));
}
else{
//Do nothing
}
}
//If smallerNumberList size is greater than nthSmallest value, nthSmallest number must be in this list
if(nthSmallest < smallerNumberList.size()){
return quickSelect(smallerNumberList, nthSmallest);
}
//If nthSmallest is greater than [ list.size() - greaterNumberList.size() ], nthSmallest number must be in this list
//The step is bit tricky. If confusing, please see the above loop once again for clarification.
else if(nthSmallest > (list.size() - greaterNumberList.size())){
//nthSmallest will have to be changed here. [ list.size() - greaterNumberList.size() ] elements are already in
//smallerNumberList
nthSmallest = nthSmallest - (list.size() - greaterNumberList.size());
return quickSelect(greaterNumberList,nthSmallest);
}
else{
return pivot;
}
}
मैंने गतिशील प्रोग्रामिंग, विशेष रूप से टूर्नामेंट पद्धति का उपयोग करते हुए एन अनसोल्ड तत्वों में न्यूनतम न्यूनतम खोज करना लागू किया। निष्पादन समय हे (n + klog (n)) है। उपयोग किए गए तंत्र को चयन एल्गोरिथ्म के बारे में विकिपीडिया पृष्ठ पर विधियों में से एक के रूप में सूचीबद्ध किया गया है (जैसा कि ऊपर पोस्टिंग में से एक में इंगित किया गया है)। आप एल्गोरिथ्म के बारे में पढ़ सकते हैं और मेरे ब्लॉग पेज फाइंडिंग केथ मिनिमम पर कोड (जावा) भी पा सकते हैं । इसके अलावा तर्क सूची का आंशिक क्रम कर सकते हैं - O (klog (n)) समय में पहले K मिनट (या अधिकतम) लौटें।
हालाँकि कोड परिणाम kth न्यूनतम प्रदान करता है, समान तर्क को O (klog (n)) में kth अधिकतम ज्ञात करने के लिए नियोजित किया जा सकता है, टूर्नामेंट ट्री बनाने के लिए किए गए पूर्व-कार्य की अनदेखी कर रहा है।
आप इसे O (n + kn) = O (n) (निरंतर k के लिए) समय के लिए और O (k) अंतरिक्ष के लिए, आपके द्वारा देखे गए सबसे बड़े तत्वों का ट्रैक रख कर कर सकते हैं।
सरणी में प्रत्येक तत्व के लिए आप k की सूची को स्कैन कर सकते हैं और सबसे छोटे तत्व को नए के साथ बदल सकते हैं यदि यह बड़ा है।
वारेन की प्राथमिकता हीप समाधान है, हालांकि नट है।
O(n log k)
... फिर भी बड़े कश्मीर के मामले में ओ (पतवार) को पतित करता है। मुझे लगता है कि यह कश्मीर के छोटे मूल्यों के लिए ठीक काम करेगा, लेकिन संभवत: यहां उल्लिखित कुछ अन्य एल्गोरिदम की तुलना में तेज है [???]
पायथन में सेक्सी तेज
def quickselect(arr, k):
'''
k = 1 returns first element in ascending order.
can be easily modified to return first element in descending order
'''
r = random.randrange(0, len(arr))
a1 = [i for i in arr if i < arr[r]] '''partition'''
a2 = [i for i in arr if i > arr[r]]
if k <= len(a1):
return quickselect(a1, k)
elif k > len(arr)-len(a2):
return quickselect(a2, k - (len(arr) - len(a2)))
else:
return arr[r]
a1 = [i for i in arr if i > arr[r]]
और a2 = [i for i in arr if i < arr[r]]
kth को सबसे बड़ा तत्व लौटा देगा ।
numpy.sort
लिए numpy array
या इसके साथ) के लिए तेज़ है sorted
।
रैखिक समय में सरणी के माध्यिका का पता लगाएं, फिर विभाजन प्रक्रिया का उपयोग करें जैसे कि एस्कॉर्ट में दो भागों में सरणी को विभाजित करने के लिए, माध्यिका की तुलना में मंझले कम (<) के बाईं ओर मान और दाएं से अधिक (>) मध्यिका की तुलना में सही है। , यह भी लाइन के समय में किया जा सकता है, अब, सरणी के उस हिस्से पर जाएं जहां kth तत्व निहित है, अब पुनरावृत्ति बन जाती है: T (n) = T (n / 2) + cn जो मुझे O (n) ओवरल प्रदान करता है।
नीचे पूरी तरह से एक व्यापक व्याख्या के साथ पूर्ण कार्यान्वयन की कड़ी दी गई है कि एक एल्गोरिथ्म एल्गोरिथ्म में Kth तत्व को खोजने के लिए एल्गोरिथ्म कैसे काम करता है। मूल विचार QuickSort की तरह सरणी को विभाजित करना है। लेकिन चरम मामलों से बचने के लिए (उदाहरण के लिए जब हर चरण में सबसे छोटे तत्व को धुरी के रूप में चुना जाता है, ताकि एल्गोरिथ्म ओ (एन ^ 2) रनिंग टाइम में पतित हो जाए), विशेष पिवट चयन को लागू किया जाता है, जिसे माध्यियन-ऑफ-मेडियन एल्गोरिथम कहा जाता है। संपूर्ण समाधान ओ (एन) समय में सबसे खराब और औसत मामले में चलता है।
यहाँ पूरा लेख के लिए लिंक है (यह KTH खोजने के बारे में है सबसे छोटी तत्व है, लेकिन सिद्धांत KTH को खोजने के लिए एक ही है सबसे बड़ा ):
इस पेपर के अनुसार , n आइटम की सूची में Kth सबसे बड़ी वस्तु ढूँढना निम्न एल्गोरिथम O(n)
सबसे खराब स्थिति में समय लेगा ।
विश्लेषण: जैसा मूल पेपर में सुझाया गया है:
हम सूची को दो हिस्सों में विभाजित करने के लिए माध्यिका का उपयोग करते हैं (पहली छमाही, यदि
k <= n/2
, और दूसरी छमाही अन्यथा)। यह एल्गोरिथ्म अगले स्तर पर ,cn
कुछ स्तर के लिए पुनरावृत्ति के पहले स्तर पर समय लेता है (चूंकि हम आकार n / 2 की सूची में पुन: गणना करते हैं), तीसरे स्तर पर, और इसी तरह। कुल समय लिया है ।c
cn/2
cn/4
cn + cn/2 + cn/4 + .... = 2cn = o(n)
विभाजन का आकार 5 क्यों लिया गया और 3 नहीं?
जैसा कि मूल पेपर में बताया गया है :
सूची को 5 से विभाजित करने पर 70 - 30 का सबसे खराब स्थिति विभाजन होता है। मध्य-मध्य-मध्य की तुलना में मध्ययुगीन आधे से अधिक होता है, इसलिए n / 5 खंडों में से आधे में कम से कम 3 तत्व होते हैं और यह एक
3n/10
विभाजन देता है , जो इसका मतलब है कि अन्य विभाजन सबसे खराब स्थिति में 7n / 10 है। इससे पताT(n) = T(n/5)+T(7n/10)+O(n). Since n/5+7n/10 < 1
चलता है कि सबसे खराब स्थिति चल रही हैO(n)
।
अब मैंने उपरोक्त एल्गोरिथ्म को लागू करने की कोशिश की है:
public static int findKthLargestUsingMedian(Integer[] array, int k) {
// Step 1: Divide the list into n/5 lists of 5 element each.
int noOfRequiredLists = (int) Math.ceil(array.length / 5.0);
// Step 2: Find pivotal element aka median of medians.
int medianOfMedian = findMedianOfMedians(array, noOfRequiredLists);
//Now we need two lists split using medianOfMedian as pivot. All elements in list listOne will be grater than medianOfMedian and listTwo will have elements lesser than medianOfMedian.
List<Integer> listWithGreaterNumbers = new ArrayList<>(); // elements greater than medianOfMedian
List<Integer> listWithSmallerNumbers = new ArrayList<>(); // elements less than medianOfMedian
for (Integer element : array) {
if (element < medianOfMedian) {
listWithSmallerNumbers.add(element);
} else if (element > medianOfMedian) {
listWithGreaterNumbers.add(element);
}
}
// Next step.
if (k <= listWithGreaterNumbers.size()) return findKthLargestUsingMedian((Integer[]) listWithGreaterNumbers.toArray(new Integer[listWithGreaterNumbers.size()]), k);
else if ((k - 1) == listWithGreaterNumbers.size()) return medianOfMedian;
else if (k > (listWithGreaterNumbers.size() + 1)) return findKthLargestUsingMedian((Integer[]) listWithSmallerNumbers.toArray(new Integer[listWithSmallerNumbers.size()]), k-listWithGreaterNumbers.size()-1);
return -1;
}
public static int findMedianOfMedians(Integer[] mainList, int noOfRequiredLists) {
int[] medians = new int[noOfRequiredLists];
for (int count = 0; count < noOfRequiredLists; count++) {
int startOfPartialArray = 5 * count;
int endOfPartialArray = startOfPartialArray + 5;
Integer[] partialArray = Arrays.copyOfRange((Integer[]) mainList, startOfPartialArray, endOfPartialArray);
// Step 2: Find median of each of these sublists.
int medianIndex = partialArray.length/2;
medians[count] = partialArray[medianIndex];
}
// Step 3: Find median of the medians.
return medians[medians.length / 2];
}
बस पूरा होने के लिए, एक और एल्गोरिथ्म प्राथमिकता कतार का उपयोग करता है और समय लेता है O(nlogn)
।
public static int findKthLargestUsingPriorityQueue(Integer[] nums, int k) {
int p = 0;
int numElements = nums.length;
// create priority queue where all the elements of nums will be stored
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
// place all the elements of the array to this priority queue
for (int n : nums) {
pq.add(n);
}
// extract the kth largest element
while (numElements - k + 1 > 0) {
p = pq.poll();
k++;
}
return p;
}
इन दोनों एल्गोरिदम का परीक्षण निम्नानुसार किया जा सकता है:
public static void main(String[] args) throws IOException {
Integer[] numbers = new Integer[]{2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};
System.out.println(findKthLargestUsingMedian(numbers, 8));
System.out.println(findKthLargestUsingPriorityQueue(numbers, 8));
}
अपेक्षित आउटपुट है:
18
18
कैसे इस थोड़े दृष्टिकोण के बारे में
A buffer of length k
और a बनाए रखें tmp_max
, tmp_max प्राप्त करना O (k) है और n ऐसा कुछ बार किया जाता हैO(kn)
क्या यह सही है या मुझे कुछ याद आ रहा है?
यद्यपि यह औसत आंकड़े के औसत मामले और सबसे खराब स्थिति को हरा नहीं पाता है, लेकिन इसे समझने और कार्यान्वित करने में बहुत आसान है।
सूची के माध्यम से पुनरावृति। यदि वर्तमान मूल्य संग्रहीत सबसे बड़े मूल्य से बड़ा है, तो इसे सबसे बड़े मूल्य के रूप में संग्रहीत करें और सूची से 1-4 नीचे और 5 बूँदें टकराएं। यदि नहीं, तो इसे नंबर 2 से तुलना करें और वही काम करें। दोहराएँ, सभी 5 संग्रहीत मूल्यों के खिलाफ इसकी जाँच। इसे O (n) में करना चाहिए
मैं एक उत्तर देना चाहूंगा
यदि हम पहले k तत्वों को लेते हैं और उन्हें k मानों की लिंक की गई सूची में सॉर्ट करते हैं
अब हर दूसरे मूल्य के लिए भी सबसे खराब स्थिति के लिए अगर हम बाकी nk मूल्यों के लिए प्रविष्टि सॉर्ट करते हैं तो भी सबसे खराब स्थिति में तुलनाओं की संख्या k * (nk) होगी और prev k मानों को सॉर्ट करने के लिए इसे k * किया जाएगा (k- k- 1) तो यह निकलता है (n-k) जो ओ (n) है
चियर्स
मंझला का - की - ध्येय एल्गोरिदम खोजने के लिए k-th सबसे बड़ा पूर्णांक एन से बाहर पाया जा सकता है: http://cs.indstate.edu/~spitla/pretation.pdf
सी ++ में कार्यान्वयन नीचे है:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int findMedian(vector<int> vec){
// Find median of a vector
int median;
size_t size = vec.size();
median = vec[(size/2)];
return median;
}
int findMedianOfMedians(vector<vector<int> > values){
vector<int> medians;
for (int i = 0; i < values.size(); i++) {
int m = findMedian(values[i]);
medians.push_back(m);
}
return findMedian(medians);
}
void selectionByMedianOfMedians(const vector<int> values, int k){
// Divide the list into n/5 lists of 5 elements each
vector<vector<int> > vec2D;
int count = 0;
while (count != values.size()) {
int countRow = 0;
vector<int> row;
while ((countRow < 5) && (count < values.size())) {
row.push_back(values[count]);
count++;
countRow++;
}
vec2D.push_back(row);
}
cout<<endl<<endl<<"Printing 2D vector : "<<endl;
for (int i = 0; i < vec2D.size(); i++) {
for (int j = 0; j < vec2D[i].size(); j++) {
cout<<vec2D[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
// Calculating a new pivot for making splits
int m = findMedianOfMedians(vec2D);
cout<<"Median of medians is : "<<m<<endl;
// Partition the list into unique elements larger than 'm' (call this sublist L1) and
// those smaller them 'm' (call this sublist L2)
vector<int> L1, L2;
for (int i = 0; i < vec2D.size(); i++) {
for (int j = 0; j < vec2D[i].size(); j++) {
if (vec2D[i][j] > m) {
L1.push_back(vec2D[i][j]);
}else if (vec2D[i][j] < m){
L2.push_back(vec2D[i][j]);
}
}
}
// Checking the splits as per the new pivot 'm'
cout<<endl<<"Printing L1 : "<<endl;
for (int i = 0; i < L1.size(); i++) {
cout<<L1[i]<<" ";
}
cout<<endl<<endl<<"Printing L2 : "<<endl;
for (int i = 0; i < L2.size(); i++) {
cout<<L2[i]<<" ";
}
// Recursive calls
if ((k - 1) == L1.size()) {
cout<<endl<<endl<<"Answer :"<<m;
}else if (k <= L1.size()) {
return selectionByMedianOfMedians(L1, k);
}else if (k > (L1.size() + 1)){
return selectionByMedianOfMedians(L2, k-((int)L1.size())-1);
}
}
int main()
{
int values[] = {2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};
vector<int> vec(values, values + 25);
cout<<"The given array is : "<<endl;
for (int i = 0; i < vec.size(); i++) {
cout<<vec[i]<<" ";
}
selectionByMedianOfMedians(vec, 8);
return 0;
}
वहाँ भी है Wirth के चयन एल्गोरिथ्म , QuickSelect से आसान कार्यान्वयन है जो। Wirth का चयन एल्गोरिथ्म QuickSelect की तुलना में धीमा है, लेकिन कुछ सुधारों के साथ यह तेज हो गया है।
विस्तृत रूप में। व्लादिमीर ज़ैब्रॉड्स्की के मॉडिफाई ऑप्टिमाइज़ेशन और माध्यिका -3 पिवट चयन का उपयोग करना और एल्गोरिथ्म के विभाजन वाले भाग के अंतिम चरणों पर कुछ ध्यान देना, मैं निम्नलिखित एल्गोरिथ्म के साथ आया हूं (काल्पनिक रूप से "लेफिनेट" नाम दिया गया है):
#define F_SWAP(a,b) { float temp=(a);(a)=(b);(b)=temp; }
# Note: The code needs more than 2 elements to work
float lefselect(float a[], const int n, const int k) {
int l=0, m = n-1, i=l, j=m;
float x;
while (l<m) {
if( a[k] < a[i] ) F_SWAP(a[i],a[k]);
if( a[j] < a[i] ) F_SWAP(a[i],a[j]);
if( a[j] < a[k] ) F_SWAP(a[k],a[j]);
x=a[k];
while (j>k & i<k) {
do i++; while (a[i]<x);
do j--; while (a[j]>x);
F_SWAP(a[i],a[j]);
}
i++; j--;
if (j<k) {
while (a[i]<x) i++;
l=i; j=m;
}
if (k<i) {
while (x<a[j]) j--;
m=j; i=l;
}
}
return a[k];
}
मेरे द्वारा यहां किए गए बेंचमार्क में , LeSSlect QuickSelect की तुलना में 20-30% तेज है।
हास्केल समाधान:
kthElem index list = sort list !! index
withShape ~[] [] = []
withShape ~(x:xs) (y:ys) = x : withShape xs ys
sort [] = []
sort (x:xs) = (sort ls `withShape` ls) ++ [x] ++ (sort rs `withShape` rs)
where
ls = filter (< x)
rs = filter (>= x)
यह वास्तव में कंप्यूटिंग के बिना एक विभाजन के आकार की खोज के लिए withShape विधि का उपयोग करके मंझला समाधान के माध्यिका को लागू करता है।
यहां रेंडमाइज्ड क्विकसेलेक्ट का C ++ कार्यान्वयन है। इस विचार को बेतरतीब ढंग से एक धुरी तत्व चुनना है। यादृच्छिक विभाजन को लागू करने के लिए, हम एल और आर के बीच सूचकांक उत्पन्न करने के लिए एक यादृच्छिक फ़ंक्शन, रैंड () का उपयोग करते हैं, अंतिम तत्व के साथ यादृच्छिक रूप से उत्पन्न सूचकांक पर तत्व को स्वैप करते हैं, और अंत में मानक विभाजन प्रक्रिया को कॉल करते हैं जो अंतिम तत्व का उपयोग धुरी के रूप में करता है।
#include<iostream>
#include<climits>
#include<cstdlib>
using namespace std;
int randomPartition(int arr[], int l, int r);
// This function returns k'th smallest element in arr[l..r] using
// QuickSort based method. ASSUMPTION: ALL ELEMENTS IN ARR[] ARE DISTINCT
int kthSmallest(int arr[], int l, int r, int k)
{
// If k is smaller than number of elements in array
if (k > 0 && k <= r - l + 1)
{
// Partition the array around a random element and
// get position of pivot element in sorted array
int pos = randomPartition(arr, l, r);
// If position is same as k
if (pos-l == k-1)
return arr[pos];
if (pos-l > k-1) // If position is more, recur for left subarray
return kthSmallest(arr, l, pos-1, k);
// Else recur for right subarray
return kthSmallest(arr, pos+1, r, k-pos+l-1);
}
// If k is more than number of elements in array
return INT_MAX;
}
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
// Standard partition process of QuickSort(). It considers the last
// element as pivot and moves all smaller element to left of it and
// greater elements to right. This function is used by randomPartition()
int partition(int arr[], int l, int r)
{
int x = arr[r], i = l;
for (int j = l; j <= r - 1; j++)
{
if (arr[j] <= x) //arr[i] is bigger than arr[j] so swap them
{
swap(&arr[i], &arr[j]);
i++;
}
}
swap(&arr[i], &arr[r]); // swap the pivot
return i;
}
// Picks a random pivot element between l and r and partitions
// arr[l..r] around the randomly picked element using partition()
int randomPartition(int arr[], int l, int r)
{
int n = r-l+1;
int pivot = rand() % n;
swap(&arr[l + pivot], &arr[r]);
return partition(arr, l, r);
}
// Driver program to test above methods
int main()
{
int arr[] = {12, 3, 5, 7, 4, 19, 26};
int n = sizeof(arr)/sizeof(arr[0]), k = 3;
cout << "K'th smallest element is " << kthSmallest(arr, 0, n-1, k);
return 0;
}
उपरोक्त समाधान का सबसे खराब मामला समय जटिलता अभी भी O (n2) है। सबसे खराब स्थिति में, यादृच्छिक फ़ंक्शन हमेशा एक कोने तत्व को चुन सकता है। रैंडमाइज्ड क्विकसेलेट के ऊपर अपेक्षित समय जटिलता n (n) है
मतदान काल () k काल।
public static int getKthLargestElements(int[] arr)
{
PriorityQueue<Integer> pq = new PriorityQueue<>((x , y) -> (y-x));
//insert all the elements into heap
for(int ele : arr)
pq.offer(ele);
// call poll() k times
int i=0;
while(i<k)
{
int result = pq.poll();
}
return result;
}
यह जावास्क्रिप्ट में एक कार्यान्वयन है।
यदि आप उस बाधा को मुक्त करते हैं जिसे आप सरणी को संशोधित नहीं कर सकते हैं, तो आप "वर्तमान विभाजन" (क्लासिक एस्कॉर्ट शैली में - http://www.nczonline.net/blog/2012/) की पहचान करने के लिए दो अनुक्रमिकाओं का उपयोग करके अतिरिक्त मेमोरी के उपयोग को रोक सकते हैं । 11/27 / कंप्यूटर-विज्ञान-इन-जावास्क्रिप्ट-क्विकसॉर्ट / )।
function kthMax(a, k){
var size = a.length;
var pivot = a[ parseInt(Math.random()*size) ]; //Another choice could have been (size / 2)
//Create an array with all element lower than the pivot and an array with all element higher than the pivot
var i, lowerArray = [], upperArray = [];
for (i = 0; i < size; i++){
var current = a[i];
if (current < pivot) {
lowerArray.push(current);
} else if (current > pivot) {
upperArray.push(current);
}
}
//Which one should I continue with?
if(k <= upperArray.length) {
//Upper
return kthMax(upperArray, k);
} else {
var newK = k - (size - lowerArray.length);
if (newK > 0) {
///Lower
return kthMax(lowerArray, newK);
} else {
//None ... it's the current pivot!
return pivot;
}
}
}
यदि आप परीक्षण करना चाहते हैं कि यह कैसा प्रदर्शन करता है, तो आप इस भिन्नता का उपयोग कर सकते हैं:
function kthMax (a, k, logging) {
var comparisonCount = 0; //Number of comparison that the algorithm uses
var memoryCount = 0; //Number of integers in memory that the algorithm uses
var _log = logging;
if(k < 0 || k >= a.length) {
if (_log) console.log ("k is out of range");
return false;
}
function _kthmax(a, k){
var size = a.length;
var pivot = a[parseInt(Math.random()*size)];
if(_log) console.log("Inputs:", a, "size="+size, "k="+k, "pivot="+pivot);
// This should never happen. Just a nice check in this exercise
// if you are playing with the code to avoid never ending recursion
if(typeof pivot === "undefined") {
if (_log) console.log ("Ops...");
return false;
}
var i, lowerArray = [], upperArray = [];
for (i = 0; i < size; i++){
var current = a[i];
if (current < pivot) {
comparisonCount += 1;
memoryCount++;
lowerArray.push(current);
} else if (current > pivot) {
comparisonCount += 2;
memoryCount++;
upperArray.push(current);
}
}
if(_log) console.log("Pivoting:",lowerArray, "*"+pivot+"*", upperArray);
if(k <= upperArray.length) {
comparisonCount += 1;
return _kthmax(upperArray, k);
} else if (k > size - lowerArray.length) {
comparisonCount += 2;
return _kthmax(lowerArray, k - (size - lowerArray.length));
} else {
comparisonCount += 2;
return pivot;
}
/*
* BTW, this is the logic for kthMin if we want to implement that... ;-)
*
if(k <= lowerArray.length) {
return kthMin(lowerArray, k);
} else if (k > size - upperArray.length) {
return kthMin(upperArray, k - (size - upperArray.length));
} else
return pivot;
*/
}
var result = _kthmax(a, k);
return {result: result, iterations: comparisonCount, memory: memoryCount};
}
बाकी कोड कुछ खेल के मैदान बनाने के लिए है:
function getRandomArray (n){
var ar = [];
for (var i = 0, l = n; i < l; i++) {
ar.push(Math.round(Math.random() * l))
}
return ar;
}
//Create a random array of 50 numbers
var ar = getRandomArray (50);
अब, आप कुछ समय परीक्षण चलाएं। Math.random () के कारण यह हर बार अलग परिणाम देगा:
kthMax(ar, 2, true);
kthMax(ar, 2);
kthMax(ar, 2);
kthMax(ar, 2);
kthMax(ar, 2);
kthMax(ar, 2);
kthMax(ar, 34, true);
kthMax(ar, 34);
kthMax(ar, 34);
kthMax(ar, 34);
kthMax(ar, 34);
kthMax(ar, 34);
यदि आप इसका कुछ बार परीक्षण करते हैं तो आप अनुभव कर सकते हैं कि पुनरावृत्तियों की संख्या औसतन, O (n) ~ = स्थिर * n है और k का मान एल्गोरिथ्म को प्रभावित नहीं करता है।
मैं इस एल्गोरिथ्म के साथ आया था और लगता है कि O (n):
मान लें कि k = 3 और हम सरणी में तीसरा सबसे बड़ा आइटम ढूंढना चाहते हैं। मैं तीन चर बनाऊंगा और इन तीन चर के न्यूनतम के साथ सरणी के प्रत्येक आइटम की तुलना करूंगा। यदि सरणी आइटम हमारे न्यूनतम से अधिक है, तो हम आइटम मूल्य के साथ मिन चर को बदल देंगे। हम सरणी के अंत तक एक ही बात जारी रखते हैं। हमारे तीन वेरिएबल्स में न्यूनतम सरणी में तीसरा सबसे बड़ा आइटम है।
define variables a=0, b=0, c=0
iterate through the array items
find minimum a,b,c
if item > min then replace the min variable with item value
continue until end of array
the minimum of a,b,c is our answer
और, Kth सबसे बड़ी आइटम खोजने के लिए हमें K वैरिएबल की आवश्यकता है।
उदाहरण: (k = 3)
[1,2,4,1,7,3,9,5,6,2,9,8]
Final variable values:
a=7 (answer)
b=8
c=9
क्या कोई इसकी समीक्षा कर सकता है और मुझे बता सकता है कि मुझे क्या याद आ रहा है?
यहाँ सुझाए गए एल्गोरिथ्म एल्दव का कार्यान्वयन है (मैंने यादृच्छिक धुरी के साथ कार्यान्वयन को भी यहां रखा है):
public class Median {
public static void main(String[] s) {
int[] test = {4,18,20,3,7,13,5,8,2,1,15,17,25,30,16};
System.out.println(selectK(test,8));
/*
int n = 100000000;
int[] test = new int[n];
for(int i=0; i<test.length; i++)
test[i] = (int)(Math.random()*test.length);
long start = System.currentTimeMillis();
random_selectK(test, test.length/2);
long end = System.currentTimeMillis();
System.out.println(end - start);
*/
}
public static int random_selectK(int[] a, int k) {
if(a.length <= 1)
return a[0];
int r = (int)(Math.random() * a.length);
int p = a[r];
int small = 0, equal = 0, big = 0;
for(int i=0; i<a.length; i++) {
if(a[i] < p) small++;
else if(a[i] == p) equal++;
else if(a[i] > p) big++;
}
if(k <= small) {
int[] temp = new int[small];
for(int i=0, j=0; i<a.length; i++)
if(a[i] < p)
temp[j++] = a[i];
return random_selectK(temp, k);
}
else if (k <= small+equal)
return p;
else {
int[] temp = new int[big];
for(int i=0, j=0; i<a.length; i++)
if(a[i] > p)
temp[j++] = a[i];
return random_selectK(temp,k-small-equal);
}
}
public static int selectK(int[] a, int k) {
if(a.length <= 5) {
Arrays.sort(a);
return a[k-1];
}
int p = median_of_medians(a);
int small = 0, equal = 0, big = 0;
for(int i=0; i<a.length; i++) {
if(a[i] < p) small++;
else if(a[i] == p) equal++;
else if(a[i] > p) big++;
}
if(k <= small) {
int[] temp = new int[small];
for(int i=0, j=0; i<a.length; i++)
if(a[i] < p)
temp[j++] = a[i];
return selectK(temp, k);
}
else if (k <= small+equal)
return p;
else {
int[] temp = new int[big];
for(int i=0, j=0; i<a.length; i++)
if(a[i] > p)
temp[j++] = a[i];
return selectK(temp,k-small-equal);
}
}
private static int median_of_medians(int[] a) {
int[] b = new int[a.length/5];
int[] temp = new int[5];
for(int i=0; i<b.length; i++) {
for(int j=0; j<5; j++)
temp[j] = a[5*i + j];
Arrays.sort(temp);
b[i] = temp[2];
}
return selectK(b, b.length/2 + 1);
}
}
यह क्विकॉर्ट रणनीति के समान है, जहां हम एक मनमाना धुरी चुनते हैं, और छोटे तत्वों को इसके बाईं ओर लाते हैं, और दाएं से बड़ा
public static int kthElInUnsortedList(List<int> list, int k)
{
if (list.Count == 1)
return list[0];
List<int> left = new List<int>();
List<int> right = new List<int>();
int pivotIndex = list.Count / 2;
int pivot = list[pivotIndex]; //arbitrary
for (int i = 0; i < list.Count && i != pivotIndex; i++)
{
int currentEl = list[i];
if (currentEl < pivot)
left.Add(currentEl);
else
right.Add(currentEl);
}
if (k == left.Count + 1)
return pivot;
if (left.Count < k)
return kthElInUnsortedList(right, k - left.Count - 1);
else
return kthElInUnsortedList(left, k);
}
इस लिंक के अंत में जाएं: ………।
आप O (n) समय और निरंतर स्थान में kth सबसे छोटा तत्व पा सकते हैं। यदि हम मानते हैं कि सरणी केवल पूर्णांकों के लिए है।
दृष्टिकोण एरे मूल्यों की सीमा पर एक द्विआधारी खोज करने के लिए है। यदि हमारे पास पूर्णांक श्रेणी में एक min_value और एक अधिकतम_वायु है, तो हम उस सीमा पर एक बाइनरी खोज कर सकते हैं। हम एक तुलनित्र फ़ंक्शन लिख सकते हैं जो हमें बताएगा कि कोई मूल्य kth-smallest है या kth-smallest से छोटा है या kth-smallest से बड़ा है। बाइनरी खोज तब तक करें जब तक आप kth-smallest संख्या तक नहीं पहुंच जाते
यहाँ उस के लिए कोड है
वर्ग समाधान:
def _iskthsmallest(self, A, val, k):
less_count, equal_count = 0, 0
for i in range(len(A)):
if A[i] == val: equal_count += 1
if A[i] < val: less_count += 1
if less_count >= k: return 1
if less_count + equal_count < k: return -1
return 0
def kthsmallest_binary(self, A, min_val, max_val, k):
if min_val == max_val:
return min_val
mid = (min_val + max_val)/2
iskthsmallest = self._iskthsmallest(A, mid, k)
if iskthsmallest == 0: return mid
if iskthsmallest > 0: return self.kthsmallest_binary(A, min_val, mid, k)
return self.kthsmallest_binary(A, mid+1, max_val, k)
# @param A : tuple of integers
# @param B : integer
# @return an integer
def kthsmallest(self, A, k):
if not A: return 0
if k > len(A): return 0
min_val, max_val = min(A), max(A)
return self.kthsmallest_binary(A, min_val, max_val, k)
इसमें एक एल्गोरिथ्म भी है, जो कि क्विकचार्ज एल्गोरिथ्म को बेहतर बनाता है। इसे फ्लॉयड-रिवेट्स (FR) एल्गोरिथम कहा जाता है ।
मूल लेख: https://doi.org/10.1145/360680.360694
डाउनलोड करने योग्य संस्करण: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.309.7108&rep=rep1&type=pdf
विकिपीडिया लेख https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm
मैंने C ++ में क्विकसेलेक्ट और FR एल्गोरिदम को लागू करने की कोशिश की। इसके अलावा, मैंने उनकी तुलना मानक सी ++ लाइब्रेरी कार्यान्वयन एसटीडी :: nth_element (जो मूल रूप से क्विकलेक्ट और हाइसेप्लेक्ट का हाइब्रिड हाइब्रिड है) से की है। परिणाम तेज था और nth_element औसतन तुलनात्मक रूप से चला, लेकिन FR एल्गोरिथ्म लगभग चला। उनकी तुलना में दोगुना है।
नमूना कोड जो मैंने FR एल्गोरिथ्म के लिए उपयोग किया था:
template <typename T>
T FRselect(std::vector<T>& data, const size_t& n)
{
if (n == 0)
return *(std::min_element(data.begin(), data.end()));
else if (n == data.size() - 1)
return *(std::max_element(data.begin(), data.end()));
else
return _FRselect(data, 0, data.size() - 1, n);
}
template <typename T>
T _FRselect(std::vector<T>& data, const size_t& left, const size_t& right, const size_t& n)
{
size_t leftIdx = left;
size_t rightIdx = right;
while (rightIdx > leftIdx)
{
if (rightIdx - leftIdx > 600)
{
size_t range = rightIdx - leftIdx + 1;
long long i = n - (long long)leftIdx + 1;
long long z = log(range);
long long s = 0.5 * exp(2 * z / 3);
long long sd = 0.5 * sqrt(z * s * (range - s) / range) * sgn(i - (long long)range / 2);
size_t newLeft = fmax(leftIdx, n - i * s / range + sd);
size_t newRight = fmin(rightIdx, n + (range - i) * s / range + sd);
_FRselect(data, newLeft, newRight, n);
}
T t = data[n];
size_t i = leftIdx;
size_t j = rightIdx;
// arrange pivot and right index
std::swap(data[leftIdx], data[n]);
if (data[rightIdx] > t)
std::swap(data[rightIdx], data[leftIdx]);
while (i < j)
{
std::swap(data[i], data[j]);
++i; --j;
while (data[i] < t) ++i;
while (data[j] > t) --j;
}
if (data[leftIdx] == t)
std::swap(data[leftIdx], data[j]);
else
{
++j;
std::swap(data[j], data[rightIdx]);
}
// adjust left and right towards the boundaries of the subset
// containing the (k - left + 1)th smallest element
if (j <= n)
leftIdx = j + 1;
if (n <= j)
rightIdx = j - 1;
}
return data[leftIdx];
}
template <typename T>
int sgn(T val) {
return (T(0) < val) - (val < T(0));
}
मैं यह क्या करूंगा:
initialize empty doubly linked list l
for each element e in array
if e larger than head(l)
make e the new head of l
if size(l) > k
remove last element from l
the last element of l should now be the kth largest element
आप लिंक किए गए सूची में बस पहले और अंतिम तत्व को इंगित कर सकते हैं। वे केवल तभी बदलते हैं जब सूची के अपडेट किए जाते हैं।
अपडेट करें:
initialize empty sorted tree l
for each element e in array
if e between head(l) and tail(l)
insert e into l // O(log k)
if size(l) > k
remove last element from l
the last element of l should now be the kth largest element
पहले हम B को अनरिस्टर्ड ऐरे से बना सकते हैं जो O (n) समय लेता है और BST से हम O (n) में kth सबसे छोटा तत्व खोज सकते हैं जो O (n) के एक ऑर्डर के लिए सभी मायने रखता है।