Hızlı Sıralama: Pivotu seçme


109

Quicksort'u uygularken, yapmanız gereken şeylerden biri bir pivot seçmektir. Ancak aşağıdaki gibi sözde kodlara baktığımda, pivotu nasıl seçmem gerektiği net değil. Listenin ilk öğesi? Başka bir şey?

 function quicksort(array)
     var list less, greater
     if length(array) ≤ 1  
         return array  
     select and remove a pivot value pivot from array
     for each x in array
         if x ≤ pivot then append x to less
         else append x to greater
     return concatenate(quicksort(less), pivot, quicksort(greater))

Biri, bir pivot seçme kavramını ve farklı senaryoların farklı stratejiler gerektirip gerektirmediğini anlamama yardımcı olabilir mi?


Yanıtlar:


87

Rastgele bir pivot seçmek, en kötü durum O (n 2 ) performansıyla karşılaşma olasılığınızı en aza indirir (her zaman ilk veya sonuncuyu seçmek, neredeyse sıralanmış veya neredeyse tersine sıralanmış veriler için en kötü durum performansına neden olur). Orta öğenin seçilmesi de çoğu durumda kabul edilebilir.

Ayrıca, bunu kendiniz uyguluyorsanız, algoritmanın yerinde çalışan versiyonları vardır (yani, iki yeni liste oluşturmadan ve sonra bunları birleştirmeden).


10
Kendi kendinize bir arama yapmanın çabaya değmeyebileceği fikrini ikinci kez kullanırdım. Ayrıca, rastgele sayı üreteçleri bazen biraz yavaş olduğundan, rastgele sayıları nasıl seçeceğinize dikkat edin.
PeterAllenWebb

@Jonathan Leffler'in cevabı daha iyi
Nathan

60

Gereksinimlerinize bağlıdır. Rastgele bir pivot seçmek, O (N ^ 2) performansı oluşturan bir veri kümesi oluşturmayı zorlaştırır. 'Üçün ortası' (birinci, son, orta) da sorunlardan kaçınmanın bir yoludur. Yine de karşılaştırmaların göreceli performansına dikkat edin; Karşılaştırmalarınız maliyetliyse, Mo3 rastgele seçmekten (tek bir pivot değeri) daha fazla karşılaştırma yapar. Veritabanı kayıtlarının karşılaştırılması maliyetli olabilir.


Güncelleme: Yorumları yanıtlama.

mdkess iddia etti:

'Ortanca 3' ilk son orta DEĞİLDİR. Üç rastgele dizin seçin ve bunun orta değerini alın. Bütün mesele, pivot seçiminizin deterministik olmadığından emin olmaktır - eğer öyleyse, en kötü durum verileri oldukça kolay bir şekilde oluşturulabilir.

Ben yanıtladım:

  • Hoare'nin Üçün Ortanca Bölmeli Bulma Algoritmasının Analizi (1997), P Kirschenhofer, H Prodinger, C Martínez, iddianızı desteklemektedir ('üçün ortası' üç rastgele maddedir).

  • The Computer Journal, Cilt 27, Sayı 3, 1984'te yayınlanan, Hannu Erkiö'nin 'Üç Hızlı Sıranın Ortası için En Kötü Durum Permütasyonu' hakkında portal.acm.org'da açıklanan bir makale var . [Güncelleme 2012-02- 26: Makalenin metnini aldım . Bölüm 2 'Algoritma' başlar: ' A [L: R] ' nin ilk, orta ve son elemanlarının medyanını kullanarak, çoğu pratik durumda oldukça eşit büyüklükteki parçalara verimli bölümlemeler elde edilebilir. Bu nedenle, ilk-orta-son Mo3 yaklaşımını tartışıyor.]

  • İlginç bir başka kısa makale MD McIlroy gereğidir "Quicksort A Killer düşman" Yazılım-Uygulama ve Deneyim, Vol yayımlanan. 29 (0), 1-4 (0 1999). Hemen hemen her Quicksort'un ikinci dereceden davranmasını nasıl sağlayacağınızı açıklar.

  • AT&T Bell Labs Tech Journal, Ekim 1984 "Bir Çalışma Sıralaması Rutininin Oluşturulmasında Teori ve Uygulama", "Hoare, rastgele seçilen birkaç satırın medyanı etrafında bölümlemeyi önerdi. Sedgewick [...], ilk [. ..] son ​​[...] ve orta ". Bu, 'üçün ortası' için her iki tekniğin de literatürde bilindiğini gösterir. (Güncelleme 2014/11/23: makale mevcut gibi görünen IEEE Xplore ya dan Wiley - Eğer üyelik ya da bir ücret ödemeye hazır kullanılamaz.)

  • JL Bentley ve MD McIlroy tarafından yazılan, Software Practice and Experience, Cilt 23 (11), Kasım 1993'te yayınlanan 'Bir Sıralama Fonksiyonu Tasarlamak', sorunların kapsamlı bir tartışmasına giriyor ve kısmen, veri kümesinin boyutu. Çeşitli yaklaşımlar için birçok ödünleşim tartışması var.

  • 'Üçün ortası' için bir Google araması, daha fazla izleme için oldukça iyi çalışıyor.

