Tải bản đầy đủ (.doc) (35 trang)

Phân tích các thuật toán

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 (633.99 KB, 35 trang )

TRƯỜNG ĐẠI HỌC SƯ PHẠM TP. HỒ CHÍ MINH
KHOA CÔNG NGHỆ THÔNG TIN

MÔN PHÂN TÍCH VÀ THIẾT KẾ THUẬT GIẢI
…………………………………………………………

Đề tài: Phân tích các thuật toán Sort

Giảng viên hướng dẫn
ThS. Trịnh Huy Hoàng

Nhóm thực hiện

1/ Nguyễn Văn Hai
2/ Lý Trọng Hiếu
3/ Thiều Thị Na

TP.Cần Thơ, tháng 7 năm 2012


Thuật toán sắp xếp

MỞ ĐẦU

Đề tài nhóm chúng tôi là đánh giá độ phức tạp của các giải thuật sắp xếp. Nói
đến các giải thuật sắp xếp thì có lẽ đây là một chủ đề đã quá quen thuộc và kinh
điển. Tuy nhiên, vì chúng ta xem nó quá quen thuộc nên chúng ta thường hay
quên nó đi. Mục tiêu của đề tài này là để chúng ta cùng nhau nắm lại tư tưởng của
các thuật toán sắp xếp, độ phức tạp về mặt lý thuyết, và hơn nữa, bằng thực
nghiệm đánh giá, kiểm chứng lại các độ phức tạp này.
Nội dung của phần báo cáo được chia làm 2 phần lớn:


 Nền tảng lý thuyết: Giới thiệu tổng quan về tư tưởng, độ phức tạp của các
thuật toán sắp xếp.
 Thực nghiệm: Nêu lên cách tiến hành thực nghiệm, kết quả và nhận xét.

Trang 2


Thuật toán sắp xếp

MỤC LỤC
MỞ ĐẦU....................................................................................................................2
MỤC LỤC..................................................................................................................3
1.1 SELECTION SORT..........................................................................................4
1.1.1/ Giới thiệu sơ lược thuật toán:...................................................................4
1.2. INTERCHANGE SORT...................................................................................8
1.2.1/ Giới thiệu sơ lược thuật toán:...................................................................8
1.3. BUBBLE SORT.............................................................................................11
1.3.2/ Thuật toán bằng mã giả:.........................................................................12
1.3.3/ Mã nguồn bằng ngôn ngữ C:..................................................................12
1.3.4/ Đánh giá thuật toán:................................................................................13
1.4. INSERTION SORT........................................................................................15
1.5.1/ Giới thiệu sơ lược thuật toán:.................................................................15
* Ví dụ: Cho danh sách.....................................................................................15
1.5.2/ Thuật toán bằng mã giả:.........................................................................16
1.5.3/ Mã nguồn:...............................................................................................16
1.5. HEAP SORT..................................................................................................19
1.5.1/ Giới thiệu sơ lược thuật toán.....................................................................19
1.6. MERGE SORT..............................................................................................23
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........................................................................................................23
1.6.2/ Thuật toán bằng mã giả:.........................................................................24
1.6.4/ Đánh giá thuật toán:................................................................................26
1.7. QUICK SORT................................................................................................27
1.7.1/ Giới thiệu sơ lược thuật toán..................................................................27
1.8. SHELL SORT................................................................................................31
1.8.1/ Giới thiệu sơ lược thuật toán..................................................................31
1.8.2/ Thuật toán bằng mã giả:.........................................................................32
1.8.4/ Đánh giá thuật toán:................................................................................34

Trang 3


Thuật toán sắp xếp

1.1 SELECTION SORT
1.1.1/ Giới thiệu sơ lược thuật toán:
Selection sort là một thuật toán sắp xếp khá đơn giản nhằm cải tiến tốc độ cho
bubble sort.
Nó làm việc bằng cách đầu tiên tìm phần tử nhỏ nhất trong tập dữ liệu bằng
cách tìm kiếm tuyến tính và hoán đổi vị trí phần tử đó với phần tử đầu tiên trong tập
dữ liệu, sau đó tìm phần tử nhỏ nhất thứ hai bằng cách duyệt trong phạm vi các phần
tử còn lại trừ phần tử đầu đã xếp xong, và cứ như thế. Selection sort là thuật toán duy
nhất xét về thời gian chạy so với các thuật toán khác không bị ảnh hưởng bởi tình
trạng thứ tự của dữ liệu đầu vào, nó luôn thực hiện cùng số lượng các thao tác do
cấu trúc đơn giản của mình.
Ta chọn phần tử nhỏ nhất trong N phần tử ban đầu, đưa phần tử này về đầu dãy hiện
hành. Sau đó, ta không quan tâm đến nó nữa, ta xem dãy hiện hành chỉ còn N-1 phần tử của

