Quicksort với Python
Trong cuộc sống thực, chúng ta nên luôn sử dụng loại nội trang do Python cung cấp. Tuy nhiên, hiểu được thuật toán quicksort là hướng dẫn.
Mục tiêu của tôi ở đây là chia nhỏ chủ đề sao cho người đọc dễ hiểu và có thể nhân rộng mà không cần phải quay lại tài liệu tham khảo.
Thuật toán quicksort về cơ bản là như sau:
- Chọn một điểm dữ liệu xoay.
- Di chuyển tất cả các điểm dữ liệu nhỏ hơn (bên dưới) trục quay đến vị trí bên dưới trục - di chuyển những điểm lớn hơn hoặc bằng (bên trên) trục lên vị trí phía trên nó.
- Áp dụng thuật toán cho các khu vực trên và dưới trục
Nếu dữ liệu được phân phối ngẫu nhiên, việc chọn điểm dữ liệu đầu tiên làm trục quay tương đương với lựa chọn ngẫu nhiên.
Ví dụ có thể đọc được:
Đầu tiên, hãy xem một ví dụ có thể đọc được sử dụng chú thích và tên biến để trỏ đến các giá trị trung gian:
def quicksort(xs):
"""Given indexable and slicable iterable, return a sorted list"""
if xs:
pivot = xs[0]
below = [i for i in xs[1:] if i < pivot]
above = [i for i in xs[1:] if i >= pivot]
return quicksort(below) + [pivot] + quicksort(above)
else:
return xs
Để điều chỉnh lại thuật toán và mã được trình bày ở đây - chúng tôi di chuyển các giá trị phía trên trục xoay sang bên phải và các giá trị bên dưới trục xoay sang bên trái, rồi chuyển các phân vùng đó đến cùng một hàm để được sắp xếp thêm.
Chơi gôn:
Điều này có thể được đánh gôn tới 88 ký tự:
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Để xem cách chúng tôi đạt được điều đó, trước tiên hãy lấy ví dụ có thể đọc được của chúng tôi, xóa nhận xét và chuỗi doc, rồi tìm trục xoay tại chỗ:
def quicksort(xs):
if xs:
below = [i for i in xs[1:] if i < xs[0]]
above = [i for i in xs[1:] if i >= xs[0]]
return quicksort(below) + [xs[0]] + quicksort(above)
else:
return xs
Bây giờ hãy tìm bên dưới và bên trên, tại chỗ:
def quicksort(xs):
if xs:
return (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
else:
return xs
Bây giờ, khi biết rằng and
trả về phần tử trước nếu sai, ngược lại nếu đúng, nó sẽ đánh giá và trả về phần tử sau, chúng ta có:
def quicksort(xs):
return xs and (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
Vì lambdas trả về một biểu thức duy nhất và chúng tôi đã đơn giản hóa thành một biểu thức duy nhất (mặc dù nó ngày càng khó đọc hơn), chúng tôi có thể sử dụng lambda:
quicksort = lambda xs: (quicksort([i for i in xs[1:] if i < xs[0]] )
+ [xs[0]]
+ quicksort([i for i in xs[1:] if i >= xs[0]]))
Và để giảm bớt cho ví dụ của chúng tôi, hãy rút ngắn tên hàm và biến thành một chữ cái và loại bỏ khoảng trắng không bắt buộc.
q=lambda x:x and q([i for i in x[1:]if i<=x[0]])+[x[0]]+q([i for i in x[1:]if i>x[0]])
Lưu ý rằng lambda này, giống như hầu hết các mã chơi gôn khác, là phong cách khá tệ.
Quicksort tại chỗ, sử dụng lược đồ Phân vùng Hoare
Việc thực hiện trước tạo ra rất nhiều danh sách bổ sung không cần thiết. Nếu chúng tôi có thể làm điều này tại chỗ, chúng tôi sẽ tránh lãng phí không gian.
Việc triển khai bên dưới sử dụng lược đồ phân vùng Hoare, bạn có thể đọc thêm trên wikipedia (nhưng rõ ràng chúng tôi đã loại bỏ tối đa 4 phép tính thừa cho mỗi partition()
cuộc gọi bằng cách sử dụng ngữ nghĩa vòng lặp while thay vì do-while và chuyển các bước thu hẹp đến cuối vòng lặp while bên ngoài.).
def quicksort(a_list):
"""Hoare partition scheme, see https://en.wikipedia.org/wiki/Quicksort"""
def _quicksort(a_list, low, high):
if low < high:
p = partition(a_list, low, high)
_quicksort(a_list, low, p)
_quicksort(a_list, p+1, high)
def partition(a_list, low, high):
pivot = a_list[low]
while True:
while a_list[low] < pivot:
low += 1
while a_list[high] > pivot:
high -= 1
if low >= high:
return high
a_list[low], a_list[high] = a_list[high], a_list[low]
low += 1
high -= 1
_quicksort(a_list, 0, len(a_list)-1)
return a_list
Không chắc liệu tôi đã kiểm tra kỹ lưỡng chưa:
def main():
assert quicksort([1]) == [1]
assert quicksort([1,2]) == [1,2]
assert quicksort([1,2,3]) == [1,2,3]
assert quicksort([1,2,3,4]) == [1,2,3,4]
assert quicksort([2,1,3,4]) == [1,2,3,4]
assert quicksort([1,3,2,4]) == [1,2,3,4]
assert quicksort([1,2,4,3]) == [1,2,3,4]
assert quicksort([2,1,1,1]) == [1,1,1,2]
assert quicksort([1,2,1,1]) == [1,1,1,2]
assert quicksort([1,1,2,1]) == [1,1,1,2]
assert quicksort([1,1,1,2]) == [1,1,1,2]
Phần kết luận
Thuật toán này thường được dạy trong các khóa học khoa học máy tính và được yêu cầu trong các cuộc phỏng vấn xin việc. Nó giúp chúng ta nghĩ về đệ quy và chia để trị.
Quicksort không thực tế lắm trong Python vì thuật toán timsort tích hợp của chúng tôi khá hiệu quả và chúng tôi có giới hạn đệ quy. Chúng tôi mong đợi sắp xếp danh sách tại chỗ với list.sort
hoặc tạo danh sách được sắp xếp mới với sorted
- cả hai đều lấy một key
và reverse
đối số.
my_list = list1 + list2 + ...
. Hoặc giải nén danh sách thành danh sách mớimy_list = [*list1, *list2]