Tại sao Quicksort được mô tả như là tại chỗ, nếu các danh sách con chiếm khá nhiều bộ nhớ? Chắc chắn chỉ có một cái gì đó như sắp xếp bong bóng là tại chỗ?


7

Quicksort được mô tả là "tại chỗ" nhưng sử dụng một triển khai như:

def sort(array):
    less = []
    equal = []
    greater = []
    if len(array) > 1:
        pivot = array[0]
        for x in array:
            if x < pivot:
                less.append(x)
            if x == pivot:
                equal.append(x)
            if x > pivot:
                greater.append(x)
        return sort(less) + equal + sort(greater)
    else:
        return array

Bạn phải tạo một bản sao của danh sách cho mỗi lần đệ quy. Khi trở về đầu tiên, trong bộ nhớ chúng ta có:

  • mảng
  • lớn hơn + bằng + ít hơn

Sau đó, bằng đệ quy thứ hai trên tất cả các danh sách phụ chúng ta có:

  • mảng
  • lớn hơn, bằng nhau, ít hơn từ đệ quy đầu tiên
  • lớn hơn + bằng + ít hơn từ less1, lớn hơn + bằng + ít hơn từ lớn1

Vân vân...

Đây chỉ là mã được viết xấu hay tôi đúng khi nghĩ rằng đối với một danh sách lớn, bạn thực sự phải có, theo tỷ lệ, một lượng không gian thừa để lưu trữ?

Khi tôi nghĩ về một cái gì đó là "tại chỗ", tôi nghĩ về sắp xếp bong bóng, chỉ đơn giản là hoán đổi các yếu tố trong danh sách như: http://en.wikipedia.org/wiki/File:Bubble-sort-example-300px. gif

BubbleSort chỉ cần thêm 1 biến để lưu trữ phần tử có khả năng hoán đổi.


1
Tôi không thể nói những gì bạn đang hỏi. Bạn có muốn thử chỉnh sửa câu hỏi để làm rõ hơn câu hỏi của bạn là gì không? Bạn chỉ đang hỏi liệu có thể triển khai QuickSort tại chỗ không? Nếu vậy, đó là một câu hỏi tiêu chuẩn với câu trả lời chuẩn ở những nơi thông thường - bạn đã thực hiện đủ nghiên cứu. PS Ngoài ra, bạn có thể cố gắng để chính xác hơn trong từ ngữ của bạn. Chẳng hạn, tôi không thể nói câu đầu tiên đang nói gì (dường như thiếu một động từ). Trong câu thứ hai, tôi không thể nói "Bạn" là ai.
DW

1
Tại chỗ có nghĩa là bạn có thể tránh sử dụng các vùng dữ liệu ngoài mà chỉ sử dụng mảng ban đầu bạn đang sắp xếp. Việc thực hiện này không làm điều đó.
Thorbjørn Ravn Andersen

Câu trả lời:


21

Việc thực hiện đặc biệt này của quicksort không được thực hiện. Nó coi cấu trúc dữ liệu là một danh sách chỉ có thể phát triển theo một hướng (trong trường hợp đó, việc sắp xếp hợp nhất sẽ đơn giản và nhanh hơn). Tuy nhiên, có thể viết một triển khai tại chỗ của quicksort, và đây là cách nó thường được trình bày.

Trong một triển khai tại chỗ, thay vào đó, hàm sắp xếp không được gọi đệ quy trên các mảng được xây dựng mới trở nên nhỏ hơn và nhỏ hơn, nhưng trên các giới hạn ngày càng gần hơn.

def sort(array, start, end):
    if end >= start: return
    pivot = array[start]
    middle = start + 1
    for i in range(start+1, end):
        if array[i] >= array[middle]:
            tmp = array[i]
            array[i] = array[middle]
            array[middle] = tmp
            middle += 1
    sort(array, start, middle)
    sort(array, middle, end)

(Coi chừng mã này, tôi chỉ gõ nó, không chứng minh được. Bất kỳ lỗi nào cũng là của bạn để sửa. Việc triển khai trong thế giới thực sẽ sử dụng thuật toán khác cho kích thước nhỏ nhưng điều này không ảnh hưởng đến hành vi tiệm cận. triển khai thế giới sẽ chọn một trục tốt hơn nhưng tôi sẽ không tham gia vào đó vì nó không thực sự cho câu hỏi này.)

Các trang Wikipedia quà vừa là một nơi phi-trong-và một phiên bản tại chỗ của thuật toán.

Quicksort như được viết ở đây yêu cầu lưu trữ bổ sung , trong đó là độ sâu của đệ quy (phụ thuộc vào chất lượng trục) và là kích thước phần tử. Yêu cầu kích thước ngăn xếp có thể được cải thiện: có hai cuộc gọi đệ quy và chúng ta có thể thực hiện cuộc gọi thứ hai thành cuộc gọi đuôi (không tiêu tốn ngăn xếp). Nếu chúng ta luôn thực hiện cuộc gọi đệ quy trên nửa nhỏ hơn trước, thì kích thước ngăn xếp tối đa cho độ dài mảng lên đến thỏa mãn với , vì vậy . Do đó, chúng tôi có thể đạt được yêu cầu lưu trữ bổ sung , bất kể lựa chọn trục.O(d)+sdsnS^(n)S^(m)mn/2S^(n/2)S^(n)lg2(n)S^(1)O(logn)+s

Có rất nhiều thuật toán sắp xếp khác có thể được thực hiện tại chỗ, bao gồm sắp xếp chèn, sắp xếp lựa chọn và sắp xếp heap. Hình thức hợp nhất đơn giản không phải là tại chỗ, nhưng có một biến thể phức tạp hơn .

