Danh sách là một kiểu dữ liệu tuyến tính : Mỗi phần tử có nhiều nhất 1 phần tử đứng trước Mỗi phần tử có nhiều nhất 1 phần tử đứng sau Là kiểu dữ liệu quen thuộc trong thực tế :
Trang 1TRƯỜNG ĐH CÔNG NGHỆ THÔNG TIN
ÔN THI CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
Giáo viên: Mai Xuân Hùng Email: hungmx@uit.edu.vn
ĐT: 08.8533066
Trang 2 Giới thiệu cấu trúc dữ liệu động
Danh sách liên kết đơn và bài tập áp
dụng
Cây nhị phân tìm kiếm và bài tập áp
dụng
Trang 3 Để đơn giản trong việc trình bày giải thuật ta dùng mảng 1 chiều
a để lưu danh sách các phần tử nói trên trong bộ nhớ chính.
Tìm phần tử có khoá bằng X trong mảng
Giải thuật tìm kiếm tuyến tính (tìm tuần tự)
Giải thuật tìm kiếm nhị phân
Lưu ý: Trong quá trình trình bày thuật giải ta dùng ngôn ngữ lập
trình C.
Trang 4 Ý tưởng : So sánh X lần lượt với phần tử thứ 1, thứ 2,…của
mảng a cho đến khi gặp được khóa cần tìm, hoặc tìm hết mảng mà không thấy.
năng+ a[i] == x tìm thấy x Dừng;
+ a[i] != x sang bước 3;
Nếu i==N: Hết mảng Dừng;
Ngược lại: Lặp lại bước 2;
Trang 5Thuật Toán Tìm Kiếm Tuyến Tính
Hàm trả về 1 nếu tìm thấy, ngược lại trả về 0:
int LinearSearch(int a[],int n, int x){
Trang 8 Giả xử ta xét mảng có thứ tự tăng, khi ấy ta có
ai-1<ai<ai+1
Nếu X>ai thì X chỉ có thể xuất hiện trong đoạn [ai+1, a
n-1 ]
Nếu X<ai thì X chỉ có thể xuất hiện trong đoạn [a0, ai-1]
Ý tưởng của giải thuật là tại mỗi bước ta so sánh X với
phần tử đứng giữa trong dãy tìm kiếm hiện hành, dựa vào kết quả so sánh này mà ta quyết định giới hạn dãy tìm kiếm ở nữa dưới hay nữa trên của dãy tìm kiếm hiện hành.
Trang 9Các Bước Thuật Toán Tìm Kiếm Nhị Phân
Giả sử dãy tìm kiếm hiện hành bao gồm các phần tử nằm
trong a left , a right , các bước của giải thuật như sau:
Bước 1: left=0; right=N-1;
Bước 2:
mid=(left+right)/2; //chỉ số phần tử giữa dãy hiện hành
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 : Right= mid-1;
• a[mid]<x : Left= mid+1;
Bước 3: Nếu Left <=Right ; // còn phần tử trong dãy hiện hành
+ Lặp lại bước 2 Ngược lại : Dừng
Trang 10Cài Đặt Thuật Toán Tìm Nhị Phân
Hàm trả về giá trị 1 nếu tìm thấy, ngược lại hàm trả về giá
Trang 12Minh Họa Thuật Toán Tìm Nhị Phân (tt)
Trang 13 B2: l= mid+1=4, mid=(l+r)/2=5, A[mid] =17<19
B3: l=mid+1=6, mid=(l+r)/2=6, A[mid]=19 =X dừng
Trang 141 Đổi chỗ trực tiếp – Interchange Sort
2 Chọn trực tiếp – Selection Sort
3 Nổi bọt – Bubble Sort
4 Chèn trực tiếp – Insertion Sort
5 Quick Sort
Trang 15Đổi Chỗ Trực Tiếp – Interchange Sort
Ý tưởng: Xuất phát từ đầu dãy, tìm tất các các nghịch
thế chứa phần tử này, triệt tiêu chúng bằng cách đổi chỗ 2 phần tử trong cặp nghịch thế Lặp lại xử lý trên với phần tử kế trong dãy.
Trang 16 Bước 1: i = 0; // bắt đầu từ đầu dãy
Bước 2: j = i+1; //tìm các nghịch thế với a[i]
Bước 3:
Trong khi j < N thực hiện
Nếu a[j]<a[i] //xét cặp a[i], a[j]
Trang 23Cài Đặt Đổi Chỗ Trực Tiếp
void InterchangeSort(int a[], int N ) {
Trang 281
Trang 29 Chọn phần tử nhỏ nhất trong N phần tử trong dãy
hiện hành ban đầu
Đưa phần tử này về vị trí đầu dãy hiện hành
Xem dãy hiện hành chỉ còn N-1 phần tử của dãy hiện
hành 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 đến khi dãy hiện hành chỉ còn 1 phần tử
Trang 30dãy hiện hành từ a[i] đến a[N]
Bước 3 : Đổi chỗ a[min] và a[i]
Bước 4 : Nếu i < N-1 thì
i = i+1; Lặp lại Bước 2;
Ngược lại: Dừng
Trang 35Cài Đặt Thuật Toán Chọn Trực Tiếp
void SelectionSort(int a[],int n )
{
int min,i,j; // chỉ số phần tử nhỏ nhất trong dãy hiện hành
for (i=0; i<n-1 ; i++) //chỉ số đầu tiên của dãy hiện hành
Trang 41Swap(a[5], a[6])
Trang 421 5
Trang 43 Xuất phát từ cuối 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, do vậy ở lần xử lý thứ i sẽ 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
Trang 44Nổi Bọt – Bubble Sort
Bước 1 : i = 0; // lần xử lý đầu tiên
Bước 2 : j = N-1; //Duyệt từ cuối dãy ngược về vị trí i
Trong khi (j > i) thực hiện:
Trang 50Cài Đặt Thuật Toán Nổi Bọt
void BubbleSort(int a[],int n) {
Trang 572
Trang 58Chèn Trực Tiếp – Insertion Sort
Giả sử có một dãy a 0 , a 1 , ,a n-1 trong đó i phần tử đầu tiên
a 0 , a 1 , ,a i-1 đã có thứ tự
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 trở nên có thứ tự Vị trí này chính là vị trí giữa hai phần tử a k-1 và a k thỏa a k-1 < a i <
a k (1≤k≤i)
Trang 59Chèn Trực Tiếp – Insertion Sort
Bước 1: i = 1; //giả sử có đoạn a[1] đã được sắp
Bước 2: x = a[i]; Tìm vị trí pos thích hợp trong đoạn
a[1] đến a[i-1] để chèn a[i] 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 a[i]
Bước 4: a[pos] = x; //có đoạn a[1] 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
Trang 63Cài Đặt Thuật Toán Chèn Trực Tiếp
void InsertionSort(int d, int n )
{ int pos, i;
int x; //lưu giá trị a[i] tránh bị ghi đè khi dời chỗ các phần tử.
for(i=1 ; i<n ; i++) //đoạn a[0] đã sắp
{
x = a[i]; pos = i-1;
// tìm vị trí chèn x
while((pos >= 0)&&(a[pos] > x)) { //kết hợp dời chỗ các phần tử sẽ đứng sau x trong dãy mới
a[pos+1] = a[pos];
pos ;
} a[pos+1] = x]; // chèn x vào dãy
} }
Trang 65Minh Họa Thuật Toán Insertion Sort
Insert a[1] into
(0,0)
Trang 665 2
Minh Họa Thuật Toán Insertion Sort
Insert a[2] into
(0, 1)
8
Trang 67Minh Họa Thuật Toán Insertion Sort
Insert a[3] into
(0, 2)
5
Trang 68Minh Họa Thuật Toán Insertion Sort
Insert a[4] into
(0, 3)
1
Trang 69Minh Họa Thuật Toán Insertion Sort
Insert a[5] into
(0, 4)
6
Trang 70Minh Họa Thuật Toán Insertion Sort
Insert a[6] into
(0, 5)
4
Trang 71Minh Họa Thuật Toán Insertion Sort
Insert a[8] into
(0, 6)
1 5
Trang 73 Nếu các đoạn 1 và 3 có nhiều hơn 1 phần tử thì dãy ban
đầu chỉ có thứ tự khi các đoạn 1, 3 được sắp
Để sắp xếp các đoạn 1 và 3, ta lần lượt tiến hành việc
phân hoạch từng dãy con theo cùng phương pháp phân hoạch dãy ban đầu vừa trình bày …
Quick Sort – Ý Tưởng
Trang 74Giải Thuật Quick Sort
Bước 1: Nếu left ≥ right //dãy có ít hơn 2 phần tử
Kết thúc; //dãy đã được sắp xếp
Bước 2: Phân hoạch dãy a left … a right thành các đoạn: a left
a j , a j+1 a i-1 , a i a right
Đoạn 1 ≤ x Đoạn 2: a j+1 a i-1 = x Đoạn 3: a i a right ≥ x
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
Trang 75Giải Thuật Quick Sort
Bước 1 : Chọn tùy ý một phần tử a[k] trong dãy là giá trị
mốc ( l ≤ k ≤ r):
x = a[k]; i = l; j = r;
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 Swap(a[i],a[j]);
Bước 3 : Nếu i < j: Lặp lại Bước 2 Ngược lại:
Dừng
Trang 81if (left<j)
QuickSort (a, left, j);
if (i<right) QuickSort (a, i, right);
}
Trang 82 Phân hoạch đọan [0,7]
Trang 84ij
Phân hoạch đọan [0,2]
Trang 87ij
Phân hoạch đọan [5,7]
Trang 88Minh họa thuật toán (tt)
Phân hoạch đọan [5,7]
2
1 5 1
Trang 92 Được khai báo tường minh, có tên gọi
Tồn tại trong phạm vi khai báo
Được cấp phát trong stack
Kích thước không đổi => không tận dụng hiệu quả bộ nhớ
Trang 94 Không được khai báo tường minh, không có tên gọi
Xin khi cần, giải phóng khi sử dụng xong
Được cấp phát trong heap
Linh động về kích thước
Vấn đề : biến động không có tên gọi tường minh, làm sao
thao tác ?
Trang 95 Kiểu con trỏ dùng lưu địa chỉ của một đối tượng dữ liệu khác.
Biến thuộc kiểu con trỏ Tp là biến mà giá trị của nó là địa chỉ
cuả một vùng nhớ ứng với một biến kiểu T, hoặc là giá trị NULL.
Khai báo trong C :
typedef int *intpointer;
intpointer p ;
Bản thân biến con trỏ là không động
Dùng biến con trỏ để lưu giữ điạ chỉ của biến động => truy
xuất biến động thông qua biến con trỏ
Trang 96Các thao tác trên kiểu con trỏ
Tạo ra một biến động và cho con trỏ ‘p’ chỉ đến nó:
Trang 98 Danh sách là một kiểu dữ liệu tuyến tính :
Mỗi phần tử có nhiều nhất 1 phần tử đứng trước
Mỗi phần tử có nhiều nhất 1 phần tử đứng sau
Là kiểu dữ liệu quen thuộc trong thực tế :
Danh sách học sinh
Danh mục sách trong thư viện
Danh bạ điện thoại
Danh sách các nhân viên trong công ty
…
Trang 100address(i) = address(1) + (i-1)*sizeof(T)
Ưu điểm : Truy xuất trực tiếp, nhanh chóng
Trang 101 Thông tin bản thân
Địa chỉ của phần tử kế trong danh sách
Trang 102Các loại danh sách liên kết
Danh sách liên kết đơn: Mỗi phần tử liên kết với phần tử
đứng sau nó trong danh sách
Danh sách liên kết kép: Mỗi phần tử liên kết với
phần tử đứng trước và sau nó trong danh sách
Danh sách liên Vòng: Phần tử cuối danh sách
liên với phần tử đầu danh sách
Trang 103Các loại danh sách liên kết (tt)
Danh sách liên Vòng: Phần tử cuối danh sách liên với phần tử
Trang 105 Thành phần liên kết: Lưu địa chỉ phần tử đứng sau
trong danh sách hoặc bằng NULL nếu là phần tử cuối danh sách
x0
x1
x2
x3
Trang 106 Cấu trúc dữ liệu của 1 nút trong List đơn
typedef struct tagNode { Data Info; // Lưu thông tin bản thân
struct tagNode *pNext; //Lưu địa chỉ của Node đứng sau
}Node;
Cấu trúc dữ liệu của DSLK đơn
typedef struct tagList { Node *pHead;//Lưu địa chỉ Node đầu tiên trong List
Node *pTail; //Lưu địa chỉ của Node cuối cùng trong List}LIST; // kiểu danh sách liên kết đơn
Info
pNex
t
Trang 107Áp dụng cho bài “quản lý sinh viên”
typedef struct tagSV
{
char ten[40];
char MSSV[40];
float ĐTB;
}SV;//Cấu trúc dữ liệu của một sinh viên
typedef struct tagNode
{
struct tagNode* pNext; // Lưu địa chỉ của Node đứng sau }Node;
Trang 109NULL6
5f7
Trang 110Các thao tác cơ bản trên DSLK đơn
Tạo 1 danh sách liên kết đơn rỗng
Tạo 1 nút có trường Infor bằng x
Tìm một phần tử có Info bằng x
Thêm một phần tử có khóa x vào danh sách
Hủy một phần tử trong danh sách
Duyệt danh sách
Sắp xếp danh sách liên kết đơn
Trang 111Khởi tạo danh sách liên kết
Địa chỉ của nút đầu tiên, địa chỉ của nút cuối cùng đều
không có void CreateList(List &l) {
l.pHead=NULL;
l.pTail=NULL;
}
Trang 112p = new Node;//Cấp phát vùng nhớ cho phần tử
Trang 113return TamNode;
}
Trang 114 Nguyên tắc thêm: Khi thêm 1 phần tử vào List thì có làm
cho pHead, pTail thay đổi?
Các vị trí cần thêm 1 phần tử vào List:
Thêm vào đầu List đơn
Thêm vào cuối List
Thêm vào sau 1 phần tử q trong list
Trang 115Thuật toán thêm 1 phần tử vào đầu DSLK
Thêm nút p vào đầu danh sách liên kết đơn
Trang 116Hàm thêm 1 phần tử vào đầu List
void AddHead(LIST &l, Node* p) {
if (l.pHead==NULL) {
l.pHead = p;
l.pTail = l.pHead; }
else {
p->pNext = l.pHead;
} }
Trang 11710
9f
P P->pNext=pHead
2f N
pHead=P
Trang 118Thuật toán thêm vào cuối DSLK
Ta cần thêm nút p vào cuối list đơn
Trang 119Hàm thêm 1 phần tử vào cuối DSLKD
void AddTail(LIST &l, Node *p) {
if (l.pHead==NULL) {
l.pHead = p;
l.pTail = l.pHead;
} else {
l.pTail->Next = p;
} }
Trang 120P
N9f
pTail->pNext
pTail=P
Trang 121Thuật toán phần tử q vào sau phần tử q
Ta cần thêm nút p vào sau nút q trong list đơn
Bắt đầu:
Nếu (q!=NULL) thì
B1: p->pNext = q->pNext B2:
+ q->pNext = p
+ nếu q = pTail thì
pTail=p
Trang 122Cài đặt thuật toán
void InsertAfterQ(List &l, Node *q, Node *p) {
if(q!=NULL) {
AddHead(l,p); // thêm q vào đầu list
}
Trang 123N
P->pNext=q->pNext q->pNext=P
Trang 124Hủy phần tử trong DSLK đơn
Nguyên tắc: Phải cô lập phần tử cần hủy trước hủy.
Ở phần trên, các phần tử trong DSLK đơn được cấp phát
vùng nhớ động bằng hàm new, thì sẽ được giải phóng vùng nhớ bằng hàm delete.
Trang 125 B3:
Nếu pHead==NULL thì pTail=NULL
Trang 126Cài đặt thuật toán
Hủy được hàm trả về 1, ngược lại hàm trả về 0
void RemoveHead(List &l)
Trang 128 B2: Nếu (p!=NULL) thì // q không phải là phần tử cuối
+ q->pNext=p->pNext;// tách p ra khỏi xâu
+ nếu (p== pTail) // nút cần hủy là nút cuối
pTail=q;
+ delete p;// hủy p
Trang 129Cài đặt thuật toán
int RemoveAfterQ(List &l, Node *q, int &x) { Node *p;
if(q!=NULL) { p=q->pNext; //p l à nút cần xoá
if(p!=NULL) // q kh ông phài là nút cuối
{ if(p==l.pTail) //n út cần xoá là nút cuối cùng
l.pTail=q; // c ập nhật lạ pTail
q->pNext=p->pNext; x=p->Info;
delete p;
} return 1;
} else return 0; }
Trang 130p-=q->pNext
q->pNext=p->pNext
pHead
Trang 131Nếu (p!=NULL) thì //tìm thấy phần tử có khoá bằng x
Hủy p ra khỏi List bằng cách hủy phần tử đứng sau q
Ngược lại
Báo không tìm thấy phần tử có khoá
Trang 132Cài đặt thuật toán
void RemoveX(List &l, int x)
while((p!=NULL)&&(p->Info!=x)) //tìm { q=p;
p=p->Next;
} if(p==NULL) //không tìm thấy phần tử có khoá bằng x
printf(“Tim khong tahy”);
else //tim thay
if(q==NULL)//tìm thấy phần tử có khoá bằng x
RemoveHead(l);
Trang 133Cài đặt thuật toán (tt)
else //co q, tuc q khac NULL {
Trang 134Xóa sinh viên có mã số bằng x
void RemoveX(List &l, char *x)
while((p!=NULL)&&(strcmp(p->Info.MSSV,x)!=0)) { q=p;
p=p->Next;
} if(p==NULL) //không tìm thấy phần tử có khoá bằng x
printf(“Tim khong tahy”);
else //tim thay
if(q==NULL)//tìm thấy phần tử có khoá bằng x
RemoveHead(l);
Trang 135Xóa sinh viên có mã số bằng x
else //co q, tuc q khac NULL {
Trang 136 Tìm tuần tự (hàm trả về), các bước của thuật toán tìm nút có
Info bằng x trong list đơn Bước 1: p=pHead; // địa chỉ của phần tử đầu trong list đơn
Trang 137 Hàm tìm phần tử có Info = x, hàm trả về địa chỉ của
nút có Info = x, ngược lại hàm trả về NULL
Node *Search(LIST l, int x) {
Trang 138Tìm sinh viên có tên x trong lớp học
Node *Search(LIST l, char *X) { Node *p;
Bài tập: Đếm số sinh viên có tên là X trong lớp
học, nếu không có thì thông báo không có
Trang 1398
Trang 140 Duyệt danh sách là thao tác thường được thực hiện khi có
nhu cầu cần xử lý các phần tử trong danh sách như:
Đếm các phần tử trong danh sách
Tìm tất cả các phần tử trong danh sách thảo điều kiện
Hủy toàn bộ danh sách
Trang 143puts(P->Info.ten);
printf(“%f ”, P->Info ĐTB) ; P=P->pNext;//qua phần tử kế
} }
Trang 145Cài đặt thuật toán
void RemoveList( List &l) {
}
Trang 147N 6
5f 7
3f
N 7
5f 6
pHea
d
pTail
Trang 148 Cách 2: Thay đổi thành phần pNext (thay đổi trình tự móc
nối của các phần tử sao cho tạo lập nên được thứ tự mong muốn)
3f
N 6
5f 7
pHea
d
pTail
Trang 149Ưu, nhược điểm của 2 cách tiếp cận
Thay đổi thành phần Info (dữ liệu)
Ưu: Cài đặt đơn giản, tương tự như sắp xếp mảng
Nhược:
• Đòi hỏi thêm vùng nhớ khi hoán vị nội dung của 2 phần tử -> chỉ phù hợp với những
xâu có kích thước Info nhỏ
• Khi kích thước Info (dữ liệu) lớn chi phí cho việc hoán vị thành phần Info lớn
Làm cho thao tác sắp xếp chậm
Trang 150Ưu, nhược điểm của 2 cách tiếp cận (tt)
Thay đổi thành phần pNext
Ưu:
• Kích thước của trường này không thay đổi,
do đó không phụ thuộc vào kích thước bản chất dữ liệu lưu tại mỗi nút
Thao tác sắp xếp nhanh
Nhược: Cài đặt phức tạp
Trang 151Dùng thuật toán SX SelectionSort để SX List
void SelectionSort(LIST &l)
if(q->Info<p->Info)
min=q;
q=q->pNext;
} HV(min->Info,p->Info); p=p->pNext;
} }
Trang 152Dùng thuật toán SX InterchangeSort
void sx(List &l)
{ Node *i,*j;
i=l.pHead;
while(i!=l.pTail) {
j=i->pNext;
while(j!=NULL) {
if(j->Info<i->Info)
Swap(j->Info,i->Info); j=j->pNext;
} i=i->pNext;
} }
Trang 154 Bảo đảm nguyên tắc bố trí khoá tại mỗi nút:
Các nút trong cây trái nhỏ hơn nút hiện hành
Các nút trong cây phải lớn hơn nút hiện hành
18
Ví dụ:
Trang 155Ưu điểm của cây nhị phân tìm kiếm
Nhờ trật tự bố trí khóa trên cây :
Định hướng được khi tìm kiếm
Trang 156Cấu trúc dữ liệu của cây nhị phân tìm kiếm
Cấu trúc dữ liệu của 1 nút
typedef struct tagTNode {
int Key; //trường dữ liệu là 1 số nguyên struct tagTNode *pLeft;
struct tagTNode *pRight;
}TNode;
Cấu trúc dữ liệu của cây
typedef TNode *TREE;