You are on page 1of 32

Chương 1:

21DKTPM1B rảnh:

Sắp xếp và Tìm kiếm


Nội dung
I/. Các giải thuật sắp xếp cơ bản:
II/. Các giải thuật sắp xếp cải tiến:
III/. Các giải thuật tìm kiếm:

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.

Ôn tập về Mảng, Phương thức

I/. Các giải thuật sắp xếp cơ bản:


Giải thuật Sắp Xếp:
Trong khoa học máy tính, giải thuật sắp xếp là một giải thuật bố trí và xếp đặt các phần tử của
một danh sách (mảng) tuân theo một thứ tự (tăng hoặc giảm). Để minh họa và đơn giản hóa, người
ta thường sử dụng danh sách các phần tử cần sắp xếp là các giá trị số.
Độ phức tạp:
Thời gian mà máy tính khi thực hiện một thuật toán không chỉ phụ thuộc vào bản thân thuật toán
đó, ngoài ra còn tùy thuộc từng máy tính. Để đánh giá hiệu quả của một thuật toán, có thể xét số các
phép tính phải thực hiện khi thực hiện thuật toán này. Thông thường số các phép tính được thực
hiện phụ thuộc vào cỡ của bài toán, tức là độ lớn của đầu vào. Vì thế độ phức tạp thuật toán là một
hàm phụ thuộc đầu vào. Tuy nhiên trong những ứng dụng thực tiễn, chúng ta không cần biết chính
xác hàm này mà chỉ cần biết một ước lượng đủ tốt của chúng.

Để ướ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 (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.

input: dãy (a, n)


output: dãy (a, n) đã được sắp xếp
• Bước 1: i = 1; // giả sử có đoạn a[0] đã được sắp
• Bước 2: x = a[i]; Tìm vị trí pos thích hợp trong đoạn a[0]
đến a[i] để chèn x vào
• Bước 3: Dời chỗ các phần tử từ a[pos] đến a[i-1] sang
phải 1 vị trí để dành chổ cho x
• Bước 4: a[pos] = x; // có đoạn a[0]..a[i] đã được sắp
• Bước 5: i = i+1;
Nếu i  n : Lặp lại Bước 2.
Ngược lại : Dừng.
C/. Cài đặt:

Hàm hoán vị 2 phần tử trong mảng A


private static void Swap(int i, int j, int A[])
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}

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:

 Độ phức tạp về thời gian: O (n * 2)


 Độ phức tạp không gian bộ nhớ: O (1)

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)

2/. Giải thuật chọn trực tiếp _ Selection Sort


A/. Ý tưởng:
Dãy có thứ tự thì a[i]=min(a[i], a[i+1], …, a[n-1])
Ý tưởng của giải thuật chọn trực tiếp mô phỏng một trong những cách sắp xếp tự nhiên nhất thực tế
thường được sử dụng: 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 dãy hiện hành (vị trí chỉ số 0), 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
chỉ còn 1 phần tử. Dãy ban đầu có n phần tử, vậy tóm tắt ý tưởng giải thuật 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.
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 : Tìm phần tử a[min] nhỏ nhất trong dãy hiện hành từ a[i] đến a[n-1]
• Bước 3 : Nếu min  i: Hoán vị a[min] và a[i]
• Bước 4 : Nếu i chưa là vị trí cuối
» i = Vị trí kế(i);
» Lặp lại Bước 2
Ngược lại: Dừng. //n phần tử đã nằm đúng vị trí.
C/. Cài đặt:
Hàm Selection Sort nhận vào một mảng chứa dãy số cần sắp xếp nội dung và tiến hành sắp xếp
ngay trên mảng đã nhập.

public static void SelectionSort(int A[], int n)


