Phân vùng Quicksort: Hoare vs. Lomuto


82

Có hai phương pháp phân vùng quicksort được đề cập trong Cormen:

Hoare-Partition(A, p, r)
x = A[p]
i = p - 1
j = r + 1
while true
    repeat
        j = j - 1
    until A[j] <= x
    repeat
        i = i + 1
    until A[i] >= x
    if i < j
        swap( A[i], A[j] )
    else
        return j

và:

Lomuto-Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
    if A[j] <= x
        i = i + 1
        swap( A[i], A[j] )
swap( A[i +1], A[r] )
return i + 1

Bỏ qua phương pháp chọn trục, trong trường hợp nào thì cái này thích hợp hơn cái kia? Ví dụ, tôi biết rằng Lomuto biểu hiện tương đối kém khi có tỷ lệ cao các giá trị trùng lặp (nghĩa là hơn 2/3 mảng có cùng giá trị), khi Hoare thực hiện tốt trong tình huống đó.

Những trường hợp đặc biệt nào khác làm cho một phương thức phân vùng tốt hơn đáng kể so với phương pháp khác?


2
Tôi không thể nghĩ về bất kỳ tình huống nào trong đó Lomuto tốt hơn Hoare. Có vẻ như Lomuto thực hiện các giao dịch hoán đổi thêm bất cứ khi nào A[i+1] <= x. Trong một mảng được sắp xếp (và được đưa ra các pivots được lựa chọn hợp lý) Hoare gần như không hoán đổi và Lomuto thực hiện một tấn (một khi j đủ nhỏ thì tất cả A[j] <= x.) Tôi còn thiếu gì?
Logic lang thang

2
@WanderingLogic Tôi không chắc, nhưng dường như quyết định sử dụng phân vùng Lomuto trong cuốn sách của Cormen có thể mang tính sư phạm - nó dường như có một vòng lặp khá bất biến.
Robert S. Barnes

2
Lưu ý rằng hai thuật toán đó không làm điều tương tự. Vào cuối thuật toán của Hoare, trục không nằm ở vị trí cuối cùng. Bạn có thể thêm một swap(A[p], A[j])đoạn cuối Hoare để có cùng hành vi cho cả hai.
Aurélien Ooms

Bạn cũng nên kiểm tra i < jtrong 2 vòng lặp lặp lại của phân vùng Hoare.
Aurélien Ooms

@ AurélienOoms Mã được sao chép trực tiếp từ cuốn sách.
Robert S. Barnes

Câu trả lời:


92

Kích thước sư phạm

Do tính đơn giản của nó, phương pháp phân vùng của Lomuto có thể dễ thực hiện hơn. Có một giai thoại hay trong Lập trình viên ngọc của Jon Bentley về Sắp xếp:

Hầu hết các cuộc thảo luận về Quicksort đều sử dụng sơ đồ phân vùng dựa trên hai chỉ số tiếp cận [...] [tức là Hoare]. Mặc dù ý tưởng cơ bản của sơ đồ đó rất đơn giản, tôi luôn thấy các chi tiết khó hiểu - tôi đã từng dành phần tốt hơn trong hai ngày để theo đuổi một lỗi ẩn trong một vòng lặp phân vùng ngắn. Một độc giả của một bản thảo sơ bộ đã phàn nàn rằng phương pháp hai chỉ số trên thực tế đơn giản hơn Lomuto và đã phác thảo một số mã để đưa ra quan điểm của mình; Tôi đã ngừng tìm kiếm sau khi tôi tìm thấy hai lỗi.

Kích thước hiệu suất

Để sử dụng thực tế, dễ thực hiện có thể được hy sinh vì hiệu quả. Trên cơ sở lý thuyết, chúng ta có thể xác định số lượng so sánh phần tử và hoán đổi để so sánh hiệu suất. Ngoài ra, thời gian chạy thực tế sẽ bị ảnh hưởng bởi các yếu tố khác, chẳng hạn như hiệu suất bộ đệm và các dự đoán sai chi nhánh.

Như được hiển thị bên dưới, các thuật toán hoạt động rất giống nhau trên các hoán vị ngẫu nhiên ngoại trừ số lượng giao dịch hoán đổi . Có Lomuto cần ba lần nhiều như Hoare!

