Algoritma pengurutan yang menerima pembanding acak

22

Algoritma pemilahan generik umumnya mengambil satu set data untuk disortir dan fungsi komparator yang dapat membandingkan dua elemen individual. Jika komparator adalah relasi urutan¹, maka output dari algoritma adalah daftar / array yang diurutkan.

Saya bertanya-tanya meskipun algoritma semacam apa yang benar-benar akan bekerja dengan komparator yang bukan hubungan urutan (khususnya yang mengembalikan hasil acak pada setiap perbandingan). Yang saya maksud dengan "kerja" di sini adalah bahwa mereka terus mengembalikan permutasi input mereka dan berjalan pada kompleksitas waktu yang biasanya dikutip (sebagai kebalikan dari merendahkan ke skenario kasus terburuk selalu, atau pergi ke loop tak terbatas, atau elemen yang hilang). Namun, pemesanan hasil akan ditentukan. Bahkan lebih baik, pemesanan yang dihasilkan akan menjadi distribusi yang seragam ketika pembandingnya adalah koin flip.

Dari perhitungan mental kasar saya tampak bahwa semacam gabungan akan baik-baik saja dengan ini dan mempertahankan biaya runtime yang sama dan menghasilkan pemesanan acak yang adil. Saya pikir sesuatu seperti semacam cepat akan merosot, mungkin tidak selesai, dan tidak adil.

Algoritme pengurutan apa lagi (selain gabungan jenis) yang akan berfungsi seperti yang dijelaskan dengan pembanding acak?


  1. Untuk referensi, komparator adalah relasi urutan jika itu adalah fungsi yang tepat (deterministik) dan memenuhi aksioma relasi urutan:

    • itu deterministik: compare(a,b)untuk yang khusus adan bselalu mengembalikan hasil yang sama.
    • itu transitif: compare(a,b) and compare(b,c) implies compare( a,c )
    • itu antisimetris compare(a,b) and compare(b,a) implies a == b

(Asumsikan bahwa semua elemen input berbeda, sehingga refleksivitas tidak menjadi masalah.)

Komparator acak melanggar semua aturan ini. Namun ada komparator yang tidak memesan hubungan namun tidak acak (misalnya mereka mungkin melanggar mungkin hanya satu aturan, dan hanya untuk elemen tertentu di set).

edA-qa mort-ora-y
sumber
(1) Apa yang Anda maksud dengan fungsi bandingkan menjadi stabil? (2) Apakah "tidak stabil" dan "acak" sama?
Tsuyoshi Ito
"Berlari pada kompleksitas waktu mereka yang biasanya dikutip (bukan kebalikan dari skenario terburuk" - biasanya kompleksitas waktu yang dikutip adalah yang terburuk! "pemesanan akan menjadi urutan acak yang adil" - DENGAN "adil" yang Anda maksudkan seragam? Apakah Anda berasumsi bahwa pembandingnya juga seragam?
Raphael
Mungkin tidak dalam teori formal, tetapi dalam praktiknya (bahasa pemrograman) banyak hal dikutip dalam waktu diamortisasi. Misalnya, quicksort sering ditampilkan sebagai tetapi sebenarnya O ( n 2 ) . HAI(logn)HAI(n2)
edA-qa mort-ora-y
4
@ edA-qamort-ora-y: (1) Maksud Anda , bukan O ( log n ) . (2) Bukan itu artinya " waktu diamortisasi "; maksud Anda " waktu yang diharapkan ", atau kurang formal, "waktu khas". HAI(nlogn)HAI(logn)
JeffE
1
Tidak ada yang menjawab pertanyaan yang lebih menarik (kepada saya) yang diajukan di atas: algoritma pengurutan mana (jika ada) yang memiliki properti bahwa jika pembandingnya berupa koin balik, maka hasilnya adalah permutasi yang seragam.
Joe

Jawaban:

13

Jadi pada dasarnya, Anda ingin tahu apakah ada algoritma pengurutan yang tidak akan menurun dari kasus rata-rata jika diberi fungsi bandingkan yang mirip dengan:

int Compare(object a, object b) { return Random.Next(-1,1); }

... di mana Random.Next () adalah beberapa metode yang akan menghasilkan bilangan bulat yang dihasilkan secara acak antara batas bawah dan atas inklusif yang ditentukan.

Jawabannya sebenarnya adalah bahwa sebagian besar algoritma pengurutan dasar akan bekerja sesuai dengan kasus rata-rata mereka, karena mereka mematuhi setidaknya satu dari dua kondisi berikut:

  1. Perbandingan antara dua elemen unik tidak pernah dilakukan dua kali dalam pengurutan, dan / atau
  2. Dalam setiap iterasi dari jenis, posisi yang benar dari setidaknya satu elemen ditentukan dan elemen tersebut tidak pernah dibandingkan lagi.

