Tải bản đầy đủ (.docx) (21 trang)

Tổng quan về thuật toán sắp xếp

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (219.4 KB, 21 trang )

Tổng quan về thuật toán sắp xếp
Trước tiên để hiểu rõ về thuật toán sắp xếp mình xin nói qua về nó trước
Một trong những vấn đề nền tảng của khoa học máy tính là sắp xếp một tập các phần tử
cho trước theo thứ tự nào đó. Có rất nhiều các giải pháp cho vấn đề này, được biết đến
như là các thuật toán sắp xếp (sorting algorithms). Bên cạnh các thuật toán sắp xếp đơn
giản và rất rỏ ràng, như là sắp xếp nổi bọt (bubble sort). Một số khác, như phương pháp
sắp xếp nhanh (quick sort) thì lại rất phức tạp nhưng đổi lại có kết quả thực thi nhanh hơn
một cách đáng kể.
Dưới đây là liên kết tới các mô tả, phân tích, và mã nguồn cho 7 thuật toán sắp xếp quan
trọng, phổ biến nhất hiện nay.
Bubble sort
Heap sort
Insertion sort
Merge sort
Quick sort
Selection sort
Shell sort
Những thuật toán sắp xếp quen thuộc này có thể được chia thành hai nhóm dựa theo độ
phức tạp của chúng. Độ phức tạp của thuật toán (Algorithmic complexity) là một chủ đề
khá rắc rối đòi hỏi sự tưởng tượng mà sẽ tốn nhiều thời gian để giải thích, nhưng ở đây có
thể hiểu thông qua mối tương quan trực tiếp giữa độ phức tạp của thuật toán và hiệu quả
của nó. Độ phức tạp của thuật toán thường được kí hiệu bởi một hàm O, trong đó O biểu
diễn độ phức tạp của thuật toán đi kèm với một giá trị n biểu diễn kích thước của số lần
chạy tối đa mà thuật toán đó dựa vào để xử lý trên dữ liệu.
Ví dụ, O(n) có nghĩa là thuật toán có độ phức tạp tuyến tính. Cụ thể hơn , nó sẽ mất thời
gian gấp 10 lần cho việc xử lý trên tập dữ liệu có 100 phần tử so với tập chỉ có 10 phần tử
(10 * 10 = 100). Nếu độ phức tạp là O(n2) (quadratic complexity), thì nó sẽ phải tiêu tốn
thời gian gấp 100 lần để xử lý trên tập 100 phần tử so với tập dữ liệu chỉ gồm 10 phần tử.
Hai nhóm thuật toán sắp xếp được phân như sau: nhóm thứ nhất có độ phức tạp là O(n2)
bao gồm bubble, insertion, selection, và shell sorts; Nhóm thứ hai có độ phức tạp là O(n
log n) gồm heap, merge, và quick sorts.


