Các thuật toán sắp xếp chấp nhận một bộ so sánh ngẫu nhiên


22

Các thuật toán sắp xếp chung thường lấy một tập hợp dữ liệu để sắp xếp và một hàm so sánh có thể so sánh hai phần tử riêng lẻ. Nếu bộ so sánh là một quan hệ thứ tự¹, thì đầu ra của thuật toán là một danh sách / mảng được sắp xếp.

Tôi tự hỏi mặc dù thuật toán sắp xếp nào thực sự sẽ hoạt động với một bộ so sánh không phải là một mối quan hệ thứ tự (cụ thể là một thuật toán trả về một kết quả ngẫu nhiên trên mỗi so sánh). Bằng cách "làm việc", ý tôi ở đây là họ tiếp tục trả về một hoán vị đầu vào của họ và chạy theo độ phức tạp thời gian được trích dẫn thông thường của họ (trái ngược với việc giảm xuống theo kịch bản trường hợp xấu nhất luôn luôn, hoặc đi vào một vòng lặp vô hạn hoặc các phần tử bị thiếu). Tuy nhiên, thứ tự của các kết quả sẽ không được xác định. Thậm chí tốt hơn, thứ tự kết quả sẽ là một phân phối đồng đều khi bộ so sánh là một đồng xu lật.

Từ tính toán tinh thần thô sơ của tôi, có vẻ như một loại hợp nhất sẽ ổn với điều này và duy trì cùng một chi phí thời gian chạy và tạo ra một thứ tự ngẫu nhiên công bằng. Tôi nghĩ rằng một cái gì đó giống như một loại nhanh chóng tuy nhiên sẽ thoái hóa, có thể không hoàn thành, và không công bằng.

Những thuật toán sắp xếp nào khác (ngoài sắp xếp hợp nhất) sẽ hoạt động như được mô tả với một bộ so sánh ngẫu nhiên?


  1. Để tham khảo, một bộ so sánh là một quan hệ thứ tự nếu nó là một hàm thích hợp (xác định) và thỏa mãn các tiên đề của một quan hệ thứ tự:

    • nó mang tính quyết định: compare(a,b)đối với một cụ thể abluôn trả về cùng một kết quả.
    • nó mang tính bắc cầu: compare(a,b) and compare(b,c) implies compare( a,c )
    • nó là đối xứng compare(a,b) and compare(b,a) implies a == b

(Giả sử rằng tất cả các yếu tố đầu vào là khác biệt, do đó tính phản xạ không phải là vấn đề.)

Một so sánh ngẫu nhiên vi phạm tất cả các quy tắc. Tuy nhiên, có những bộ so sánh không phải là quan hệ thứ tự nhưng không phải là ngẫu nhiên (ví dụ: chúng có thể vi phạm có lẽ chỉ có một quy tắc và chỉ dành cho các thành phần cụ thể trong tập hợp).


(1) Ý của bạn là gì khi hàm so sánh ổn định? (2) Có phải đồng nghĩa với những người không ổn định và có nghĩa là không?
Tsuyoshi Ito

"chạy theo độ phức tạp thời gian được trích dẫn của chúng (trái ngược với sự xuống cấp theo kịch bản trường hợp xấu nhất" - độ phức tạp thời gian được trích dẫn trường hợp xấu nhất! "Thứ tự sẽ là một thứ tự ngẫu nhiên công bằng" - B "NG" công bằng "bạn có nghĩa là đồng phục? Bạn có cho rằng bộ so sánh cũng đồng nhất không?
Raphael

Có lẽ không phải trong lý thuyết chính thức, nhưng trong thực tế (ngôn ngữ lập trình), nhiều thứ được trích dẫn trong thời gian khấu hao. Ví dụ: quicksort thường được hiển thị là nhưng thực tế là . O ( n 2 )O(logn)O(n2)
edA-qa mort-ora-y