Số lượng so sánh

Cả hai phương pháp có thể được thực hiện bằng cách sử dụng so sánh để phân vùng một mảng có độ dài . Điều này về cơ bản là tối ưu, vì chúng ta cần so sánh mọi yếu tố với trục để quyết định nơi đặt nó.n1n

Số lần hoán đổi

Số lượng giao dịch hoán đổi là ngẫu nhiên cho cả hai thuật toán, tùy thuộc vào các yếu tố trong mảng. Nếu chúng ta giả sử hoán vị ngẫu nhiên , tức là tất cả các yếu tố là khác biệt và mọi hoán vị của các yếu tố đều có khả năng như nhau, chúng ta có thể phân tích số lượng giao dịch hoán đổi dự kiến .

Khi chỉ tính thứ tự tương đối, chúng tôi giả sử rằng các phần tử là các số . Điều đó làm cho cuộc thảo luận dưới đây dễ dàng hơn vì thứ hạng của một yếu tố và giá trị của nó trùng khớp.1,,n

Phương pháp của Lomuto

Biến chỉ số quét toàn bộ mảng và bất cứ khi nào chúng ta tìm thấy một phần tử nhỏ hơn p , chúng ta thực hiện trao đổi. Trong số các phần tử , chính xác là các phần tử nhỏ hơn , vì vậy chúng tôi nhận được hoán đổi nếu trục là .jA[j]x1,,nx1xx1x

Các kỳ vọng tổng thể sau đó kết quả bằng cách tính trung bình trên tất cả các trục. Mỗi giá trị trong đều có khả năng trở thành trục chính (cụ thể là với đầu . ), vì vậy chúng tôi có{1,,n}1n

1nx=1n(x1)=n212.

trung bình hoán đổi để phân vùng một mảng có độ dài bằng phương thức của Lomuto.n

Phương pháp của Hoare

Ở đây, phân tích phức tạp hơn một chút: Ngay cả khi sửa trục , số lần hoán đổi vẫn là ngẫu nhiên.x

Chính xác hơn: Các chỉ số và chạy về phía nhau cho đến khi chúng giao nhau, điều này luôn xảy ra ở (theo tính chính xác của thuật toán phân vùng của Hoare!). Điều này có hiệu quả phân chia mảng thành hai phần: Một phần bên trái được quét bởi và một phần bên phải được quét bởi .ijxij

Bây giờ, việc hoán đổi được thực hiện chính xác cho mọi cặp yếu tố của người đặt nhầm vị trí, tức là một phần tử lớn (lớn hơn , do đó nằm trong phân vùng bên phải) hiện nằm ở phần bên trái và một phần tử nhỏ nằm ở phần bên phải. Lưu ý rằng cặp hình thành này luôn hoạt động, tức là có số lượng phần tử nhỏ ban đầu ở phần bên phải bằng với số phần tử lớn ở phần bên trái.x

Người ta có thể chỉ ra rằng số lượng các cặp này là hypergeometrically phân phối: Đối với các phần tử lớn chúng ta vẽ ngẫu nhiên các vị trí của chúng trong mảng và có vị trí trong phần còn lại. Theo đó, số lượng cặp dự kiến ​​là với điều kiện là trục là .Hyp(n1,nx,x1)nxx1(nx)(x1)/(n1)x

Cuối cùng, chúng tôi trung bình một lần nữa trên tất cả các giá trị trục để có được tổng số lần hoán đổi dự kiến ​​cho phân vùng của Hoare:

1nx=1n(nx)(x1)n1=n613.

(Một mô tả chi tiết hơn có thể được tìm thấy trong luận án thạc sĩ của tôi , trang 29.)

Mẫu truy cập bộ nhớ

Cả hai thuật toán sử dụng hai con trỏ vào mảng quét nó tuần tự . Do đó, cả hai hành xử gần như tối ưu bộ nhớ đệm wrt.

Các yếu tố bằng nhau và danh sách đã được sắp xếp

Như đã đề cập bởi Wandering Logic, hiệu suất của các thuật toán khác nhau mạnh hơn đối với các danh sách không phải là hoán vị ngẫu nhiên.