Bilgi için teşekkürler; Daha önce yalnızca deterministik 'üçün ortası' ile karşılaşmıştım.


4
Ortanca 3, ilk son orta DEĞİLDİR. Üç rastgele dizin seçin ve bunun orta değerini alın. Bütün mesele, pivot seçiminizin deterministik olmadığından emin olmaktır - eğer öyleyse, en kötü durum verileri oldukça kolay bir şekilde oluşturulabilir.
mindvirus

Quicksort ve heapsort'un iyi özelliklerini birleştiren abt introsort okuyordum. Üç medyanı kullanarak pivotu seçme yaklaşımı her zaman uygun olmayabilir.
Sumit Kumar Saha

4
Rastgele indeks seçmenin sorunu, rastgele sayı üreticilerinin oldukça pahalı olmasıdır. Ayırma işleminin büyük maliyetini artırmasa da, muhtemelen ilk, son ve orta öğeleri seçmiş olmanızdan daha yavaş hale getirecektir. (Gerçek dünyada, bahse girerim hiç kimse hızlı sıralamanızı yavaşlatmak için uydurma durumlar yapmıyordur.)
Kevin Chen

20

Heh, bu dersi şimdi öğrettim.

Birkaç seçenek var.
Basit: Aralığın ilk veya son öğesini seçin. (kısmen sıralanmış girdide kötü) Daha İyi: Aralığın ortasındaki öğeyi seçin. (kısmen sıralanmış girdide daha iyi)

Bununla birlikte, herhangi bir rastgele elemanın seçilmesi, n boyutundaki diziyi 1 ve n-1 boyutunda iki diziye kötü bir şekilde bölümleme riskini taşır. Bunu yeterince sık yaparsanız, hızlı sıralamanız O (n ^ 2) olma riskini taşır.

Gördüğüm bir gelişme medyan seçmektir (ilk, son, orta); En kötü durumda, yine de O (n ^ 2) 'ye gidebilir, ancak olasılıksal olarak, bu nadir bir durumdur.

Çoğu veri için ilkini veya sonunu seçmek yeterlidir. Ancak, en kötü senaryolarla sık sık karşılaştığınızı fark ederseniz (kısmen sıralı girdi), ilk seçenek merkezi değeri seçmek olacaktır (Bu, kısmen sıralanmış veriler için istatistiksel olarak iyi bir pivottur).

Hala sorun yaşıyorsanız, orta rotaya gidin.


1
Sınıfımızda bir diziden en küçük k elemanı sıralı sırayla alarak bir deney yaptık. Rastgele diziler oluşturduk, sonra bir min-heap ya da rastgele seçilmiş ve sabit pivot hızlı sıralama kullandık ve karşılaştırma sayısını saydık. Bu "rastgele" veriler üzerinde, ikinci çözüm ortalama olarak ilkinden daha kötü performans gösterdi. Rastgele bir eksene geçmek performans sorununu çözer. Dolayısıyla, sözde rastgele veriler için bile, sabit pivot, rastgele pivottan önemli ölçüde daha kötü performans gösterir.
Robert S. Barnes

N boyutundaki diziyi 1 ve n-1 boyutundaki iki diziye bölmek neden O (n ^ 2) olma riskini taşır?
Aaron Franke

N boyutunda bir Dizi varsayın. Boyutlara ayırın [1, N-1]. Bir sonraki adım, sağ yarıyı [1, N-2] 'ye bölmektir. ve bu şekilde, 1 boyutunda N bölümümüz olana kadar. Ancak, ikiye bölersek, karmaşıklığın Log (n) terimine yol açan her adımda 2 N / 2 bölümü yapıyor olurduk;
Chris Cudmore

11