Bên cạnh độ phức tạp của thuật toán, tốc độ của các thuật toán sắp xếp có thể được so
sánh dựa vào kinh nghiệm có được từ việc thử trên các tập dữ liệu. Vì tốc độ sắp xếp có
thể thay đổi rất nhiều tùy theo đặc điểm của dữ liệu, nên để các kết quả thống kê chính
xác dựa trên kinh nghiệm đòi hỏi việc chạy các thuật toán nhiều lần trên các dữ liệu khác
nhau và tính trung bình. Thông thường tập dữ liệu kiểm tra được tạo ngẫu nhiên.
Trong các biểu đồ thể hiện mức độ hiệu quả của thuật toán dưới đây, đường thấp nhất là
"tốt nhất". Ghi nhớ rằng "tốt nhất" ở đây là một khái niệm không tuyệt đối vì nó còn tùy
vào trường hợp sử dụng của bạn - nếu nhìn biểu đồ bạn sẽ thấy quick sort có lẽ là thuật
toán nhanh nhất, nhưng nếu sử dụng nó để sắp xếp một tập 20 phần tử thì cũng giống như
vác đại bác ra bắn ruồi
Đồ thị trên đã thể hiện khá rỏ, Bubble sort là giải pháp cực kì không hiệu quả, và
shell sort là sự cải tiến tạo ra cuộc bứt phá hết sức ngoạn mục. Chú ý rằng vào đường
ngang đầu tiên của khu vực đồ thị thể hiện thời gian 100 giây- bạn sẽ thấy rằng không có
thuật toán nào trong số này mà bạn muốn sử dụng cho tập dữ liệu lớn của một ứng dụng
tương tác. Ngay cả khi dùng shell sort, người sử dụng cũng có nguy cơ ngồi bấm móng
tay nếu bạn cố gắng sắp xếp nhiều hơn 10,000 phần tử.
Nhìn từ phương diện tươi sáng hơn, tất cả những thuật toán thuộc nhóm O(n2) đều đơn
giản một cách không ngờ (có thể shell sort là một ngoại lệ hơi phức tạp hơn). Với những
chương trình dùng kiểm tra nhanh, những mẩu thử nghiệm gấp, hay các phần mềm dành
cho người sử dụng nội bộ, chúng không phải là những lựa chọn tồi nếu bạn không đặt
quá nặng về hiệu năng.
Trong trường hợp tốc độ là ưu tiên hàng đầu, những giải thuật thuộc nhóm O(n log n) nên
được sử dụng. Chú ý rằng thời gian trong đồ thị ngay trên đây được đo theo từng phần 10
của giây thay vì từng trăm giây như đồ thị của nhóm O(n2).
Tuy nhiên mọi thứ thì không thật sự dễ dàng với các ứng dụng thực tiễn, bạn phải đứng
trước sự lựa chọn cân bằng (trade-offs) giữa các yếu tố. Những thuật toán thuộc nhóm
O(n log n) thì rất nhanh, nhưng tốc độ đó có được do phải trả giá cho sự phức tạp trong
triển khai. Trong trường hợp đệ quy, các cấu trúc dữ liệu nâng cao, mảng đa chiều - việc
sử dụng những thuật toán này sẽ tạo ra nhiều vấn đề phát sinh rất khó chịu. ( nhưng mà
nên nhớ thuật có độ phức tạp O(n log n) nó khó nhưng mà nó nhanh)

Post added at 01:36 PM Previous post was at 01:30 PM
Bây giờ vào phần chính thôi
Trước hết mình nói qua về thuật heap sort
Heap sort là thuật toán chậm nhất trong số các thuật toán sắp xếp thuộc nhóm có độ phức
tạp O(n log n), nhưng không giống như Merge và Quick sort nó không đòi hỏi sự đệ quy
phức tạp hay nhiều mảng dữ liệu để xử lý. Điều này làm cho nó trở thành một lựa chọn
hấp dẫn với tập dữ liệu rất lớn hàng triệu phần tử. Tuy nhiên sự lựa chọn thích hợp lúc
nào cũng còn tùy thuộc vào kết cấu hạ tầng và mục tiêu của ứng dụng.
Heap sort hoạt động cũng như sự gợi ý trong tên gọi - nó bắt đầu bằng việc xây dựng một
heap out của tập dữ liệu, và sau đó loại phần tử lớn nhất và đặt nó vào vị trí cuối của
mảng được sắp xếp. Sau việc xóa phần tử lớn nhất, nó tái cấu trúc heap và di chuyển
phần tử lớn nhất kế tiếp còn lại và đặt nó vào vị trí mở kế cuối của mảng được sắp xếp.
Thao tác này được lặp lại cho tới khi không còn phần tử bên trái trong heap và mảng
được sắp xếp đã đầy. Cách triển khai căn bản đòi hỏi hai mảng dữ liệu - một giữ heap và
một giữ những phần tử đã được sắp xếp.
Việc thực hiện tiến trình sắp xếp chỉ trong một mảng duy nhất nhằm tiết kiệm không gian
của mảng thứ hai là cần thiết, giải thuật sau đây dùng một ít kỉ xảo để chỉ sử dụng cùng
một mảng cho lưu trử Heap và mảng đã được sắp xếp. Bất cứ khi nào một phần tử được
xóa khỏi Heap, nó giải phóng một không gian lưu trử ở cuối mảng mà phần tử bị xóa có
thể được đặt vào.
0
1
2
3
4
<SPAN style="FONT-FAMILY: arial">//doi cho </SPAN>
<SPAN style="FONT-FAMILY: arial">void Swap(int
&a,int &b)
{
int t; t=a; a=b; b=t;

}
//thu tuc hieu chinh heap
void Shift(int a[],int r,int l)
{ int i,j,x; i=l; j=2*i; //phan tu lien doi
x=a[i];
while(j<=r)
{
if (j<r)
if(a[j]<a[j+1]) //xac dinh phan tu lien doi lon hon
j++;
if(a[j]<=x) break; //thoa quan he lien doi,dung
else
{
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
a[i]=a[j];
i=j;

j=2*i;
a[i]=x;
}
}
}
//hieu chinh day ban dau thanh heap
void CreateHeap(int a[],int n)
{
int l; l=(n-1)/2;
while(l>=0)
{
Shift(a,n-1,l);
l ;
}
}
//phuong phap cay
void HeapSort(int a[],int n)
{ int r;
CreateHeap(a,n);
r=n-1; //vi tri phan tu cuoi mang
while(r>0)
0
151
{
Swap(a[0],a[r]);
r ;
Shift(a,r,0);
}
}</SPAN>
Thuật toán sắp xếp nổi bọt (Bubble sort)

