Professional Documents
Culture Documents
Tailieu 1 BG Sort Search
Tailieu 1 BG Sort Search
1
Lưu ý: Các giải thuật có thể được trình bày khác nhau, tùy theo hướng xử lý (Top Down hoặc Bottom Up, hoặc cách chọn phần tử, chọn vị trí,..) và tùy theo tài
liệu tham khảo.
● A/. Ý tưởng:
Mọi dãy a[0] , a[1] ,..., a[n-1] luôn có (i-1) phần tử đầu tiên a[0] , a[1] ,... ,a[i-2] đã có thứ tự (1 ≤ i).
Ý tưởng chính: Tìm cách chèn phần tử a[i] vào vị trí thích hợp của đoạn đã được sắp để có dãy mới
a[0] , a[1] ,... ,a[i-1] trở nên có thứ tự.
Vị trí chèn này chính là pos thỏa : a[pos-1] ≤ a[i ]< a[pos] , với (1≤pos≤i).
B/. Các bước tiến hành như sau :
Mô tả một cách đơn giản:
● Dãy ban đầu a[0] , a[1] ,..., a[n-1], xem như đã có đoạn gồm một phần tử a[0] đã được sắp.
● Thêm a[1] vào đoạn a[0] sẽ có đoạn a[0] a[1] được sắp
● Thêm a[2] vào đoạn a[0] a[1] để có đoạn a[0] a[1] a[2] được sắp
● Tiếp tục cho đến khi thêm xong a[n-1] vào đoạn a[0] a[1] ...a[n-1] sẽ có dãy a[0] a[1]…....
A[n-1] được sắp.
2
● C/. Cài đặt:
1 19 57 24 12 9 13 45 25
2 19 24 57 12 9 13 45 25
3 12 19 24 57 9 13 45 25
4 9 12 19 24 57 13 45 25
5 9 12 13 19 24 57 45 25
6 9 12 13 19 24 45 57 25
3
7 9 12 13 19 24 25 45 57
5
3/. Giải thuật nổi bọt _ Bubble Sort
A/. Ý tưởng: dùng cách so sánh trực tiếp từng cặp số, có 2 cách: sắp xếp từ trên xuống (Top down)
hoặc sắp xếp từ dưới lên (Bottom up).
● Xuất phát từ đầu dãy, đổi chỗ các cặp phần tử kế cận để đưa phần tử nhỏ hơn trong cặp phần
tử đó về vị trí đúng ở đầu dãy hiện hành, sau đó sẽ không xét đến nó ở bước tiếp theo. (có
thể thực hiện ngược lại từ cuối đến đầu để đưa phần tử lớn hơn về cuối dãy hiện hành)
● Ở lần xử lý thứ i có vị trí đầu dãy là i
● Lặp lại xử lý trên cho đến khi không còn cặp phần tử nào để xét.
6
public static void BubbleSort(int A[] , int n) // sắp xếp từ dưới lên.
{ int i, j, temp;
for (i=0; i<n-1; i++)
for (j=n-1; j>i; j--)
if(A[j]< A[j-1])
Swap(j, j-1, A);
}
7
4 9 12 13 19 24 57 25 45
5 9 12 13 19 24 25 57 45
6 9 12 13 19 24 25 45 57
8
k = 0; left = 0; right = n - 1;
while (left < right)
{ for (i = right; i > left; i--)
if (A[i] < A[i - 1])
{ Swap(i, i-1, A);
k = i; // biến k dùng đánh dấu để bỏ qua đọan đã có thứ tự
}
left = k;
for (i = left; i < right; i++)
if (A[i] > A[i + 1])
{ Swap(i, i+1, A);
k = i;
}
right = k;
}
}
1 9 19 24 12 13 25 45 57
2 9 12 19 13 24 25 45 57
3 9 12 13 19 24 25 45 57
● Để sắp xếp một dãy số, ta có thể xét các nghịch thế có trong dãy và làm triệt tiêu dần chúng
đi.
● Ý tưởng chính của giải thuật này là tìm các cặp nghịch thế và triệt tiêu chúng. Ta xuất phát
từ phần tử đầu tiên của dãy, tìm tất các các cặp nghịch thế chứa phần tử này, triệt tiêu chúng
bằng các hoán vị phần tử này với phần tử tương ứng trong cặp nghịch thế. Ta dễ nhận thấy
sau lần duyệt đầu tiên, phần tử đầu tiên chính là phần tử nhỏ nhất của dãy. Ta tiếp tục xử lý
với phần tử thứ hai, ta có được phần tử thứ hai chính là phần tử nhỏ thứ hai của dãy. Cứ như
vậy, sau khi xử lý với phần tử thứ (n -1) của dãy, ta được một dãy sắp .
9
B/. Các bước tiến hành như sau :
input: dãy (a, n)
output: dãy (a, n) đã được sắp xếp
• Bước 1 : i = 0; // bắt đầu từ đầu dãy
• Bước 2 : j = i+1; //tìm các cặp phần tử nghịch thế
a[j] < a[i], với j>i
Ví dụ:
Bước 57 19 24 12 9 13 45 25
0 9 57 24 19 12 13 45 25
1 9 12 57 24 19 13 45 25
2 9 12 13 57 24 19 45 25
10
3 9 12 13 19 57 24 45 25
4 9 12 13 19 24 57 45 25
5 9 12 13 19 24 25 57 45
6 9 12 13 19 24 25 45 57
Yếu tố quyết định tính hiệu quả của một giải thuật là cách chọn khoảng cách h trong từng bước sắp
xếp. Giả sử quyết định sắp xếp k bước, các khoảng cách chọn phải thoả điều kiện:
hi > hi+1 và hk = 1
Tuy nhiên đến nay vẫn chưa có tiêu chuẩn rõ ràng trong việc lựa chọn dãy giá trị khoảng cách tốt
nhất, một số khoảng h đã được Knuth đề nghị:
hi = ( hi-1 –1)/3 và hk =1, k= log3n-1 (h1=121, h2=40, h3=13, h4=4, h5=1)
hay
hi = ( hi-1 –1)/2 và hk =1, k= log2-1 (h1=15, h2=7, h3=3, h4=1)
B/. Cài đặt:
11
Xem file Xem_ShellSort.MP4
Ví dụ:
Bước h 57 19 24 12 9 13 45 25
4 9 13 24 12 57 19 45 25
2 9 12 24 13 45 19 57 25
1 9 12 13 19 24 25 45 57
12
2/. Phương pháp Heap Sort:
A/. Ý tưởng:
Định nghĩa Heap: hiểu sơ lược Heap là 1 cây nhị phân đầy đủ như hình sau:
Vd:
Vd:
13
Biểu diễn Heap bằng mảng:
● Thứ tự lưu trữ trên mảng được thực hiện từ trái => phải
● Nếu ta biết được chỉ số của 1 phần tử trên mảng, ta sẽ dễ dàng xác định được chỉ số của node
cha và các node con của nó:
o Node gốc ở chỉ số 0
o Node cha của node i có chỉ số (i – 1)/2
o Các node con của node i (nếu có) có chỉ số [2i + 1] và [2i + 2]
o Node cuối cùng có con trong 1 Heap có n phần tử là: [n/2 – 1]
14
Vd: Thử thực hiện Heapify theo Max Heap. Tức là node con của node đang xét mà lớn hơn node
cha thì thực hiện đổi chỗ.
15
● Thực hiện thao tác Heapify để điều chỉnh phần tử đầu tiên
16
6 35 27 23 19 21 22 44 45
5 27 22 23 19 21 35 44 45
4 23 22 21 19 27 35 44 45
3 22 19 21 23 27 35 44 45
2 21 19 22 23 27 35 44 45
1 19 21 22 23 27 35 44 45
0 19 21 22 23 27 35 44 45
Ví dụ:
Bước i 9 12 4 6 1 25 5
6 12 6 9 5 1 2 25
5 9 6 2 1 9 12 25
4 6 5 2 1 9 12 25
3 5 1 2 6 9 12 25
2 2 1 5 6 9 12 25
1 1 2 5 6 9 12 25
0 1 2 5 6 9 12 25
● Giải thuật QuickSort sắp xếp dãy a[0], a[1] ..., a[n-1] dựa trên việc chọn 1 phần tử làm mốc
x (pivot) và phân hoạch dãy ban đầu thành 3 phần :
– Phần 1: Gồm các phần tử có giá trị không lớn hơn x
– Phần 2: Gồm các phần tử có giá trị bằng x
– Phần 3: Gồm các phần tử có giá trị không nhỏ hơn x
với x là giá trị của một phần tử tùy ý trong dãy ban đầu.
● Sau khi thực hiện phân hoạch, dãy ban đầu được phân thành 3 đoạn:
– 1. a[k] x , vi k = 1 .. j
– 2. a[k ] = x , với k = j+1 .. i-1
17
– 3. a[k ] ≥ x , với k = i..n-1
Nhận xét:
● Về lý thuyết ta có thể chọn giá trị mốc x là một phần tử tùy ý trong dãy, nhưng để đơn giản,
phần tử có vị trí giữa thường được chọn, khi đó pivot = (left +right)/ 2.
● Giá trị mốc x (pivot) được chọn sẽ có tác động đến hiệu quả thực hiện giải thuật vì nó quyết
định số lần phân hoạch.
– Số lần phân hoạch sẽ ít nhất nếu ta chọn được x là phần tử có giá trị là trung bình các
phần tử, nhiều nhất nếu x là cực trị (lớn nhất / nhỏ nhất) của dãy.
– Tuy nhiên do chi phí xác định phần tử trung bình khá cao nên trong thực tế người ta
không chọn phần tử này mà chọn phần tử nằm chính giữa dãy làm mốc với hy vọng
nó có thể gần với giá trị trung bình.
● Bước 1 :
Chọn tuỳ ý một phần tử a[k] trong dãy là giá trị mốc left<=k<=right
X = a[k]; i=left, j=right;
● Bước 2 :
Phát hiện và hiệu chỉnh cặp phần tử a[i], a[j] nằm sai chỗ:
18
int temp;
int pivot = A[(left + right) / 2]; // Phần tử pivot là phần tử giữa
while (i <= j)
{ while (A[i] < pivot) i++;
while (A[j] > pivot) j--;
if (i <= j)
{ /** hoán vị 2 phần tử **/
temp = A[i];
A[i] = A[j];
A[j] = temp;
i++; j--;
}
}
if (left < j)
QuickSort(A, left, j);
if (i < right)
QuickSort(A, i, right);
}
12 0 7 9 12 24 19 57 13 45 25
9 0 1 9 12 24 19 57 13 45 25
57 2 7 9 12 24 19 25 13 45 57
25 2 6 9 12 24 19 13 25 45 57
19 2 4 9 12 13 19 24 25 45 57
25 5 6 9 12 13 19 24 25 45 57
19
9 0 1 9 12 23 19 13 25 45 57
24 2 4 9 12 13 19 24 25 45 57
13 2 3 9 12 13 19 24 25 45 57
25 5 6 9 12 13 19 24 25 45 57
4/. Giải thuật Merge Sort: Sinh viên tìm hiểu và tự cài đặt
A/. Ý tưởng: Giải thuật Merge sort là giải thuật chia để trị (tương tự Quick Sort). Merge sort chia
dãy thành 2 nửa thành 2 dãy con, tiếp tục chia các dãy con thành 2 nửa,… cho đến khi chỉ còn 1
phần tử. Sau đó thực hiện trộn các dãy con có sắp xếp các phần tử theo đúng thứ tự của nó, tiếp tục
trộn cho đến khi trở lại thành 1 dãy, kết quả dãy đã được sắp xếp.
B/. Các bước tiến hành như sau :
input: dãy con (a, left, right)
output: dãy con (a, left, right) được sắp tăng dần
● Bước 2: Tiếp tục chia nhỏ, dãy vẫn còn chứa hơn 2 phần tử nên tiếp tục chia nhỏ
● Bước 3: Trộn các các cặp dãy con thành 1 dãy tuân theo thứ tự, tiếp tục trộn cho đến khi chỉ
còn 1 dãy
20
C/. Cài đặt:
21
/* Merge the temp arrays */
// Initial indexes of first and second subarrays
int i = 0, j = 0;
// Hàm MergeSort()
public static void MergeSort(int A[], int l, int r)
{
if (l < r)
{ // Find the middle point
int m = (l+r)/2;
22
// Sort first and second halves
MergeSort(A, l, m);
MergeSort(A , m+1, r);
5. Giải thuật Radix Sort: Sinh viên tìm hiểu và tự cài đặt
Radix Sort: sắp xếp dựa trên cơ số. Có thể khái quát là sắp xếp theo hàng đơn vị trước,
sau đó đến hàng chục, hàng trăm,…
Đánh giá các phương pháp sắp xếp: (trong khuôn khổ giáo trình này, ngoài các giải thuật đã
giới thiệu còn nhiều giải thuật khác nữa)
● Phần lớn các giải thuật sắp xếp cơ bản dựa trên sự so sánh giá trị giữa các phần tử. Bắt đầu
từ nhóm các giải thuật cơ bản, đơn giản nhất. Đó là các giải thuật Selecttion Sort (chọn trực
tiếp), Insertion Sort (chèn trực tiếp), Bubble Sort (nổi bọt), Interchange Sort (đổi chỗ trực
tiếp). Các giải thuật này đều có một điểm chung là chi phí thực hiện tỷ lệ với n2.
● Tiếp theo, chúng ta khảo sát một số cải tiến của các giải thuật trên. Nếu như các giải thuật
Binary Insertion Sort (chèn nhị phân), Shaker Sort (rung); tuy chi phí có ít hơn các giải thuật
gốc nhưng chúng vẫn chỉ là các giải thuật thuộc nhóm có độ phức tạp O(n 2), thì các giải
thuật Shell Sort (cải tiến của chèn trực tiếp), Heap Sort lại có độ phức tạp nhỏ hơn hẳn các
giải thuật gốc. Giải thuật Shell Sort có độ phức tạp O(n x) với 1<x<2 và giải thuật Heap sort
có độ phức tạp O(n log2 n).
● Các giải thuật Merge Sort và Quick sort là những giải thuật thực hiện theo chiến lược chia để
trị. Cài đặt chúng tuy phức tạp hơn các giải thuật khác nhưng chi phí thực hiện lại thấp, cả
hai giải thuật đều có độ phức tạp O(n log 2 n). Merge sort có nhược điểm là cần dùng thêm bộ
nhớ đệm. Giải thuật này sẽ phát huy tốt ưu điểm của mình hơn khi cài đặt trên các cấu trúc
dữ liệu khác phù hợp hơn như danh sách liên kết hay file.
● Giải thuật Quick Sort, như tên gọi của mình được đánh gía là giải thuật sắp xếp nhanh nhất
trong số các giải thuật sắp xếp dựa trên nền tảng so sánh giá trị của các phần tử. Tuy có chi
phí trong trường hợp xấu nhất là O(n2) nhưng trong kiểm nghiệm thực tế, giải thuật Quick
sort chạy nhanh hơn hai giải thuật cùng nhóm O(n log 2 n) là Merge Sort và Heap Sort. Từ
23
giải thuật Quick Sort, ta cũng có thể xây dựng được một giải thuật hiệu quả tìm phần tử
trung vị (median) của một dãy số.
● Người ta cũng chứng minh được rằng O(n log2 n) là ngường chặn dưới của các giải thuật sắp
xếp dựa trên nền tảng so sánh giá trị của các phần tử. Để vượt qua ngưỡng này, ta cần phát
triển giải thuật mới theo hướng khác các giải thuật trên. Radix sort là một giải thuật như vậy.
Nó được phát triển dựa trên sự mô phỏng qui trình phân phối thư của những người đưa thư.
Giải thuật này đại diện cho nhóm các giải thuật sắp xếp có độ phức tạp tuyến tính. Tuy
nhiên, thường thì các giải thuật này không thích hợp cho việc cài đặt trên cấu trúc dữ liệu
mảng một chiều.
● Trên thực tế dữ liệu cần thao tác có thể rất lớn do vậy không thông thường thì các dữ liệu
được lưu trên bộ nhớ thứ cấp, tức trên các đĩa từ. Việc thực hiện các thao tác sắp xếp trên các
dữ liệu này đòi hỏi phải có các phương pháp khác thích hợp.
24
1/. Tìm kiếm tuyến tính _ Linear Search
A/. Ý tưởng
Tìm kiếm tuyến tính là một kỹ thuật tìm kiếm cơ bản và cổ điển. Giải thuật tiến hành so sánh giá trị
cần tìm (khóa) với phần tử thứ nhất, thứ hai,..., của dãy số cung cấp cho đến khi gặp được phần tử
có khóa cần tìm.
B/. Các bước tiến hành:
Gọi x là giá trị cần tìm và a là mảng chứa dãy số dữ liệu. Các bước tiến hành như sau:
Bước 1: i=0;
Bước 2 : So sánh a[i] với x, có 2 khả năng :
a[i] = x: Tìm thấy. Dừng.
a[i] != x: Sang bước 3.
Bước 3 : i = i + 1; //xét phần tử kế
Nếu i > n: hết mảng, không tìm thấy. Dừng
Ngược lại: lặp lại bước 2.
C/. Cài đặt
Hàm LinearSearch được cài đặt bên dưới sẽ nhận vào một mảng các số nguyên a và một giá trị cần
tìm x. Sau khi thực hiện xong hàm trả về vị trí đầu tiên tìm thấy giá trị x nếu tìm thấy x trong mảng
a và trả về giá trị -1 nếu x không có trong mảng.
Từ giải thuật trên, ta có thể sửa đổi để tìm phần tử = x cuối cùng, đếm số phần tử = x, …
25
Tốt nhất 1 Phần tử đầu tiên có giá trị là x.
Xấu nhất n Phần tử cuối cùng có giá trị là x.
Trung bình (n+1)/2 Giả sử xác xuất các phần tử trong mảng nhận giá
trị x là như nhau.
Vậy giải thuật tìm kiếm tuyến tính có độ phức tạp O(n).
E/. Nhận xét
Giải thuật tìm kiếm tuyến tính không phụ thuộc vào thứ tự của các phần tử trong mảng. Do đó, đây
là phương pháp tổng quát nhất để tìm kiếm trên một dãy số bất kỳ.
Giải thuật có thể được cài đặt theo nhiều cách khác nhau như dùng for,…, và có thể cải tiến để đạt
giảm bớt các thao tác thừa và tốc độ thực hiện.
● a[mid] > x : Chuẩn bị tìm tiếp x trong dãy con a[left]..a[mid -1] : right = mid – 1;
26
● a[mid] < x: Chuẩn bị tìm tiếp x trong dãy con a[mid +1]..a[right] : left = mid + 1;
Bước 3:
● Nếu left <= right : Dãy tìm kiếm hiện hành vẫn còn phần tử. Lặp lại bước 2.
● Ngược lại : Dãy tìm kiếm hiện hành hết phần tử. Dừng.
Hàm BinarySearch cũng chỉ đơn giản sử dụng một vòng lặp để duyệt qua các phần tử trong dãy.
Tuy nhiên, vòng lặp này sẽ không duyệt qua hết tất cả các phần tử như đối với tìm kiếm tuyến tính.
Tư tưởng cài đặt của hàm này cũng giống như đối với LinearSearch, nghĩa là khi kết thúc vòng lặp
cũng có 2 khả năng xảy ra:
27
- Chỉ số left vẫn còn bé hơn hoặc bằng chỉ số right. Điều này có nghĩa là đã có một phần tử
nào đó bằng với giá trị x cần tìm, cụ thể là giá trị của phần tử middle. Do đó, hàm sẽ trả về
giá trị nằm trong biến mid.
- Chỉ số left đã vượt qua chỉ số right, nghĩa là đã duyệt qua hết các phần tử trong dãy mà
không tìm thấy phần tử nào có giá trị bằng x. Do đó, hàm trả về giá trị -1.
Vậy giải thuật tìm kiếm nhị phân có độ phức tạp O(log2 n).
28