dãy ban đầu tính từ vị trí thứ 2. Cứ vậy, cho đến khi dãy hiện hành chỉ còn 1 phần tử, ta
được 1 dãy sắp tăng.
* Các bước tiến hành như sau:
o Bước 1: Khởi động i = 1
o Bước 2: Tìm phần tử nhỏ nhất a[min] trong dãy hiện hành từ a[i] đến a[N]
o Bước 3: Hoán vị a[min] và a[i]
o Bước 4: i = i+1
 Nếu i < =N-1: quay trở lại bước 2
 Ngược lại: STOP!

* 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
1.1.2/ Thuật toán bằng mã giả:
Trang 4


Thuật toán sắp xếp

Void SelectionSort(int a[], int n)
{
int min;
for(int i=0;i{

min=i;
for(int j=i+1;jif(a[j]HoanVi(a[min],a[i]);
}
}
1.1.3/ Mã nguồn bằng ngôn ngữ C:
#include <iostream>
#include <conio.h>
#define max 100
//nhap mang
void NhapMang(int A[],int n)
{
for(int i=0; i{
cout<<"nhap Phan tu thu A["<cin>>A[i];
}
}
//xuat mang
void XuatMang(int A[],int n)
{
cout<for(int i=0; icout<}
//hoan vi 2 phan tu
void Swap(int &a,int &b)
{
int temp = a;

a = b;
b = temp;
}
//thuat toan Selection Sort
void SelectionSort(int A[],int n)
{
int min;
// chi so phan tu nho nhat trong day hien hanh
for(int i=0; i{
Trang 5


Thuật toán sắp xếp

min = i;
for(int j=i+1; jif(A[min]>A[j])
min = j; //ghi nhan vi tri phan tu nho nhat
if(min != i)
Swap(A[i],A[min]);
}
}
//chuong trinh chinh
void main()
{
int A[max],n;
cout<<"Nhap so phan tu:";cin>>n;
NhapMang(A,n);
cout<<"\nMang vua nhap la:";

XuatMang(A,n);
cout<SelectionSort (A,n);
cout<<"\nMang vua sap xep la:";
XuatMang(A,n);
cout<}
Kết quả hiển thị với đầu vào là 1 List các số nguyên :

1.1.4/ Đánh giá thuật toán:
void SelectionSort(int A[],int n)
{
int min;
// chi so phan tu nho nhat trong day hien hanh
for(int i=0; i(1)
Trang 6


Thuật toán sắp xếp

{
min = i;
for(int j=i+1; jif(A[min]>A[j])
min = j; //ghi nhan vi tri phan tu nho nhat
if(min != i)
Swap(A[i],A[min]);

(2)

(3)
(4)
(5)

}
}
Trong hàm trên, swap là hàm thực hiện trao đổi giá trị của hai biến. Phân
tích sắp xếp lựa chọn:
- Thân của lệnh lặp (1) là các lệnh (2), (3) và (5).
- Các lệnh (2) và (5) có thời gian chạy là O(1).
- Ta đánh giá thời gian chạy của lệnh lặp (3). Số lần lặp là (n-1-i), thời gian
thực hiện lệnh (4) là O(1), do đó thời gian chạy của lệnh (3) là (n-1-i)O(1).
- Như vậy, thân của lệnh lặp (1) có thời gian chạy ở lần lặp thứ i là (n-1i)O(1).
Do đó lệnh lặp (1) đòi hỏi thời gian (n-1-i)O(1) = O(1)(1 + 2 + …+ n-1)
= O(1)n(n-1)/2 = O(n2)
Vậy thời gian chạy của hàm sắp xếp lựa chọn là O( n 2 )
1.1.5/ Ước lượng độ phức tập thuật toán
Phân loại:
Cấu trúc dữ liệu:
Phức tạp thời gian:
Phức tạp dữ liệu:
Tối ưu:

Thuật toán sắp xếp
Ngẫu nhiên
Trung bình O(n2)
Không tốn thêm vùng nhớ
Thỉnh thoảng

Để chọn được phần tử nhỏ nhất, ta cần duyệt qua n phần tử (tốn n-1 phép so sánh) và

sau đó hoán vị nó với phần tử đầu tiên của dãy hiện hành. Để tìm phần tử nhỏ nhất tiếp theo,
ta cần duyệt qua n-1 phần tử (tốn n-2 phép so sánh). Cứ như vậy, ta thấy ngay thuật toán sẽ
tốn (n-1) + (n-2) + … + 1 = n(n-1)/2 = O(n 2) phép so sánh. Mỗi lần duyệt, ta luôn phải hoán
vị 1 lần (1 hoán vị tương đương với 3 phép gán), nghĩa là thuật toán sẽ tốn 3(n-1) + 3(n-2) +
… + 3 = 3n(n-1)/2 = O(n2) phép gán.
Tổng kết lại, ta luôn có độ phức tạp của thuật toán Selection Sort thuộc O(n 2) trong
mọi trường hợp.

Trang 7


Thuật toán sắp xếp

1.2. INTERCHANGE SORT
1.2.1/ Giới thiệu sơ lược thuật toán:
Ý tưởng chính của thuật toán này là ta 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 tăng.
* Các bước tiến hành như sau:
o Bước 1: Khởi động i = 1
o Bước 2: j = i+1
o Bước 3: Trong khi j <= N thực hiện:
 Nếu a[i]>a[j]: Hoán vị a[i] và a[j]
 j = j+1
o Bước 4: i = i+1
 Nếu i <= N-1: quay trở lại bước 2

 Ngược lại: STOP!

* Ví dụ: Mảng : 5 4 1 2 3
Vòng lặp i=0 :
Xét a[0]=5
Vòng lặp bắt đầu từ vị trí j=1
5>4 => a[0]=4
Đến j=2
4>1 => a[0]=1
Đến j=3
1 < 2 => Ko thay thay thế
Đến j=4
1 < 3 => Ko thay thế
Giá trị mảng sau khi kết thúc I = 0 : 1 4 2 3 5
1.2.2/ Thuật toán bằng mã giả:

void InterchangeSort(int a[], int n)
{
int i, j;
Trang 8


Thuật toán sắp xếp

for (i = 0 ; ifor (j =i+1; j < n ; j++)
if(a[j]< a[i]) //nếu có nghịch thế thì đổi chỗ
Swap(a[i],a[j]);
}
1.2.3/ Mã nguồn bằng ngôn ngữ C:


#include <iostream.h>
#include <stdio.h>
#include <conio.h>
#define max 100
void NhapMang(int A[], int &n)
{
cout<<"Nhap bao nhieu phan tu: ";cin>>n;
for(int i = 0; i{
cout<<"Phan tu A["<}
}
void XuatMang(int A[], int n)
{
cout<<"Mang sau khi sap xep la: ";cout<for(int i = 0; icout<cout<}
void InterchangeSort(int A[], int n)
{
for (int i=0; ifor (int j=i+1; jif (A[i]>A[j])
{
int temp=A[i];
A[i]=A[j];
A[j]=temp;
}

}
void main()
{
int A[max],n;
NhapMang(A,n);
InterchangeSort(A,n);
XuatMang(A,n);
}
Kết quả hiển thị với đầu vào là 1 List các số nguyên :
Trang 9


Thuật toán sắp xếp

1.2.4/ Đánh giá thuật toán:
Số lượng các phép so sánh xảy ra không phụ thuộc vào tình trạng của dãy số
ban đầu.
Số lượng phép hoán vị thực hiện tùy thuộc vào kết quả so sánh
Thấy ngay số phép so sánh là luôn không đổi, tức không phụ thuộc vào tình
trạng ban đầu của dãy. Ta có thể ước lượng số phép so sánh bằng (n-1) + (n-2) + … +
1 = n(n-1)/2 (phần tử thứ i được so sánh với n-i phần tử còn lại.)
Số phép hoán vị (tương đương 3 phép gán) lại phụ thuộc vào tình trạng ban
đầu của dãy. Cụ thể như sau:
+ Trường hợp tốt nhất: Dãy ban đầu đã có thứ tự. Ta thấy ngay ta không tốn
một phép hoán vị nào.
+ Trường hợp xấu nhất: Dãy ban đầu có thứ tự ngược. Ta thấy ngay mỗi lần so
sánh phần tử thứ i với n-i phần tử còn lại, ta đều phải thực hiện hoán vị. Điều này có
nghĩa là số phép hoán vị bằng n(n-1)/2.
1.2.5/ Ước lượng độ phức tạp thuật toán:
Trường hợp tốt nhất: Dãy ban đầu đã có thứ tự. Ta thấy ngay ta không tốn một

phép hoán vị nào.
Trường hợp xấu nhất: Dãy ban đầu có thứ tự ngược. Ta thấy ngay mỗi lần so
sánh phần tử thứ i với n-i phần tử còn lại, ta đều phải thực hiện hoán vị. Điều này có
nghĩa là số phép hoán vị bằng n(n-1)/2.

Tổng kết lại, ta có độ phức tạp của Interchange Sort thuộc O(n 2) trong mọi
trường hợp.

Trang 10


Thuật toán sắp xếp

1.3. BUBBLE SORT
1.3.1/ Giới thiệu sơ lược thuật toán:
Như đã biết về thuật toán Sắp xếp chọn ta thấy rằng sau mỗi vòng lặp ta lựa
được một phần tử nhỏ nhất đưa về đầu danh sách, các phần tử còn lại hầu như không
ảnh hưởng. Ta thấy rằng kết quả của vòng lặp trước không ảnh hưởng đến vòng lặp
sau. Trong bài viết này chúng ta sẽ xét tới thuật toán sắp xếp Nổi bọt – Cải tiển hơn
so với Thuật toán sắp xếp chọn.
Với thuật toán này, sẽ có 2 vòng lặp lồng nhau. Tại mỗi vòng lặp con sẽ chạy
từ cuối danh sách trở về đầu danh sách. tại mỗi bước sẽ so sánh 2 phần tử kế cận
nhau nếu phần tử nào nhỏ hơn sẽ được đảo về phía trước. Trong mỗi vòng lặp con,
phần tử nhỏ nhất sẽ lần lượt được đưa về đầu dòng theo phương pháp tráo đổi – Đúng
với tư tưởng “Nổi bọt” phần tử nhỏ sẽ dần được nổi lên trên cùng. Đặc điểm tối ưu
hơn so với Sắp xếp chọn là tại mỗi vòng lặp con do có quá trình tráo đổi phần tử nhỏ
về đầu dãy nên các phần tử lớn dần được đưa về cuối dãy và đồng thời những giá trị
nhỏ cũng dần được đưa về đầu dãy. Đối với phương pháp sắp xếp chọn có tối đa n –
1 lần tráo đổi phần tử, còn trường hợp sắp xếp nổi bọt thì tối đa n*(n-1) lần tráo đổi
(Trường hợp danh sách sắp theo thứ tự ngược lại). Nhưng ưu điểm trông thấy rõ của

phương pháp Nổi bọt là sau mỗi bước thì danh sách dần “ổn định” hơn. Nên có thể
không cần chạy hết vòng lặp danh sách đã được theo thứ tự.
* 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]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.
* Ví dụ:
Bên dưới là ví dụ sắp xếp mảng số nguyên theo phương pháp nổi bọt được
thực hiện từng bước. Bước 0 là trạng thái ban đầu của danh sách. Ta thấy sau mỗi
bước thì các phần tử đầu tiên lần lượt được đưa vào thứ tự, các phần tử còn lại cũng
dần ổn định hơn. Đến bước 6 thì đã hoàn thành công việc sắp xếp.

Trang 11


Thuật toán sắp xếp

1.3.2/ Thuật toán bằng mã giả:

void boublesort(int *a,int n)
{

int i,j;
for (i=0;ifor (j=n-1;j>i;j--)
if (a[j] < a[j-1])
hoanvi(a[j],a[j-1]);
}
1.3.3/ Mã nguồn bằng ngôn ngữ C:

#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void NhapMang(int A[], int &n)
{
cout<<"Nhap bao nhieu phan tu: ";cin>>n;
for(int i = 0; i{
cout<<"Phan tu thu A["<cin>>A[i];
}
}
void XuatMang(int A[], int n, int j)
{
for(int i = 0; i//if(i>=j)
cout<//else
//cout<}
void HoanVi(int &a, int &b)
{

int temp = a;
a = b;
b = temp;
}
void BubleSort(int A[], int n)
{
for(int i = 0; i{
for(int j = n-1; j>i; j--)
if(A[j]HoanVi(A[j],A[j-1]);
XuatMang(A,n,i);
Trang 12


Thuật toán sắp xếp

}
}
void main()
{
int C[100], n, S;
NhapMang(C,n);
cout<<"Mang sau khi sap sep: ";cout<XuatMang(C,n,0);
BubleSort(C,n);
}
Kết quả hiển thị với đầu vào là 1 List các số nguyên :

1.3.4/ Đánh giá thuật toán:

- Thấy ngay số phép so sánh là luôn không đổi, tức không phụ thuộc vào tình trạng
ban đầu của dãy. Với i bất kỳ, ta luôn phải so sánh V[j] với V[j-1], mà j chạy từ n đến i+1,
tức ta tốn n-i phép so sánh. Thêm nữa, i chạy từ 1 đến n-1. Vậy ta tính được số phép so sánh
tổng cộng: ∑(n-i) với i chạy từ 1 đến n-1 = (n-1) + (n-2) + … + 1 = n(n-1)/2.
- Số phép hoán vị (tương đương 3 phép gán) lại phụ thuộc vào tình trạng ban đầu của
dãy. Cụ thể như sau:
+ Trường hợp tốt nhất: Dãy ban đầu đã có thứ tự. Ta thấy ngay ta không tốn
một phép hoán vị nào.
+ Trường hợp xấu nhất: Dãy ban đầu có thứ tự ngược. Xét i bất kỳ, ta thấy
rằng mỗi lần so sánh a[j] với a[j-1], ta đều phải thực hiện hoán vị. Điều này có nghĩa là số
phép hoán vị bằng n(n-1)/2.

- Khuyết điểm:
+ Không nhận diện được tình trạng dãy đã có thứ tự hay có thứ tự từng
phần.
+ Các phần tử nhỏ được đưa về vị trí đúng rất nhanh, trong khi các phần
tử lớn lại được đưa về vị trí đúng rất chậm.

Trang 13


Thuật toán sắp xếp

1.3.5/ Ước lượng độ phức tạp thuật toán:
- Trường hợp tốt nhất: Dãy ban đầu đã có thứ tự. Ta thấy ngay ta không tốn một phép
hoán vị nào.
- Trường hợp xấu nhất: Dãy ban đầu có thứ tự ngược. Xét i bất kỳ, ta thấy rằng mỗi
lần so sánh a[j] với a[j-1], ta đều phải thực hiện hoán vị. Điều này có nghĩa là số phép hoán
vị bằng n(n-1)/2.


Tổng kết lại, ta có độ phức tạp của Bubble Sort thuộc O(n2) trong mọi trường hợp.

Trang 14


Thuật toán sắp xếp

1.4. INSERTION SORT
1.5.1/ Giới thiệu sơ lược thuật toán:
Sắp xếp chèn (insertion sort) là một thuật toán sắp xếp rất hiệu quả với các
danh sách nhỏ. Nó lần lượt lấy các phần tử của danh sách chèn vào vị trí thích hợp
trong một danh sách mới đã được sắp.
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.
Giả sử ta có dãy a1, a2, …, an trong đó i phần tử đầu tiên a1, a2, …, ai đã có thứ tự. Ý
tưởng của thuật toán là tìm vị trị thích hợp và chèn phần tử a i+1 vào dãy đã có thứ tự trên để

có được một dãy mới có thứ tự. Cứ thế, làm đến cuối dãy ta sẽ được một dãy có thứ tự.
Với dãy ban đầu a1, a2, …, an ta có thể coi đoạn chỉ có một phần tử a 1 là một đoạn đã có
thứ tự, sau đó ta chèn phần tử a2 vào dãy a1 để có dãy a1a2 có thứ tự. Tiếp đó, ta lại chèn
phần tử a3 vào dãy a1a2 để có dãy a1a2a3 có thứ tự. Cứ thế, đến cuối cùng ta chèn phần tử a n
vào dãy a1a2…an-1 ta sẽ được dãy a1a2…an có thứ tự.

* Các bước thực hiện:
//input: dãy (a, n)
//output: dãy (a, n) đã được sắp xếp
 Bước 1: i = 2;
// 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.
* Ví dụ: Cho danh sách
1376425

Trang 15


Thuật toán sắp xếp

Danh sách con gồm 3 phần tử bên trái 1,3,7 đã được sắp. Để tiếp tục sắp xếp phần
tử thứ tư a4 = 6 vào danh sách con đó, ta tìm vị trí thích hợp của nó là sau 3 và trước
7.

1367425
Làm tiếp theo với a5 = 4 ta được:
1346725
Làm tiếp theo với a6 = 2 ta được:
1234675
Cuối cùng chèn a7 = 5:
1234567
1.5.2/ Thuật toán bằng mã giả:
void insertion_sort(int x[],int length)
{
int key,i;
for(int j=1;j{
key=x[j];
i=j-1;
while(x[i]>key && i>=0)
{
x[i+1]=x[i];
i--;
}
x[i+1]=key;
}
}
1.5.3/ Mã nguồn:
#include <iostream.h>
#include<conio.h>
//#define size 6
void insertion_sort(int x[],int length)
{
int key,i;

for(int j=1;j{
key=x[j];
i=j-1;
while(x[i]>key && i>=0)
{
x[i+1]=x[i];
i--;
}
x[i+1]=key;
}
}
Trang 16


Thuật toán sắp xếp

int main()
{
int A[25];
int size,i;
int x;
cout<<"Nhap mang bao nhieu phan tu: ";cin>>size;
for(x=0;x{ cout<<"Phan tu thu A["<cout<<"Mang nhap la:"<for(x=0;x{
cout<}

insertion_sort(A,size);
cout<for(x=0;x{
cout<}
cout<}
Kết quả hiển thị với đầu vào là 1 List các số nguyên :

1.5.4/ Đánh giá thuật toán:
void InsertionSort(int a[], int n )
{ int pos, x;
for(int i=0 ; i{ x = a[i+1];pos= i;
while(pos>=0 && a[pos]>x)
Trang 17


Thuật toán sắp xếp

{

a[pos+1] = a[pos];
pos--;

}
a[pos]=x;
}
}

Khi tìm vị trí thích hợp để chèn a[i] vào đoạn a[0] đến a[i-1], do đoạn đã được
sắp  có thể sử dụng giải thuật tìm nhị phân để thực hiện việc tìm vị trí pos  giải
thuật sắp xếp chèn nhị phân Binary Insertion Sort
Lưu ý: Chèn nhị phân chỉ làm giảm số lần so sánh, không làm giảm số lần dời
chỗ.
Ngoài ra, có thể cải tiến giải thuật chèn trực tiếp với phần tử cầm canh để
giảm điều kiện kiểm tra khi xác định vị trí pos.
Các phép so sánh xảy ra trong mỗi vòng lặp tìm vị trí thích hợp pos. Mỗi lần
xác định vị trí pos đang xét không thích hợp  dời chỗ phần tử a[pos-1] đến vị trí
pos.
Giải thuật thực hiện tất cả N-1 vòng lặp tìm pos, do số lượng phép so sánh và
dời chỗ này phụ thuộc vào tình trạng của dãy số ban đầu, nên chỉ có thể ước lược
trong từng trường hợp như sau:
1.5.5/ Ước lượng độ phức tạp thuật toán
Ta thấy các phép so sánh xảy ra trong vòng lặp nhằm tìm vị trí thích hợp pos để chèn
x. Mỗi lần so sánh mà thấy vị trí đang xét không thích hợp, ta dời phần tử a[pos] sang phải.
Ta cũng thấy số phép gán và số phép so sánh của thuật toán phụ thuộc vào tình trạng
của dãy ban đầu. Do đó ta chỉ có thể ước lượng như sau:
- Trường hợp tốt nhất: dãy ban đầu đã có thứ tự. Ta tìm được ngay vị trí thích hợp để
chèn ngay lần so sánh đầu tiên mà không cần phải vô vòng lặp. Như vậy, với i chạy từ 2 đến
n thì số phép so sánh tổng cộng sẽ là n-1. Còn với số phép gán, do thuật toán không chạy
vào vòng lặp nên xét i bất kỳ, ta luôn chỉ phải tốn 2 phép gán(x = a[i] và a[pos] = x). Từ
đây, ta tính được số phép gán tổng cộng bằng 2(n - 1).
- Trường hợp xấu nhất: dãy ban đầu có thứ tự ngược. Ta thấy ngay vị trí thích hợp
pos luôn là vị trí đầu tiên của dãy đã có thứ tự, và do đó, để tìm ra vị trí này ta phải duyệt
hết dãy đã có thứ tự. Xét i bất kỳ, ta có số phép so sánh là i-1, số phép gán là (i - 1) + 2 = i +
1. Với i chạy từ 2 đến n, ta tính được số phép so sánh tổng cộng bằng 1 + 2 + … + (n - 1) =
n(n - 1)/2 và số phép gán bằng 3 + 4 + .. + (n + 1) = (n + 4)(n - 1)/2
Tổng kết lại, ta có độ phức tạp của Insertion Sort như sau:
o Trường hợp tốt nhất: O(n)

o Trường hợp xấu nhất O(n2)

Trang 18


Thuật toán sắp xếp

1.5. HEAP SORT
1.5.1/ Giới thiệu sơ lược thuật toán
Định nghĩa Heap: Heap là mảng 1 chiều chứa các phần tử từ a 1, a2 … an. Các phần
tử từ a[n/2 + 1] đến a[n] là heap tự nhiên. Các phần tử còn lại thỏa a [i] >= a[2*i] và a[i] >= a[2*i+1]
(i=1…n/2) (Điều kiện này là cho sắp xếp tăng dần)
Như vậy ta thấy gốc của heap luôn là phần tử lớn nhất, nếu ta lần lượt rút trích phần
tử gốc và xây dựng lại heap đã bị rút trích ta sẽ có một mảng có thứ tự tăng dần.

Khi tìm kiếm phần tử min ở bước thứ i trong phương pháp chọn trực tiếp,
không tận dụng được hết các phép so sánh được ở bước thứ i-1, vì vậy heap sort được
xây dựng để khắc phục nhược điểm này.
Heap sort là giải thuật sắp xếp dựa trên cây.
Ta giả sử dữ liệu cần sắp xếp được bố trí theo quan hệ so sánh và tạo thành sơ
đồ dạng cây như sau :

1

5

1

8


3
5
2

8
5

6

1
4

7
2

6

Với cây trên, phần tử ở mức i là phần tử lớn hơn cặp phần tử ở mức i+1, vì vậy
phần tử ở gốc trên cùng là phần tử lớn nhất. => 1 cây có tính chất như trên gọi là 1
heap.
Nếu loại bỏ phần tử gốc thì 2 nhánh con còn lại cũng tạo thành 1 heap, vì vậy
việc cập nhật chỉ xảy ra với nhánh liên quan đến phần tử mới loại bỏ. => sử dụng lại
kết quả đã so sánh ở 2 nhánh con

Trang 19


Thuật toán sắp xếp

1 heap

Loại bỏ phần tử gốc
1 heap

1
5

1

8

3
5
2

8
5

6

1
4

7
2

6

* Giải thuật heap sort gồm 2 giai đoạn :
+ Giai đoạn 1: hiệu chỉnh dãy số ban đầu thành heap
Heap là dãy các phần tử a1,a2,…ar thỏa các quan hệ với mọi i ∈ [l,r] :

ai ≥ a2i và ai ≥ a2i+1 với { (ai,a2i) , (ai,a2i+1) là các cặp phần tử liên
đới }.
+ Giai đoạn 2: sắp xếp dãy số dựa trên heap :
Bước 1: đưa phần tử lớn nhất về vị trí cuối dãy
r=n-1; hoán vị(a1,ar);
Bước 2: loại bỏ phẩn tử lớn nhất ra khỏi dãy
r=r-1;
Hiệu chỉnh phần còn lại của dãy từ a1,a2 ,…,ar thành 1 heap.
Bước 3: nếu r>0 (heap còn phần tử) Lặp lại bước 2
Ngược lại : dừng.
1.5.2/ Thuật toán bằng mã giả
void HeapSort (int a[], int N)
{ int r;
CreateHeap(a,N)
r = N-1; // r là vị trí đúng cho phần tử nhỏ nhất
while(r > 0) do
{
Hoanvi(a[1],a[r]);
r = r -1;
Shift(a,1,r);

Trang 20


Thuật toán sắp xếp

}
1.5.3/ Mã nguồn bằng ngôn ngữ C:

#include <iostream.h>

#include <conio.h>
#define max 100
void NhapMang(int A[],int n) {
for(int i=0; icout<<"nhap Phan tu thu A["<cin>>A[i];
}
}
void XuatMang(int A[],int n) {
cout<for(int i=0; icout<}
void Swap(int &a,int &b) {
int temp = a;
a = b;
b = temp;
}
//hoan vi nut cha thu i phai lon hon nut con
void Heapify(int A[],int n, int i) {
int Left = 2*(i+1)-1;
int Right = 2*(i+1);
int Largest;
if(Left<n && A[Left]>A[i])
Largest = Left;
else
Largest = i;
if(Right<n && A[Right]>A[Largest])
Largest = Right;
if(i!=Largest) {

Swap(A[i],A[Largest]);
Heapify(A,n,Largest);
}
}
//xay dung Heap sao cho moi nut cha luon lon hon nut con tren cay
void BuildHeap(int A[], int n) {
for(int i = n/2-1; i>=0; i--)
Heapify(A,n,i);
}
void HeapSort(int A[], int n) {
BuildHeap(A,n);
for(int i = n-1; i>0; i--){
Trang 21


Thuật toán sắp xếp

Swap(A[0],A[i]);
Heapify(A,i,0);
}
}
void main() {
int A[max], n;
cout<<"Nhap so phan tu:";
cin>>n;
NhapMang(A,n);
cout<<"\nMang vua nhap la:";
XuatMang(A,n);
cout<<"\nSap xep theo Heap Sort:";
HeapSort(A,n);

XuatMang(A,n);
}
Kết quả hiển thị với đầu vào là 1 List các số nguyên :

1.5.4/ Đánh giá thuật toán:
- Heap sort không có khả năng nhận biết dãy được sắp đã có thứ tự hay chưa.
- Sắp xếp vun đống (heapsort) là một trong các phương pháp sắp xếp chọn. Ở mỗi
bước của sắp xếp chọn ta chọn phần tử lớn nhất (hoặc nhỏ nhất) đặt vào cuối (hoặc
đầu) danh sách, sau đó tiếp tục với phần còn lại của danh sách. Thông thường sắp xếp
chọn chạy trong thời gian O(n2).
- Nhưng heapsort đã giảm độ phức tạp này bằng cách sử dụng một cấu trúc dữ liệu
đặc biệt được gọi là đống (heap). Đống là cây nhị phân mà trọng số ở mỗi đỉnh cha
lớn hơn hoặc bằng trọng số các đỉnh con của nó. Một khi danh sách dữ liệu đã được
vun thành đống, gốc của nó là phần tử lớn nhất, thuật toán sẽ giải phóng nó khỏi đống
để đặt vào cuối danh sách. Sắp xếp vun đống chạy trong thời gian O(nlogn).
1.5.5/ Ước lượng độ phức tạp
Trang 22


Thuật toán sắp xếp

Việc đánh giá giải thuật Heapsort rất phức tạp, nhưng đã chứng minh được trong
trường hợp xấu nhất độ phức tạp là O(nlog2n).

1.6. MERGE SORT
1.6.1/ Giới thiệu sơ lượt thuật toán:
Sắp xếp trộn (merge sort) cùng với sắp xếp nhanh là hai thuật toán sắp xếp dựa
vào tư tưởng "chia để trị" (divide and conquer). Thủ tục cơ bản là việc trộn hai danh
sách đã được sắp xếp vào một danh sách mới theo thứ tự. Nó có thể bắt đầu trộn bằng
cách so sánh hai phần tử một (chẳng hạn phần tử thứ nhất với phần tử thứ hai, sau đó

thứ ba với thứ tư...) và sau khi kết thúc bước 1 nó chuyển sang bước 2. Ở bước 2 nó
trộn các danh sách hai phần tử thành các danh sách bốn phần tử. Cứ như vậy cho đến
khi hai danh sách cuối cùng được trộn thành một.
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.
Cho dãy ban đầu a1, a2, …, an. Ta luôn có thể coi nó là tập hợp liên tiếp của các dãy
có thứ tự. Ta gọi các dãy có thứ tự này là các dãy con.
Trong phương pháp Merge Sort, vấn đề là ta tìm cách phân hoạch dãy ban đầu thành
các dãy con. Sau khi phân hoạch xong, dãy ban đầu sẽ được tách thành hai dãy phụ theo
nguyên tắc phân phối luân phiên dãy con. Sau đó, ta trộn từng cặp dãy con của hai dãy phụ
thành một dãy con của dãy ban đầu. Ta nhận thấy số dãy con của dãy ban đầu lúc này giảm
đi ít nhất là một nửa. Cứ thế sau một số bước, ta sẽ nhận được dãy ban đầu với số dãy con
bằng 1, có nghĩa là ta đã sắp xếp xong.
Trộn trực tiếp: đây là phương pháp trộn đơn giản nhất. Việc phân hoạch dãy ban đầu
đơn giản như sau: Với dãy ban đầu có n phân tử, ta cứ phân hoạch thành n dãy con. Vì rằng
mỗi dãy con chỉ có 1 phần tử nên nó là dãy có thứ tự. Cứ mỗi lần tách – trộn, chiều dài của
dãy con sẽ được nhân đôi.

* Các bước tiến hành:
Mô tả bài toán: Cho 2 danh sách A và B lần lượt có m và n phần tử đã sắp xếp theo
thứ tự. Bài toán đặt ra trộn 2 danh sách A và B với nhau thành danh sách C cũng là
một danh sách có thứ tự.
Thuật toán:
Bước 1: khởi tạo ba chỉ số chạy trong vòng lặp i = 0, j = 0, k = 0 tương ứng cho
ba mảng A, B và C.
Bước 2: tại mỗi bước nếu cả hai chỉ số (ilưu nó vào trong C[k]. Chuyển sang Bước 4.
Bước 3: tăng giá trị k lên 1 và quay về Bước 2.

Bước 4: sao chép tất cả các giá trị còn lại từ các danh sách mà chỉ số còn vi
phạm (tức iTrang 23


Thuật toán sắp xếp

1.6.2/ Thuật toán bằng mã giả:
void MergeSort(int m, int n, int &k, int A[], int B[], int C[])
{
int i = 0, j = 0;
k = 0;
while (i < m && j < n) {
if (A[i] <= B[j]) {
C[k] = A[i];
i++;
} else {
C[k] = B[j];
j++;
}
k++;
}
if (i < m) {
for (int p = i; p < m; p++) {
C[k] = A[p];
k++;
}
} else {
for (int p = j; p < n; p++) {
C[k] = B[p];

k++;
}
}
}
1.6.3/ Mã nguồn bằng ngôn ngữ C++
#include <iostream.h>
#include <conio.h>
#define max 100
void NhapMang(int A[],int n) {
for(int i=0; icout<<"Phan tu "<cin>>A[i];
}
}
void XuatMang(int A[],int n) {
cout<for(int i=0; icout<}
void MergeSort(int m, int n, int &k, int A[], int B[], int C[]) {
Trang 24


Thuật toán sắp xếp

int i = 0, j = 0;
k = 0;
while (i < m && j < n) {
if (A[i] <= B[j]) {
C[k] = A[i];

i++;
} else {
C[k] = B[j];
j++;
}
k++;
}
if (i < m) {
for (int p = i; p < m; p++) {
C[k] = A[p];
k++;
}
} else {
for (int p = j; p < n; p++) {
C[k] = B[p];
k++;
}
}
}
void main() {
int A[max],B[max],C[max],n,m,k;
cout<<"Nhap mang A bao nhieu phan tu: ";
cin>>m;
cout<<"Nhap phan B bao nhieu phan tu: ";
cin>>n;
cout<<"Nhap danh sach co thu tu A:\n";
NhapMang(A,m);
cout<<"Nhap danh sach co thu tu B:\n";
NhapMang(B,n);
cout<<"\nSap xep tron 2 mang A, B\n";

MergeSort(m,n,k,A,B,C);
XuatMang(C,k);
cout<}
Kết quả hiển thị với đầu vào là 1 List các số nguyên :

Trang 25


Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×