4
@ edA-qamort-ora-y: (1) Ý bạn là , không phải . (2) Đó không phải là " thời gian khấu hao " nghĩa là gì; bạn có nghĩa là " thời gian dự kiến ", hoặc ít chính thức hơn, "thời gian điển hình". O ( log n )O(nlogn)O(logn)
JeffE

1
Không ai trả lời câu hỏi thú vị hơn (với tôi) được đặt ra ở trên: thuật toán sắp xếp nào (nếu có) có thuộc tính mà nếu bộ so sánh là một đồng xu lật, thì kết quả là hoán vị đồng nhất.
Joe

Câu trả lời:


13

Về cơ bản, bạn muốn biết liệu có bất kỳ thuật toán sắp xếp nào sẽ không làm giảm so với trường hợp trung bình của nó hay không nếu được cung cấp một hàm so sánh tương tự như:

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

... Trong đó Random.Next () là một số phương thức sẽ tạo ra một số nguyên được tạo ngẫu nhiên giữa một giới hạn bao gồm thấp hơn và giới hạn trên.

Câu trả lời thực sự là hầu hết các thuật toán sắp xếp cơ bản sẽ thực hiện theo trường hợp trung bình của chúng, bởi vì chúng tuân theo ít nhất một trong hai điều kiện sau:

  1. So sánh giữa hai yếu tố duy nhất không bao giờ được thực hiện hai lần trong sắp xếp và / hoặc
  2. Trong mỗi lần lặp của sắp xếp, vị trí chính xác của ít nhất một phần tử được xác định và do đó phần tử đó không bao giờ được so sánh lại.

Chẳng hạn, SelectionSort lặp qua danh sách phụ của các phần tử chưa được sắp xếp, tìm phần tử "ít nhất" và / hoặc "lớn nhất" (bằng cách so sánh từng phần tử với phần tử lớn nhất cho đến nay), đặt nó vào vị trí chính xác và lặp lại. Kết quả là, ngay cả với một bộ so sánh không xác định, vào cuối mỗi lần lặp, thuật toán sẽ tìm thấy một giá trị mà nó nghĩ là ít nhất hoặc lớn nhất, hoán đổi nó với phần tử ở vị trí mà nó đang cố gắng xác định và không bao giờ xem xét yếu tố đó một lần nữa, do đó nó tuân theo Điều kiện 2. Tuy nhiên, A và B có thể được so sánh nhiều lần trong quá trình này (như ví dụ cực đoan nhất, hãy xem xét một vài lượt của SelectionSort trên một mảng được sắp xếp theo thứ tự ngược lại) vì vậy nó vi phạm Điều kiện 1 .

MergeSort tuân theo Điều kiện 1 nhưng không phải 2; khi các mảng con được hợp nhất, các phần tử trong cùng một mảng con (ở bên trái hoặc bên phải) không được so sánh với nhau bởi vì chúng đã được xác định rằng các phần tử ở bên đó của mảng được sắp xếp theo thứ tự; thuật toán chỉ so sánh phần tử chưa hợp nhất ít nhất của mỗi phần tử con khác để xác định phần tử nào nhỏ hơn và sẽ đi tiếp trong danh sách được hợp nhất. Điều này có nghĩa là bất kỳ hai đối tượng duy nhất A và B sẽ được so sánh với nhau tối đa một lần, nhưng không biết chỉ số "cuối cùng" của phần tử nào trong bộ sưu tập đầy đủ cho đến khi thuật toán hoàn tất.

Chèn cũng chỉ tuân theo Điều kiện 1 mặc dù chiến lược tổng thể và độ phức tạp của nó trông giống như SelectionSort. Mỗi phần tử chưa được sắp xếp được so sánh với các phần tử được sắp xếp, trước hết, cho đến khi tìm thấy phần tử nhỏ hơn phần tử được kiểm tra. phần tử được chèn vào thời điểm đó, và sau đó phần tử tiếp theo được xem xét. Kết quả là thứ tự tương đối của bất kỳ A và B nào được xác định bằng một so sánh và không bao giờ so sánh giữa A và B đó, nhưng vị trí cuối cùng của bất kỳ yếu tố nào cũng không thể được biết cho đến khi tất cả các yếu tố được xem xét.