{ int i, j, vtmin, temp;
for (i = 0; i < n-1; i++) // xác định vị trí của phần tử nhỏ nhất và đổi chỗ cho phần tử thứ i.
{ vtmin = i;
for (j = i+1; j < n; j++)
{ if (A[j] < A[vtmin])
vtmin = j;
}
/* Swap(i, vtmin, A) */
5
temp = A[i];
A[i] = A[vtmin];
A[vtmin]= temp;
}
}

Xem file: Xem_Selection Sort.MP4


Ví dụ:
i 57 19 24 12 9 13 45 25
0 9 19 24 12 57 13 45 25
1 9 12 24 19 57 13 45 25
2 9 12 13 19 57 24 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 45 57
6 9 12 13 19 24 25 45 57

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.

C/. Cài đặt:


Bubble sort (sắp xếp bằng cách đổi chỗ)

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);
}

Xem file: Xem_Bubble Sort.MP4 (sắp xếp từ trên xuống)


Ví dụ:
i 57 19 24 12 9 13 45 25
0 9 57 19 24 12 13 25 45

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

21DKTPM1B học đên đây _ 11/10/2022


21DKTPM1A học đên đây _ 13/10/2022

4/.  Giải thuật Shaker Sort


A/. Ý tưởng: Shaker Sort (giải thuật rung) là một cải tiến của Bubble Sort. Sau khi đưa phần
tử nhỏ nhất về đầu dãy, giải thuật sẽ giúp chúng ta đưa phần tử lớn nhất về cuối dãy. Do
đưa các phần tử về đúng vị trí ở cả hai đầu nên Shaker Sort sẽ giúp cải thiện thời gian sắp
xếp dãy số.

B/. Giải thuật:

input: dãy (a, n)


output: dãy (a, n) đã được sắp xếp
Bước 1:
Khởi tạo: l=0; r = n-1; // từ l(eft) đến r(ight) là đoạn cần được sắp xếp
K= 0; n-1; // ghi nhận lại vị trí l xảy ra hoán vị sau cùng để làm cơ sở
thu hẹp đoạn l đến r

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.

C/. Cài đặt:


public static void ShakerSort(int A[], int n)
{ int i, k, left, right;
k = 0; left = 0; right = n - 1;
while (left < right)
{ for (i = right; i > left; i--) //chạy từ rightleft, đẩy phần tử nhỏ về đầu
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++) // chạy từ leftright, đẩy phần tử lớn về cuối
if (A[i] > A[i + 1])
{ Swap(i, i+1, A);
k = i;
}
right = k;
}
}

Xem file: Xem_SHAKERSORT.MP4


Ví dụ:
Bước 57 19 24 12 9 13 45 25

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

Xem để hiểu kỹ hơn:

5/.  Giải thuật Interchange Sort


A/. Ý tưởng:

 Để 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 .

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
10
• 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

• Bước 3 : Trong khi j  n thực hiện


• Nếu a[j]<a[i]: a[i]a[j]; // đổi chỗ cặp phần tử này
• j = j+1;
• Bước 4 : i = i+1;
– Nếu i < n: Lặp lại Bước 2.
– Ngược lại: Dừng.

C/. Cài đặt:

public static void InterchangeSort(int A[], int n)


{ int temp;
for(int i=0; i<n-1; i++)
{ for(int j=i+1; j<n; j++)
if(A[j]<A[i]) //nếu có nghịch thế thì đổi chỗ
{ temp = A[j];
A[j] = A[i];
A[i]= temp;
}
}
}

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:

 Khởi tạo giá trị h (có nhiều cách khác nhau)


 Chia danh sách thành các danh sách con theo khoảng h
 Sử dụng Insertion Sort để sắp xếp các danh sách con
 Lặp lại liên tục việc giảm h đến khi danh sách hoàn thành với h = 1.

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:

public static void ShellSort(int A[], int n)


