Bài toán sắp xếp là chúng ta sẽ sắp xếp lại các phần tử của một danh sách theo chiều tăng hoặc giảm dần theo một tiêu chí nào đó của phần tử trong danh sách.Ví dụ như bạn sắp xếp danh sá
Trang 1TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI TP HỒ CHÍ MINH
KHOA CÔNG NGHỆ THÔNG TIN
BÁO CÁO BÀI TẬP NHÓM PHÂN TÍCH THIẾT KẾ GIẢI THUẬT
Đề tài: Phân tích độ phức tạp của giải thuật sắp xếp Quick sort
Chuyên ngành: CÔNG NGHỆ THÔNG TIN
Giảng viên hướng dẫn : Trần Anh Tuấn Môn học: Phân tích thiết kế giải thuật
Trang 2TP Hồ Chí Minh, 2021
DANH SÁCH THÀNH VIÊN:
MỤC LỤC:
I Tổng quan về đề tài
II Cơ sở lý thuyết
1 Quick sort là gì?
2 Thuật toán phân đoạn (partition)
3 Ý tưởng thuật toán
4 Cách chọn phần tử làm pivot
5 Phương pháp phân hoạch
6 Giải thuật Quick sort III Minh họa chương trình và phân tích độ phức tạp của thuật toán
IV Kết luận
V Câu hỏi củng cố
VI Tài liệu tham khảo
Trang 3I Tổng quan về đề tài
Bài toán sắp xếp chắc chắn không còn xa lạ gì với mỗi chúng ta, nó là một trong
những bài toán được bắt gặp nhiều nhất trong thực tế Ví dụ như sắp xếp danh sách
lớp học, sắp xếp quyển sách, sắp xếp tiền… Vậy thì bài toán sắp xếp là gì? Bài
toán sắp xếp là chúng ta sẽ sắp xếp lại các phần tử của một danh sách theo chiều
tăng hoặc giảm dần theo một tiêu chí nào đó của phần tử trong danh sách.Ví dụ
như bạn sắp xếp danh sách lớp học theo điểm trung bình từ cao đến thấp, sắp
những quyển sách theo kích cỡ từ nhỏ đến lớn, sắp xếp những tờ tiền theo mệnh
giá từ thấp đến cao… Mục đích của việc sắp xếp chính là giúp ta có cái nhìn tổng
quan hơn về những dữ liệu mà ta có, dễ dàng tìm kiếm những phần tử đứng nhất
về một tiêu chí nào đó Trong lập trình, sắp xếp không chỉ đơn giản là để tìm một
hoặc nhiều phần tử đứng đầu về một tiêu chí nào đó hay để có cái nhìn tổng quan
về dữ liệu, sắp xếp còn làm cơ sở cho các giải thuật nâng cao với hiệu suất cao
hơn Có rất nhiều thuật toán sắp xếp được sử dụng như: Merge sort, Selection sort,
Quick sort, Bubble sort, Trong khuôn khổ đề tài được giao, nhóm chúng em xin
được nghiên cứu và trình bày về giải thuật sắp xếp Quick sort, một thuật toán có
khà năng sắp xếp với tốc độ cao hơn hẳn so với các thuật toán Insertion sort,
Selection sort hay Bubble sort
Trang 4II Cơ sở lý thuyết:
Sắp xếp nhanh (Quick sort) hay sắp xếp phân đoạn (Partition) là là thuật toán sắp
xếp dựa trên kỹ thuật chia để trị, cụ thể ý tưởng là: chọn một điểm làm chốt (gọi là
pivot), sắp xếp mọi phần tử bên trái chốt đều nhỏ hơn chốt và mọi phần tử bên
phải đều lớn hơn chốt, sau khi xong ta được 2 dãy con bên trái và bên phải, áp
dụng tương tự cách sắp xếp này cho 2 dãy con vừa tìm được cho đến khi dãy con
chỉ còn 1 phần tử
Phần tử được chọn làm chốt rất quan trọng, nó quyết định thời gian thực thi của
thuật toán Phần tử được chọn làm chốt tối ưu nhất là phần tử trung vị, phần tử này
làm cho số phần tử nhỏ hơn trong dãy bằng hoặc sấp xỉ số phần tử lớn hơn trong
dãy Tuy nhiên, việc tìm phần tử này rất tốn kém, phải có thuật toán tìm riêng, từ
đó
NHẬN XÉT CỦA GIÁO VIÊN:
Trang 5làm giảm hiệu suất của thuật toán tìm kiếm nhanh, do đó, để đơn giản, người
ta thường sử dụng phần tử chính giữa làm chốt
2 Thuật toán phân đoạn (Partition)
Đặt pivot là phần tử cuối cùng của dãy số Arr Chúng ta bắt đầu từ phần tử trái
nhất của dãy số có chỉ số là left, và phần tử phải nhất của dãy số có chỉ số là right
- 1 (bỏ qua phần tử pivot) Khi nào left < right mà arr[left] > pivot và arr[right] <
pivot thì đổi chỗ hai phần tử left và right Sau cùng, ta đổi chỗ hai phần tử left và
pivot cho nhau Khi đó, phần tử left đã đứng đúng vị trí và chia dãy số làm đôi
(bên trái và bên phải)
● Chọn một giá trị khóa v làm chốt (pivot)
● Phân hoạch dãy A[0]…A[n-1] thành hai mảng con “bên trái” và “bên phải” Mảng con “bên trái” bao gồm các phần tử có khóa nhỏ hơn chốt, mảng con “bên phải” bao gồm các phần
tử có khóa lớn hơn oặc bằng chốt
● Sắp xếp mảng con “bên trái” và mảng con “bên phải”
● Sau khi đã sắp xếp được mảng con “bên trái” và mảng con “bên phải” thì mảng đã cho sẽ được sắp xếp bởi vì tất cả các khóa trong mảng con “bên trái” đều nhỏ hơn các khóa trong mảng con “bên phải”
● Việc sắp xếp các mảng con “bên trái” và “bên phải” cũng được tiến hành bằng phương pháp nói trên
● Một mảng chỉ gồm một phần tử hoặc gồm nhiều phần tử có khóa bằng nhau thì dã
có thứ tự
● Chọn giá trị khóa lớn nhất trong hai phần tử có khóa khác nhau đầu tiên kể từ trái qua
● Nếu mảng chỉ gồm một phần tử hay gồm nhiều phần tử có khóa bằng nhau thì không có chốt
● Chọn phần tử bên trái ngoài cùng (phần tử đầu tiên của mảng)
● Chọn phần tử bên phải ngoài cùng (phần tử cuối cùng của mảng)
● Ngẫu nhiên một phần tử (sử dụng hàm random mà ngôn ngữ lập trình cung cấp)
● Phần tử chính giữa của mảng
Trang 6● Để phân hoạch mảng, ta dùng hai “con nháy” L và R trong đó L từ bên trái và R từ bên phải
● Ta cho L chạy sang phải cho tới khi gặp phần tử có khóa lớn hơn hoặc bằng chốt
● Cho R chạy sang trái cho tới khi gặp phần tử có khóa nhỏ hơn chốt
● Tại chỗ dừng của L và R nếu L < R thì hoán vị A[L], A[R]
● Lặp lại quá trình dịch sang phải, sang trái của hai “con nháy” L và R cho đến khi L
> R
● Khi đó L sẽ là điểm phân hoạch, cụ thể là A[L] là phần tử đầu tiên của mảng con
“bên phải”
● Để sắp xếp mảng A[i]…A[j] ta làm các bước sau:
− Xác định chốt trong mảng A[i]…A[j]
− Phân hoạch mảng A[i]…A[j] đã cho thành hai mảng con A[i]…A[k-1] và A[k]…A[j]
− Sắp xếp mảng A[i]…A[k-1] bằng đệ quy
− Sắp xếp mảng A[k]…A[j] bằng đệ quy
● Đệ quy sẽ dừng khi không còn tìm thấy chốt
Trang 7III Minh họa chương trình và phân tích độ phức tạp của thuật toán
int partition ( int arr [], int low , int high )
{
int pivot = arr [ high ];
int i = ( low - 1 );
for ( int j = low ; j <= high - 1 ; j ++) {
if ( arr [ j ] <= pivot ) {
i ++;
swap (& arr [ ], & arr [ j ]);
} }
swap (& arr [ i + 1 ], & arr [ high ]);
return ( i + 1 );
}
- Hàm Quick sort
void quickSort ( int arr [], int low , int high )
{
if ( low < high ) {
int pi = partition ( arr , low , high );
quickSort ( arr , low , pi - 1 );
quickSort ( arr , pi + 1 , high );
} }
- Toàn bộ source code chương trình
#include <iostream>
using namespace std ;
void swap ( int* a , int* b )
{
int t = * a ;
* = * b
* = t ; }
int partition ( int arr [], int low , int high )
{
Trang 8int pivot = arr [ high ];
int i = ( low - 1 );
for ( int j = low ; j <= high - 1 ; j ++) {
if ( arr [ j ] <= pivot ) {
i ++;
swap (& arr [ ], & arr [ j ]);
} }
swap (& arr [ i + 1 ], & arr [ high ]);
return ( i + 1 );
}
void quickSort ( int arr [], int low , int high )
{
if ( low < high ) {
int pi = partition ( arr , low , high );
quickSort ( arr , low , pi - 1 );
quickSort ( arr , pi + 1 , high );
} }
/* Function to print an array */
void printArray ( int arr [], int size )
{
int i ;
for ( i = ; i < size ; i ++)
printf ( "%d " , arr [ i ]);
printf ( \n " );
}
int main ()
{
int n ;
cout << "Nhap so phan tu: " ;
cin >> n ;
int a [ n ];
cout << "Nhap vao gia tri moi phan tu cua mang: " << endl ;
for ( int i = ; < ; ++) {
cout << "a[" << i << "]" << " = " ;
cin >> a [ ];
}
quickSort ( a 0 , n - 1 );
cout << "Mang sau khi duoc sap xep: " << endl ;
printArray ( , n );
system ( "pause" );
return 0 ; }
Trang 92 Đánh giá độ phức tạp của thuật toán
- Trường hợp xấu nhất
Trong trường hợp xấu nhất, phân hoạch bị lệch Nghĩa là mảng có n phần tử phân
hoạch thành hai mảng con Mảng con “bên trái” gồm n-1 phần tử và mảng con bên
phải chỉ gồm 1 phần tử
void quickSort ( int arr [], int low , int high )
{
if ( low < high ) {
int pi = partition ( arr , low , high );
quickSort ( arr , low , pi - 1 );
quickSort ( arr , pi + 1 , high );
} }
T(n) = T(n-1) + T(1) + n
= T(n-2) + (n-1) + n + 2
= T(n-3) + (n-2) + (n-1) + n +3
…
= T(1) +(2 + 3+ 4 +…+ n) + n - 1
= 1 + 2 + 3 +…+ n + n - 1 = n(n2+1) + n – 1 O(n2)
- Trường hợp tốt nhất
Trong trường hợp tốt nhất khi ta chọn được chốt sao cho hai mảng con có kích
thước bằng nhau và bằng n/2 Khi đó ta có độ phức tạp của thuật toán như sau
quickSort ( int arr [], int low , int high )
{
if ( low < high )
Trang 10int pi = partition ( arr , low , high );
quickSort ( arr , low , pi - 1 );
quickSort ( arr , pi + 1 , high );
} }
T(n) = 2T( n2) + n
= 2[2T( 2n2 ) + n2] + n
= 22[2T(2n3 ) + 2n2 ] + 2n
= 23[2T(2n4 ) + 2n3 ] + 3n
…
= 2kT(2n k ) + kn
= 2kT(1) + kn = n.1 + n.logn O(n.logn)
Trang 11IV Kết luận
Quick Sort là một thuật toán sắp xếp hiệu quả dựa trên việc phân chia mảng dữ
liệu thành các nhóm phần tử nhỏ hơn Giải thuật sắp xếp nhanh chia mảng thành
hai phần bằng cách so sánh từng phần tử của mảng với một phần tử được gọi là
phần tử chốt Một mảng bao gồm các phần tử nhỏ hơn hoặc bằng phần tử chốt và
một mảng gồm các phần tử lớn hơn phần tử chốt
Quá trình phân chia này diễn ra cho đến khi độ dài của các mảng con đều bằng 1
Với phương pháp đệ quy ta có thể sắp xếp nhanh các mảng con sau khi kết thúc
chương trình ta được một mảng đã sắp xếp hoàn chỉnh Giải thuật sắp xếp nhanh tỏ
ra khá hiệu quả với các tập dữ liệu lớn khi mà độ phức tạp là O(nlogn)
Trang 12V Câu hỏi củng cố
1 Khi nào thì độ phức tạp của thuật toán là lớn nhất và khi nào là nhỏ nhất?
Trả lời: Độ phức tạp của thuật toán là lớn nhất khi mảng bị phân hoạch lệch Nghĩa
là sau khi phân hoạch mảng đã cho gồm n phần tử sẽ được chia thành hai mảng con Mảng con bên trái gồm n-1 phần tử và mảng con bên phải chỉ có một phần tử duy nhất Khi đó độ phức tạp của thuật toán sẽ là O(n2) Độ phức tạp của thuật toán là nhỏ nhất khi ta chọn được chốt sao cho hai mảng con có kích thước bằng nhau và bằng n/2 Khi đó độ phức tạp của thuật toán là O(n.logn)
2 Cho mảng sau:
Hãy nêu cách chọn pivot sao cho khi sắp xếp mảng trên ta được độ phức tạp
của thuật toán là nhỏ nhất.
Trả lời:
Đầu tiên, ta chọn 4 là pivot Khi đó mảng sẽ được chia thành hai mảng con
Mảng con bên trái gồm các phần tử nhỏ hơn 4
Và mảng con bên phải sẽ gồm các phần tử lớn hơn 4
Ở mảng con bên trái, ta chọn 2 là pivot Khi đó mảng sẽ được sắp xếp thành
Ở mảng con bên phải, ta chọn 6 là pivot Khi đó mảng sẽ được sắp xếp thành
Kết thúc thuật toán, mảng đã cho được sắp xếp theo đúng thứ tự với độ phức tạp là
O(n.logn)
12
Trang 133 Dùng giải thuật Quick Sort để sắp xếp mảng sau theo đúng thứ tự tăng dần.
Trong ví dụ ta luôn chọn phần tử pivot là phần tử đứng giữa danh sách
Do ngẫu nhiên, phần tử chốt a[4]=7 là phần tử lớn nhất trong dãy, ta tìm từ trái sang phải không có phần tử nào lớn hơn phần tử chốt, do đó ta đổi phần tử chốt với phần tử cuối cùng, danh sách được chia thành hai danh sách con
là a[1 6] và a[7 7]
Việc phân chia tiếp tục với danh sách con a[1 6] Phần tử pivot được chọn
là a[4]=1 Từ trái sang phải tìm được phần tử đầu tiên lớn hơn a[4]=1 là a[1]=2, từ phải sang phần tử đầu tiên <=1 là chính a[4] Đổi chố hai phần
tử này
Đi tiếp sang phải ta được a[2]>1 ở phía ngược lại đi tiếp sang trái tìm được phần tử nhỏ hơn hoặc bằng chốt là chính a[1]=1nhưng lúc này hai đường đã chạm nhau nên ta không đổi nữa Do vậy a[1 6] được phân chia thành hai
danh sách con a[1 1] và a[2 6]
Tiếp tục phân chia a[2 6] với phần tử chốt {\displaystyle a[4]= 2 ta được
Tiếp tục phân chia a[3 6] với phần tử chốt a[5]=4 ta được
Tiếp tục phân chia a[3 4] với phần tử chốt a[4]=4 và a[5 6] với phần tử chốt a[6]=5 ta được
Trang 14VI Tài liệu tham khảo
7.https://www.geeksforgeeks.org/quick-sort/?
fbclid=IwAR0pBWN3sUaoGkzOh6PRwi-d7BDnLfc5EZrKfo2iWoNtaJUHwxHK5EIAfhQ
Trang 157.