QuickSort tuân theo cả haiĐiều kiện. Ở mỗi cấp độ, một trục được chọn và sắp xếp sao cho phía "bên trái" chứa các phần tử nhỏ hơn trục và bên "bên phải" chứa các phần tử lớn hơn trục. Kết quả của cấp đó là QuickSort (trái) + p Pivot + QuickSort (phải) về cơ bản có nghĩa là vị trí của phần tử trục được biết đến (một chỉ số lớn hơn chiều dài của bên trái), trục không bao giờ được so sánh với bất kỳ phần tử nào khác sau khi nó được chọn làm trục (nó có thể được so sánh với các yếu tố trục trước đó, nhưng các yếu tố đó cũng được biết đến và không được bao gồm trong bất kỳ phân đoạn nào), VÀ bất kỳ A và B nào nằm ở phía đối diện của trục đều không bao giờ so. Trong hầu hết các triển khai QuickSort thuần túy, trường hợp cơ sở là một yếu tố, tại đó chỉ số hiện tại của nó là chỉ mục cuối cùng và không có so sánh nào được thực hiện.

Loại so sánh duy nhất tôi có thể nghĩ rằng sẽ không tuân theo một trong hai điều kiện là BubbleSort không được tối ưu hóa. Nếu loại sắp xếp không chấp nhận rằng các phần tử X lớn nhất nằm ở vị trí thích hợp của chúng sau khi chạy X vượt qua và / hoặc sử dụng thẻ "kiểm tra hai lần" để xác minh danh sách được sắp xếp, sắp xếp sẽ chỉ được coi là "hoàn thành" khi bộ so sánh ngẫu nhiên đã trả về -1 hoặc 0 cho mỗi hai phần tử liền kề trong danh sách trong khi vượt qua và do đó không có giao dịch hoán đổi nào được thực hiện (một sự kiện, nếu thực sự ngẫu nhiên, sẽ xảy ra với xác suất ; đối với một danh sách tương đối nhỏ gồm 25 yếu tố, đó là cơ hội trong năm 2000, trong khi đối với 100 yếu tố thì xác suất là 3,7 * 10 -18(2/3)N1). Khi giá trị tuyệt đối tối đa của kết quả của bộ so sánh tăng lên, xác suất cho bất kỳ một so sánh nào trả về âm hoặc bằng 0 sẽ giảm xuống 0,5, làm cho cơ hội kết thúc thuật toán ít có khả năng hơn (cơ hội 99 đồng xu lật tất cả các đầu hạ cánh , về cơ bản, cái này có nghĩa là, 1 trong 1,2 * 10 30 )

EDIT A LATER LỚN THỜI GIAN: Có một vài "loại" được thiết kế cụ thể như các ví dụ về những việc không nên làm mà kết hợp một bộ so sánh ngẫu nhiên; có lẽ nổi tiếng nhất là BogoSort. "Đưa ra một danh sách, nếu danh sách không theo thứ tự, hãy xáo trộn danh sách và kiểm tra lại". Về mặt lý thuyết, cuối cùng nó sẽ đạt được sự hoán vị đúng của các giá trị, giống như "BubbleSort không được tối ưu hóa" ở trên, nhưng trường hợp trung bình là thời gian giai đoạn (N! / 2) và vì vấn đề sinh nhật (sau khi đủ hoán vị ngẫu nhiên trở nên có khả năng gặp phải các hoán vị trùng lặp hơn so với các hoán vị duy nhất) có khả năng thuật toán không bao giờ hoàn thành để chính thức thuật toán không bị ràng buộc theo thời gian.


Điều kiện 2 cũng sẽ bao gồm sắp xếp nhanh chóng? Hoặc nó sẽ là một điều kiện thứ ba về mỗi lần lặp lại nhỏ hơn lần lặp trước.
edA-qa mort-ora-y

