O (n) n uzunluğunda sıralanmamış bir dizi kth büyük elemanı bulmak için bir yol olduğuna inanıyorum. Ya da belki "beklenen" O (n) falan. Bunu nasıl yapabiliriz?
O (n) n uzunluğunda sıralanmamış bir dizi kth büyük elemanı bulmak için bir yol olduğuna inanıyorum. Ya da belki "beklenen" O (n) falan. Bunu nasıl yapabiliriz?
Yanıtlar:
Buna k . Dereceden istatistiği bulma denir . Ortalama zaman, en kötü durum süresi alan çok basit bir randomize algoritma ( quickselect olarak adlandırılır ) ve en kötü durum zamanını alan oldukça karmaşık rastgele olmayan bir algoritma ( introselect olarak adlandırılır ) vardır . Wikipedia hakkında bazı bilgiler var , ama çok iyi değil.O(n)
O(n^2)
O(n)
İhtiyacınız olan her şey bu powerpoint slaytlarında . Sadece en O(n)
kötü durum algoritmasının (introselect) temel algoritmasını çıkarmak için :
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)
Cormen ve ark.'nın Algoritma Giriş kitabında da çok güzel detaylandırılmıştır.
Eğer gerçek istiyorsanız O(n)
aksine, algoritma O(kn)
veya buna benzer bir şey, o zaman (sen ilgilenen olmadığınızı bölümü dışarı atmak nerede temelde quicksort var) QuickSelect kullanmalıdır. Prof profilimin çalışma zamanı analizi ile harika bir yazımı var: ( referans )
QuickSelect algoritması, ayrıştırılmamış bir öğe dizisinin k. En küçük öğesini hızlı bir şekilde bulur n
. Bu bir olan RandomizedAlgorithm yüzden kötü durum hesaplamak, beklenen çalışma süresi.
İşte algoritma.
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
Bu algoritmanın çalışma süresi nedir? Düşman bizim için paraları çevirir, biz Pivot her zaman büyük unsurdur ve fark edebilirsiniz k
çalışan bir zaman vererek, daima 1'dir
T(n) = Theta(n) + T(n-1) = Theta(n2)
Ancak seçenekler gerçekten rastgele ise, beklenen çalışma süresi
T(n) <= Theta(n) + (1/n) ∑i=1 to nT(max(i, n-i-1))
Biz tekrarlama hep daha büyük topraklarda değil tamamen makul varsayım yapıyoruz nerede A1
ya A2
.
T(n) <= an
Bazıları için tahmin edelim a
. Sonra anlıyoruz
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
ve şimdi bir şekilde artı işaretinin sağındaki korkunç toplamı cn
soldaki emmeyi almak zorundayız . Eğer sadece onu bağlarsak , kabaca alırız . Ama bu çok büyük - fazladan sıkmak için yer yok . Şimdi aritmetik seri formülünü kullanarak toplamı genişletelim: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
burada n'nin çirkin floor(n/2)
faktörleri daha temiz (ve daha küçük) ile değiştirmek için "yeterince büyük" olmasından yararlanıyoruz n/4
. Şimdi devam edebiliriz
cn + 2 (1/n) ∑i=floor(n/2) to n ai,
<= cn + (2a/n) (15/32) n2
= n (c + (15/16)a)
<= an
sağladı a > 16c
.
Bu verir T(n) = O(n)
. Açıkça Omega(n)
, yani anlıyoruz T(n) = Theta(n)
.
k > length(A) - length(A2)
?
A
içine A1
ve A2
etrafına bölündüğümüz göz önüne alındığında , bunu biliyoruz length(A) == length(A1)+length(A2)+1
. Yani, k > length(A)-length(A2)
eşdeğerdir k > length(A1)+1
, ki k
bu bir yerde olduğunda doğrudur A2
.
Bu konuda hızlı bir Google ('k. En büyük öğe dizisi') şunu döndürdü: http://discuss.joelonsoftware.com/default.asp?interview.11.509587.17
"Make one pass through tracking the three largest values so far."
(özellikle en büyük 3d içindi)
ve bu cevap:
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)
Quicksort'u seviyorsun. Bir öğeyi rastgele seçin ve her şeyi daha yüksek veya daha düşük şekilde itin. Bu noktada gerçekte hangi elemanı seçtiğinizi bileceksiniz ve eğer yaptığınız kth elemanı ise, aksi takdirde kth elemanının düşeceği çöp kutusu (daha yüksek veya daha düşük) ile tekrar edersiniz. kth elementinin n, O (n) ile büyüdüğünü bulmak gerekir.
Algoritma Analizi A Programcı Companion bir versiyonunu verir ise sabit faktör kadar yüksek olduğunu yazar devletleri rağmen, muhtemelen naif sort--listesi-o-select yöntemi tercih ediyorum, O (n).
Sorunuzun mektubunu cevapladım :)
Verilerinizi değiştirmesine rağmen, C ++ standart kitaplığı neredeyse tam olarak bu işlev çağrısına sahiptir nth_element
. Doğrusal çalışma zamanı, O (N) bekleniyor ve kısmi bir sıralama yapıyor.
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) karmaşıklığından emin olmasa da, O (n) ve nLog (n) arasında olacağından emin olacaktır. Ayrıca O (n) 'ye nLog (n)' den daha yakın olduğunuzdan emin olun. İşlev Java ile yazılmıştır
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;
}
}
Dinamik programlamayı, özellikle de turnuva yöntemini kullanarak ayrıştırılmamış öğelerde kth minimimum bulmayı uyguladım. Yürütme süresi O (n + klog (n)) şeklindedir. Kullanılan mekanizma Wikipedia sayfasında Seçim Algoritması ile ilgili yöntemlerden biri olarak listelenmiştir (yukarıdaki gönderilerden birinde belirtildiği gibi). Sen algoritması hakkında okumak ve aynı zamanda benim blog sayfasında kod (java) bulabilirsiniz Bulma Kth Asgari . Buna ek olarak, mantık listenin kısmi sıralamasını yapabilir - O (klog (n)) zamanında ilk K min (veya maks.) Değerini döndürün.
Verilen kod minimum kth sonucuna rağmen, turnuva ağacı oluşturmak için yapılan ön çalışmaları göz ardı ederek O (klog (n)) 'de kth maksimumu bulmak için benzer mantık kullanılabilir.
Gördüğünüz en büyük k öğelerini takip ederek zaman için O (n + kn) = O (n) (sabit k için) ve boşluk için O (k) olarak yapabilirsiniz.
Dizideki her öğe için en büyük k listesini tarayabilir ve daha büyükse en küçük öğeyi yenisiyle değiştirebilirsiniz.
Warren'ın öncelikli yığın çözümü daha temiz.
O(n log k)
... büyük k durumunda hala O'ya (nlogn) dejenere olur. Ancak k küçük değerleri için iyi çalışacağını düşünürdüm ... ... muhtemelen burada belirtilen diğer algoritmalardan daha hızlı [???]
Seksi hızlı seçim içinde piton
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]]
ve a2 = [i for i in arr if i < arr[r]]
, kth dönecektir büyük elemanı.
numpy.sort
için numpy array
veya sorted
listeler için) sıralama daha hızlıdır .
Dizinin medyanını lineer zamanda bulun, sonra diziyi iki parçaya bölmek için tam olarak hızlı sıralamada olduğu gibi bölüm prosedürünü kullanın, değerleri medyandan daha küçük (<) soldan ve (>) medyandan daha büyük sağdan , bu da dizilim zamanında yapılabilir, şimdi, dizinin kth öğesinin bulunduğu kısmına gidin, Şimdi nüks olur: T (n) = T (n / 2) + cn, bana O (n) fazlalık verir.
Aşağıda, sıralanmamış bir algoritmada Kth öğesini bulma algoritmasının nasıl çalıştığına dair kapsamlı bir açıklama ile tam uygulamaya bağlantı verilmiştir. Temel fikir diziyi QuickSort'ta olduğu gibi bölümlendirmektir. Ancak aşırı durumlardan kaçınmak için (örneğin her adımda pivot olarak en küçük eleman seçildiğinde, algoritma O (n ^ 2) çalışma süresinde dejenere olur), medyan-of-median algoritması adı verilen özel pivot seçimi uygulanır. Bütün çözelti en kötü ve ortalama durumda O (n) zamanında çalışır.
İşte tam makaleye bağlantı (Kth'ın en küçük öğesini bulmakla ilgilidir , ancak prensip Kth'ı en büyük bulmak için aynıdır ):
Bu makaleye göre n öğe listesindeki en büyük Kth öğesini bulmak, aşağıdaki algoritmanın O(n)
en kötü durumda zaman alacaktır .
Analiz: Orijinal makalede önerildiği gibi:
Listeyi iki yarıya bölmek için medyanı kullanıyoruz (ilk yarı, eğer
k <= n/2
ve ikinci yarı aksi takdirde). Bu algoritma, zaman alırcn
bazı sabiti yineleme ilk seviyedec
,cn/2
(biz boyutu, n / 2 arasında bir listede Recurse beri), bir sonraki düzeydecn/4
üçüncü seviyede, vb. Toplam sürecn + cn/2 + cn/4 + .... = 2cn = o(n)
.
Bölüm boyutu neden 3 değil 5 alınır?
Orijinal makalede belirtildiği gibi :
Listeyi 5'e bölmek, en kötü durumun 70 - 30 arasında bölünmesini sağlar. Medyanların medyandan daha büyük olan yarısının en az yarısı, dolayısıyla n / 5 bloklarının en az yarısının en az 3 elementi vardır ve bu
3n/10
, diğer bölümün en kötü durumda 7n / 10 olduğu anlamına gelir. BuT(n) = T(n/5)+T(7n/10)+O(n). Since n/5+7n/10 < 1
, en kötü çalışma süresini verirO(n)
.
Şimdi yukarıdaki algoritmayı şu şekilde uygulamaya çalıştım:
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];
}
Sadece tamamlama uğruna, başka bir algoritma Öncelik Kuyruğunu kullanır ve zaman alır 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;
}
Bu algoritmaların her ikisi de şu şekilde test edilebilir:
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));
}
Beklendiği gibi çıktı:
18
18
Bu tür yaklaşıma ne dersiniz?
A buffer of length k
ve tmp_max
a'yı koruyun , tmp_max elde etmek O (k) 'dir ve n kez yapılır, böyleceO(kn)
Doğru mu yoksa bir şey mi kaçırıyorum?
Her ne kadar ortalama hızlı seçim durumu ve en kötü medyan istatistik yöntemini yenmezse de, anlaşılması ve uygulanması oldukça kolaydır.
listeyi tekrarlayın. geçerli değer saklanan en büyük değerden büyükse, değeri en büyük değer olarak saklayın ve 1-4 aşağı ve 5 damla listeden çarpın. Değilse, 2 numarayla karşılaştırın ve aynı şeyi yapın. Kaydedilen 5 değerin tümünü kontrol ederek tekrarlayın. bunu O (n) de yapmalı
bir cevap önermek istiyorum
ilk k öğelerini alıp bunları bağlantılı k değerleri listesine ayırırsak
şimdi en kötü durumda bile diğer tüm değerler için en kötü durumda bile dinlenme nk değerleri için ekleme sıralaması yaparsak karşılaştırma sayısı k * (nk) olur ve sıralanacak önceki k değerleri için k * (k- 1) yani o (n) olan (nk-k)
şerefe
N'den k'ıncı en büyük tamsayıyı bulmak için medyan - medyan algoritmasının açıklaması burada bulunabilir: http://cs.indstate.edu/~spitla/presentation.pdf
C ++ ile uygulama aşağıdadır:
#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;
}
Orada da Wirth'in seçim algoritması QuickSelect göre daha kolaydır uygulama vardır. Wirth'in seçim algoritması QuickSelect'ten daha yavaştır, ancak bazı iyileştirmelerle daha hızlı hale gelir.
Daha ayrıntılı olarak. Vladimir Zabrodsky'nin MODIFIND optimizasyonunu ve medyan-3 pivot seçimini kullanarak ve algoritmanın bölümleme bölümünün son adımlarına biraz dikkat ederek, aşağıdaki algoritmayı (hayal edilebilir şekilde "LefSelect" olarak adlandırdım) buldum:
#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];
}
Burada yaptığım kriterlerde LefSelect, QuickSelect'ten% 20-30 daha hızlı.
Haskell Çözümü:
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)
Bu, bir bölümün boyutunu gerçekten hesaplamadan keşfetmek için withShape yöntemini kullanarak medyan çözümlerin medyanını uygular.
İşte Randomized QuickSelect'in bir C ++ uygulaması. Fikir rastgele bir pivot elemanı seçmek. Rastgele bölüm uygulamak için, l ve r arasında dizin oluşturmak için rastgele bir işlev, rand () kullanırız, rastgele oluşturulan dizindeki öğeyi son öğeyle değiştiririz ve son olarak pivot olarak son öğeyi kullanan standart bölüm işlemini çağırırız.
#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;
}
Yukarıdaki çözeltinin en kötü zaman karmaşıklığı hala O (n2) 'dir. En kötü durumda, randomize fonksiyon her zaman bir köşe elemanı seçebilir. Yukarıdaki randomize QuickSelect'in beklenen zaman karmaşıklığı Θ (n)
Anket () k kez çağırın.
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;
}
Bu Javascript bir uygulamadır.
Diziyi değiştiremediğiniz kısıtlamayı kaldırırsanız, "geçerli bölümü" (klasik hızlı sıralama tarzında - http://www.nczonline.net/blog/2012/) tanımlamak için iki dizin kullanarak fazladan bellek kullanımını engelleyebilirsiniz. 11/27 / javascript-quicksort bilgisayar bilimi / ).
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;
}
}
}
Nasıl çalıştığını test etmek istiyorsanız, bu varyasyonu kullanabilirsiniz:
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};
}
Kodun geri kalanı sadece oyun alanı oluşturmak için:
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);
Şimdi, birkaç kez test yapın. Math.random () nedeniyle her seferinde farklı sonuçlar üretecektir:
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);
Birkaç kez test ederseniz, yinelemenin sayısının ortalama olarak O (n) ~ = sabit * n olduğunu ve k değerinin algoritmayı etkilemediğini ampirik olarak görebilirsiniz.
Ben bu algoritma ile geldi ve O (n) gibi görünüyor:
Diyelim ki k = 3 ve dizideki en büyük 3. öğeyi bulmak istiyoruz. Üç değişken oluşturur ve dizinin her bir öğesini bu üç değişkenin minimum değeriyle karşılaştırırdım. Dizi öğesi minimumumuzdan büyükse, min değişkenini öğe değeriyle değiştiririz. Dizinin sonuna kadar aynı şeyi sürdürüyoruz. Üç değişkenimizin minimum değeri dizideki 3. en büyük öğedir.
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'ın en büyük maddesini bulmak için K değişkenlerine ihtiyacımız var.
Örnek: (k = 3)
[1,2,4,1,7,3,9,5,6,2,9,8]
Final variable values:
a=7 (answer)
b=8
c=9
Birisi lütfen bunu gözden geçirip eksik olduğumu bana bildirebilir mi?
İşte eladv önerilen algoritmanın uygulanması (Ben de burada rastgele pivot ile uygulama koymak):
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);
}
}
keyfi bir pivot seçtiğimiz ve daha küçük elemanları sola, daha büyük olanı sağa getirdiğimiz quickSort stratejisine benzer.
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);
}
Bu bağlantının sonuna gidin: ...........
En küçük elementi O (n) zaman ve sabit boşlukta bulabilirsiniz. Dizinin yalnızca tamsayılar için olduğunu düşünürsek.
Yaklaşım, Array değerleri aralığında ikili bir arama yapmaktır. Tamsayı aralığında hem min_value hem de max_value varsa, bu aralıkta ikili arama yapabiliriz. Herhangi bir değerin kth-small veya kth-smallest'den daha küçük veya kth-smallest'den daha büyük olup olmadığını bize söyleyen bir karşılaştırıcı işlevi yazabiliriz. En küçük sayıya ulaşana kadar ikili aramayı yapın
İşte bunun kodu
sınıf Çözümü:
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)
Hızlı seçim algoritmasından daha iyi performans gösteren bir algoritma da vardır. Adı Floyd-Rivets (FR) algoritması .
Orijinal makale: https://doi.org/10.1145/360680.360694
İndirilebilir sürüm: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.309.7108&rep=rep1&type=pdf
Wikipedia makalesi https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm
C ++ 'da quickselect ve FR algoritması uygulamaya çalıştım. Ayrıca onları standart C ++ kütüphane uygulamaları ile karşılaştırdım std :: nth_element (temel olarak hızlı seçim ve heapselect'in introselect hibriti). Sonuç hızlı seçildi ve nth_element ortalama olarak karşılaştırılabilir, ancak FR algoritması yakl. iki kat daha hızlı.
FR algoritması için kullandığım örnek kod:
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));
}
Ne yapacağım şudur:
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
İşaretçileri bağlı listedeki ilk ve son öğeye depolayabilirsiniz. Yalnızca listede güncellemeler yapıldığında değişir.
Güncelleme:
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
İlk önce O (n) zaman alan sıralanmamış diziden bir BST oluşturabiliriz ve BST'den O (log (n)) 'deki kth en küçük elementi bulabiliriz.