Cảm ơn Aryabhata đã chỉ ra rằng Quicksort luôn có thể được thực hiện trong ngăn xếp và có một biến thể sắp xếp hợp nhất với cả lưu trữ bổ sung và thời gian chạy .lg(n)O(1)O(nlog(n))


1
@Gilles: Không. Ngay cả khi thuật toán chọn trục luôn chọn mức tối thiểu, chúng ta có thể triển khai Quicksort để sử dụng không gian . (Tuy nhiên, bạn có thể lập luận rằng đó là một biến thể của quicksort). Điều quan trọng là luôn luôn lặp lại trên phân vùng ngắn hơn và đuôi lặp lại trên phân vùng dài hơn. O(logn)
Aryabhata

1
btw, tôi nghĩ rằng việc hợp nhất tại chỗ với cũng đã đạt được. akira.ruc.dk/~keld/teaching/algoritmedesign_f04/Artikler/04/ Yêu cầu một sự hợp nhất tại chỗ thời gian tuyến tính. O(nlogn)
Aryabhata

3

Ngoài câu trả lời của Gilles, bạn cũng có thể đặt Quicksort tại chỗ với mã của mình, nếu bạn sử dụng danh sách được liên kết thay vì mảng. Chỉ cần đảm bảo xóa phần tử khỏi danh sách ban đầu, khi thêm chúng vào một trong các danh sách nhỏ hơn.

Các giả định / đảm bảo mã giả sau đây:

  • Mục đầu tiên trong mỗi danh sách (trong phần sau được gọi là phần đầu) không nhìn chằm chằm vào một phần tử và cho phép giữ các con trỏ tới danh sách nguyên vẹn khi sửa đổi. new listtạo một danh sách bao gồm một đầu với NILcon trỏ tiếp theo.
  • sortđưa một con trỏ đến một cái đầu và trả về một con trỏ đến phần tử cuối cùng của danh sách được sắp xếp. Con trỏ đưa ra làm đối số cũng là phần đầu của danh sách được sắp xếp.
  • Vị trí bộ nhớ của từng thành phần và đầu danh sách vẫn giữ nguyên.

.

sort(list):
    less_cur = less = new list
    equal_cur = equal = new list
    greater_cur = greater = new list
    if list.next != NIL:
        cur = list.next
        pivot = cur.value
        while cur != NIL:
            list.next = cur.next
            if cur.value < pivot:
                less_cur.next = cur
                less_cur = less_cur.next
            if cur.value == pivot:
                equal_cur.next = cur
                equal_cur = equal_cur.next
            if cur.value > pivot:
                equal_cur.next = cur
                equal_cur = equal_cur.next
            cur = cur.next
        less_cur.next = greater_cur.next = NIL
        less_last = sort(less) 
        list.next = less.next
        less_last.next = equal
        greater_last = sort(greater)
        equal_cur.next = greater.next
        return greater_last
    else:
        return list

Mặc dù có một số tối ưu hóa có thể có từ đoạn mã trên, việc triển khai này có chi phí bộ nhớ lớn hơn so với triển khai dựa trên mảng trong câu trả lời của Gilles. Ngoài ra, trong thực tế, vấn đề là các danh sách được liên kết thường ít cục bộ hơn và do đó gây ra số lượng bộ nhớ cache lớn hơn so với việc duy trì cùng một dữ liệu trong một mảng.

Tuy nhiên, việc thực hiện này là lợi thế, nếu bạn phải duy trì các con trỏ tới các phần tử thông qua việc sắp xếp. Cũng tốt để biết về nó, nếu bạn đang lưu trữ dữ liệu của mình trong một danh sách được liên kết vì những lý do không liên quan đến việc sắp xếp. (Nếu việc sắp xếp tại chỗ không đáng lo ngại, việc chuyển đổi giữa danh sách và mảng có thể không còn tồn tại.)


Điều này coi "tại chỗ" là "không phải sao chép các phần tử" nhưng đến lượt nó thực hiện rất nhiều công việc với tất cả các danh sách trỏ đến các phần tử. Tôi sẽ coi việc sử dụng tất cả các cấu trúc dữ liệu phụ trợ này (tất cả kết thúc là rác cần phải được thu thập) là không hoàn toàn "tại chỗ"
Thorbjørn Ravn Andersen

@ Thorbjørn Bạn có thể xóa khỏi danh sách ban đầu và nối thêm vào danh sách nhỏ hơn bằng cách thay đổi 2 con trỏ (4 cho danh sách liên kết đôi). Tôi không thấy, nơi này tạo ra rác.
FrankW

Có lẽ bạn nên thể hiện việc thực hiện những gì bạn đề xuất để đảm bảo chúng tôi đang thảo luận về điều tương tự.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Một trong Lisp thông thường là trò lừa cừu từ The Pitmanual . Nó không "đúng chỗ" vì nút danh sách là điểm bắt đầu ban đầu của danh sách có thể không phải là sau đó, nhưng nó không yêu cầu bất kỳ lưu trữ bổ sung nào. Việc phân vùng một cách triệt để sửa đổi cấu trúc danh sách để các danh sách con được xây dựng từ cùng các nút danh sách như danh sách ban đầu. Rõ ràng điều này đòi hỏi khả năng sửa đổi cấu trúc của danh sách, điều mà một số triển khai (ví dụ: giao diện Danh sách của Java) không cung cấp.
Joshua Taylor

@FrankW Tôi không nghĩ thật chính xác khi gọi đây là "tại chỗ" nhiều đến mức "không có thêm dung lượng". Một nút danh sách ở vị trí i trước khi danh sách được sắp xếp có thể không ở vị trí i sau đó.
Joshua Taylor
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.