QuickSort, trong tâm trí của tôi, được bao phủ bởi cả hai điều kiện. Trong QuickSorts hiệu quả, bạn chọn trục, sau đó so sánh từng yếu tố với nó và hoán đổi các yếu tố nằm ở "bên" sai của trục. Khi các yếu tố được sắp xếp, hàm sẽ trả về QuickSort (trái) + trục + QuickSort (phải) và trục không được chuyển xuống các mức thấp hơn. Vì vậy, cả hai điều kiện đều đúng; bạn không bao giờ so sánh bất kỳ a và b duy nhất nào hơn một lần và bạn đã xác định chỉ số của trục theo thời gian bạn hoàn thành việc sắp xếp các yếu tố khác.
KeithS

Câu trả lời tuyệt vời, nhưng tôi không đồng ý với bạn về BubbleSort. Khi sử dụng một bộ so sánh nhất quán, tại BubbleSort lặp lại i-th biết rằng các phần tử cuối cùng của i-1 nằm ở vị trí cuối cùng của chúng và bất kỳ triển khai hợp lý nào của BubbleSort sẽ đi qua ít phần tử hơn mỗi lần lặp, do đó, nó cũng sẽ dừng sau n lần lặp .
Boris Khayvas

Sau khi suy nghĩ thêm tôi sẽ có xu hướng đồng ý với bạn; sau khi X vượt qua, các giá trị X lớn nhất nằm ở vị trí thích hợp của chúng, vì vậy bạn có thể giảm không gian vấn đề trên mỗi lần vượt qua và do đó, một thuật toán hiệu quả sẽ tuân theo Điều kiện 2. Tôi sẽ chỉnh sửa
KeithS

Bạn phải cẩn thận với việc triển khai Quicksort. Có thể có một giả định rằng việc tìm kiếm một phần tử không nhỏ hơn trục sẽ kết thúc khi chúng ta gặp trục hoặc phần tử lớn hơn trục; đó không phải là trường hợp cần thiết
gnasher729

10

Bất kỳ thuật toán nào so sánh hai yếu tố hai lần không phải là một thuật toán rất thông minh và đặc biệt thuật toán đó sẽ hoạt động kém hơn các thuật toán sắp xếp phổ biến nhất (hợp nhất sắp xếp, quicksort, sắp xếp bong bóng, sắp xếp chèn). Bất kỳ thuật toán nào so sánh các cặp phần tử nhiều nhất đều có cùng chi phí thời gian chạy (trung bình) bất kể hành vi của hàm so sánh, nếu lớn hơn và nhỏ hơn - đều có kết quả như nhau . Mặt khác, ít nhất bạn có thể đảm bảo rằng thuật toán sắp xếp không tệ hơn thời gian chạy trong trường hợp xấu nhất, nhỏ hơn cho bất kỳ thuật toán sắp xếp hợp lý nào.O(n2)

Tôi tin rằng một câu hỏi thú vị hơn là thuật toán như vậy sẽ hoạt động tốt như thế nào nếu hàm so sánh chỉ đưa ra câu trả lời đúng trong trung bình 90% trường hợp. Bằng cách nào nó sẽ thực hiện tốt như thế nào tôi có nghĩa là để trả lời câu hỏi: "trung bình, số lượng các mục bị thất lạc khi sắp xếp một danh sách kích thước theo thuật toán này là gì?"n


Chỉnh sửa: Vấn đề thú vị hơn như tôi nghĩ đầu tiên, vì vậy đây là một nhận xét thêm:

Giả sử rằng hàm của bạn là công bằng , đó là với xác suất và với xác suất cũng là . Nhớ lại thuật toán sắp xếp chèn (kiểu chức năng):c o m p một r e ( x , y ) = t r u e 1 / 2 f một l s e 1 / 2comparecompare(x,y)=true1/2false1/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 []

