CTDL&TT
CTDL&TT
CTDL&TT
THUẬT TOÁN
Khái niệm cơ bản
1
Nội dung
• Định nghĩa và khái niệm cơ bản
• Mã giả
• Độ phức tạp tính toán
• Ký hiệu tiệm cận
• Ví dụ mở đầu
Định nghĩa và khái niệm
• Cấu trúc dữ liệu
• Cách thức tổ chức dữ liệu trong bộ nhớ để truy cập và cập
nhật thuận tiện
Định nghĩa và khái niệm
• Cấu trúc dữ liệu
• Cách thức tổ chức dữ liệu trong bộ nhớ để truy cập và cập
nhật thuận tiện
• Thuật toán
• Dãy hữu hạn các bước tính toán để thu được đầu ra ứng với
đầu vào
Định nghĩa và khái niệm
• Cấu trúc dữ liệu
• Cách thức tổ chức dữ liệu trong bộ nhớ để truy cập và cập
nhật thuận tiện
• Thuật toán
• Dãy hữu hạn các bước tính toán để thu được đầu ra ứng với
đầu vào
• Mục tiêu môn học
• Trang bị kiến thức để thiết kế và cài đặt các cấu trúc dữ liệu
và thuật toán hiệu quả để giải quyết các bài toán tính toán
• Ứng dụng
• Hệ quản trị cơ sở dữ liệu
• Tính toán tối ưu hóa
• Trí tuệ nhân tạo, thị giác máy tính
• Hệ điều hành
• …
Mã giả
• Mô tả thuật toán đơn giản, gần gũi, ngắn gọn và không phụ thuộc vào cú
pháp ngôn ngữ lập trình cụ thể
Assignment Condition max(a[1..n]){
x = <expression>; if a < b then { ans = a[1];
x <expression>; . . . for i = 2 to n do
} if ans < a[i] then
max = a[i];
For loop Procedures, funtions return ans;
for i = 1 to n do{ }
. . . proc(a,b,x){
} . . .
return ans;
While loop }
while i 100 do{
. . .
}
Mã giả
• Một bài toán (ví dụ sắp xếp) có thể có nhiều thuật toán giải quyết
selectionSort(a[1..n]){ insertionSort(a[1..n]){
for k = 1 to n do{ for k = 2 to n do{
min = k; last = a[k];
for j = k+1 to n do{ j = k;
if a[min] > a[j] then while(j > 1 and a[j-1] > last){
min = j; a[j] = a[j-1];
} j--;
swap(a[k],a[min]); }
} a[j] = last;
} }
}
Phân tích độ phức tạp thuật toán
• Phân tích độ phức tạp thuật toán
• Thời gian
• Bộ nhớ sử dụng
• Phân tích thời gian thực hiện
• Thông qua thí nghiệm
• Phân tích câu lệnh cơ bản
Phân tích độ phức tạp thuật toán
• Thực nghiệm
• Viết chương trình bằng ngôn ngữ lập trình cụ thể
• Chạy chương trình trên một máy tính với nhiều bộ dữ liệu đầu vào
khác nhau
• Vẽ biểu đồ thời gian thực hiện
Phân tích độ phức tạp thuật toán
• Thực nghiệm
• Viết chương trình bằng ngôn ngữ lập trình cụ thể
• Chạy chương trình trên một máy tính với nhiều bộ dữ liệu đầu vào
khác nhau
• Vẽ biểu đồ thời gian thực hiện
s = 0;
for i = 1 to n do
s = s + a[i];
Câu lệnh cơ bản là câu lệnh gán → thời gian thực hiện là T(n) = n+1
Phân tích độ phức tạp thuật toán
1. insertionSort(a[1..n]){ Dòng Thời gian Số lần
2. for j = 2 to n do{ 2 c2 n
3. key = a[j];
3 c3 n-1
4. i = j-1;
5. while i > 0 and a[i] > key do{ 4 C4 n-1
𝑛
6. a[i+1] = a[i]; 5 C5
7. i = i – 1; 𝑡𝑗
𝑗=2
8. }
𝑛
9. a[i+1] = key; 6 C6
(𝑡𝑗 − 1)
10. } 𝑗=2
11. } 𝑛
7 C7
(𝑡𝑗 − 1)
𝑗=2
Ký hiệu tj: số lần điều kiện của vòng lặp while (dòng 5)
được thực hiện ứng với 1 giá trị j (vòng lặp bên ngoài) 9 c9 n-1
Thời gian thực hiện T(n) = c2n + c3(n-1) + c4(n-1) +c5σ𝑛𝑗=2 𝑡𝑗 + c6σ𝑛𝑗=2(𝑡𝑗 − 1)
+ c7σ𝑛𝑗=2(𝑡𝑗 − 1) + c9(n-1)
Phân tích độ phức tạp thuật toán
maxSubSeq3(a[1..n]){ maxSubSeq2(a[1..n]){
ans = -; ans = -;
for i = 1 to n do{ for i = 1 to n do{
for j = i to n do{ s = 0;
s = 0; for j = i to n do{
for k = i to j do s = s + a[k];
s = s + a[k]; if s > ans then
if s > ans then ans = s;
ans = s; }
} }
} return ans;
return ans; }
}
Thuật toán trực tiếp: thời Cải tiến: Thời gian O(n2)
gian O(n3)
Ví dụ mở đầu
• Quy hoạch động maxSubSeq1(a[1..n]){
• Ký hiệu s[i]: trọng số của dãy con lớn s[1] = a[1];
nhất của dãy a1, . . ., ai trong đó phần ans = s[1];
tử cuối cùng là ai. for i = 2 to n do{
if s[i-1] > 0 then
s[i] = s[i-1] + a[i];
else s[i] = a[i];
if ans < s[i] then
ans = s[i];
}
• s[1] = a1 return ans;
}
• s[i] = s[i-1] + ai, if s[i-1] > 0
ai, ngược lại
Thuật toán tốt nhất: Thời gian
O(n)
CẤU TRÚC DỮ LIỆU
VÀ THUẬT TOÁN
Sơ đồ thuật toán cơ bản
NỘI DUNG
• Đệ quy
• Đệ quy có nhớ
• Đệ quy quay lui
• Thuật toán nhánh và cận
• Thuật toán tham lam
• Thuật toán chia để trị
• Thuật toán quy hoạch động
ĐỆ QUY
• Hàm được định nghĩa đệ quy
• Bước cơ sở: định nghĩa giá trị hàm với tham số cơ sở (nhỏ nhất)
• Bước đệ quy: giá trị hàm phụ thuộc vào giá trị của hàm với tham
số nhỏ hơn
F(n) = 1, nếu n = 1 (bước cơ sở)
F(n-1) + n, với n > 1 (bước đệ quy)
ĐỆ QUY
• Hàm được định nghĩa đệ quy
• Bước cơ sở: định nghĩa giá trị hàm với tham số cơ sở (nhỏ nhất)
• Bước đệ quy: giá trị hàm phụ thuộc vào giá trị của hàm với tham
số nhỏ hơn
F(n) = 1, nếu n = 1 (bước cơ sở)
F(n-1) + n, với n > 1 (bước đệ quy)
A B C A B C
move(n, A, B, C) {
if(n == 1) {
print(“Move 1 disk from A to B”)
}else{
move(n-1, A, C, B);
move(1, A, B, C);
move(n-1, C, B, A);
}
A B C }
ĐỆ QUY CÓ NHỚ
C(3,5)
C(2,4) C(3,4)
int m[MAX][MAX];
• Khắc phục tình trạng một chương
trình con với tham số xác định int C(int k, int n) {
được gọi đệ quy nhiều lần if (k == 0 || k == n)
m[k][n] = 1;
• Sử dụng bộ nhớ để lưu trữ kết else {
quả của một chương trình con với if(m[k][n] < 0){
tham số xác định m[k][n] = C(k-1,n-1) +
C(k,n-1);
• Bộ nhớ được khởi tạo với giá trị }
đặc biệt để ghi nhận mỗi chương }
trình con chưa được gọi lần nào return m[k][n];
}
• Địa chỉ bộ nhớ sẽ được ánh xạ int main() {
với các giá trị tham số của for(int i = 0; i < MAX; i++)
chương trình con for(int j = 0; j < MAX; j++)
m[i][j] = -1;
printf(“%d\n”,C(16,32));
}
ĐỆ QUY QUAY LUI
• Áp dụng để giải các bài toán liệt kê, bài toán tối ưu tổ hợp
• A = {(x1, x2, . . ., xn ) | xi Ai, i = 1,. . ., n}
• Liệt kê tất cả các bộ x A thoả mãn một thuộc tính P nào đó
• Thủ tục TRY(k):
• Thử các giá trị v có thể gán cho xk mà không vi phạm thuộc tính P
• Với mỗi giá trị hợp lệ v:
• Gán v cho xk
• Nếu k < n: gọi đệ quy TRY(k+1) để thử tiếp giá trị cho xk+1
• Nếu k = n: ghi nhận cấu hình
ĐỆ QUY QUAY LUI
TRY(k)
Begin
Foreach v thuộc Ak
if check(v,k) /* kiểm tra xem v có hợp lệ không */
Begin
xk = v;
if(k = n) ghi_nhan_cau_hinh;
else TRY(k+1);
End
End
Main()
Begin
TRY(1);
End
ĐỆ QUY QUAY LUI: liệt kê xâu nhị phân
• Mô hình hoá cấu hình:
• Mảng x[n] trong đó x[i] {0,1} là bít thứ i của xâu nhị phân
(i= 1, . . ., n)
X1 = 0 X1 = 1
X2 = 0 X2 = 1 X2 = 0 X2 = 1
X3 = 0 X3 = 1 X3 = 0 X3 = 1
X3 = 0 X3 = 1 X3 = 0 X3 = 1
ĐỆ QUY QUAY LUI: liệt kê xâu nhị phân
void solution(){
int main() {
x[0] = 0;
TRY(1);
}
ĐỆ QUY QUAY LUI: liệt kê tổ hợp
int main() {
x[0] = 0;
TRY(1);
}
ĐỆ QUY QUAY LUI: liệt kê hoán vị
void TRY(int i) {
• Liệt kê các hoán vị của 1, 2, …, n for(int v = 1; v <= n; v++){
• Mô hình hoá cấu hình: if(!m[v]) {
• Mảng x[1,. . . , n] trong đó x[i] {1, . x[i] = v;
m[v] = 1; // đánh dấu
. ., n} là phần tử thứ i của cấu hình
if(i == n)
hoán vị (i = 1, . . ., n) printSolution();
• Thuộc tính P: else TRY(i+1);
• x[i] x[j], với mọi 1 i < j n m[v] = 0;// khôi phục
}
• Mảng đánh dấu m[v] = 1 (0) nếu
}
giá trị v đã xuất hiện (chưa xuất }
hiện) trong cấu hình bộ phận, với
mọi v = 1, …, n void main() {
for(int v = 1; v <= n; v++)
m[v] = 0;
TRY(1);
}
ĐỆ QUY QUAY LUI: bài toán xếp hậu
• Xếp n quân hậu trên một bàn cờ quốc tế
sao cho không có 2 quân hậu nào ăn được
nhau
• Mô hình hoá 1 2 3 4
• x[1, . . ., n] trong đó x[i] là hàng của 1 X
quân hậu xếp trên cột i, với mọi i = 1, 2 X
…, n
• Thuộc tính P 3 X
• x[i] x[j], với mọi 1 i < j n 4 X
• x[i] + i x[j] + j, với mọi 1 i < j n
• x[i] – i x[j] - j, với mọi 1 i < j n Lời giải x = (3, 1, 4, 2)
ĐỆ QUY QUAY LUI: bài toán xếp hậu
0 1 2
ĐỆ QUY QUAY LUI: bài toán Sudoku
• Mô hình hoá
1 2 3 4 5 6 7 8 9
• Mảng 2 chiều x[0..8, 0..8]
4 5 6 7 8 9 1 2 3
• Thuộc tính P
• x[i, j2] x[i, j1], với mọi i = 7 8 9 1 2 3 4 5 6
0,…,8, và 0 j1 < j2 8 2 1 4 3 6 5 8 9 7
• x[i1, j] x[i2, j], với mọi j = 3 6 5 8 9 7 2 1 4
0,…,8, và 0 i1 < i2 8
8 9 7 2 1 4 3 6 5
• x[3I+i1, 3J+j1] x[3I+i2, 3J+j2],
với mọi I, J = 0,…, 2, và i1, j1, 5 3 1 6 4 2 9 7 8
i2, j2 {0,1, 2} sao cho i1 i2 6 4 2 9 7 8 5 3 1
hoặc j1 j2 9 7 8 5 3 1 6 4 2
ĐỆ QUY QUAY LUI: bài toán Sudoku
return false; }
for(int j1 = 0; j1 <= j-1; j1++) }
if(x[3*I+i][3*J+j1] == v)
return false; void main(){
return true; TRY(0,0);
} }
ĐỆ QUY QUAY LUI: bài tập
(x1,…,xk) }
}
Main()
{ f* = ;
TRY(1);
}
Thuật toán nhánh và cận
• Duyệt nhánh và cận: TRY(k) {
(x1,…,xk) }
không giảm của ai được danh L = Sắp xếp các đoạn trong X
sách L theo thứ tự không giảm của ai;
while (L ) {
• Lặp lại thao tác sau cho đến khi L
(ac,bc) = chọn đoạn đầu tiên
không còn phần tử nào
trong L;
• Chọn đoạn (ac, bc) đầu tiên
Loại bỏ (ac,bc) khỏi L;
trong L và loại nó ra khỏi L
if feasible(S {(ac,bc)}) {
• Nếu (ac, bc) không có giao S = S {(ac,bc)};
nhau với đoạn nào trong lời }
giải thì đưa (ac, bc) vào lời giải
}
return S;
}
Thuật toán tham lam
• Tham lam 1 không đảm bảo cho lời giải tối ưu, ví dụ
X = {(1,11), (2,5), (6,10)}
• Greedy 1 sẽ cho lời giải là {(1,11)} trong khi lời giải tối ưu là {(2,5),
(6,10)}
Thuật toán tham lam
• Tham lam 2 Greedy2() {
không giảm của độ dài bi - ai được L = Sắp xếp các đoạn trong X
danh sách L theo thứ tự không giảm của
bi- ai;
• Lặp lại thao tác sau cho đến khi L
while (L ) {
không còn phần tử nào
(ac,bc) = chọn đoạn đầu tiên
• Chọn đoạn (ac, bc) đầu tiên
trong L;
trong L và loại nó ra khỏi L
Loại bỏ (ac,bc) khỏi L;
• Nếu (ac, bc) không có giao nhau if feasible(S {(ac,bc)}) {
với đoạn nào trong lời giải thì S = S {(ac,bc)};
đưa (ac, bc) vào lời giải
}
}
return S;
}
Thuật toán tham lam
• Tham lam 2 không đảm bảo cho lời giải tối ưu, ví dụ
X = {(1,5), (4,7), (6,11)}
• Greedy 1 sẽ cho lời giải là {(4,7)} trong khi lời giải tối ưu là {(1,5),
(6,11)}
Thuật toán tham lam
giảm của bi được danh sách L L = Sắp xếp các đoạn trong X
theo thứ tự không giảm của bi;
• Lặp lại thao tác sau cho đến khi L
while (L ) {
không còn phần tử nào
(ac,bc) = chọn đoạn đầu tiên
• Chọn đoạn (ac, bc) đầu tiên trong L;
trong L và loại nó ra khỏi L
Loại bỏ (ac,bc) khỏi L;
• Nếu (ac, bc) không có giao nhau if feasible(S {(ac,bc)}) {
với đoạn nào trong lời giải thì S = S {(ac,bc)};
đưa (ac, bc) vào lời giải }
• Tham lam 3 đảm bảo cho lời giải tối ưu }
return S;
}
Thuật toán tham lam: bài tập
• Có n công việc 1, 2, ..., n. Công việc i có thời hạn hoàn thành là d[i] và có
lợi nhuận khi được đưa vào thực hiện là p[i] (i=1,...,n). Biết rằng chỉ
được nhiều nhất 1 công việc được thực hiện tại mỗi thời điểm và khi thời
gian thực hiện xong mỗi công việc đều là 1 đơn vị. Hãy tìm cách chọn ra
các công việc để đưa vào thực hiện sao cho tổng lợi nhuận thu được là
nhiều nhất đồng thời mỗi công việc phải hoàn thành trước hoặc đúng
thời hạn.
Thuật toán tham lam: bài tập
• Ví dụ: có 4 công việc với thời hạn hoàn thành d[i] và lợi nhuận là p[i] được
cho như sau:
• Công việc 1: 4 20
• Công việc 2: 1 10
• Công việc 3: 1 40
• Công việc 4: 1 30
• Khi đó giải pháp tối ưu là chọn công việc 1 và 3 để thực hiện với thứ tư
thực hiện là 3 trước rồi đến 1:
• công việc 3 bắt đầu thực hiện tại thời điểm 0 và kết thúc tại thời
điểm 1
• tiếp đó, công việc 1 được bắt đầu thực hiện tại thời điểm 1 và kết
thúc tại thời điểm 2
• tổng lợi nhuận thu được là 20 + 40 = 60
Thuật toán chia để trị
• Sơ đồ chung
• Chia bài toán xuất phát thành các bài toán con độc lập nhau
• Giải các bài toán con (đệ quy)
• Tổng hợp lời giải của các bài toán con để xây dựng lời giải của bài
toán xuất phát
Thuật toán chia để trị: tìm kiếm nhị phân
• Bài toán tìm kiếm nhị phân: bSearch(x, start, finish, y) {
cho dãy x[1..n] được sắp xếp if(start == finish) {
theo thứ tự tăng dần và 1 giá if(x[start] == y)
trị y. Tìm chỉ số i sao cho x[i] return start;
=y else return -1;
}else{
m = (start + finish)/2;
if(x[m] == y) return m;
if(x[m] < y)
return bSearch(x, m+1,finish,y);
else
return bSearch(x,start,m-1,y);
}
}
Thuật toán chia để trị: dãy con cực đại
• Bài toán dãy con cực đại int maxSeq(int* a, int l, int r){
• Chia bài toán xuất phát thành a bài toán procedure D-and-C(n) {
con, mỗi bài toán con kích thước n/b 1. if(n n0)
2. xử lý trực tiếp
• T(n): thời gian của bài toán kích thước
3. else{
n
4. chia bài toán xuất phát
• Thời gian phân chia (dòng 4): D(n) thành a bài toán con kích thước n/b
• Thời gian tổng hợp lời giải (dòng 6): 5. gọi đệ quy a bài toán con
C(n) 6. tổng hợp lời giải
7. }
• Công thức truy hồi:
}
1 , 𝑛 ≤ 𝑛0
T 𝑛 =ቐ 𝑛
𝑎𝑇 + 𝐶 𝑛 + 𝐷 𝑛 , 𝑛 > 𝑛0
𝑏
52
Thuật toán chia để trị: định lí thợ
53
Thuật toán quy hoạch động
• Sơ đồ chung
• Chia bài toán xuất phát thành các bài toán con không nhất thiết độc
lập với nhau
• Giải các bài toán con từ nhỏ đến lớn, lời giải được lưu trữ lại vào 1
bảng
• Bài toán con nhỏ nhất phải được giải 1 cách trực tiếp
• Xây dựng lời giải của bài toán lớn hơn từ lời giải đã có của các bài
toán con nhỏ hơn (truy hồi)
• Số lượng bài toán con cần được bị chặn bởi đa thức của kích
thước dữ liệu đầu vào
• Phù hợp để giải hiệu quả một số bài toán tối ưu tổ hợp
54
Thuật toán quy hoạch động
• Tổng các phần tử của dãy con cực đại của bài toán xuất phát là
max{S1, S2, …, Sn}
55
Thuật toán quy hoạch động
56
Thuật toán quy hoạch động
57
Thuật toán quy hoạch động
58
Thuật toán quy hoạch động
59
Thuật toán quy hoạch động
X 3 7 2 5 1 4 9
Y 4 3 2 3 6 1 5 4 9 7
1 2 3 4 5 6 7 8 9 10
1 0 1 1 1 1 1 1 1 1 1
2 0 1 1 1 1 1 1 1 1 2
3 0 1 2 2 2 2 2 2 2 2
4 0 1 2 2 2 2 3 3 3 3
5 0 1 2 2 2 3 3 3 3 3
6 1 1 2 2 2 3 3 4 4 4
7 1 1 2 2 2 3 3 4 5 5
60
Thuật toán quy hoạch động
void solve(){
rs = 0;
for(int i = 0; i <= n; i++) S[i][0] = 0;
for(int j = 0; j <= m; j++) S[0][j] = 0;
61
Thuật toán quy hoạch động: Bài tập
• Cho dãy a = a1, a2, . . ., an. Một dãy con của dãy a là một dãy thu được
bằng cách loại bỏ 1 số phần tử khỏi a. Tìm dãy con của a là một cấp số
cộng với bước nhảy bằng 1 có độ dài lớn nhất
62
Thank you
for your
attentions!
CẤU TRÚC DỮ LIỆU VÀ
THUẬT TOÁN
Danh sách tuyến tính
Nội dung
2
Định nghĩa danh sách tuyến tính
3
Kiểu dữ liệu trừu tượng danh sách tuyến tính
4
Kiểu dữ liệu trừu tượng danh sách tuyến tính
• Thao tác
• Insert(x, p, L): chèn phần tử x vào vị trí p trên danh sách L
• Locate(x, L): trả về vị trí của phần tử x trên danh sách L
• Retrieve(p, L): trả về phần tử ở vị trí p trong danh sách L
• Delete(p, L): loại bỏ phần tử ở vị trí p trên danh sách L
• Next(p, L): trả về vị trí tiếp theo của p trên danh sách L
• Prev(p, L): trả về vị trí trước của p trên danh sách L
• MakeNull(L): thiết lập danh sách L về danh sách rỗng
• First(L): trả về vị trí đầu tiên của danh sách L
5
Kiểu mảng
• Các đối tượng được cấp phát liên tiếp nhau trong bộ nhớ
• Truy cập các đối tượng thông qua chỉ số (kiểu vị trí chính là giá trị
nguyên xác định chỉ số)
• Việc thêm hoặc loại bỏ phần tử khỏi mảng cần thiết phải dồn mảng
6
Kiểu mảng
7
Kiểu mảng
int locate(int x) {// vi trí đầu tiên của
x trong danh sách
• Tổ chức dữ liệu for(int j= 0; j <= n-1; j++)
• int a[100000]; // bộ nhớ lưu if(a[j] == x) return j;
trữ return -1;
8
Con trỏ và danh sách liên kết đơn
• Con trỏ: địa chỉ của biến
• int* p: p là con trỏ trỏ đến 1 biến int. Giá trị của p xác định địa chỉ của
biến
• int a; int* p = &a;// p trỏ vào biến a
• Kiểu cấu trúc
typedef struct TNode{
int a;
double b;
char* s;
}TNode;
• TNode* q: q là con trỏ trỏ đến 1 biến có kiểu TNode
9
Con trỏ và danh sách liên kết đơn
q = (TNode*)malloc(sizeof(TNode)): int* p;
10
Danh sách liên kết đơn
• Mỗi phần tử, ngoài trường dữ liệu, có thêm con trỏ để trỏ đến phần tử
tiếp theo
struct Node{
int value;
Node* next;
};
Node* head;
head
5 9 3 ... 6 4
11
Danh sách liên kết đơn
h
5 9 3 ... 6 4
void printList(Node* h){
Node* p = h;
p while(p != NULL){
printf(“%d “,p->value);
p = p->next;
Tìm kiếm
}
cần duyệt lần lượt từ phần
}
tử đầu tiên của danh sách Node* findLast(Node* h){
Dùng con trỏ next để truy Node* p = h;
cập đến phần tử tiếp theo while(p != NULL){
if(p->next == NULL) return p;
p = p->next;
}
return NULL;
}
12
Danh sách liên kết đơn
h
5 9 3 ... 6 4
Node* makeNode(int x){
Node* q = new Node;
p
q->value = x; q->next = NULL;
return q;
Chèn 1 phần tử mới có value }
bằng x vào sau phần tử được
trỏ bởi p Node* insertAfter(Node* h, Node* p, int x){
if(p == NULL) return h;
Node* q = makeNode(x);
if(h == NULL) return q;
q->next = p->next;
p->next = q;
return h;
}
13
Danh sách liên kết đơn
h
5 9 3 ... 6 4
h
5 9 3 ... 6 4
14
Danh sách liên kết đơn
h
5 9 3 ... 6 4
15
Danh sách liên kết đơn
h
5 9 3 ... 6 4
p
Node* prev(Node* h, Node* p){
Tìm một phần tử đứng trước Node* q = h;
một phần tử được trỏ bởi con while(q != NULL){
trỏ p if(q->next == p) return q;
q = q->next;
}
return NULL;
}
16
Danh sách liên kết đơn
h
5 9 3 ... 6 4
17
Danh sách liên kết đơn
h
5 9 3 ... 6 4
p
Node* insertAtRecursive(Node* h, Node* p, int x){
Chèn 1 phần tử mới có value
if(p == NULL) return h;
bằng x vào trước phần tử
if(h == NULL || p == h){
được trỏ bởi p (đệ quy)
return makeNode(x);
}else{
h->next = insertAtRecursive(h->next,p,x);
return h;
}
}
18
Danh sách liên kết đơn
h
5 9 3 ... 6 4
19
Danh sách liên kết đơn
h
5 9 3 ... 6 4
20
Danh sách liên kết đôi
struct DNode{
int val;
DNode* prev; prev next
DNode* next;
}; val
DNode* first;
DNode* last;
first last
3 3 ... 3
21
Danh sách liên kết đôi
first last
3 3 ... 3
p
DNode* makeNode(int v){
Tạo mới phần tử DNode* p = new DNode;
p->val = v;
p->next = NULL;
p->prev = NULL;
return p;
}
22
Danh sách liên kết đôi
p
first last
3 3 ... 3
void remove(DNode* p) {
if(p == NULL) return;
if(first == last && p == first){
first = NULL; last = NULL; delete p;
}
if(p == first){
first = p->next; first->prev = NULL;
delete p; return;
}
if(p == last){
last = p->prev; last->next = NULL;
delete p; return;
}
p->prev->next = p->next; p->next->prev = p->prev; delete p;
}
23
Danh sách liên kết đôi
first last
3 3 ... 3
p
void insertLast(int x){
DNode* q = makeNode(x);
if(first == NULL && last == NULL){
first = q;
last = q;
return;
}
q->prev = last;
last->next = q;
last = q;
}
24
Thư viện C++
• Kiểu list:
http://www.cplusplus.com/refer
ence/list/list/ #include <list>
#include <stdio.h>
• Phương thức using namespace std;
• push_front()
• push_back() int main(){
list<int> L;
• pop_front() for(int i = 1; i <= 10; i++)
• pop_back() L.push_back(i);
list<int>::iterator it;
• size() for(it = L.begin(); it != L.end();
• clear() it++){
• erase() int x = *it;
printf("%d ",x);
}
}
25
Ngăn xếp (Stack)
26
Ngăn xếp (Stack)
• Ví dụ: kiểm tra tính hợp lệ của dãy ngoặc
• [({})](): hợp lệ
• ([} {}): không hợp lệ
27
Ngăn xếp (Stack)
• Ví dụ: kiểm tra tính hợp lệ của dãy ngoặc
• [({})](): hợp lệ
• ([} {}): không hợp lệ
• Thuật toán:
• Sử dụng 1 ngăn xếp S
• Duyệt dãy ngoặc từ trái qua phải
• Nếu gặp ngoặc mở thì đẩy vào S
• Nếu gặp ngoặc đóng
• Nếu S rỗng thì trả về FALSE
• Nếu S còn phần tử thì lấy 1 ngoặc mở ra khỏi S, kiểm tra
đối sánh với ngoặc đóng: nếu ngoặc mở và đóng không
tương ứng với nhau thì trả về FALSE
• Kết thúc việc duyệt, nếu S vẫn còn phần tử thì trả về FALSE,
ngược lại thì trả về TRUE
28
Ngăn xếp (Stack)
29
Ngăn xếp (Stack)
• Ví dụ: kiểm tra tính hợp lệ bool solve(char* x, int n){
30
Hàng đợi (Queue)
31
Hàng đợi (Queue)
32
Hàng đợi (Queue)
33
Hàng đợi (Queue)
• Bài toán rót nước: a = 4, b = 19, c = 21
34
Hàng đợi (Queue)
• Bài toán rót nước: a = 4, b = 19, c = 21 (tiếp)
35
Hàng đợi (Queue)
• Thiết kế thuật toán và cấu trúc dữ liệu
• Trạng thái là bộ (x, y): lượng nước có trong cốc 1 và 2
• Trạng thái ban đầu (0, 0)
• Trạng thái kết thúc: x = c hoặc y = c hoặc x + y = c
• Chuyển trạng thái
• (1) Đổ đầy nước từ bể vào cốc 1: (a, y)
• (2) Đổ đầy nước từ bể vào cốc 2: (x, b)
• (3) Đổ hết nước từ cốc 1 ra ngoài: (0, y)
• (4) Đổ hết nước từ cốc 2 ra ngoài: (x, 0)
• (5) Đổ nước từ cốc 1 vào đầy cốc 2: (x + y - b, b), nếu x + y b
• (6) Đổ hết nước từ cốc 1 sang cốc 2: (0, x + y), nếu x + y b
• (7) Đổ nước từ cốc 2 vào đầy cốc 1: (a, x + y - a), nếu x + y a
• (8) Đổ hết nước từ cốc 2 sang cốc 1: (x + y, 0), nếu x + y a
36
Hàng đợi (Queue)
• Đưa (0,0) vào hàng đợi
(0,0)
37
Hàng đợi (Queue)
• Lấy (6,8) ra và không đưa trạng thái mới nào vào hàng
đợi
(0,0) (6,0) (0,8) (0,6) (6,8) (6,2) (6,6)
38
Hàng đợi (Queue)
39
Hàng đợi (Queue)
#include <stdio.h>
• Khai báo cấu trúc dữ liệu #include <stdlib.h>
#include <queue>
#include <stack>
#include <list>
using namespace std;
struct State{
int x;
int y;
char* msg;// action to generate current state
State* p;// pointer to the state generating current state
};
bool visited[10000][10000];
queue<State*> Q;
list<State*> L;
State* target;
int a,b,c;
40
Hàng đợi (Queue)
void initVisited(){
• Các hàm khởi tạo mảng for(int x = 0; x < 10000; x++)
đánh dấu, đánh dấu trạng for(int y = 0; y < 10000; y++)
thái, kiểm tra trạng thái visited[x][y] = false;
đích, giải phóng bộ nhớ }
bool reachTarget(State* S){
return S->x == c || S->y == c ||
S->x + S->y == c;
}
void markVisit(State* S){
visited[S->x][S->y] = true;
}
void freeMemory(){
list<State*>::iterator it;
for(it = L.begin(); it != L.end(); it++){
delete *it;
}
}
41
Hàng đợi (Queue)
42
Hàng đợi (Queue)
43
Hàng đợi (Queue)
bool genMove1Full2(State* S){
if(S->x+S->y < b) return false;
• Hàm sinh trạng thái bởi if(visited[S->x + S->y - b][b]) return false;
hành động (5): đổ nước từ State* newS = new State;
cốc 1 vào đầy cốc 2 newS->x = S->x + S->y - b;
newS->y = b;
newS->msg = "Do nuoc tu coc 1 vao day coc 2";
newS->p = S;
Q.push(newS); markVisit(newS);
L.push_back(newS);
if(reachTarget(newS)){
target = newS;
return true;
}
return false;
}
44
Hàng đợi (Queue)
bool genMove2Full1(State* S){
if(S->x+S->y < a) return false;
• Hàm sinh trạng thái bởi
if(visited[a][S->x + S->y - a]) return false;
hành động (7): đổ nước từ
State* newS = new State;
cốc 2 vào đầy cốc 1
newS->x = a;
newS->y = S->x + S->y - a;
newS->msg = "Do nuoc tu coc 2 vao day coc 1";
newS->p = S;
Q.push(newS); markVisit(newS);
L.push_back(newS);
if(reachTarget(newS)){
target = newS;
return true;
}
return false;
}
45
Hàng đợi (Queue)
bool genMoveAll12(State* S){
if(S->x + S->y > b) return false;
• Hàm sinh trạng thái bởi if(visited[0][S->x + S->y]) return false;
hành động (6): đổ hết State* newS = new State;
nước từ cốc 1 sang cốc 2 newS->x = 0;
newS->y = S->x + S->y;
newS->msg = "Do het nuoc tu coc 1 sang coc 2";
newS->p = S;
Q.push(newS); markVisit(newS);
L.push_back(newS);
if(reachTarget(newS)){
target = newS;
return true;
}
return false;
}
46
Hàng đợi (Queue)
bool genMoveAll21(State* S){
if(S->x + S->y > a) return false;
• Hàm sinh trạng thái bởi if(visited[S->x + S->y][0]) return false;
hành động (8): đổ hết State* newS = new State;
nước từ cốc 2 sang cốc 1 newS->x = S->x + S->y;
newS->y = 0;
newS->msg = "Do het nuoc tu coc 2 sang coc 1";
newS->p = S;
Q.push(newS); markVisit(newS);
L.push_back(newS);
if(reachTarget(newS)){
target = newS;
return true;
}
return false;
}
47
Hàng đợi (Queue)
bool genMoveFill1(State* S){
if(visited[a][S->y]) return false;
• Hàm sinh trạng thái bởi
State* newS = new State;
hành động (1): đổ đầy
newS->x = a;
nước từ bể vào cốc 1
newS->y = S->y;
newS->msg = "Do day nuoc vao coc 1";
newS->p = S;
Q.push(newS); markVisit(newS);
L.push_back(newS);
if(reachTarget(newS)){
target = newS;
return true;
}
return false;
}
48
Hàng đợi (Queue)
bool genMoveFill2(State* S){
if(visited[S->x][b]) return false;
• Hàm sinh trạng thái bởi State* newS = new State;
hành động (2): đổ đầy newS->x = S->x;
nước từ bể vào cốc 2 newS->y = b;
newS->msg = "Do day nuoc vao coc 2";
newS->p = S;
Q.push(newS); markVisit(newS);
L.push_back(newS);
if(reachTarget(newS)){
target = newS;
return true;
}
return false;
49
Hàng đợi (Queue)
void print(State* target){
printf("-----------RESULT------------\n");
• Hàm in chuỗi hành động if(target == NULL)
để thu được kết quả printf("Khong co loi giai!!!!!!!");
State* currentS = target;
stack<State*> actions;
while(currentS != NULL){
actions.push(currentS);
currentS = currentS->p;
}
while(actions.size() > 0){
currentS = actions.top();
actions.pop();
printf("%s, (%d,%d)\n",
currentS->msg,currentS->x,
currentS->y);
}
}
50
Hàng đợi (Queue)
• Khởi tạo, sinh trạng thái void solve(){
ban đầu và đưa vào hàng initVisited();
đợi // sinh ra trang thai ban dau (0,0) va dua vao Q
State* S = new State;
• Tại mỗi bước, lấy 1 trạng
S->x = 0; S->y = 0; S->p = NULL;
thái ra khỏi hàng đợi, sinh
Q.push(S); markVisit(S);
trạng thái mới và đưa vào
while(!Q.empty()){
hàng đợi
State* S = Q.front(); Q.pop();
if(genMove1Out(S)) break;
if(genMove2Out(S)) break;
if(genMove1Full2(S)) break;
if(genMoveAll12(S)) break;
if(genMove2Full1(S)) break;
if(genMoveAll21(S)) break;
if(genMoveFill1(S)) break;
if(genMoveFill2(S)) break;
}
}
51
Hàng đợi (Queue)
int main(){
a = 4;
• Hàm main
b = 7;
• Thử nghiệm với bộ dữ liệu: a = c = 9;
4, b = 7, c = 9 target = NULL;
solve();
print(target);
freeMemory();
}
52
CẤU TRÚC DỮ LIỆU VÀ
THUẬT TOÁN
Cây
1
Nội dung
2
Cây
10
... 11 1 3
5 4 8 2 7
6 9
3
Định nghĩa cây
Cấu trúc lưu trữ các đối tượng có
quan hệ phân cấp
Định nghĩa đệ quy
r
Bước cơ sở: r là 1 nút, cây T
có nút gốc là r
Bước đệ quy:
Giả sử T1, T2, …, Tk là các r1 r2 ... rk
cây có gốc là r1, r2, …, rk
Nút r
Liên kết các nút r1, r2, .., rk
như la nút con của r sẽ tạo
ra cây T
4
Các ứng dụng của cây: Cây gia phả
• Cây gia phả của các nhà toán học dòng họ Bernoulli
Nikolaus
1623-1708
5
Các ứng dụng của cây
6
Các ứng dụng của cây: cây biểu thức
+
/ /
1 3 * 4
6 7
1/3 + 6*7 / 4
7
Các khái niệm trên cây
• Đường đi: dãy các nút x1, x2, …, xq trong đó xi là cha của xi+1, i = 1, 2,
…, q-1. Độ dài đường đi là q-1
• Nút lá: không có nút con
• Nút trong: có nút con
• Nút anh em: 2 nút u và v là 2 nút anh em nếu có cùng nút cha
• Tổ tiên: nút u là tổ tiên của v nếu có đường đi từ u đến v
• Con cháu: nút u là con cháu của v nếu v là tổ tiên của u
• Độ cao: độ cao của 1 nút là độ dài đường đi dài nhất từ nút đó đến nút
lá + 1
• Độ sâu: độ sâu của 1 nút v là độ dài đường đi duy nhất từ nút gốc tới
nút đó + 1
8
Các khái niệm trên cây
• Gốc: nút không có cha (ví dụ: nút A)
• Anh em: nút có cùng cha (ví dụ: B, C, D là anh em; I, J, K là anh em; E và F là anh
em; G và H là anh em)
• Nút trong: nút có ít nhất 1 con (ví dụ: các nút màu xanh dương: A, B, C, F)
• Nút ngoài (lá ): nút không có con (ví dụ: các nút xanh lá: E, I, J, K, G, H, D)
• Tổ tiên của 1 nút: là các nút cha / ông bà / cụ.. của nút đó.
• Hậu duệ của 1 nút: là các nút con/cháu/chắt… của nút đó
• Cây con của 1 nút: là cây có gốc là con của nút đó A
Con của B:
• E, F B C D
Cha của E:
• B
Tổ tiên của F: E F G H
• B, A
Hậu duệ của B: Cây con của nút A
• E, F, I, J, K I J K
9
Các khái niệm trên cây
sâu=1 A cao=3
Chiều cao của cây= 3
sâu=2 sâu=2 C
B D sâu=2
cao=1 cao=2 cao=1
sâu=3 E sâu=3 F
cao=1 cao=1
10
Phép duyệt cây
11
Phép duyệt cây
12
Phép duyệt cây
13
Phép duyệt cây
14
Cấu trúc dữ liệu biểu diễn cây
• Mảng:
• Giả sử các nút trên cây được định danh 1, 2, …, n
• a[1..n] trong đó a[i] là nút cha của nút i
• Cấu trúc lưu trữ đơn giản, tuy nhiên khó cài đặt rất nhiều thao tác
trên cây
• Con trỏ liên kết: với mỗi nút, lưu 2 con trỏ
• Con trỏ trỏ đến nút con trái nhất
• Con trỏ trỏ đến nút anh em bên phải
15
Cấu trúc dữ liệu biểu diễn cây
struct Node{
int id;
Node* leftMostChild;// con tro tro den nut con trai nhat
Node* rightSibling;// con tro tro den nut anh em ben phai
};
Node* root;// con tro tro den nut goc
16
Thao tác trên cây
17
Thao tác trên cây
• Tìm một nút có nhãn cho trước Node* find(Node* r, int v){
trên cây if(r == NULL) return NULL;
if(r->id == v) return r;
Node* p = r->leftMostChild;
while(p != NULL){
Node* h = find(p,v);
if(h != NULL) return h;
p = p->rightSibling;
}
return NULL;
}
18
Thao tác trên cây
19
Thao tác trên cây
20
Thao tác trên cây
21
Thao tác trên cây
22
Thao tác trên cây
23
Thao tác trên cây
24
Thao tác trên cây
int depth(Node* r, int v, int d){
// d la do sau cua nut r
• Độ sâu của một nút if(r == NULL) return -1;
if(r->id == v) return d;
Node* p = r->leftMostChild;
while(p != NULL){
if(p->id == v) return d+1;
int dv = depth(p,v,d+1);
if(dv > 0) return dv;
p = p->rightSibling;
}
return -1;
}
int depth(Node* r, int v){
return depth(r,v,1);
}
25
Thao tác trên cây
• Tìm nút cha của một nút Node* parent(Node* p, Node* r){
if(r == NULL) return NULL;
Node* q = r->leftMostChild;
while(q != NULL){
if(p == q) return r;
Node* pp = parent(p, q);
if(pp != NULL) return pp;
q = q->rightSibling;
}
return NULL;
}
26
Cây nhị phân
27
Cây nhị phân
• Phân loại
10 10 10
1 2 1 2 1 2
5 6 4 7 5 6 5 4 7
Cây nhị phân hoàn chỉnh Cây nhị phân đầy đủ Cây nhị phân cân bằng
28
Thao tác trên cây nhị phân
29
Thao tác trên cây nhị phân
30
Thao tác trên cây nhị phân
31
Thao tác trên cây nhị phân
32
Cây biểu thức
33
Tính giá trị của biểu thức hậu tố
34
Tính giá trị của biểu thức hậu tố
35
Tính giá trị của biểu thức hậu tố
36
Tính giá trị của biểu thức hậu tố
37
Tính giá trị của biểu thức hậu tố
38
CẤU TRÚC DỮ LIỆU
VÀ THUẬT TOÁN
Sắp xếp
NỘI DUNG
• Tổng quan bài toán sắp xếp
• Thuật toán sắp xếp lựa chọn
• Thuật toán sắp xếp chèn
• Thuật toán sắp xếp nổi bọt
• Thuật toán sắp xếp trộn
• Thuật toán sắp xếp nhanh
• Thuật toán sắp xếp vun đống
TỔNG QUAN BÀI TOÁN SẮP XẾP
• Sắp xếp là việc đưa các phần tử của một dãy theo đúng thứ tự (không
giảm hoặc không tăng) dựa vào 1 giá trị khoá
• Thiết kế thuật toán sắp xếp hiệu quả là một việc đặc biệt quan trọng do
việc sắp xếp xuất hiện trong rất nhiều tình huống tính toán
• Hai thao tác cơ bản trong một thuật toán sắp xếp
• Compare(a, b): so sánh khoá của 2 phần tử a và b
• Swap(a, b): đổi chỗ 2 phần tử a và b cho nhau
• Không giảm tổng quát, giả sử cần sắp xếp dãy a1, a2, …, an theo thứ tự
không giảm của giá trị
TỔNG QUAN BÀI TOÁN SẮP XẾP
• Phân loại thuật toán sắp xếp
• Sắp xếp tại chỗ: sử dụng bộ nhớ trung gian là hằng số, không phụ
thuộc độ dài dãy đầu vào
• Sắp xếp ổn định: duy trì thứ tự tương đối giữa 2 phần tử có cùng giá
trị khoá (vị trí tương đối giữa 2 phần tử có cùng khoá không đổi trước
và sau khi sắp xếp)
• Thuật toán sắp xếp dựa trên so sánh: sử dụng phép so sánh để
quyết định thứ tự phần tử (counting sort không phải là thuật toán sắp
xếp dựa trên so sánh)
SẮP XẾP LỰA CHỌN
• Chọn số nhỏ nhất xếp vào vị trí void selectionSort(int A[], int N) {
thứ 1 // index tu 1 -> N
for(int k = 1; k <= N; k++){
• Chọn số nhỏ thứ 2 xếp vào vị trí
thứ 2 int min = k;
for(int j = k+1; j <= N; j++){
• Chọn số nhỏ thứ 3 xếp vào vị trí if(A[min] > A[j]) min = j;
thứ 3 }
• … int tmp = A[min];
A[min] = A[k];
A[k] = tmp;
}
}
SẮP XẾP LỰA CHỌN
• Ví dụ: 5, 7, 3, 8, 1, 2, 9, 4, 6
5 7 3 8 1 2 9 4 6 1 2 3 4 5 7 9 8 6
1 7 3 8 5 2 9 4 6 1 2 3 4 5 6 9 8 7
1 2 3 8 5 7 9 4 6 1 2 3 4 5 6 7 8 9
1 2 3 8 5 7 9 4 6
1 2 3 4 5 7 9 8 6
SẮP XẾP CHÈN
• Thuật toán diễn ra qua các bước void insertionSort(int A[], int N) {
lặp k = 2, 3, …, n // index tu 1 -> N
for(int k = 2; k <= N; k++){
• Tại mỗi bước thứ k: chèn ak vào
đúng vị trí trong dãy đã được sắp int last = A[k];
7
SẮP XẾP CHÈN
• Ví dụ: 5, 7, 3, 8, 1, 2, 9, 4, 6
5 7 3 8 1 2 9 4 6 1 2 3 5 7 8 9 4 6
3 5 7 8 1 2 9 4 6 1 2 3 4 5 7 8 9 6
3 5 7 8 1 2 9 4 6 1 2 3 4 5 6 7 8 9
1 3 5 7 8 2 9 4 6
1 2 3 5 7 8 9 4 6
8
SẮP XẾP NỔI BỌT
• Duyệt dãy từ trái qua phải (hoặc từ void bubleSort(int A[], int N) {
9
SẮP XẾP NỔI BỌT
• Ví dụ: 5, 7, 3, 8, 1, 2, 9, 4, 6
5 3 7 1 2 8 4 6 9
3 5 1 2 7 4 6 8 9
3 1 2 5 4 6 7 8 9
1 2 3 4 5 6 7 8 9
10
SẮP XẾP TRỘN
11
SẮP XẾP TRỘN
void merge(int A[], int L, int M, int R) {
12
SẮP XẾP TRỘN
5 7 3 8 1 2 9 4 6
• Ví dụ: 5, 7, 3, 8, 1, 2, 9, 4, 6
5 7 3 8 1 2 9 4 6
5 7 3 8 1 2 9 4 6
5 7 3 1 8 2 9 4 6
5 7 3 1 8 2 4 6 9
3 5 7 1 8 2 4 6 9
1 3 5 7 8 2 4 6 9
1 2 3 4 5 6 7 8 9
13
SẮP XẾP NHANH
14
SẮP XẾP NHANH
void quickSort(int A[], int L, int R) { int partition(int A[], int L, int R, int
if(L < R){ indexPivot) {
int index = (L + R)/2; int pivot = A[indexPivot];
index = partition(A, L, R, index); swap(A[indexPivot], A[R]);
if(L < index)
int storeIndex = L;
quickSort(A, L, index-1);
for(int i = L; i <= R-1; i++){
if(index < R)
if(A[i] < pivot){
quickSort(A, index+1, R);
swap(A[storeIndex], A[i]);
}
} storeIndex++;
}
}
swap(A[storeIndex], A[R]);
return storeIndex;
}
15
SẮP XẾP NHANH
• Ví dụ: 5, 7, 3, 8, 1, 2, 9, 4, 6
Pivot = 5
5 7 3 8 1 2 9 4 6 3 1 2 4 5 6 9 8 7
Pivot = 3
3 1 2 4 1 2 3 4
Pivot = 7
6 9 8 7 6 7 8 9
16
SẮP XẾP VUN ĐỐNG
• Cấu trúc đống (max-heap)
• Cây nhị phân đầy đủ (complete tree) 19
• Khoá của mỗi nút lớn hơn hoặc bằng
khoá của 2 nút con (tính chất của
max-heap) 11 9
• Ánh xạ từ dãy A[1…N] sang cây nhị phân
đầy đủ
• Gốc là A[1] 8 4 6 7
• A[2i] và A[2i+1] là con trái và con phải
của A[i]
• Chiều cao của cây là logN + 1 6 2
17
SẮP XẾP VUN ĐỐNG
6 2
18
SẮP XẾP VUN ĐỐNG
19
SẮP XẾP VUN ĐỐNG
20
SẮP XẾP VUN ĐỐNG
4 2
Cây nhị phân đầy đủ
7 6 9 11
19 5
8, 4, 2, 7, 6, 9, 11, 19, 5
21
SẮP XẾP VUN ĐỐNG
8 8
4 2 4 11
Vun nút 2
19 6 9 11 19 6 9 2
7 5 7 5
22
SẮP XẾP VUN ĐỐNG
8 8
4 11 19 11
Vun nút 4
19 6 9 2 7 6 9 2
7 5 4 5
23
SẮP XẾP VUN ĐỐNG
19 11
Đổi chỗ 19 và
8 11 5 cho nhau, 8 9
vun lại heap
7 6 9 2 7 6 5 2
4 5 4 19
24
SẮP XẾP VUN ĐỐNG
11 9
Đổi chỗ 11 và
8 9 4 cho nhau, 8 5
vun lại heap
7 6 5 2 7 6 4 2
4 19 11 19
11, 8, 9, 7, 6, 5, 2, 4, 19 9, 8, 5, 7, 6, 4, 2, 11, 19
25
SẮP XẾP VUN ĐỐNG
9 8
Đổi chỗ 9 và
8 5 2 cho nhau, 7 5
vun lại heap
7 6 4 2 2 6 4 9
11 19 11 19
9, 8, 5, 7, 6, 4, 2, 11, 19 8, 7, 5, 2, 6, 4, 9, 11, 19
26
SẮP XẾP VUN ĐỐNG
8 7
Đổi chỗ 8 và
7 5 4 cho nhau, 6 5
vun lại heap
2 6 4 9 2 4 8 9
11 19 11 19
8, 7, 5, 2, 6, 4, 9, 11, 19 7, 6, 5, 2, 4, 8, 9, 11, 19
27
SẮP XẾP VUN ĐỐNG
7 6
Đổi chỗ 7 và
6 5 4 cho nhau, 4 5
vun lại heap
2 4 8 9 2 7 8 9
11 19 11 19
7, 6, 5, 2, 4, 8, 9, 11, 19 6, 4, 5, 2, 7, 8, 9, 11, 19
28
SẮP XẾP VUN ĐỐNG
6 5
Đổi chỗ 6 và
4 5 2 cho nhau, 4 2
vun lại heap
2 7 8 9 6 7 8 9
11 19 11 19
6, 4, 5, 2, 7, 8, 9, 11, 19 5, 4, 2, 6, 7, 8, 9, 11, 19
29
SẮP XẾP VUN ĐỐNG
5 4
Đổi chỗ 5 và
4 2 2 cho nhau, 2 5
vun lại heap
6 7 8 9 6 7 8 9
11 19 11 19
5, 4, 2, 6, 7, 8, 9, 11, 19 4, 2, 5, 6, 7, 8, 9, 11, 19
30
SẮP XẾP VUN ĐỐNG
4 2
Đổi chỗ 4 và
2 5 2 cho nhau, 4 5
vun lại heap
6 7 8 9 6 7 8 9
11 19 11 19
4, 2, 5, 6, 7, 8, 9, 11, 19 2, 4, 5, 6, 7, 8, 9, 11, 19
31
Thank you
for your
attentions!
CẤU TRÚC DỮ LIỆU VÀ
THUẬT TOÁN
Tìm kiếm
1
Nội dung
• Tìm kiếm tuần tự
• Tìm kiếm nhị phân
• Cây nhị phân tìm kiếm
• Bảng băm
2
Tìm kiếm tuần tự
3
Tìm kiếm nhị phân
• Dãy đối tượng được sắp xếp theo binarySearch(X[], int L, int R,
thứ tự không tăng (hoặc không giảm) int Y) {
của giá trị khóa if(L = R){
if(X[L] = Y) return L;
• Dựa trên chia để trị:
return -1;
• So sánh khóa đầu vào Y với }
phần tử ở chính giữa của dãy, và int mid = (L+R)/2;
quyết định tiếp tục tìm kiếm ở
if(X[mid] = Y) return mid;
nửa bên trái hoặc nửa bên phải
if(X[mid] < Y)
đối với phần tử ở chính giữa
return binarySearch(X,mid+1,R,Y);
• Độ phức tạp thời gian: O(logn) return binarySearch(X,L,mid-1,Y);
}
4
Tìm kiếm nhị phân
• Bài tập Cho dãy số gồm các phần tử đôi một khác nhau a1, a2, …, aN
và một giá trị b. Đếm số cặp (ai, aj) sao cho ai + aj = b (i < j)
5
Cây nhị phân tìm kiếm - BST
• BST là một cấu trúc dữ liệu lưu trữ
các đối tượng dưới dạng cây nhị
phân:
• Khóa của mỗi nút lớn hơn
khóa của tất cả các nút ở cây 20
con trái và nhỏ hơn khóa của
tất cả các nút ở cây con bên
phải 26
10
struct Node{ 7 15 23 30
int key;
Node* leftChild;
Node* rightChild;
};
Node* root; 3 8
6
Cây nhị phân tìm kiếm - BST
7
Cây nhị phân tìm kiếm - BST
8
Cây nhị phân tìm kiếm - BST
9
Cây nhị phân tìm kiếm - BST
Node* del(Node* r, int v) {
if(r == NULL) return NULL;
else if(v < r->key) r->leftChild = del(r->leftChild, v);
else if(v > r->key) r->rightChild = del(r->rightChild, v);
else{
if(r->leftChild != NULL && r->rightChild != NULL){
Node* tmp = findMin(r->rightChild);
r->key = tmp->key; r->rightChild = del(r->rightChild, tmp->key);
}else{
Node* tmp = r;
if(r->leftChild == NULL) r = r->rightChild;
else r = r->leftChild;
delete tmp;
}
}
return r;
}
10
Cây nhị phân tìm kiếm cân bằng - AVL
11
Cây nhị phân tìm kiếm cân bằng - AVL
20 20
10 26 10 26
7 15 23 30 7
3 8 3 8
12
Cây nhị phân tìm kiếm cân bằng - AVL
• Chênh lệnh độ cao của 2 nút con của một nút nào đó có thể bằng 2
sau khi thêm mới hoặc loại bỏ 1 nút khỏi AVL
• Thực hiện các phép xoay để khôi phục thuộc tính cân bằng của AVL
13
Cây nhị phân tìm kiếm cân bằng - AVL
K2 K1
K1 C K2
Right rotation at K2 A
A B B C
TC
TA
TB TB TC
TA
14
Balanced Binary Search Tree - AVL
Case 2
K1 C K2 C K3
K1
A K2 K1 B
TC TC A D B C
D B A D
TA TB
TA TD TB TC
TD TB TA TD
15
Bảng băm
• Ánh xạ: một cấu trúc dữ liệu lưu trữ các cặp khóa – giá trị (key, value)
• put(k,v): Ánh xạ khóa k với giá trị v
• get(k): trả về giá trị tương ứng với khóa k
• Cài đặt
• Cây nhị phân tìm kiếm
• Bảng băm
16
Bảng băm
17
Bảng băm
• Hàm băm h(k) xác định địa chỉ nơi (k, value) được lưu trữ
• h(k) cần đơn giản và tính toán hiệu quả
18
Bảng băm
• Xung đột: hai khóa khác nhau cho cùng giá trị hàm băm
• Giải quyết xung đột:
• Nhóm chuỗi (chaining): nhóm các khóa có cùng giá trị hàm băm
vào các cụm
• Địa chỉ mở (Open addressing)
19
Hashing
20
Bảng băm: địa chỉ mở
• Cặp (key, value) được lưu vào các slot của bảng
• Các thao tác put(k, v) và get(k) cần thực hiện việc dò (probe) để tìm ra
vị trí mong muốn trong bảng nới lưu trữ khóa – giá trị
• put(k, v): dò để tìm ra vị trí trống để lưu (k, v)
• get(k): dò để tìm ra vị trí trong bảng nơi k được lưu trữ
• Thứ tự dò: h(k, 0), h(k, 1), h(k, 2), …, h(k, m-1)
• Các phương pháp dò
• Dò tuyến tính: h(k, i) = (h1(k) + i) mod m trong đó h1 làm hàm
băm thông thường
• Dò toàn phương: h(k, i) = (h1(k) + c1i + c2i2) mod m trong đó h1
là hàm băm thông thường
• Băm kép: h(k, i) = (h1(k) + i * h2(k) mod m trong đó h1 và h2 là
các hàm băm thông thường
21
Bảng băm: địa chỉ mở
get(k) put(k, v)
{ {
// T: the table // T: the table
i = 0; x.key = k; x.value = v;
while(i < m) { i = 0;
j = h(k,i); while(i < m) {
if(T[j].key = k) { j = h(k,i);
return T[j]; if(T[j] = NULL) {
} T[j] = x; return j;
i = i + 1; }
} i = i + 1;
return NULL; }
} error(“Hash table overflow”);
}
22
Bảng băm: địa chỉ mở
• Bài tập: Một bảng có m ô lưu trữ, áp dụng chiến lược địa chỉ mở với
h(k, i) có dạng:
h(k, i) = (k mod m + i) mod m
• Ban đầu, bảng trống rỗng, hãy cho biết trạng thái của bảng sau khi
thực hiện chèn lần lượt các khóa 7, 8, 6, 17, 4, 28 vào bảng với m = 10
23
CẤU TRÚC DỮ LIỆU
VÀ THUẬT TOÁN
Đồ thị
NỘI DUNG
• Đồ thị là một đối tượng toán học mô hình hoá các thực thể và các liên
kết giữa các thực thể đó
• Đồ thị G = (V, E) trong đó V là tập đỉnh và E là tập cạnh (cung)
• Với mỗi (u, v) E: ta nói v kề với u
6 2 6 2
1 5 1 5
3 4 3 4
Đồ thị vô hướng Đồ thị có hướng
V = {1, 2, 3, 4, 5, 6} V = {1, 2, 3, 4, 5, 6}
E = {(1, 3), (1,6), (2, 4), (2, 5), E = {(1, 3), (1,6), (2, 4), (2, 5),
(2, 6), (3, 4), (3, 6), (4, 5)} (6, 2), (3, 4), (6, 3), (4, 5)}
KHÁI NIỆM VÀ ĐỊNH NGHĨA
• Bậc của một đỉnh trên đồ thị vô hướng là số đỉnh kề với đỉnh đó:
deg(v) = #{u | (u, v) E}
• Bán bậc vào (ra) của một đỉnh trên đồ thị có hướng là số cung đi vào
(ra) đỉnh đó:
deg-(v) = #{u | (u, v) E}, deg+(v) = #{u | (v, u) E}
6 2 6 2
1 5 1 5
3 4 3 4
6 2 6 2
1 5 1 5
3 4 3 4
6 2 6
4 2
3 6
1 5 1 5 9 5
2
3 4
7 3
8 4
1 2 3 4 5 6 1 2 3 4 5 6
1 0 0 1 0 0 1 1 0 0 7 0 0 3
2 0 0 0 1 1 1 2 0 0 0 9 6 4
3 1 0 0 1 0 1 3 7 0 0 8 0 5
4 0 1 1 0 1 0 4 0 9 8 0 2 0
5 0 1 0 1 0 0 5 0 6 0 4 0 0
6 1 1 1 0 0 0 6 3 4 5 0 0 0
BIỂU DIỄN ĐỒ THỊ
Danh sách kề
Với mỗi v V, A(v) là tập các bộ (v, u, w) trong đó w là trọng
số của cung (v, u)
A(1) = {(1, 6, 3), (1, 3, 7)}
6
4 2
A(2) = {(2, 4, 9), (2, 5, 6)} 3 6
A(3) = {(3, 4, 8)}
1 5 9 5
A(4) = {(4, 5, 2)} 2
A(5) = {}
7 3
8 4
A(6) = {(6, 3, 5), (6, 2, 4)}
DUYỆT ĐỒ THỊ
6 2
đỉnh 1 2 3 4 5 6 7
1 5 d
f
3 4 p - - - - - - -
color W W W W W W W
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 d 1
f
p - - - - - - -
color G W W W W W W
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 d 1 2
f
3 p - - 1 - - - -
color G W G W W W W
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 d 1 2 3
f
3 4 p - - 1 3 - - -
color G W G G W W W
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4
f
3 4 p - - 1 3 4 - -
color G W G G G W W
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4
f 5
3 4 p - - 1 3 4 - -
color G W G G B W W
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4 6
f 5
3 4 p - - 1 3 4 - 4
color G W G G B W G
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4 6
f 5 7
3 4 p - - 1 3 4 - 4
color G W G G B W B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4 6
f 8 5 7
3 4 p - - 1 3 4 - 4
color G W G B B W B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4 6
f 9 8 5 7
3 4 p - - 1 3 4 - 4
color G W B B B W B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
6
đỉnh 1 2 3 4 5 6 7
1 5 d 1 2 3 4 10 6
f 9 8 5 7
3 4 p - - 1 3 4 1 4
color G W B B B G B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
6 2
đỉnh 1 2 3 4 5 6 7
1 5 d 1 11 2 3 4 10 6
f 9 8 5 7
3 4 p - 6 1 3 4 1 4
color G G B B B G B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
6 2
đỉnh 1 2 3 4 5 6 7
1 5 d 1 11 2 3 4 10 6
f 12 9 8 5 7
3 4 p - 6 1 3 4 1 4
color G B B B B G B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
6 2
đỉnh 1 2 3 4 5 6 7
1 5 d 1 11 2 3 4 10 6
f 12 9 8 5 13 7
3 4 p - 6 1 3 4 1 4
color G B B B B B B
7
DUYỆT THEO CHIỀU SÂU
DFS(1)
6 2
đỉnh 1 2 3 4 5 6 7
1 5 d 1 11 2 3 4 10 6
f 14 12 9 8 5 13 7
3 4 p - 6 1 3 4 1 4
color B B B B B B B
7
DUYỆT THEO CHIỀU SÂU
• Kết quả DFS trên đồ thị sẽ cho 1 rừng, bao gồm các cây DFS
• Phân loại cạnh
• Cạnh của cây (tree edge): (u,v) là cạnh của cây nếu v được thăm từ
u
• Cạnh ngược (back edge): (u,v) là cạnh ngược nếu v là tổ tiên của u
trong cây DFS
• Cạnh thuận (forward edge): (u,v) là cạnh thuận nếu u là tổ tiên của v
trong cây DFS
• Cạnh ngang (crossing edge): các cạnh còn lại
DUYỆT THEO CHIỀU SÂU
6 2
1 5
3 4
7
DUYỆT THEO CHIỀU SÂU
• Cài đặt thuật toán DFS tìm các thành phần liên thông của đồ thị
DUYỆT THEO CHIỀU SÂU
• Cài đặt thuật toán minh họa thuật toán DFS tìm các thành phần liên
thông của đồ thị
• Dữ liệu đầu vào
• Dòng 1: ghi n và m (số đỉnh và số cạnh của đồ thị)
• Dòng i+1 (i = 1,…,m): ghi u và v là đầu mút của 1 cạnh (u,v) của đồ
thị
• Kết quả đầu ra
• Dòng thứ k: ghi danh sách các đỉnh thuộc thành phần liên thông
thứ k
DUYỆT THEO CHIỀU SÂU
#include <stdio.h>
#define N 1000
int n,m;
int A[N][N];// A[v] is the list of adjacent nodes of v
int szA[N];// szA[v] is the size of A[v]
// data structure for connected component
int ncc;
int icc[N];// icc[u] is the index of connected component of u
void input(){
scanf("%d%d",&n,&m);
for(int v = 1; v <= n; v++) szA[v] = 0;
for(int k = 1; k <= m; k++){
int u,v;
scanf("%d%d",&u,&v);
A[v][szA[v]] = u; A[u][szA[u]] = v;
szA[v]++; szA[u]++;
}
}
DUYỆT THEO CHIỀU SÂU
void dfs(int u){
icc[u] = ncc;
for(int i = 0; i < szA[u]; i++){
int v = A[u][i];
if(icc[v] == -1){// not visited
dfs(v);
}
}
}
DUYỆT THEO CHIỀU SÂU
void dfsGraph(){
for(int v = 1; v <= n; v++) icc[v] = -1;
ncc = 0;
for(int v = 1; v <= n; v++) if(icc[v] == -1){
ncc++;
dfs(v);
}
for(int k = 1; k <= ncc; k++){
printf("connected component %d: ",k);
for(int v = 1; v <= n; v++) if(icc[v] == k) printf("%d ",v);
printf("\n");
}
}
void main(){
input();
dfsGraph();
}
DUYỆT THEO CHIỀU RỘNG
BFS(1)
6 2
1 5
3 4
7
DUYỆT THEO CHIỀU RỘNG
BFS(1)
1
DUYỆT THEO CHIỀU RỘNG
BFS(1)
6
3
DUYỆT THEO CHIỀU RỘNG
BFS(1)
6 2
3 4
7
DUYỆT THEO CHIỀU RỘNG
BFS(1)
6 2
1 5
3 4
7
DUYỆT THEO CHIỀU RỘNG
• Cài đặt thuật toán minh họa thuật toán BFS tìm các thành phần liên
thông của đồ thị
• Dữ liệu đầu vào
• Dòng 1: ghi n và m (số đỉnh và số cạnh của đồ thị)
• Dòng i+1 (i = 1,…,m): ghi u và v là đầu mút của 1 cạnh (u,v) của đồ
thị
• Kết quả đầu ra
• Dòng thứ k: ghi danh sách các đỉnh thuộc thành phần liên thông
thứ k
DUYỆT THEO CHIỀU RỘNG
#include <stdio.h>
#define N 1000
// Data Structure for the queue
int q[N];
int head = -1, tail = -1;
// data structure for the graph
int n,m;// number of nodes and edges
int A[N][N]; // A[v] is the list of adjacent nodes to v
int szA[N];// szA[v] is the size of A[v]
void input(){
cin >> N >> M;
for(int i = 0; i < M; i++){
cin >> u[i] >> v[i] >> c[i];
}
}
int main(){
input();
solve();
}
Đường đi ngắn nhất – thuật toán Dijkstra
6
4 2
6 • Mỗi ô của bảng tương ứng với 1
3
đỉnh v của bảng có nhãn (d(v),
1 5 1 9 5 p(v))
2 • Nút nguồn s = 1
7 3
8 4 1 2 3 4 5 6
Init (0,1) (,1) (7, 1) (, 1) (, 1) (3,1)
Step 1 -
Step 2 -
Step 3 -
Step 4 -
Step 5 -
Đường đi ngắn nhất – thuật toán Dijkstra
6
4 2
6
3
1 5 1 9 5
2
7 3
8 4
1 2 3 4 5 6
Init (0,1) (,1) (7, 1) (, 1) (, 1) (3,1) *
Step 1 - (7,6) (7,1) (4,6) (,1) -
Step 2 - -
Step 3 - -
Step 4 - -
Step 5 - -
Đường đi ngắn nhất – thuật toán Dijkstra
6
4 2
6
3
1 5 1 9 5
2
7 3
8 4
1 2 3 4 5 6
Init (0,1) (,1) (7, 1) (, 1) (, 1) (3,1) *
Step 1 - (7,6) (7,1) (4,6) * (,1) -
Step 2 - (7,6) (7,1) - (6, 4) -
Step 3 - - -
Step 4 - - -
Step 5 - - -
Đường đi ngắn nhất – thuật toán Dijkstra
6
4 2
6
3
1 5 1 9 5
2
7 3
8 4
1 2 3 4 5 6
Init (0,1) (,1) (7, 1) (, 1) (, 1) (3,1) *
Step 1 - (7,6) (7,1) (4,6) * (,1) -
Step 2 - (7,6) (7,1) - (6, 4) * -
Step 3 - (7,6) (7,1) - - -
Step 4 - - - -
Step 5 - - - -
Đường đi ngắn nhất – thuật toán Dijkstra
6
4 2
6
3
1 5 1 9 5
2
7 3
8 4 1 2 3 4 5 6
Init (0,1) (,1) (7, 1) (, 1) (, 1) (3,1) *
Step 1 - (7,6) (7,1) (4,6) * (,1) -
Step 2 - (7,6) (7,1) - (6, 4) * -
Step 3 - (7,6) (7,1) * - - -
Step 4 - (7,6) - - - -
Step 5 - - - - -
Đường đi ngắn nhất – thuật toán Dijkstra
6
4 2
6
3
1 5 1 9 5
2
7 3
8 4
1 2 3 4 5 6
Init (0,1) (,1) (7, 1) (, 1) (, 1) (3,1) *
Step 1 - (7,6) (7,1) (4,6) * (,1) -
Step 2 - (7,6) (7,1) - (6, 4) * -
Step 3 - (7,6) (7,1) * - - -
Step 4 - (7,6) * - - - -
Step 5 - - - - - -
Đường đi ngắn nhất – thuật toán Dijkstra
3,2
1,5 4,8
8,10 9,11
Đường đi ngắn nhất – thuật toán Dijkstra
1,5 1,5
int deleteMin(){
int sel_node = node[0];
swap(0,sH-1);
sH--;
downHeap(0);
return sel_node;
}
Đường đi ngắn nhất – thuật toán Dijkstra
void input(){
scanf("%d%d",&n,&m);
for(int k = 1; k <= m; k++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
A[u].push_back(v);
c[u].push_back(w);
}
scanf("%d%d",&s,&t);
}
Đường đi ngắn nhất – thuật toán Dijkstra
void init(int s){
sH = 0;
for(int v = 1; v <= n; v++){
fixed[v] = false; idx[v] = -1;
}
d[s] = 0; fixed[s] = true;
for(int i = 0; i < A[s].size(); i++){
int v = A[s][i];
insert(v,c[s][i]);
}
}
Đường đi ngắn nhất – thuật toán Dijkstra
void solve(){
init(s);
while(sH > 0){
int u = deleteMin(); fixed[u] = true;
for(int i = 0; i < A[u].size(); i++){
int v = A[u][i];
if(fixed[v]) continue;
if(!inHeap(v)){
int w = d[u] + c[u][i]; insert(v,w);
}else{
if(d[v] > d[u] + c[u][i]) updateKey(v,d[u]+c[u][i]);
}
}
}
int rs = d[t]; if(!fixed[t]) rs = -1;
printf("%d",rs);
}
Đường đi ngắn nhất – thuật toán Dijkstra
int main(){
input();
solve();
}
Thank you
for your
attentions!