Quicksort là giải thuật sắp xếp đệ qui dựa trên cách tiếp cận chia để trị (divide and conquer). Quicksort thực hiện các bước lặp chia một mảng thành hai mảng nhỏ hơn và một phần tử khóa (medium). Trong đó, một mảng con chứa các phần tử bé hơn (hoặc bằng) phần tử khóa, mảng con
còn lại chứa các phần tử lớn hơn (hoặc bằng) phần tử khóa. Phần tử khóa được đặt đúng vị trí.
Quá trình này được tiếp tục một cách đệ qui.
Phần này giới thiệu về hai giải thuật song song dựa trên giải thuật sắp xếp Quicksort đó là Quicksort song song (Parallel Quicksort) và Siêu Quicksort (HyperQuicksort).
a. Thuật toán Quicksort song song (Parallel Quicksort).
Để ý rằng trong giải thuật Quicksort tuần tự thì sau khi phân hoạch thành hai mảng con, thì hai mảng con này có thể được sắp xếp độc lập với nhau.
Giả sử ta có một số tiến trình, mỗi tiến trình được thực hiện trên bộ xử lý, để thực hiện giải thuật
song song. Các phần tử cần sắp xếp được lưu vào một mảng chứa trong bộ nhớ toán cục. Một ngăn xếp trong bộ nhớ toàn cục để chứa các con trỏ trái và phải của mảng con chưa được sắp xếp.
Khi một tiến trình rảnh rỗi, nó sẽ lấy các giá trị con trỏ trái và phải của mảng con khỏi ngăn xếp
BITONIC MERGE SORT(HYPERCUBE PROCESSOR ARRAY) Biến toàn cục: d {khoảng cách giữa các phần tử cần so sánh}
Biến cục bộ: a {một trong các phần tử cần sắp xếp }
t {giá trị lấy từ bộ xử lý kế tiếp}
Begin
For i = 0 to m-1 do For j = i downto 1 do
d = 2j
For all Pk where 0 ≤ k≤ 2m-1 do If k mod 2d < d then
t = [k+d]a {lấy giá trị từ bộ xử lý liền kề}
if k mod 2i+2 <2i+1 then
[k+d]a = max (t,a) {sắp xếp từ bé đến lớn}
a = min (a,t) else
[k+d]a = min (t,a) {sắp xếp từ lớn đến bé}
a = max (a,t) endif endif endfor endfor endfor End.
trong bộ nhớ toàn cục. Nếu thành công, bộ xử lý sẽ chia đôi mảng thành hai mảng con (kích thước mỗi mảng con phụ thuộc vào giá trị của phần tử khóa). Trong đó, có một mảng con chứa
các giá trị nhỏ hơn (hoặc bằng) phần tử khóa và mảng con còn lại chứa các phần tử lớn hơn (hoặc
bằng) phần tử khóa.
Sau khi phân hoạch (giống như phân hoạch trong thuật toán tuần tự), tiến trình sẽ đặt giá trị con
trỏ trái và phải của một mảng con vào ngăn xếp trong bộ nhớ toàn cục và lặp lại quá trình phân hoạch cho mảng con còn lại.
Hàm Stack.init() khởi tạo ngăn xếp chứa các con trỏ trái và phải của mảng ban đầu chưa được sắp
xếp.Các hàm Stack.Push() và Stack.Pop() đặt vào và lấy ra các giá trị con trỏ trái và phải của
mảng con trên đỉnh ngăn xếp.
Về độ phức tạp của thuật toán : trong trường hợp xấu nhất thì sau khi phân hoạch, một mảng con
rỗng và mảng còn lại gồm n-1 phần tử, cần đến n-min lần phân hoạch để phân hoạch xong mảng.
Chính vì điều này làm cho Quicksort song song có cùng độ phức tạp với Quicksort tuần tự là O (n2). Tuy nhiên, người ta đã chứng minh và thử nghiệm được rằng tôc độ (speedup) của Quicksort
PARALLEL QUICKSORT()
Biến toàn cục: n {kích thước của mảng}
A[0,..,n-1] {mảng cần sắp xếp}
sorted {số phần tử tại vị trí đã được sắp xếp}
min, partition {mảng con nhỏ nhất được phân hoạch}
Biến cục bộ: bounds {con trỏ trái và phải của mảng chưa được sắp xếp}
median {chỉ số của giá trị khóa}
Begin
Sorted = 0 Stack.Init()
For all Pi where 0 ≤ i < p do bounds =Stack.pop()
while (bounds.low <bounds.high) do
if (bounds.high - bounds.low < min.partition) then InsertionSort (A, bounds.low, bounds.high)
AddToSorted(bounds.high - bounds.low+1) exit while
else
median = Partition(bounds.low, bounds.high) Stack.Push (median+1, bounds.high)
bounds.high = median -1
if bounds.low = bounds.high then AddToSort(1) endif endif endwhile endfor End.
song song tốt hơn so với Quicksort tuần tự (việc chứng minh cụ thể khá phức tạp, xin được không
nêu ở đây).
b. Thuật toán Siêu Quicksort (HyperQuicksort).
Thuật toán Quicksort song song ở trên làm tăng tốc độ xử lý nhờ việc sử dụng nhiều tiến trình
được thực hiện đồng thời trên nhiều bộ vi xử lý khác nhau. Ngoài ra, có một số biến thể của Quicksort song song được phát triển để tăng số lượng các bộ vi xử lý làm việc đồng thời. Ở đây,
ta giới thiệu một trong số giải thuật đó được thực hiện trên máy tính song song với các bộ vi xử lý được tổ chức theo hình siêu khối: thuật toán Siêu Quicksort được trình bày dưới đây.
Cho một dãy các phần tử được phân phối đều (evenly distributed) cho các bộ vi xử lý, ta định
nghĩa một dãy được sắp xếp khi:
(1). Mọi dãy con trên các bộ xử lý được sắp xếp và
(2). Phần tử cuối cùng của dãy con trên bộ xử lý Pi nhỏ hơn hoặc bằng phần tử đầu tiên của dãy con trên bộ xử lý Pi+1 với mọi 0 ≤ i ≤ p-2 . Các phần tử đã được sắp xếp không cần phải phân phối đều trên các bộ xử lý.
Để phát triển thuật toán, ta áp dụng chiến lược: cho mỗi bộ xử lý giải một bài toán con và sử dụng
cách giải tuần tự hiệu quả nhất trên mỗi bộ xử lý, sau đó sử dụng một thuật toán song song có
hiệu quả truyền thông tốt để kết hợp các lời giải bài toán con để thu được lời giải của bài toán ban
đầu.
Trong pha đầu của thuật toán Siêu Quicksort, mỗi bộ xử lý sử dụng giải thuật Quicksort tuần tự để sắp xếp dãy con cục bộ của nó và quá trình này diễn ra đồng thời đối với tất cả các bộ vi xử lý. Khi pha này kết thúc thì điều kiện (1) được thỏa mãn nhưng điều kiện (2) chưa được thỏa mãn. Siêu Quicksort là một giải thuật đệ qui, sử dụng cách tiếp cận chia để trị (divide and conquer) để làm điều kiện (2) được thỏa mãn. Trong mỗi bước của pha 2, một siêu khối được hai siêu khối
con (subcubes). Mỗi bộ xử lý gửi các phần tử của mình tới bộ xử lý khác trong một siêu khối con
khác, thì mỗi bộ xử lý sẽ trộn các phần tử của dãy con nó giữ với các phần tử của dãy con nó nhận được từ bộ xử lý bên cạnh.Kết quả của thao tác chia và trộn (split and merge) để chia một
siêu khối với dãy đã được sắp xếp thành hai siêu khối với 2 dãy con đã được sắp xếp, và giá trị
lớn nhất trong siêu khối dưới sẽ bé hơn phần tử nhỏ nhất của siêu khối trên. Sau d bước chia và trộn, thì từ một mạng siệu khối với 2d bộ xử lý sẽ được chia thành 2d siêu khối con với mỗi siêu khối là một bộ xử lý, và điều kiện (2) được thỏa mãn.
Bước chia và trộn chia một siêu khối d chiều thành hai siêu khối d-1 chiều. Để ý rằng các phần tử
trên mỗi bộ xử lý đã được sắp xếp. Bộ xử lý được chỉ định trong siêu khối d chiều sẽ phân phát
(broadcast) giá trị khóa (median value) tới 2d-1 bộ xử lý khác trong siêu khối. Mỗi bộ xử lý sẽ sử
dụng giá trị này để chia dãy con mà nó đang chứa thành hai nửa con sao cho các phần tử của nửa dưới nhỏ hơn hoặc bằng giá trị khóa và các phần tử thuộc nửa trên lớn hơn giá trị khóa. Mỗi bộ
xử lý Pi trong nửa dưới của siêu khối sẽ gửi nửa dãy con trên (nửa chứa các phần tử lớn hơn giá
trị khóa) tới bộ xử lý “partner” của nó ở trên nửa trên của siêu khối, đó là bộ xử lý Pi 2d-1 trong
đó là phép tính loại trừ OR (eXclusive OR). Mỗi bộ xử lý Pi trong nửa trên của siêu khối sẽ
gửi nửa dãy con dưới (nửa chứa các phần tử nhỏ hơn hoặc bằng giá trị khóa) tới bộ xử lý
“partner” của nó ở trên nửa dưới của siêu khối, đó là bộ xử lý Pi 2d-1 trong đó là phép tính loại trừ OR.
Mỗi bộ xử lý sẽ trộn dãy phần tử mà nó nhận được từ “partner” với dãy các phần tử mà nó đang
chứa để tạo thành một dãy mới đã được sắp xếp. Vì vậy, tất cả các phần tử nhỏ hơn hoặc bằng
phần tử khóa sẽ được chứa trong các bộ xử lý của siêu khối d-1 chiều phía dưới và tất cả các phần
tử lớn hơn phần tử khóa sẽ được chứa trong các bộ xử lý trên siêu khối d-1 chiều phía trên.
Dưới đây là một minh họa:
Hình 2.27. Một minh họa về các bước thực hiện giải thuật Siêu Quicksort
Giả sử tại thời điểm ban đầu mỗi bộ xử lý có n phần tử. Trong pha ban đầu, độ phức tạp về thời
gian kỳ vọng là O (nlogn). Giả sử rằng mỗi bộ xử lý giữ lại n/2 phần tử và chuyển n/2 phần tử
khác cho bộ xử lý partner sau mỗi bước chia và trộn, số phép so sánh kỳ vọng cần để trộn hai dãy
con đã được sắp xếp thành một dãy đã được sắp xếp là O(n). Do thao tác chia và trộn được thực
hiện trên các siêu khối d, d-1,…, 1 chiều, nên số phép so sánh kỳ vọng là O(nd). Nên số phép so
sánh kỳ vọng của thuật toán Siêu Quicksort là O (n(logn+d)).
HYPERQUICKSORT()
Biến toàn cục: n {số phần tử trên mỗi bộ xử lý}
d {chiều của siêu khối}
i {số chiều của siêu khối hiên tại}
Biến cục bộ: logicalNum {chỉ số của bộ xử lý}
partner {bộ xử lý partner để trao đổi}
root {bộ xử lý gốc của siêu khôi hiện tại}
splitter {giá trị khóa}
Begin
For all Pi where 0 ≤ i < 2ddo
Sắp xếp các phần tử sử dụng Quicksort tuần tự
If d>0 then
For I = d downto 1 do
root = gốc của khối i chiều chứa bộ xử lý logicalNum
if logicalNum = root then
splitter = giá trị trung bình của danh sách lưu bởi bộ xử lý logicalNum
endif
Bộ xử lý root phân phát splitter tới các bộ xử lý trong siêu khối i chiều
Sử dụng splitter để phân hoạch các dãy phần tử trong các bộ xử lý
Partner = logicalNum 2d-1 if logicalNum <partner then
Gửi danh sách chứa các giá trị bé hơn khóa tới bộ xử lý partner
Nhận danh sách chứa các giá trị lớn hơn khóa từ partner
Else
Gửi danh sách chứa các giá trị lớn hơn khóa tới bộ xử lý partner
Nhận danh sách chứa các giá trị bé hơn khóa từ partner
endif
Trộn hai dãy con trên mỗi bộ xử lý thành một dãy đã được sắp xếp
endfor endif
endfor