Thuật toán sắp xếp nổi bọt là một trong các thuật toán phổ biến nhất với những lập trình
viên mới bắt đầu sự nghiệp. Thuật toán này vận hành dựa trên việc so sánh liên tục các
phần tử cạnh nhau của một mảng các số chưa được sắp xếp và cho phép phần tử nào nhẹ
hơn sẽ được nổi lên trên (chuyển vị trí sang trái hoặc phải tùy theo việc muốn sắp xếp
theo thứ tự tăng dần hay giảm dần). Bubble sort là thuật toán dễ triển khai nhưng cũng là
một trong các thuật toán sắp xếp có hiệu suất kém nhất (độ phức tạp lên tới O(n2)). Để có
thể xem minh họa về thuật toán này, bạn có thể truy cập trang Algolist (trang web chuyên
minh họa về các thuật toán). Tuy vậy, có một cách hay hơn và dễ hiểu hơn để biết về
thuật toán nổi tiếng (về sự đơn giản và chậm chạp) này là xem video dưới đây do các
thành viên thuộc trường đại học Sapientia (Romania) trình diễn :
Giải thuật
Sắp xếp từ trên xuống
Giả sử dãy cần sắp xếp có n phần tử. Khi tiến hành từ trên xuống, ta so sánh hai phần tử
đầu, nếu phần tử đứng trước lớn hơn phần tử đứng sau thì đổi chỗ chúng cho nhau. Tiếp
tục làm như vậy với cặp phần tử thứ hai và thứ ba và tiếp tục cho đến cuối tập hợp dữ
liệu, nghĩa là so sánh (và đổi chỗ nếu cần) phần tử thứ n-1 với phần tử thứ n. Sau bước
này phần tử cuối cùng chính là phần tử lớn nhất của dãy.
Sau đó, quay lại so sánh (và đổi chố nếu cần) hai phần tử đầu cho đến khi gặp phần tử thứ
n-2
Ghi chú: Nếu trong một lần duyệt, không phải đổi chỗ bất cứ cặp phần tử nào thì danh
sách đã được sắp xếp xong.
Sắp xếp từ dưới lên
Sắp xếp từ dưới lên so sánh (và đổi chỗ nếu cần) bắt đầu từ việc so sánh cặp phần tử thứ
n-1 và n. Tiếp theo là so sánh cặp phần tử thứ n-2 và n-1, cho đến khi so sánh và đổi
chỗ cặp phần tử thứ nhất và thứ hai. Sau bước này phần tử nhỏ nhất đã được nổi lên vị trí
trên cùng (nó giống như hình ảnh của các "bọt" khí nhẹ hơn được nổi lên trên). Tiếp theo
tiến hành với các phần tử từ thứ 2 đến thứ n.
Mô tả:

void BubbleSort(int *c, int n) // Sap xep noi bot