Misalnya, SelectionSort beralih melalui sub-daftar elemen yang tidak disortir, menemukan elemen "paling sedikit" dan / atau "terbesar" (dengan membandingkan masing-masing dengan yang terbesar sejauh ini), menempatkannya pada posisi dan pengulangan yang benar. Akibatnya, bahkan dengan pembanding non-deterministik, pada akhir setiap iterasi algoritma akan menemukan nilai yang dianggap paling atau paling besar, menukarnya dengan elemen di posisi yang ia coba untuk menentukan, dan tidak pernah mempertimbangkan elemen itu lagi, sehingga mematuhi Kondisi 2. Namun, A dan B dapat dibandingkan beberapa kali selama proses ini (sebagai contoh paling ekstrim, pertimbangkan beberapa lintasan SelectionSort pada array yang diurutkan dalam urutan terbalik) sehingga melanggar Kondisi 1 .

MergeSort mematuhi Kondisi 1 tetapi tidak 2; karena sub-array digabung, elemen-elemen dalam sub-array yang sama (di sisi kiri atau kanan) tidak dibandingkan satu sama lain karena telah ditentukan bahwa elemen-elemen di sisi array tersebut berada dalam urutan di antara mereka sendiri; algoritme hanya membandingkan elemen yang paling tidak dihapus dari masing-masing subarray dengan yang lain untuk menentukan mana yang lebih rendah dan harus masuk berikutnya dalam daftar gabungan. Ini berarti bahwa setiap dua objek unik A dan B akan dibandingkan satu sama lain maksimum satu kali, tetapi indeks "final" elemen apa pun yang diberikan dalam koleksi lengkap tidak diketahui hingga algoritme selesai.

InsertionSort hanya mematuhi Persyaratan 1 meskipun strategi keseluruhan dan kompleksitasnya lebih mirip dengan SelectionSort. Setiap elemen yang tidak disortir dibandingkan dengan elemen yang diurutkan, terbesar-pertama, hingga ditemukan lebih sedikit dari elemen yang sedang diperiksa. elemen dimasukkan pada titik itu, dan kemudian elemen berikutnya dipertimbangkan. Hasilnya adalah bahwa urutan relatif dari setiap A dan B ditentukan oleh satu perbandingan, dan perbandingan lebih lanjut antara A dan B tidak pernah dilakukan, tetapi posisi akhir dari setiap elemen tidak dapat diketahui sampai semua elemen dipertimbangkan.

QuickSort mematuhi keduanyaKondisi. Di setiap tingkat, pivot dipilih dan disusun sedemikian rupa sehingga sisi "kiri" mengandung elemen lebih sedikit dari pivot dan sisi "kanan" mengandung elemen yang lebih besar daripada pivot. Hasil level tersebut adalah QuickSort (kiri) + pivot + QuickSort (kanan) yang pada dasarnya berarti posisi elemen pivot diketahui (satu indeks lebih besar dari panjang sisi kiri), pivot tidak pernah dibandingkan dengan elemen lain setelah dipilih sebagai pivot (mungkin telah dibandingkan dengan elemen pivot sebelumnya, tetapi elemen-elemen tersebut juga diketahui dan tidak termasuk dalam sub-susun apa pun), DAN setiap A dan B yang berakhir pada sisi berlawanan dari pivot tidak pernah dibandingkan. Dalam sebagian besar implementasi QuickSort murni, casing dasar adalah satu elemen, di mana titik indeks saat ini adalah indeks akhir dan tidak ada perbandingan lebih lanjut yang dibuat.

Satu-satunya jenis komparatif yang dapat saya pikirkan yang tidak mematuhi kedua kondisi ini adalah BubbleSort yang tidak dioptimalkan. Jika pengurutan tidak menerima bahwa elemen X terbesar berada di tempat yang tepat setelah menjalankan X pass, dan / atau menggunakan pass "periksa ulang" untuk memverifikasi daftar diurutkan, pengurutan hanya akan dianggap "selesai" ketika pembanding acak telah kembali -1 atau 0 untuk setiap dua elemen yang berdekatan dalam daftar selama lulus dan dengan demikian tidak ada swap dilakukan (sebuah acara yang, jika benar-benar acak, akan terjadi dengan probabilitas (2/3)N-1 , karena relatif daftar kecil 25 elemen, itu peluang satu dalam 2000, sedangkan untuk 100 elemen probabilitasnya adalah 3,7 * 10 -18). Ketika nilai absolut maksimum hasil komparator meningkat, probabilitas untuk setiap perbandingan untuk mengembalikan negatif atau nol menurun ke 0,5, membuat peluang untuk mengakhiri algoritma yang jauh lebih kecil kemungkinannya (kemungkinan 99 koin membalik semua kepala pendaratan , yang pada dasarnya adalah intinya, adalah 1 dalam 1,2 * 10 30 )