Asla sabit bir pivot seçmeyin - bu, algoritmanızın sadece sorun arayan en kötü durumdaki O (n ^ 2) çalışma süresinden yararlanmak için saldırıya uğrayabilir. Quicksort'un en kötü çalışma zamanı, bölümleme bir dizi 1 öğe ve bir n-1 öğe dizisi ile sonuçlandığında ortaya çıkar. Bölümünüz olarak ilk öğeyi seçtiğinizi varsayalım. Birisi algoritmanıza azalan sırayla bir dizi beslerse, ilk pivotunuz en büyük olacaktır, bu nedenle dizideki diğer her şey onun soluna hareket edecektir. Sonra tekrar ettiğinizde, ilk öğe yine en büyük olacak, bu yüzden bir kez daha her şeyi soluna koyarsınız ve bu böyle devam eder.

Daha iyi bir teknik, rastgele üç öğeyi seçip ortayı seçtiğiniz 3'ün ortası yöntemidir. Seçtiğiniz elementin ilk veya son olmayacağını biliyorsunuz, aynı zamanda merkezi limit teoremine göre, orta elementin dağılımı normal olacak, bu da ortaya doğru eğilimli olacağınız anlamına gelir (ve dolayısıyla , n lg n kez).

Algoritma için O (nlgn) çalışma süresini kesinlikle garanti etmek istiyorsanız, bir dizinin medyanını bulmaya yönelik 5 sütunları yöntemi O (n) zamanında çalışır; bu, en kötü durumda hızlı sıralama için yineleme denkleminin olacağı anlamına gelir. olmak T (n) = O (n) (medyanı bulun) + O (n) (bölüm) + 2T (n / 2) (sola ve sağa tekrarlayın.) Ana Teoreme göre, bu O (n lg n) . Bununla birlikte, sabit faktör çok büyük olacaktır ve birincil endişeniz en kötü durum performansıysa, bunun yerine bir birleştirme sıralaması kullanın; bu, ortalamada hızlı sıralamadan yalnızca biraz daha yavaştır ve O (nlgn) süresini garanti eder (ve çok daha hızlı olacaktır) bu topal medyan hızlı sıralamadan daha fazla).

Medyan Ortanca Algoritmasının Açıklaması


6

Çok zeki olmaya ve dönme stratejilerini birleştirmeye çalışmayın. Ortanca 3'ü rastgele pivot ile ilk, sonuncu ve ortadaki rastgele indeksin medyanını seçerek birleştirirseniz, medyan 3 ikinci dereceden gönderen dağılımların çoğuna karşı hala savunmasız olacaksınız (yani aslında düz rastgele pivot)

Örneğin, ilk ve sonuncu boru organı dağılımı (1,2,3 ... N / 2,3,2,1) hem 1 olacak hem de rastgele indeks 1'den büyük bir sayı olacak, medyan alındığında 1 ( ya birinci ya da sonuncu) ve son derece dengesiz bir bölümleme elde edersiniz.


2

Bunu yaparak hızlı sıralamayı üç bölüme ayırmak daha kolaydır

  1. Veri öğesi değişimi veya takas işlevi
  2. Bölüm işlevi
  3. Bölümlerin işlenmesi

Uzun bir işlevden yalnızca biraz daha verimsizdir, ancak anlaşılması çok daha kolaydır.

Kod aşağıdaki gibidir:

/* This selects what the data type in the array to be sorted is */

#define DATATYPE long

/* This is the swap function .. your job is to swap data in x & y .. how depends on
data type .. the example works for normal numerical data types .. like long I chose
above */

void swap (DATATYPE *x, DATATYPE *y){  
  DATATYPE Temp;

  Temp = *x;        // Hold current x value
  *x = *y;          // Transfer y to x
  *y = Temp;        // Set y to the held old x value
};


/* This is the partition code */

int partition (DATATYPE list[], int l, int h){

  int i;
  int p;          // pivot element index
  int firsthigh;  // divider position for pivot element

  // Random pivot example shown for median   p = (l+h)/2 would be used
  p = l + (short)(rand() % (int)(h - l + 1)); // Random partition point

  swap(&list[p], &list[h]);                   // Swap the values
  firsthigh = l;                                  // Hold first high value
  for (i = l; i < h; i++)
    if(list[i] < list[h]) {                 // Value at i is less than h
      swap(&list[i], &list[firsthigh]);   // So swap the value
      firsthigh++;                        // Incement first high
    }
  swap(&list[h], &list[firsthigh]);           // Swap h and first high values
  return(firsthigh);                          // Return first high
};



/* Finally the body sort */