{
for (int i = 0; i < n - 1; ++i)
for (int j = n - 1; j > i; j)
if (c[j] < c[j - 1])
{
int t = c[j]; // t: bien tam
c[j] = c[j - 1];
c[j - 1] = t;
}
}
Thuật toán sắp xếp Nhanh (Quick-sort)
Sắp xếp nhanh (Quicksort), còn được gọi là sắp xếp kiểu phân chia (part sort) là một
thuật toán sắp xếp phát triển bởi C.A.R. Hoare, dựa trên phép phân chia danh sách được
sắp thành hai danh sách con. Khác với sắp xếp trộn, chia danh sách cần sắp xếp a[1 n]
thành hai danh sách con có kích thước tương đối bằng nhau nhờ chỉ số đứng giữa danh
sách, sắp xếp nhanh chia nó thành hai danh sách bằng cách so sánh từng phần tử của
danh sách với một phần tử được chọn được gọi là phần tử chốt. Những phần tử nhỏ hơn
hoặc bằng phần tử chốt được đưa về phía trước và nằm trong danh sách con thứ nhất, các
phần tử lớn hơn chốt được đưa về phía sau và thuộc danh sách đứng sau. Cứ tiếp tục chia
như vậy tới khi các danh sách con đều có độ dài bằng 1.
Phần tử chốt (pivot)
Kỹ thuật chọn phần tử chốt ảnh hưởng khá nhiều đến khả năng rơi vào các vòng lặp vô
hạn đối với các trường hợp đặc biệt. Tốt nhất là chọn phần tử chốt là trung vị của danh
sách. Khi đó sau log2(n) lần phân chia ta sẽ đạt tới kích thước danh sách bằng 1. Tuy
nhiên điều đó rất khó. Có các cách chọn phần tử chốt như sau:
- Chọn phần tử đứng đầu hoặc đứng cuối làm phần tử chốt.
- Chọn phần tử đứng giữa danh sách làm phần tử chốt.
- Chọn phần tử trung vị trong 3 phần tử đứng đầu, đứng giữa và đứng cuối làm phần tử
chốt.
- Chọn phần tử ngẫu nhiên làm phần tử chốt. (Cách này có thể dẫn đến khả năng rơi vào

các trường hợp đặc biệt)
Thuật giải
Sau khi phần tử chốt được chọn giải thuật phân chia nên tiến hành như thế nào?
- Một giải pháp đơn giản nhất cho vấn đề này là duyệt từ đầu đến cuối lần lượt so sánh
các phần tử của danh sách với phần tử chốt. Theo cách này, ta phải tiến hành n phép so
sánh, ngoài ra còn phải dành n đơn vị bộ nhớ để lưu giữ các giá trị trung gian.
- Một giải pháp khác được đề nghị là duyệt theo hai đường. Một đường từ đầu danh sách,
một đường từ cuối danh sách. Theo cách này, ta tìm phần tử đầu tiên tính từ trái lớn hơn
phần tử chốt và phần tử đầu tiên phía phải nhỏ hơn hoặc bằng phần tử chốt rồi đổi chỗ
cho nhau. Tiếp tục như vậy cho đến khi hai đường gặp nhau.
- Để có thể gọi đệ quy ta xét bài toán phân chia một danh sách con của a: a[k1,k2] thành
hai danh sách.
0
1
2
3
4
void QuickSort(int a[m], int l, int r)
{
int v = a[(l+r)/2]; //chọn phần tử ở giữa đoạn làm chốt
int i = l;
int j = r;
int temp;
do
{
while (a[i] < v) i++; //tìm phần tử phía đầu đoạn mà ≥ v
while (a[j] > v) j ; //tìm phần tử phía cuối đoạn mà ≤ v
//lúc này: a[i] ≥ v ≥ a[j]
if (i<=j)
{

if (i<j)
{
temp = a[i];
a[i] = a[j];
5
6
7
8
9
0
12
a[j] = temp;
}
//sau khi hoán đổi, ta có: a[i] ≤ v ≤ a[j]
i++;
j ;
}
} while (i<=j);
//lúc này, a[l] a[j] a[i] a[r], nghĩa là: l ≤ j ≤ i ≤ r
if (l<j) QuickSort(a,l,j); //nếu a[l] a[j] là 1 đoạn (nhiều hơn 1 phần
tử) thì
if (i<r) QuickSort(a,i,r); //nếu a[i] a[r] là 1 đoạn (nhiều hơn 1 phần
tử) thì
}
Độ phức tạp
-Về thời gian: O(nlog n)
- Về dữ liệu: Khác nhau tùy vào cách hiện thực
Thuật toán sắp xếp Chọn (Selection sort)
Giải thuật
Chọn phần tử nhỏ nhất trong n phần tử ban đầu, đưa phần tử này về vị trí đúng là đầu tiên