EDIT A LATER TIME LATER: Ada beberapa "macam" yang dirancang khusus sebagai contoh apa yang tidak boleh dilakukan yang menggabungkan pembanding acak; mungkin yang paling terkenal adalah BogoSort. "Diberikan daftar, jika daftar tidak berurutan, kocok daftar dan periksa lagi". Secara teoritis pada akhirnya akan mencapai permutasi nilai yang tepat, seperti "BubbleSort yang tidak dioptimalkan" di atas, tetapi case rata-rata adalah faktorial-waktu (N! / 2), dan karena masalah ulang tahun (setelah permutasi yang cukup Anda menjadi lebih mungkin untuk menghadapi permutasi duplikat daripada yang unik) ada kemungkinan bukan nol dari algoritma tidak pernah menyelesaikan secara resmi algoritma tidak terikat waktu.

KeithS
sumber
Apakah kondisi 2 juga mencakup pengurutan cepat? Atau apakah lebih dari kondisi ketiga tentang setiap iterasi yang lebih kecil daripada yang terakhir.
edA-qa mort-ora-y
QuickSort akan, dalam pikiran saya, dilindungi oleh kedua kondisi tersebut. Dalam QuickSorts yang efisien, Anda memilih pivot, lalu membandingkan setiap elemen dengannya dan menukar elemen yang ada di "sisi" yang salah dari pivot. Setelah elemen diatur, fungsi mengembalikan QuickSort (kiri) + pivot + QuickSort (kanan) dan pivot tidak diturunkan ke level yang lebih rendah. Jadi, kedua kondisi itu benar; Anda tidak pernah membandingkan unik dan b lebih dari sekali, dan Anda telah menentukan indeks poros pada saat Anda selesai mengatur elemen-elemen lainnya.
KeithS
Jawaban yang bagus, tapi saya tidak setuju dengan Anda tentang BubbleSort. Saat menggunakan komparator yang konsisten, pada iterasi ke-2 BubbleSort tahu bahwa elemen terakhir i-1 ada di tempat terakhirnya, dan implementasi BubbleSort yang masuk akal akan melewati lebih sedikit elemen setiap iterasi, jadi itupun harus berhenti setelah iterasi .
Boris Trayvas
Setelah berpikir lagi, saya cenderung setuju dengan Anda; setelah X berlalu, nilai X terbesar ada di tempatnya, sehingga Anda dapat mengurangi ruang masalah pada setiap lintasan dan jadi algoritma yang efisien akan mematuhi Ketentuan 2. Saya akan mengedit
KeithS
Anda harus berhati-hati dengan implementasi Quicksort. Mungkin ada asumsi bahwa pencarian elemen tidak kurang dari pivot akan berakhir ketika kita menemukan pivot atau elemen lebih besar dari pivot; itu tidak akan menjadi masalah.
gnasher729
10

HAI(n2) untuk setiap algoritma pengurutan yang layak.

n


Sunting: Masalahnya lebih menarik seperti yang saya pikirkan, jadi inilah komentar selanjutnya:

cHaimhalSebuahrecHaimhalSebuahre(x,y)=trkamue1/2fSebuahlse1/2

insert x [] = [x]
insert x y:ys = if x < y then x:y:ys
                else y:insert x ys

sort_aux l e = match l with
                 [] -> e
                 x:xs -> sort_aux xs (insert x ys)

sort l = sort_aux l []

k=1nf(k)nlf(k)sayansertk: memiliki biaya (jika kita menghitung juga kerusakan, rumusnya sama).

cHaimhalSebuahre

saya=1ksaya2-sayasaya=1saya2-saya=2

Ini memberikan waktu berjalan rata-rata HAI(2n)HAI(n2)

Akan menyenangkan untuk bekerja di luar rata-rata waktu berjalan untuk algoritma lain yang berbeda mengingat fungsi perbandingan yang seragam ini.