{ //có thể dùng nhiều cách tính khoảng h khác nhau
for (int h = n/2; h > 0; h /= 2) //bắt đầu với h lớn và giảm dần
{ for (int i = h; i < n; i += 1)
{ int temp = A[i];
int j;
for (j = i; j >= h && A[j - h] > temp; j -= h)
A[j] = A[j - h];

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

C/. Độ phức tạp giải thuật:

 Trường hợp xấu nhất:  O(n log n)


 Trường hợp tốt nhất:  O(n log n)

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:

Tính chất Heap: có 1 trong 2 tính chất sau:


+ Max Heap: Giá trị của mỗi node >= giá trị của các node con nó
=> Node lớn nhất là node gốc
+ Min Heap: Giá trị của mỗi node <= giá trị của các node con nó
=> Node nhỏ nhất là node gốc

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ỗ.

Ý tưởng giải thuật HeapSort


Bước 1: Xây dựng Heap
 Sử dụng thao tác Heapify để chuyển đổi mảng bình thường thành Heap
Bước 2: Sắp xếp
 Hoán vị phần tử cuối cùng của Heap với phần tử đầu tiên của Heap
 Loại bỏ phần tử cuối cùng

16
 Thực hiện thao tác Heapify để điều chỉnh phần tử đầu tiên

B/. Cài đặt:


Cần viết 3 hàm sau:
Heapify: Tạo Heap từ mảng.
public static void Heapify(int A[], int n, int vt)
{ while (vt <=n/2 -1)
{ int child1 = 2*vt+1; int child2 = 2*vt+2; int childmax = child1;
if (child2 <n && A[child2] > A[childmax])
childmax = child2;
if (A[vt] < A[childmax])
Swap(vt, childmax, A);
vt = childmax;
System.out.println("\nHeapify");
}
}

Bulid Heap: Thực hiện tạo các Heap


public static void BuildHeap(int A[], int n)
{ for(int i = n/2 -1; i>=0; i--) Heapify(A, n, i);
}

HeapSort: Thực hiện sắp xếp Heap


public static void HeapSort(int A[], int n)
{ BuildHeap(A, n);
for(int i = n-1; i>=0; i--) //thực hiện từ cuối về đầu
{ Swap(0, i , A);
Heapify(A, i, 0);
}
}

Xem file Xem_HeapSort.MP4


Ví dụ:
Bước i 45 35 23 27 21 22 44 19
7 44 35 23 27 21 22 19 45
6 35 27 23 19 21 22 44 45

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]

C/. Cài đặt:

public static void QuickSort(int A[], int left, int right)


{ int i = left, j = right;
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);
}

Xem file Xem_QuickSort.MP4 (Pivot là phần tử đầu)


Ví dụ: Pivot là phần tử giữa
20
Pivot Left Right 57 19 24 12 9 13 45 25

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

Ví dụ: Pivot là phần tử đầu


Pivot Left Right 57 19 24 12 9 13 45 25
57 0 7 25 19 24 12 9 13 45 57
25 0 6 13 19 24 12 9 25 45 57
13 0 4 9 12 24 19 13 25 45 57
9 0 1 9 12 24 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

21
Ví dụ về Quick Sort

Vd Bổ sung: Chọn phần tử giữa là Pivot

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

 Bước 1: Chia mảng lớn thành hai dãy con


 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

24
C/. Cài đặt:

public static void Merge(int A[], int l, int m, int r)


{ // Find sizes of two subarrays to be merged
int n1 = m - l + 1;
int n2 = r - m;

/* Create temp arrays */


int L[] = new int [n1];
int R[] = new int [n2];

/*Copy data to temp arrays*/


for (int i=0; i<n1; ++i)
L[i] = A[l + i];
for (int j=0; j<n2; ++j)
R[j] = A[m + 1+ j];

/* Merge the temp arrays */


// Initial indexes of first and second subarrays
int i = 0, j = 0;

// Initial index of merged subarry array


int k = l;
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
{
A[k] = L[i];
i++;
}
else
{
A[k] = R[j];
j++;
}
k++;
}

/* Copy remaining elements of L[] if any */


while (i < n1)
{
A[k] = L[i];
i++;
k++;
}
25
/* Copy remaining elements of R[] if any */
while (j < n2)
{
A[k] = R[j];
j++;
k++;
}
}