Thời gian chạy trung bình của thuật toán này là trong đó là độ dài của và là thời gian chạy trung bình của trên danh sách độ dài , đó là nếu chúng ta chỉ tính các ứng dụng như có chi phí (nếu chúng ta cũng tính các lần phá hủy, thì công thức cũng tương tự).n l f ( k ) i n s e r t k :k=1nf(k)nlf(k)insertk:

compare

i=1ki2ii=1i2i=2

O(2n)O(n2)

Sẽ rất vui khi tính ra thời gian chạy trung bình cho các thuật toán khác nhau được cung cấp chức năng so sánh thống nhất này.


Quicksort có thể lặp lại so sánh nếu cùng một yếu tố được chọn làm trục nhiều lần (nó có thể xảy ra nhiều lần trong danh sách).
Raphael

2
@Raphael: Sự lựa chọn từ ngữ của tôi rất kém: Tôi có nghĩa là so sánh lặp lại giữa các lần xuất hiện của các yếu tố, không xảy ra nhiều hơn một lần trong Quicksort.
cody

1
@Gilles: Tôi có thể sai, nhưng tôi không tin rằng tính siêu việt của việc so sánh là rất quan trọng đối với thời gian chạy của hầu hết các thuật toán sắp xếp; tính chính xác chắc chắn, nhưng đó không phải là đối tượng của câu hỏi.
cody

@Gilles: OP không hỏi về các thuật toán thực sự sắp xếp. Anh ta hỏi về những gì xảy ra với các thuật toán sắp xếp tiêu chuẩn khi tất cả các so sánh được thay thế bằng các lần lật đồng xu. Các thuật toán kết quả không sắp xếp (ngoại trừ xác suất nhỏ), nhưng chúng vẫn là các thuật toán được xác định rõ.
JeffE

@JeffE Bây giờ tôi hiểu rồi. Đó không phải là cách tôi đọc câu hỏi ban đầu, nhưng đưa ra ý kiến ​​của người hỏi, đó là những gì có nghĩa.
Gilles 'SO- ngừng trở nên xấu xa'

2

Sáp nhập với một bộ so sánh ngẫu nhiên công bằng là không công bằng. Tôi không có bằng chứng, nhưng tôi có bằng chứng thực nghiệm RẤT mạnh mẽ. (Hội chợ có nghĩa là phân phối đồng đều.)

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

Bây giờ là Haskell hay Caml trong thời trang?
Yai0Phah

Tôi không có ý kiến. Nhưng Haskell là một ngôn ngữ yêu thích của tôi, vì vậy tôi đã lập trình nó trong đó; khớp mẫu làm cho điều này dễ dàng hơn.
Thomas Eding

0

Một câu hỏi rất liên quan đã được trả lời trong Tất cả các loại phép (Ngọc chức năng) của Christiansen, Danilenko và Dylus. Họ chạy một thuật toán sắp xếp trong danh sách đơn nguyên , về cơ bản mô phỏng tính không xác định, trả về tất cả các hoán vị của một danh sách đầu vào nhất định. Các tài sản thú vị là mỗi hoán vị được trả lại chính xác một lần.

Trích dẫn từ bản tóm tắt:

...

Trong bài báo này, chúng tôi xem xét sự kết hợp của tính không xác định và sắp xếp theo một ánh sáng khác: được cung cấp một hàm sắp xếp, chúng tôi áp dụng nó cho một vị từ không xác định để có được một hàm liệt kê các hoán vị của danh sách đầu vào. Chúng ta đi đến tận cùng các thuộc tính cần thiết của các thuật toán sắp xếp và các vị từ đang chơi cũng như thảo luận về các biến thể của tính không xác định được mô hình hóa.

Trên hết, chúng tôi xây dựng và chứng minh một định lý cho biết cho dù chúng tôi sử dụng hàm sắp xếp nào, hàm hoán vị tương ứng liệt kê tất cả các hoán vị của danh sách đầu vào. Chúng tôi sử dụng các định lý miễn phí, có nguồn gốc từ loại hàm duy nhất, để chứng minh tuyên bố.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.