của dãy hiện hành. Sau đó không quan tâm đến nó nữa, xem dãy hiện hành chỉ còn n-1
phần tử của dãy ban đầu, bắt đầu từ vị trí thứ 2. Lặp lại quá trình trên cho dãy hiện hành
đến khi dãy hiện hành chỉ còn 1 phần tử. Dãy ban đầu có n phần tử, vậy tóm tắt ý tưởng
thuật toán là thực hiện n-1 lượt việc đưa phần tử nhỏ nhất trong dãy hiện hành về vị trí
đúng ở đầu dãy.
Các bước tiến hành như sau:
Bước 1: i=1
Bước 2: Tìm phần tử a[min] nhỏ nhất trong dãy hiện hành từ a[i] đến a[n]
Bước 3: Hoán vị a[min] và a[i]
Bước 4: Nếu i<=n-1 thì i=i+1; Lặp lại bước 2
Ngược lại: Dừng. n-1 phần tử đã nằm đúng vị trí.
Ví dụ: Cho dãy a = (12,2,8,5,1,6,4,15)
12 2 8 5 1 6 4 15
Bước 1: 1 2 8 5 12 6 4 15
Bước 2: 1 2 8 5 12 6 4 15
Bước 3: 1 2 4 5 12 6 8 15
Bước 4: 1 2 4 5 12 6 8 15
Bước 5: 1 2 4 5 6 12 8 15
Bước 6: 1 2 4 5 6 8 12 15
Bước 7: 1 2 4 5 6 8 12 15
void SelectionSort(int *c, int n) // Sap xep chon
{
for (int i = 0; i < n - 1; ++i)
{
// Tim phan tu co gia tri nho nhat (m: chi so phan tu
0
1
2
3
nho nhat)

int m = i;
for (int j = i + 1; j < n; ++j)
if (c[j] < c[m]) // <= dieu kien sap xep
m = j;
// Doi cho cho phan tu dau
int t = c[i]; // t: bien tam
c[i] = c[m];
c[m] = t;
}
}
Thuật toán sắp xếp Chèn (Insertion sort)
Thuật toán sắp xếp chèn làm việc cũng giống như tên gọi - Nó thực hiện việc quét một
tập dữ liệu, với mỗi phân tử, thủ tục kiểm tra và chèn phần tử đó vào vị trí thích hợp
trong danh sách đích (chứa các phần tử đứng trước nó đã được sắp xếp) được tiến hành.
Phương pháp dễ dàng nhất để thực hiện thuật toán này là dùng hai vùng chứa dữ liệu
khác nhau - tập dữ liệu nguồn và tập dữ liệu mà các phần tử đã sắp xếp được chèn vào.
Tuy nhiên để tiết kiệm bộ nhớ, hầu hết các ứng dụng đều chỉ sử dụng một tập dữ liệu duy
nhất. Thuật toán được tiến hành bằng cách dịch chuyển phân tử hiện tại đang xét tuần tự
qua những phân tử ở vùng dữ liệu phía trước đã được sắp xếp, phép hoán vị nó với phần
tử liền kề được thực hiện một cách lặp lại cho tới khi tiến tới được vị trí thích hợp.
Do với mỗi phân tử, insertion sort cần thực hiện so sánh với các phần tử trước nó nên tối
đa số lần duyệt dữ liệu là !N. Vì vậy cũng giống như Bubble sort, Insertion sort được coi
là có độ phức tạp O(n2). Mặc dù có cùng độ phức tạp, Insertion sort hiệu quả hơn gần
như hai lần so với Bubble sort, tuy nhiên vẫn không hiệu quả với tập dữ liệu lớn.
Mô tả:
1
2
3
4
5