cody
sumber
Quicksort dapat mengulangi perbandingan jika elemen yang sama dipilih sebagai pivot lebih dari sekali (ini dapat terjadi beberapa kali dalam daftar).
Raphael
2
@ Raphael: Pilihan kata-kata saya buruk: Maksud saya perbandingan berulang antara kemunculan elemen, yang tidak muncul lebih dari sekali di Quicksort.
cody
1
@Gilles: Saya mungkin salah, tapi saya tidak percaya bahwa transitivitas bandingkan sangat penting untuk runtime dari sebagian besar algoritma penyortiran; benar tentu saja, tapi itu bukan objek pertanyaan.
cody
@Gilles: OP tidak bertanya tentang algoritma yang sebenarnya mengurutkan. Dia bertanya tentang apa yang terjadi pada algoritma penyortiran standar ketika semua perbandingan diganti dengan membalik koin. Algoritme yang dihasilkan tidak mengurutkan (kecuali dengan probabilitas kecil), tetapi masih merupakan algoritma yang terdefinisi dengan baik.
JeffE
@ Jeff Aku mengerti itu sekarang. Itu bukan cara saya membaca pertanyaan pada awalnya, tetapi memberikan komentar si penanya, itulah yang dimaksud.
Gilles 'SANGAT berhenti menjadi jahat'
2

Mergesort dengan pembanding acak yang adil tidak adil. Saya tidak punya bukti, tapi saya punya bukti empiris yang SANGAT kuat. (Adil berarti terdistribusi secara merata.)

module Main where

import Control.Monad
import Data.Map (Map)
import qualified Data.Map as Map
import System.Random (randomIO)

--------------------------------------------------------------------------------

main :: IO ()
main = do
  let xs = [0..9]
  xss <- replicateM 100000 (msortRand xs)
  print $ countFrequencies xss

msortRand :: [a] -> IO [a]
msortRand = msort (\_ _ -> randomIO)

countFrequencies :: (Ord a) => [[a]] -> [Map a Int]
countFrequencies [] = []
countFrequencies xss = foldr (\k m -> Map.insertWith (+) k 1 m) Map.empty ys : countFrequencies wss
  where
    ys = map head xss
    zss = map tail xss
    wss = if head zss == []
      then []
      else zss

--------------------------------------------------------------------------------

msort :: (Monad m) => (a -> a -> m Bool) -> [a] -> m [a]
msort (<) [] = return []
msort (<) [x] = return [x]
msort (<) xs = do
  ys' <- msort (<) ys
  zs' <- msort (<) zs
  merge (<) ys' zs'
  where
    (ys, zs) = split xs

merge :: (Monad m) => (a -> a -> m Bool) -> [a] -> [a] -> m [a]
merge (<) [] ys = return ys
merge (<) xs [] = return xs
merge (<) (x:xs) (y:ys) = do
  bool <- x < y
  if bool
    then liftM (x:) $ merge (<) xs (y:ys)
        else liftM (y:) $ merge (<) (x:xs) ys

split :: [a] -> ([a], [a])
split [] = ([], [])
split [x] = ([x], [])
split (x:y:zs) = (x:xs, y:ys)
  where
    (xs, ys) = split zs
Thomas Eding
sumber
Apakah Haskell atau Caml dalam mode sekarang?
Yai0Phah
Saya tidak punya ide. Tapi Haskell adalah bahasa favorit saya, jadi saya memprogram ini di dalamnya; pencocokan pola membuat ini lebih mudah.
Thomas Eding
0

Pertanyaan yang sangat terkait dijawab dalam Semua Urusan Permutasi (Mutiara Fungsional) oleh Christiansen, Danilenko dan Dylus. Mereka menjalankan algoritma pengurutan dalam daftar monad , yang pada dasarnya mensimulasikan non-determinisme, mengembalikan semua permutasi dari daftar input yang diberikan. Properti yang menarik adalah bahwa setiap permutasi dikembalikan tepat sekali.

Mengutip dari abstrak:

...

Dalam makalah ini kita melihat kombinasi non-determinisme dan pengurutan dalam cahaya yang berbeda: diberikan fungsi pengurutan, kami menerapkannya pada predikat non-deterministik untuk mendapatkan fungsi yang menyebutkan permutasi daftar input. Kita sampai ke bagian bawah sifat-sifat yang diperlukan dari algoritma pengurutan dan predikat dalam permainan serta mendiskusikan variasi dari non-determinisme yang dimodelkan.

Selain itu, kami merumuskan dan membuktikan teorema yang menyatakan bahwa apa pun fungsi penyortiran yang kami gunakan, fungsi permutasi yang sesuai menyebutkan semua permutasi dari daftar input. Kami menggunakan teorema bebas, yang berasal dari jenis fungsi saja, untuk membuktikan pernyataan itu.

Petr Pudlák
sumber