// 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);

// Merge the sorted halves


Merge(A, l, m, 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,…

Lưu ý: Radix Sort chỉ xử lý dãy số nguyên >0.

Đá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.

public static int LinearSearch(int A[], int n, int x)


{ int i = 0;
while (i < n && A[i] != x) i++;
if (i == n) return -1; //Không tìm thấy
else return i; //Tìm thấy x tại vị trí i
}

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, …

D/. Đánh giá giải thuật


Có thể ước lượng độ phức tạp của giải thuật tìm kiếm qua số lượng các phép so sánh được tiến
hành để tìm ra x. Các trường hợp giải thuật tìm tuyến tính có thể có:

Trường hợp Số lần so sánh Giải thích


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).
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

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.
2/. Tìm nhị phân _ Binary Search
A/. Ý tưởng
Trường hợp dãy số đã cho là có thứ tự (giả sử thứ tự tăng), các phần tử trong dãy có quan hệ a[i-1]
≤ a[i] ≤ a[i+1], từ đó kết luận được nếu x > a[i] thì x chỉ có thể xuất hiện trong đoạn [a[i+1], a[n-1]]
của dãy, ngược lại nếu x < a[i] thì x chỉ có thể xuất hiện trong đoạn [a[0], a[i-1]] của dãy. Giải
thuật tìm nhị phân áp dụng cho dãy đã có thứ tự để tìm các giới hạn phạm vi tìm kiếm sau mỗi lần
so sánh x với một phần tử trong dãy.
Ý tưởng của giải thuật là tại mỗi bước tiến hành so sánh x với phần tử nằm ở giữa của dãy tìm kiếm
hiện hành, dựa vào kết quả so sánh này để quyết định giới hạn dãy tìm kiếm ở bước kế tiếp là nửa
trên hay nửa dưới của dãy tìm kiếm hiện hành.
B/. Các bước tiến hành:

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 :

 a[mid] = x : Tìm thấy. Dừng


 a[mid] > x : Chuẩn bị tìm tiếp x trong dãy con a[left]..a[mid -1] : right = mid – 1;
 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.

C/. Cài đặt


Tương tự như hàm LinearSearch, hàm BinarySearch cũng nhận vào một dãy n phần tử và một
giá trị cần tìm x. Ngoài ra, để BinarySearch thực hiện đúng thì dãy ban đầu phải có thứ tự tăng
dần (hoặc giảm dần).
public static int BinarySearch(int A[], int n, int x)
{
int l = 0, r = n - 1; //l: left, r: right
while (l <= r)
{
int m = (l + r)/2; //m: mid
// Nếu x = phần tử mid là tìm thấy
if (A[m] == x)
return m;

// Nếu x lớn hơn, bỏ qua nửa trái


if (A[m] < x)
l = m + 1;
// Nếu x nhỏ hơn, bỏ qua nửa phải
else
r = m - 1;
}
// đến đây là tìm không thấy phần tử = x
return -1;
}

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.

D/. Đánh giá giải thuật


Trường hợp giải thuật tìm nhị phân ta có bảng phân tích sau:
Trường hợp Số lần so sánh Giải thích
Tốt nhất 1 Phần tử giữa của dãy có giá trị = x.
Xấu nhất log2 n Phần tử cần tìm x ở cuối dãy.
Trung bình (log2 n) / 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 nhị phân có độ phức tạp O(log2 n).
Ví dụ: n= 8; x = 24

Left right mid 1 3 5 18 22 24 35 62

0 7 3 1 3 5 18 22 24 35 62

35
4 7 5 1 3 5 18 22 24 62

Tìm thấy x = 24 tại vị trí thứ 5

Ví dụ: n= 8; x = 10

Left right mid 1 3 5 18 22 24 35 62

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

You might also like