6
7
8
9
10
11
12
void doicho(int &a, int &b)
{
int t; t=a; a=b; b=t;
}
void sapxepchen(int a[], int n)
{
int x, j;
for(int i = 0; i < n; i++)
{
x = a[i];
j = i-1;
while ((j >= 0) && (a[j] > x))
13
14
15
16
17
18
19
{
a[j+1] = a[j];
j ;
}

a[j+1] = x;
}
}
Thuật toán Shell-sort
Được phát minh bởi Donald Shell vào năm 1959, Shell sort là thuật toán hiệu quả nhất
trong nhóm các thuật toán sắp xếp có độ phức tạp O(n2). Đương nhiên, Shell sort cũng
phức tạp nhất trong các thuật giải thuộc lớp này.
Shell sort là sự cải tiến của Insertion sort dựa vào hai nhận xét sau đây:
Insertion sort sẽ rất hiệu quả nếu dữ liệu đầu vào hầu như đã được sắp xếp (đã được xếp
trong từng khoảng cục bộ).
Insertion sort hoạt động kém hiệu quả vì nó di chuyển các giá trị phần tử mỗ i lần chỉ một
vị trí.
Shell sort là môt thuật toán sắp xếp với số gia giãm dần, thường được biết đến như là
"comb sort" dành cho những khối chương trình hỗn độn chưa được làm sạch. Thuật toán
tạo ra nhiều luồng chạy duyệt qua danh sách dữ liệu, và mỗi lần sắp xếp một số trong
những tập dữ liệu được định kích cở như nhau (tập phân đoạn được tách ra từ tập dữ liệu
ban đầu) dùng Insertion sort. Sau mỗi lần duyệt qua hết bộ dữ liệu thông qua các phân
đoạn (có kích thước giãm dần >= 1) , kích thước của tập được sắp xếp trở nên lớn hơn,
cho tới khi nó chứa toàn bộ danh sách dữ liệu. (Chú ý rằng do kích thước của tập tăng
lên, số lượng tập dữ liệu cần được sắp xếp sẽ giảm dần) Điều này làm cho Insertion sort
đạt tới trường hợp tối ưu nhất, chạy mỗi vòng lặp với độ phức tạp tiến tới O(n).
Các phần tử chứa trong mỗi tập phân đoạn thì không liền kề nhau - cụ thể hơn, nếu có i
tập phân đoạn thì mỗi tập chứa các phần tử thứ i liên tiếp kể từ điểm xuất phát của tập đó.
Ví dụ, nếu có 3 tập phân đoạn thì tập đầu tiên sẽ chứa những phần tử tại các vị trí 1, 4, 7
và cứ như thế tiếp tục. Tập thứ hai sẽ chứa những phần tử tại các vị trí 2, 5, 8, và cứ thế
tiếp tục về sau; trong khi tập thứ ba sẽ chứa các phần tử ở vị trí 3, 6, 9, và tương tự kế đó.
Kích thước của tập dữ liệu được sử dụng trong mỗi lần duyệt dữ liệu có ảnh hưỡng lớn
tới hiệu quả của việc sắp xếp. Vài nhà nghiên cứu hàng đầu về khoa học máy tính, trong
đó có cả Donald Knuth và Robert Sedgewick đã phát triển những phiên bản phức tạp hơn
cho shell sort nhằm nâng cao hiệu quả tính toán bằng việc xử lý một cách cẩn thận những

