Professional Documents
Culture Documents
21DKTPM1B rảnh:
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.
Để ước lượng độ phức tạp của một thuật toán ta thường dùng khái niệm bậc O-lớn và bậc Θ (bậc
Theta, viết thường là: ).
Bậc O-lớn:
Gọi n là độ lớn đầu vào tùy thuộc vào bài toán mà n có thể nhận các giá trị khác nhau. Độ phức tạp
của bài toán phụ thuộc vào n. Gọi R(n) là một đại lượng tổng quát cho tài nguyên cần dùng (có thể
số lần truy cập bộ nhớ / số lần đọc ghi / thời gian thực hiện chương trình hoặc không gian cho bộ
nhớ) để chạy chương trình. Nếu ta tìm được hằng số C>0, C không phụ thuộc vào n, sao cho với n
đủ lớn, các hàm R(n), g(n) đều là số dương và R(n) C.g(n) thì ta nói thuật toán có độ phức tạp cỡ
O(g(n)).
Độ phức tạp không phải độ đo chính xác lượng tài nguyên cho máy tính chạy chương trình, mà nó
đăc trưng cho động thái của chương trình khi kích thước đầu vào tăng lên.
Ví dụ:
Độ phức tạp hằng số, O(1): số phép tính / thời gian chạy / dung lượng bộ nhớ không phụ
thuộc đầu vào n, ví dụ như thao tác đóng, mở tập tin.
Độ phức tạp tuyến tính, O(n): số phép tính / thời gian chạy / dung lượng bộ nhớ phụ thuộc
đầu vào n, ví dụ như tính tổng n phần tử của mảng 1 chiều.
Độ phức tạp đa thức, O(P(n)): với P là đa thức bậc cao từ 2 trở lên, ví dụ như tính định thức
của ma trận.
Độ phức tạp Logarit, O(log(n)): có bậc thấp hơn O(n), ví dụ như thuật toán Euclid để tìm
ước số chung lớn nhất của 2 số nguyên dương.
Độ phức tập hàm mũ, O(2n): là trường hợp xấu nhất và không thực tế với thuật toán có độ
phức tạp này.
2
1/. Giải thuật Insertion Sort
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 (1posi).
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.
3
public static void InsertionSort(int A[], int n)
{ int x, pos;
for (int i=1; i<n; ++i)
{ x = A[i];
pos = i-1; //vị trí chèn
while (pos>=0 && A[pos] > x)
{ A[pos+1] = A[pos];
pos = pos-1;
}
A[pos+1] = x;
}
}
Xem file: Xem_Insertion Sort.MP4
Ví dụ:
Bước i 57 19 24 12 9 13 45 25
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
7 9 12 13 19 24 25 45 57
4
D/. Độ phức tạp:
Các thuật toán khác sinh viên tự xác định độ phức tạp (xem như bài tập và phần tự học)
6
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.
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 = Vị trí đầu;
• Bước 2 : j = Vị trí cuối; //Duyệt từ cuối dãy ngược về vị trí i
– Trong khi (j > i) thực hiện:
• Nếu a[j]<a[j-1]: a[j]a[j-1]; //đổi chỗ 2 phần tử kế cận
• j = Vị trí trước(j);
• Bước 3 : i = Vị trí kế(i); // lần xử lý kế tiếp
– Nếu i = Vị trí cuối: Dừng. // Hết dãy.
– Ngược lại : Lặp lại Bước 2.
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
1 9 12 57 19 24 13 25 45
2 9 12 13 57 19 24 25 45
3 9 12 13 19 57 24 25 45
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
Bước 2:
Bước 2a: i = r; // đẩy phần tử nhỏ nhất về đầu mảng
Trong khi (i>l)
Nếu a[i] < a[i-1]: a[i]↔ a[i-1]; k=i; // lưu lại nơi xảy ra hoán vị
i = i-1;
l = k; //loại bớt phần tử đã có thứ tự ở đầu dãy
Bước 2b: i=l; // đẩy phần tử lớn về cuối mảng
Trong khi (i<r)
Nếu a[i]>a[i+1]: a[i]↔ a[i+1]; k=i; //lưu lại nơi xảy ra hoán vị
i=i+1;
r = k; //loại bớt phần tử đã có thứ tự ở cuối dãy
Bước 3:
Nếu l < r : lặp lại bước 2.
9
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 .
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
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
11
II/. Các giải thuật sắp xếp cải tiến:
1/. Giải thuật Shell Sort: chèn với các khoảng giảm dần
A/. Ý tưởng:
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:
12
A[j] = temp;
}
}
}
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
13
2/. Phương pháp Heap Sort: thuật toán vun đống
A/. Ý tưởng: thuật toán phức tạp
Định nghĩa Heap: hiểu sơ lược Heap là 1 cây nhị phân đầy đủ như hình sau:
Vd:
Vd:
14
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 (lấy phần nguyên)
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]
15
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ỗ.
16
Thực hiện thao tác Heapify để điều chỉnh phần tử đầu tiên
17
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 4 25
5 9 6 4 5 1 12 25
4 6 5 4 1 9 12 25
3 5 1 4 6 9 12 25
2 4 1 5 6 9 12 25
1 1 4 5 6 9 12 25
0 1 4 5 6 9 12 25
18
3/. Giải thuật Quick Sort:
A/. Ý tưởng:
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 , với k = 1 .. j
– 2. a[k ] = x , với k = j+1 .. i-1
– 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.
19
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 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ỗ:
Bước 2a : Trong khi a[i] <x i++;
Bước 2b : Trong khi a[j]>x j--;
Bước 2c : Nếu i<j Hoán vị (a[i],a[j])
Bước 3: Sắp xếp đoạn 1: a[left].. a[j]
Bước 4: Sắp xếp đoạn 3: a[i].. a[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
21
Ví dụ về Quick Sort
21 24 42 29 23 13 8 39 38
21 13 8 23 24 42 29 30 38
8 13 21 23 24 29 38 39 40
8 13 21 23 24 29 38 39 42
21244229231383938
21138 23 2442293938
8 13 21 24 29 423938
38 39 42
22
Vd Bổ sung: Chọn phần tử giữa là Pivot và sắp xếp theo thứ tự ngược
37 20 17 26 44 41 27 28 50 17
44 50 41 37 20 17 26 27 28 17
50 44 41 37 20 17 26 27 28 17
50 44 41 37 28 27 26 20 17 17
50 44 41 37 28 27 26 20 17 17
37201726444127285017
5044 41 37201726272817
50 44 372827 26 201717
37 28 27 20 17 17
23
4/. Giải thuật Merge Sort:
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
24
C/. Cài đặt:
// 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;
// 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
26
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(nx) 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 log2 n) là Merge Sort và Heap Sort. Từ
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.
27
III/. Các giải thuật tìm kiếm:
Tìm kiếm là một bài toán cơ bản, rất cần trong nhiều ứng dụng thực tế. Có nhiều dạng bài toán
tìm kiếm khác nhau như: tìm kiếm không có thông tin – tìm kiếm có thông tin; tìm kiếm trên
danh sách – tìm kiếm trên cây- tìm kiếm trên đồ thị; tìm kiếm đối kháng, …
Trong phạm vi môn học CTDL, ta quan tâm đến tìm kiếm trên danh sách với 2 phương pháp là
tìm kiếm tuyến tính và tìm kiếm nhị phân.
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
28
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, …
Vậy giải thuật tìm kiếm tuyến tính có độ phức tạp O(n).
Ví dụ: T/h tìm thấy.
29
Ví dụ: n = 8, x = 62 (T/h không tìm thấy, dừng khi: i = n = 8)
34 94 2 4 9 8 23 12
0 1 2 3 4 5 6 7
30
Gọi x là giá trị cần tìm, a là mảng chứa các giá trị dữ liệu gồm n phần tử đã dược sắp xếp (có thứ
tự), left và right là chỉ số đầu và cuối của đoạn cần tìm, mid là chỉ số của phần tử nằm giữa của
đoạn cần tìm. Các bước tìm kiếm được tiến hành như sau:
Bước 1: Khởi đầu tìm kiếm trên tất cả các phần tử
left = 0; right = n;
Bước 2: mid = (left+right)/2;
So sánh a[mid] với x, có 3 khả năng :
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.
31
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:
- 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.
0 7 3 1 3 5 18 22 24 35 62
35
4 7 5 1 3 5 18 22 24 62
Ví dụ: n= 8; x = 10
0 7 3 1 3 5 18 22 24 35 62
1
0 2 1 3 5 18 22 24 35 62
2 2 2 1 3 5 18 22 24 35 62
Không tìm thấy, dừng vì l > r
32