void quicksort(DATATYPE list[], int l, int h){

  int p;                                      // index of partition 
  if ((h - l) > 0) {
    p = partition(list, l, h);              // Partition list 
    quicksort(list, l, p - 1);        // Sort lower partion
    quicksort(list, p + 1, h);              // Sort upper partition
  };
};

1

Başlangıçta tamamen verilerinizin nasıl sıralandığına bağlıdır. Sözde rastgele olacağını düşünüyorsanız, en iyi bahsiniz ya rastgele bir seçim yapmak ya da ortayı seçmek.


1

Rastgele erişilebilen bir koleksiyonu sıralıyorsanız (bir dizi gibi), fiziksel ortadaki öğeyi seçmek genel olarak en iyisidir. Bununla, dizinin tümü hazır sıralanırsa (veya neredeyse sıralanırsa), iki bölüm bile eşit olacak ve en iyi hızı elde edeceksiniz.

Yalnızca doğrusal erişime sahip bir şeyi sıralıyorsanız (bağlantılı liste gibi), o zaman ilk öğeyi seçmek en iyisidir, çünkü erişilmesi en hızlı öğedir. Bununla birlikte, burada, eğer liste zaten sıralandıysa, hata yaparsınız - bir bölüm her zaman boş olacak ve diğerinde her şey olacak ve en kötü zamanı üretecektir.

Ancak, bağlantılı bir liste için, ilkinden başka bir şey seçmek, işleri daha da kötüleştirecektir. Listelenen bir listede ortadaki öğeyi seçer, her bölüm adımında adım adım ilerlemeniz gerekir - logN kez yapılan ve toplam süre O (1.5 N * log N) yapan bir O (N / 2) işlemi eklemeniz gerekir. ve eğer başlamadan önce listenin ne kadar uzun olduğunu bilirsek - genellikle bilmiyoruz, bu yüzden onları saymak için sonuna kadar adım atmamız, sonra ortasını bulmak için yarı yolda ilerlememiz, sonra da gerçek bölümü yapmak için üçüncü kez: O (2.5N * log N)


0

İdeal olarak pivot, dizinin tamamındaki orta değer olmalıdır. Bu, en kötü durum performansını elde etme şansını azaltacaktır.


1
at arabası burada.
ncmathsadist

0

Hızlı sıralamanın karmaşıklığı, pivot değerinin seçimine göre büyük ölçüde değişir. örneğin, bir özet olarak her zaman ilk öğeyi seçerseniz, algoritmanın karmaşıklığı O (n ^ 2) kadar kötü olur. işte pivot elemanını seçmek için akıllı bir yöntem - 1. Dizinin ilk, orta ve son elemanını seçin. 2. Bu üç sayıyı karşılaştırın ve birden büyük ve diğerinden küçük olan yani medyandan daha küçük olan sayıyı bulun. 3. bu elemanı pivot eleman yapın.

pivotun bu yöntemle seçilmesi, diziyi neredeyse ikiye böler ve dolayısıyla karmaşıklık O (nlog (n)) 'ye düşer.


0

Ortalama olarak, Medyan 3 küçük n için iyidir. Medyan 5, daha büyük n için biraz daha iyidir. "Üçün üç medyanı" olan dokuzuncu, çok büyük n için daha da iyidir.

Örneklemeyle ne kadar yükseğe çıkarsanız, n arttıkça o kadar iyi olursunuz, ancak örnekleri artırdıkça iyileşme önemli ölçüde yavaşlar. Ve numune alma ve numuneleri ayırma yükünü üstleniyorsunuz.


0

Kolayca hesaplanabileceği için orta indeksi kullanmanızı tavsiye ederim.

Yuvarlayarak (dizi.length / 2) hesaplayabilirsiniz.


-1

Gerçekten optimize edilmiş bir uygulamada, pivot seçme yöntemi dizi boyutuna bağlı olmalıdır - büyük bir dizi için, iyi bir pivot seçmek için daha fazla zaman harcamak işe yarar. Tam bir analiz yapmadan, "O (log (n)) öğelerinin ortasının" iyi bir başlangıç ​​olduğunu tahmin ediyorum ve bu, fazladan bellek gerektirmeme avantajına sahiptir: Daha büyük bölümde kuyruk çağrısı kullanmak ve yer bölümleme, algoritmanın hemen hemen her aşamasında aynı O (log (n)) ekstra belleği kullanırız.


1
3 elementin ortasını bulmak sabit zamanda yapılabilir. Daha fazlası ve esasen alt diziyi sıralamak zorundayız. N büyüdükçe, sıralama problemine tekrar dönüyoruz.
Chris Cudmore
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.