tập phân đoạn sao cho có kích thước tốt nhất để dùng cho danh sách dữ liệu được cho.
1
2
3
4
5
6
7
8
9
10
11
void ShellSort(int a[],int n)
{
int h[]={5,3,1},k=3;
int buoc,x,t;
for(int j=0;j<k;j++)
{
buoc=h[j];
for(int i=buoc;i<n;i++)
{
x=a[i];
t=i-buoc;
12
13
14
15
16
17
18

19
while((x<a[t]) && (t>=0))
{
a[t+buoc]=a[t];
t=t-buoc;
}
a[t+buoc]=x;
}
}
}
Thuật toán sắp xếp trộn (Merge-sort)
Trong khoa học máy tính, sắp xếp trộn (merge sort) là một thuật toán sắp xếp để
sắp xếp các danh sách (hoặc bất kỳ cấu trúc dữ liệu nào có thể truy cập tuần tự, v.d. luồng
tập tin) theo một trật tự nào đó. Thuật toán này là một ví dụ tương đối điển hình của lối
thuật toán chia để trị. Nó được xếp vào thể loại sắp xếp so sánh.
Giải thuật
Các bước thực hiện thuật toán trộn tự nhiên như sau:
Bước 1 : // Chuẩn bị r = 0; // r dùng để đếm số đường chạy
Bước 2 : Tách dãy a1, a2, , an thành 2 dãy b, c theo nguyên tắc luân phiên từng đường
chạy:
Bước 2.1 : Phân phối cho b một đường chạy; r = r+1; Nếu a còn phần tử chưa phân phối
Phân phối cho c một đường chạy; r = r+1;
Bước 2.2 : Nếu a còn phần tử: quay lại bước 2.1;
Bước 3 : Trộn từng cặp đường chạy của 2 dãy b, c vào a
. Bước 4 : Nếu r <= 2 thì trở lại bước 2; Ngược lại: Dừng;
Ưu và nhược điểm
Thuật toán trộn tự nhiên khác thuật toán trộn trực tiếp ở chỗ thay vì luôn cứng nhắc phân
hoạch theo dãy con có chiều dài k, việc phân hoạch sẽ theo đơn vị là đường chạy. ta chỉ
cần biết số đường chạy của a sau lần phân hoạch cuối cùng là có thể biết thời điểm dừng
của thuật toán vì dãy đã có thứ tự là dãy chi có một đường chạy.

Một nhược điểm lớn nữa của thuật toán trộn là khi cài đặt thuật toán đòi hỏi thêm không
gian bộ nhớ để lưu các dãy phụ b, c. Hạn chế này khó chấp nhận trong thực tế vì các dãy
cần sắp xếp thường có kích thước lớn. Vì vậy thuật toán trộn thường được dùng để sắp
xếp các cấu trúc dữ liệu khác phù hợp hơn như danh sách liên kết hoặc file.
1
2
#include<stdio.h>
#include<conio.h>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

25
#include<alloc.h>
void nhap(int *a,int *n);
void xuat(int a[],int n);
void sapxep(int a[],int n);
int *hoanhap(int *a,int *b,int na,int nb);
void quicksort(int a[],int l,int r);
void main()
{
int *a,*b,*c,na,nb,i,j;
//char tena[5],tenb[5];
a=(int *)malloc(1*sizeof(int));
b=(int *)malloc(1*sizeof(int));
c=(int *)malloc(1*sizeof(int));
nhap(a,&na);
nhap(b,&nb);
sapxep(a,na);
sapxep(b,nb);
xuat(a,na);
xuat(b,nb);
c=hoanhap(a,b,na,nb);
xuat(c,na+nb);
getch();
}
26
27
28
29
30
31

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void nhap(int *a,int *n)
{
int i;
//printf("\nMoi ban nhap ten mang: "); gets(ten);
printf("\nMoi ban nhap s phan tu cua mang:
");scanf("%d",n);
for(i=0;i<(*n);i++)
{
printf("Nhap [%d]=",i); scanf("%d",&a[i]);
}
}
void xuat(int *a,int n)
{

int i;
printf("\n");
for(i=0;i<n;i++) printf("%5d",a[i]);
}
void sapxep(int *a,int n)
{
quicksort(a,0,n-1);
}
int * hoanhap(int *a,int *b,int na,int nb)
{
int * k,i=0,j=0;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

68
69
70
71
k=(int *)malloc((na+nb)*sizeof(int));
while((i<na)&&(j<nb))
{ if(a[i]<b[j])
{ k[i+j]=*(a+i);
i++;
}
else
{ k[i+j]=*(b+i);
j++;
}
}
while(i<na)
{ k[i+j]=a[i];
i++;
}
while(j<nb)
{
k[i+j]=b[j];
j++;
}
j=na+nb;
xuat(k,j);
return k;
72
73
74

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
}
void quicksort(int *a,int l, int r)
{
int i=l,j=r,x=a[(l+r)/2];
if (i>=j) return;
while(i<=j)
{
while(a[i]<x) i++;
while(a[j]>x) j ;
if(i<=j)
{
int tg=a[i];
a[i]=a[j];
a[j]=tg;

i++; j ;
}
}
quicksort(a,l,j);
quicksort(a,i,r);
}

×