Trên một mảng đã được sắp xếp, phương thức của Hoare không bao giờ hoán đổi, vì không có cặp nào bị đặt sai (xem ở trên), trong khi phương thức của Lomuto vẫn thực hiện khoảng hoán đổi!n/2

Sự hiện diện của các yếu tố bằng nhau đòi hỏi sự chăm sóc đặc biệt trong Quicksort. (Tôi đã tự mình bước vào cái bẫy này; xem luận án thạc sĩ của tôi , trang 36, để biết một câu chuyện về Tối ưu hóa sớm) Hãy xem xét như một ví dụ cực đoan về một mảng chứa s. Trên một mảng như vậy, phương thức của Hoare thực hiện hoán đổi cho mọi cặp phần tử - đó là trường hợp xấu nhất cho phân vùng của Hoare - nhưng và luôn gặp nhau ở giữa mảng. Do đó, chúng tôi có phân vùng tối ưu và tổng thời gian chạy vẫn còn trong .i j O ( n log n )0ijO(nlogn)

Phương pháp của Lomuto hành xử ngu ngốc hơn nhiều trên tất cả mảng: So sánh sẽ luôn đúng, vì vậy chúng tôi thực hiện hoán đổi cho mọi yếu tố ! Nhưng thậm chí còn tệ hơn: Sau vòng lặp, chúng tôi luôn có , vì vậy chúng tôi quan sát phân vùng trường hợp xấu nhất, làm cho hiệu suất tổng thể giảm xuống còn !i = n Θ ( n 2 )0A[j] <= xi=nΘ(n2)

Phần kết luận

Phương pháp của Lomuto đơn giản và dễ thực hiện hơn, nhưng không nên được sử dụng để thực hiện phương pháp sắp xếp thư viện.


16
Wow, đó là một câu trả lời chi tiết. Làm tốt lắm!
Raphael

Phải đồng ý với Raphael, câu trả lời thực sự tốt đẹp!
Robert S. Barnes

1
Tôi sẽ làm rõ một chút, khi tỷ lệ các yếu tố duy nhất trên tổng số yếu tố trở nên thấp hơn, số lượng so sánh mà Lomuto tăng nhanh hơn đáng kể so với Hoare. Điều này có thể là do phân vùng kém trên phần của Lomuto và phân vùng trung bình tốt trên phần của Hoare.
Robert S. Barnes

Giải thích tuyệt vời về hai phương pháp! Cảm ơn bạn!
v kouk

Bạn có thể dễ dàng tạo một biến thể của phương thức Lomuto có thể trích xuất tất cả các phần tử bằng với trục, và loại bỏ chúng khỏi đệ quy, mặc dù tôi không chắc liệu nó có giúp hoặc cản trở trường hợp trung bình không.
Jakub Narębski

5

Một số ý kiến ​​thêm vào câu trả lời tuyệt vời của Sebastian.

Tôi sẽ nói về thuật toán sắp xếp lại phân vùng nói chung và không nói về việc sử dụng cụ thể của nó cho Quicksort .

Ổn định

Thuật toán của Lomuto có thể bán được : thứ tự tương đối của các yếu tố không thỏa mãn vị ngữ được giữ nguyên. Thuật toán của Hoare không ổn định.

Mẫu truy cập phần tử

Thuật toán của Lomuto có thể được sử dụng với danh sách liên kết đơn hoặc các cấu trúc dữ liệu chỉ chuyển tiếp tương tự. Thuật toán của Hoare cần tính hai chiều .

Số lượng so sánh

Thuật toán của Lomuto có thể được triển khai thực hiện ứng dụng của vị từ để phân vùng một chuỗi có độ dài . (Hoare cũng vậy).n1nn

Nhưng để làm được điều này, chúng ta phải hy sinh 2 thuộc tính:

  1. Trình tự được phân vùng không được để trống.
  2. Thuật toán không thể trả về điểm phân vùng.

Nếu chúng ta cần bất kỳ thuộc tính nào trong 2 thuộc tính này, chúng ta sẽ không có lựa chọn nào khác ngoài thực hiện thuật toán bằng cách thực hiện so sánh.n

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.