Professional Documents
Culture Documents
ĐÀ NẴNG 2021
C
D
B
E
AB AC AD
BA BC BD
DA DB DC
s
EA EB EC ED
Xanh: AB , AC , AD , BA , DC , ED
Vàng: BC , BD , EA
Đỏ: DA , DB
Tím: EB , EC
Hình 1. 2. Đồ thị các tuyến đi mâu thuẫn. AB phải khác nhóm với BC , DA , EA.
Hình 1.3 là biểu diễn bảng của đồ thị hình 1.2 với 1 ở dòng i cột j là có cạnh nối
đỉnh i với đỉnh j.
1 2 3 4
AB AC AD BA BC BD DA DB DC EA EB EC ED
AB 0 0 0 0 1 1 1 0 0 1 0 0 0
AC 0 0 0 0 0 1 1 1 0 1 1 0 0
AD 0 0 0 0 0 0 0 0 0 1 1 1 0
BA 0 0 0 0 0 0 0 0 0 0 0 0 0
BC 1 0 0 0 0 0 0 1 0 0 1 0 0
BD 1 1 0 0 0 0 1 0 0 0 1 1 0
DA 1 1 0 0 0 1 0 0 0 0 0 0 0
DB 0 1 0 0 1 0 0 0 0 0 0 1 0
DC 0 0 0 0 0 0 0 0 0 0 0 0 0
EA 1 1 1 0 0 0 0 0 0 0 0 0 0
EB 0 1 1 0 1 1 1 0 0 0 0 0 0
EC 0 0 1 0 0 1 1 1 0 0 0 0 0
Cấu trúc dữ liệu – trang 5
ED 0 0 0 0 0 0 0 0 0 0 0 0 0
1.3. CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG (ABSTRACT DATA TYPES ADT)
Định nghĩa :
ADT là mô hình toán cùng với tập hợp các phép toán xác định trên mô hình.
Ví dụ các số nguyên và tập các phép toán : cộng, trừ, nhân là một ADT đơn giản.
Trong ADT, các phép toán có thể sử dụng như các toán hạng không chỉ là những thành
phần của chính ADT đó mà có thể là các kiểu khác của các toán hạng, nghĩa là các số
nguyên hoặc các thành phần của ADT khác. Nhưng chúng ta giả thiết có ít nhất một
toán hạng hoặc kết quả của một phép toán nào đó là của ADT.
Hai tính chất của các thủ tục nêu trên là : Tính khái quát (generalization) và tính
khép kín (encapsulation) áp dụng như nhau đối với ADT. ADT là sự khái quát các kiểu dữ
liệu nguyên thủy (nguyên, thực...) cũng như các thủ tục là sự khái quát của các phép toán
nguyên thủy (+, -, ...). ADT khép kín kiểu dữ liệu theo nghĩa là định nghĩa kiểu và tất cả
các phép toán trên kiểu đó có thể cục bộ hóa trong một phần của chương trình. Nếu
Cấu trúc dữ liệu – trang 9
chúng ta muốn thay đổi các thực hiện của ADT, chúng ta sẽ biết được nó ở đâu và chỉ
cần xem xét một phần nhỏ ta có thể tin chắc rằng có chỗ khác trong chương trình bị sai
liên quan đến kiểu dữ liệu này. Một điều cần đặc biệt lưu ý là một số phép toán lại có
thể xuất hiện trong nhiều ADT, và sự tham chiếu của chúng phải xuất hiện trong các
phần của ADT.
Để minh họa ta xét thủ tục greedy của hình 1.8 sử dụng các phép toán nguyên
thủy trên kiểu dữ liệu LIST (của các số nguyên).
Các phép toán thực hiện trên LIST newcolor là :
1. Làm trống danh sách (list).
2. Nhận phần tử đầu của danh sách và trả về null nếu danh sách trống.
3. Nhận phần tử tiếp theo của danh sách và trả về null nếu không có phần tử tiếp
(next) và ...
4. Chèn số nguyên vào danh sách.
Nếu trong hình 1.8 ta thay các phép toán bằng các lệnh :
1. MAKENULL (newcolor) ;
2. W = FIRST (newcolor) ;
3. W = NEXT (newcolor) ;
4. INSERT (V, newcolor) ;
thì ta thấy tầm quan trọng của kiểu dữ liệu trừu tượng (ADT). Chúng ta có thể dùng
một kiểu dữ liệu bất kỳ và các chương trình chẳng hạn như hình 1.8 sử dụng các đối
tượng của kiểu đó sẽ không thay đổi, chỉ có các thủ tục thực hiện các phép toán trên
kiểu đó là cần thay đổi.
Khi quay lại kiểu dữ liệu trừu tượng GRAPH, chúng ta thấy cần các phép toán sau
:
1. Nhận đỉnh chưa tô màu đầu tiên;
2. Kiểm tra xem có cạnh nối hai đỉnh không;
3. Tô màu đỉnh;
4. Nhận đỉnh chưa tô tiếp theo;
Còn có các phép toán cần thiết ngoài thủ tục greedy như bổ sung các đỉnh và các
cạnh vào đồ thị và tô màu tất cả các đỉnh chưa tô. Có nhiều cấu trúc dữ liệu cần các đồ
thị có các phép toán đó - bạn đọc tự nghiên cứu ở các chương 6 - 7 của [1].
Chúng ta không hạn chế các phép toán trên các đối tượng của mô hình toán. Mỗi
tập phép toán định nghĩa riêng cho một ADT. Một ví dụ về các phép toán định nghĩa
cho kiểu dữ liệu trừu tượng LIST là :
1. MAKENULL (A) - thủ tục lấy tập null làm giá trị cho tập A.
1 1.2 3
2 3.4 0
1.2 3
3 5.6 2
3.4 0
4 7.8 1
5.6 2
reclist
7.8 1
0
100
0
n
0 5 10 15 20
Hình 1.11 : Thời gian chạy 4 chương trình.
Thời gian chạy Kích thước bài Kích thước bài toán Tỉ lệ tăng về kích
toán tối đa cho 4
T(n) 3 tối đa cho 10 giây thước
10 gy
100n 10 100 10.0 lần
5n
2 14 45 3.2 lần
3
n /2 12 27 2.3 lần
2
n 10 13 1.3 lần
Hình 1.12. Hiệu quả khi tăng tốc độ tính toán.
2.3.7. Quy tắc cộng:
Giả sử T1(n) và T2(n) là thời gian thực hiện chương trình P1 và P2 tương ứng có
độ phức tạp là 0(f(n)) và 0(g(n)).
Khi đó thời gian thực hiện chương trình P1 xong rồi thực hiện chương trình P2 là:
T(n)=T1(n)+T2(n) và T(n)=0(max(f(n), g(n))
Chứng minh : Ta cần chứng minh:
T(n)=O(f(n)) ( hằng số c>0, hằng số n0>0 : n n0 => T(n) c * f(n) )
T(n)=O(max(f(n), g(n))
T(n)=O(max(f(n),g(n))) (c>0, n0>0 : n n0 => T(n) c * max(f(n),g(n) )
Có: T1(n)=O(f(n)) => (c1>0, n01>0 : nn01 => T1(n) c1 * f(n) )
Có: T2(n)=O(g(n)) => (c2>0, n02>0 : nn02 => T2(n) c2 * g(n) )
Nếu chọn n0=max(n01, n02) thì khi nn0 ta
sẽ có n n01 => T1(n) c1 * f(n)
Vậy nếu chọn c=c1+c2, n0=max(n01,n02) thì nếu có nn0 thì ta sẽ có:
T1(n)+T2(n) c * max(f(n),g(n)) , là điều cần phải chứng minh.
2.3.8. Hệ quả 4: Nếu một đoạn chương trình chung gồm nhiều đoạn chương trình
thành phần nối tiếp nhau thì đoạn chương trình chung sẽ có độ phức tạp là độ phức tạp
lớn nhất trong các độ phức tạp thành phần.
α α-1 α-2
2.3.9. Hệ quả 5: Nếu T(n) = a0n + a1n +a2n + . . . với a, α là các hằng số thì
α
T(n)=O(n )
6 5 4 3 2 1 0
Ví dụ : Nếu T(n)= 8n + 3n + 4n + 0n + 9n + 7n + 5n
6 5 4 2 6 6 5
Tức T(n)= 8n + 3n + 4n + 9n + 7n + 5 thì T(n)=O(n ). 834975=8*10 +6*10 +
2 3
Có 3 chương trình có thời gian thực hiện tương ứng là 0(n ) , 0(n ) và 0(n*logn).
2 3 3
Thì thời gian thực hiện 3 chương trình nối tiếp nhau là 0(max(n ,n ,n*logn) sẽ là 0(n ).
Nói chung thời gian chạy một dãy cố định các bước là thời gian chạy lớn nhất của
một bước nào đó trong dãy cũng có trường hợp có hai hay nhiều bước có thời gian
chạy không tương xứng (incomensurate) (không lớn hơn mà cũng không nhỏ hơn). Ví
dụ các bước của 0(f(n)) và 0(g(n)) là :
4 2
f(n) = n nếu n chẵn g(n)= n nếu n chẵn
2 3
n nếu n lẻ n nếu n lẻ.
Khi đó qui tắc tổng phải áp dụng trực tiếp, thời gian chạy là 0(max(f(n), g(n)) là
4 3
n nếu n chẵn và n nếu n lẻ.
Nếu g(n) f(n) với n n0; n0 là const nào đó thì 0(f(n) + g(n)) sẽ là 0(f(n)).
2 2
Ví dụ O(n +n) cũng bằng O(n ).
2.3.10. Quy tắc nhân:
Giả sử T1(n) và T2(n) là thời gian thực hiện chương trình P1 và P2 tương ứng có
độ phức tạp là O(f(n)) và O(g(n)).
Khi đó thời gian thực hiện 2 chương trình P1 và P2 lồng nhau là:
T(n)=T1(n)*T2(n) và T(n) = O( f(n)*g(n) )
For (i=1;i<=5; i++)
{s=s+a;
a=a+1
;
}
ế =0
Algorithm MaxMin(a, x, y)
Input: mảng a[x..y] với x là chỉ số trái nhất và y là chỉ số phải nhất.
Output: giá trị lớn nhất max và giá trị nhỏ nhất min.
Begin
If (y-x ≤ 1) then
Return ( max(a[x], a[y]) , min(a[x], a[y]) )
Else
(max1, min1) ← MaxMin(a, x, (x+y)/2)
(max2, min2) ← MaxMin(a, ((x+y)/2)+1,y)
Return (max(max1, max2) , min(min1, min2) )
Endif
end
trong đó max() và min() tương ứng là các hàm đơn giản tính giá trị lớn nhất và giá trị
nhỏ nhất của 2 số.
void gtlnnn(float A[] , int x , int y , float &min , float &max)
{ if (x==y) { min=A[x]; max=A[x];}
else { float min1 , max1 , min2 , max2;
gtlnnn(A , x , (x+y)/2 , min1 , max1);
gtlnnn(A , (x+y)/2+1 , y , min2,
max2);
if (max1>max2) max = max1 ; else max = max2; if
(min1 < min2) min = min1 ; else min = min2;
}
}
4.2.2. Bài toán Tháp Hà Nội (The towers of Hanoi)
Cấu trúc dữ liệu – trang 27
Hãy viết chương trình chuyển n đĩa từ cột A (trong đó đĩa lớn ở dưới, đĩa nhỏ ở
trên) sang cột B các với điều kiện:
- Mỗi lần chỉ được chuyển một đĩa.
- Trên các cọc: luôn luôn đĩa lớn ở dưới, đĩa nhỏ ở trên.
- Được dùng cọc trung gian thứ ba C.
Tất nhiên có nhiều cách giải. Ví dụ cách đơn giản là: ta hình dung các cột xếp
theo hình tam giác (3 đỉnh A, B, C). Trong số lần chuyển lẻ ta chuyển đĩa nhỏ nhất theo
chiều kim đồng hồ, còn các lần chẵn, ta chọn cách chuyển hợp lệ, trừ đĩa nhỏ nhất.
A B C
3
2
1
Giải thuật này ngắn gọn và đúng nhưng khó hiểu vì sao làm như vậy. Bây giờ ta
xét phương pháp Divide - and - conquer.
Bài toán chuyển n đĩa từ A sang B có thể gồm hai bài toán con kích thước n-1
.Đầu tiên chuyển n-1 đĩa nhỏ nhất (các đĩa phía trên) từ A sang C, giữ lại đĩa dưới cùng
trên A. Sau đó chuyển đĩa này từ A sang B, sau đó chuyển n-1 đĩa từ C sang B.
Việc chuyển n-1 đĩa đã hoàn tất do áp dụng đệ qui của phương pháp. Mặc dù cụ
thể khó thấy do chứa các lần gọi đệ qui trong stack, nhưng giải thuật là dễ hiểu và dễ
chứng minh sự đúng đắn. Đó là sự rõ ràng của giải thuật divide - conquer và ta sẽ thấy
nó có hiệu quả hơn nhiều giải thuật khác trong nhiều trường hợp. Từ giải thuật (hình
5.1) ta dễ thấy độ phức tạp thời gian của nó xác định bằng phương trình:
1 ế =1
( )={
2 ( − 1) + 1 ế > 1
Y:= C D n/2
Y= C*2 +D
Khi đó ta có thể viết :
n n/2
X * Y = A * C * 2 + (A * D + B * C) * 2 + B * D (4.1)
Nếu đánh giá trực tiếp X*Y thì tốn bốn phép nhân số nguyên dài n/2 bit, ba phép
n n/2
cộng các số nguyên dài nhất là 2n bit và hai phép dịch chuyển (nhân cho 2 và 2 ).
Các phép cộng và dịch chuyển mất 0(n) bước. Nếu T(n) là tổng các phép toán bít để
nhân hai số nguyên dài n bit theo (5.1) thì ta có phương trình truy hồi :
T (1) = 1
T (n) = 4T (n/2) + cn (4.2)
Tương tự ví dụ 5.4 ta có thể lấy hằng c trong (4.2) là 1, hàm tái d(n) đúng là n và
2
sau đó suy ra nghiệm thuần nhất và nghiệm riêng đều là 0(n ). Như vậy công thức nhân
hai số nguyên theo (4.1) cũng không gì hơn phương pháp nhân trong trường phổ thông.
Nếu dùng lại (4.2) để cải tiến thì phải giảm số bài toán con. Muốn vậy ta xét công thức
sau đây :
n n/2
X * Y = A*C * 2 + [(A - B) * (D - C) + A*C + B*D]*2 + B * D (5.3)
(4.3) tuy phức tạp hơn (4.1) nhưng chỉ có ba phéo nhân số nguyên n/2 bit. Sáu
phép cộng hoặc trừ và hai phép chuyển. Ta có công thức cho T(n) :
T(1)=1
T (n) = 3T(n/2) + cn
3 1.59
Nghiệm T (n) 0 (nlog ) hay 0 (n )
i = 1 m = 3 j = 9
m = 3
Mảng đầu
M phàn tử m phần
tử
i Hoán vị J
Cần tìm đường đi ngắn nhất cho mỗi cặp đỉnh bất kỳ.
Theo nguyên lý tối ưu của Quy hoạch động: Nếu k là đỉnh trên đường đi ngắn
nhất đi từ đỉnh i đến đỉnh j thì đoạn đường đi từ i đến k và đoạn đường từ k đến j đó
cũng phải là ngắn nhất.
Ta xây dựng ma trận A mà mỗi phần tử A[i][j] là độ dài đường đi ngắn nhất hiện tại đi từ đỉnh i đến đỉnh j. Lúc đầu gán C cho A.
Sau đó lặp n lần. Sau lần lặp thứ k (k=1,2,…,n), A sẽ cho độ dài các đường đi ngắn nhất trong k đỉnh {1, 2,..., k}. Lặp bước thứ n sẽ cho kết
quả cuối cùng. Ở bước lặp k ta tính A theo công thức:
[][] −1[ ][ ]
={
[ ][ ]
−1
[ ][ ]+ −1
30
10 15
Ma trận giá C (độ dài đường đi trực tiếp giữa các đỉnh):
Tập đỉnh V={ 1 , 2 , 3 } . n=3 đỉnh.
0 22 ∞
= [30 0
15]= 0
10 ∞ 0
0 ?]
1=[30
10 ? 0
0 [2][1] + 0[1][3]
0[3][2] ∞
1 [3][2] ={ ={ = 32
[1][2] 10+22
0 [3][1] + 0
0 22 ∞
0 15]
1=[30
10 32 0
0 15]
2=[30
? 32 0
1[1][3] ∞
2 [1][3] ={ ={ = 37
[2][3] 22+15
1 [1][2] + 1
[3][1] ={ 1[3][1]
={ 10 = 10
2
[2][1] 32+30
1 [3][2] + 1
0 22 37
0 15]
2=[30
10 32 0
0 15]
3=[?
10 32 0
2[1][2] 22
3[1][2] ={ ={ = 22
[3][2]
37+32
[1][3] + 2
2
2[2][1] 30
3[2][1] ={ ={ = 25
[3][1]
15+10
[2][3] + 2
2
Kết luận:
1 2:22
1 3: 37 1->2->3
2 1:
2 3:
3 1:
3 2:
Chương trình con:
void Floyd(int C[20][20], int n)
{
for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
A[i][j]=C[i][j];
for (k=1; k<=n; k++)
for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
if (A[i][j]>A[i][k]+A[k][j])
A[i][j]=A[i][k]+A[k][j];
}
5.2.3 World Series Olds:
Giả sử có hai đội A và B thi đấu. Giả sử khả năng thắng của mỗi đội là như nhau
tức 50% cho mỗi trận đấu. Hai đội thi đấu và đội nào đầu tiên thắng n trận với n đặc
biệt nào đó. The World Series là cuộc thi như vậy với n = 4. Đặt P(i,j) - là xác suất sao
cho A cần j trận thắng và B cần đấu j trận, cuối cùng A thắng.
Ví dụ trong World Series nếu Dodgers thắng hai trận còn Yankees thắng một trận
thì : i = 2 và j = 3 và P (2,3) sẽ tính được là 11/5.
Để tính P (i;j) ta có thể dùng phương trình truy hồi hai biến. Ta có hai trường hợp
đặc biệt :
- Nếu i = 0 và j > o (A chưa đấu đã thắng) thì P (0, j) = 1, còn j = 0, i > 0 thì rõ ràng
P (i,0) = 0
i j
i
i
Nếu i = j thì số đó là (2n / n ) (n i j) thực tế có thể chứng minh đó cũng là 0
n
(2 / n ).
(8,26 (15,2
) 6)
2 3
(0,20 (27,2
) 1)
1 4
0 5 (22,1
2)
(0,10 0 (10,0
)
)
2 0
1 4
3
0
5
0
0
i+s-1
S1+K,S-K
i+k - i
S1, K+1
Hình 5.12. Chia bài toán Sis ra hai bài toán con si,k+i và si+k, s-k
Ta biết rằng việc giải các bài toán 3 cạnh hoặc ít hơn là không tốn sức, nếu ta sử
dụng kỹ thuật đệ qui để giải các bài toán con kích thước S 4 thì có thể chứng minh
s-4
rằng mỗi lần gọi bài toán con kích thước S sẽ cho ta sự tăng lên đến tổng số 3 lần gọi
đệ qui. Vậy tổng số bước thực hiện do gọi thủ tục đệ qui cho bài toán ban đầu n đỉnh là
cấp mũ n.
Qua quá trình phân tích ta chỉ thấy ngoài bài toán ban đầu đã cho, ta chỉ có n(n-4)
bài toán con khác nhau cần phải giải. Đó là các bài toán S is so với 0 i <n, 4 s < n.
Nhưng khi sử dụng kỹ thuật đệ qui không phải tất cả các bài toán con là khác biệt nhau.
Ví dụ nếu trong hình 5.10 ta chọn cung (0, 3) và sau đó trong bài toán con của hình
5.11(b) ta chọn 4 thì ta phải giải bài toán con S 44. Nhưng ta cũng sẽ giải bài toán đó
nếu đầu tiên ta chọn cung (0, 4) hoặc nếu ta chọn cung (1, 4) và sau đó khi giải bài
toán con S45 ta lại chọn đỉnh V0 để tạo ra tam giác với i và 4.
Vì vậy ta cần áp dụng QHĐ, tức lập bảng cho các giá C is để giải bài toán Sis với
tất cả i và S, vì nghiệm của bất kỳ bài toán nào cũng chỉ phụ thuộc vào nghiệm của bài
toán kích thước nhỏ nhất, nên thứ tự hợp lý khi lập bảng là theo thứ tự tăng kích
thước : S = 4,5,...n-1. Với mỗi S ta tìm giá min cho các bài toán S is với mọi đỉnh i, chú
ý là giá Cis = 0 nếu S < 4 với mọi i.
Bằng qui tắc (1) - (3) ở trên ta tìm các bài toán con và công thức tính Cis cho S 4 là :
Cis = min [Ci, K+1 + Ci+K, s - K + D(i, i+k) + D(i+k, i+s-1)] (5.5)
1 k S-2
Trong đó D(p, q) là độ dài cung (p, q), nều p, q không kề nhau trong đa giác,
ngược lại D (p, q) = 0 khi p, q kề nhau.
Ví dụ hình 5.3 là bảng giá Cis với 0 1 6 và 4 S 6 cho đa giác và các
khoảng cách cho trước trong hình 4.10. Giá ở các dòng với s<3 đều bằng 0. Ta tính C 07
ở cột 0 dòng 7 (s=7) số này cũng như các số khác trong các dòng biểu diễn quá trình
phân tam giác cho toàn bộ đa giác.
Cấu trúc dữ liệu – trang 46
7 C07 =
75.43
6 C06 = C16 = C26 = C36 = C46 = C56 = C66 =
53.54 55.22 57.58 64.69 64.69 59.78 63.62
5 C05 = C15 = C25 = C45 = C45 = C55 = C65 =
37.54 31.31 49.35 37.74 37.74 45.50 38.09
4 C04 = C14 = C24 = C34 = C44 = C54 = C64 =
16.16 16.16 15.65 15.65 15.65 22.69 17.89
S I=0 1 2 3 4 5 6
Hình 5.13. Bảng Cis
Xem cách tính C65 = 38.09 (i = 6, s = 5). Theo (5.5). C65 là min của ba tổng ứng
với k = 1,2 và 3 là :
K=1 C2 + C04 + D (p, q) + D (p, q) (i + k = 6 + 1 = 7 là 0)
K=2 C63 + C13 + D (6, 1) + D (1, 3)
K=3 C64 + C22 + D (6, 2) + D (2, 3)
D (2, 3) = D (6, 0) = 0D (0, 2) = 26.08 D (1, 3) =
16.16 D (6, 1) = 22.36, D (0, 3) = 21.93
Vậy ba tổng trên tương ứng với K = 1,2 và 3 là 38.09, 38,52 và 43.97. Do đó C65
= 36.09. Nhưng để đạt min này ta phải giải hai bài toán con S 62 và S04 (xem hình 5.12),
nghĩa là chọn cung (0, 3) và sau đó giải bài toán con S04. Ta thấy tốt nhất là chọn
(1,3) vì D (1, 3) = 16.16 nhỏ hơn D (0, 3) = 21.93.
Tìm nghiệm từ bảng :
Bảng không cho ta lời giải trực tiếp, ta cần tìm k để (5.5) đạt min. Sau khi có k ta
suy ra nghiệm bao gồm các cung (i, i+k) và (i+k, i+s-1) một trong hai cạnh không
phải cung (chord) dù cho đường dây nối là ám chỉ bằng nghiệm của Si,k+1 và Si+k, s-k.
Ví dụ trong bảng 5.13 - C07 là nghiệm cuối cùng của bài toán đạt được khi k = 5
trong công thức (5.5). Điều đó có nghĩa là bài toán S 07 tách thành hai bài toán con S06
và S52. S06 gồm 6 đỉnh 0, 1,..., 5. Còn S52 gồm hai đỉnh (5, 6) có giá C52 là 0. Do
đó ta đưa vào cung (0, 5) có độ dài 22.09 và phải giải bài toán S06.
C06 đạt min khi k = 2 trong (5.5) và S 06 tách thành hai bài toán con S 03 và S24 với
các nhóm đỉnh tương ứng là (0, 1, 2) và (2, 3, 4, 5). S03 không cần giải. Giải S24
là tính độ dài (0, 2) và (2, 5).
Độ dài này tương ứng là 17.89 và 19.80. Min cho C 24 là k = 1 trong (4.5) và sinh
ra hai bài toán con S22 và S23 có giá đều bằng 0 vì S 3.
Vậy cung (3, 5) được đưa vào nghiệm với giá C24 là 15.65
Cấu trúc dữ liệu – trang 47
Kết quả bài toán S07 với giá min C07 = 75.43 và hình dưới đây :
2 3
1 4
0 5
6
Hình 5.4. Kết quả phân tam giác đạt giá min.
T(n) T (i ) T (n 1)
i 1
i = 1 i = 2 i = 3 i = 4
3
Giải thuật lập bảng có độ phức tạp 0(n ).
5.2.6. Bài toán du lịch
Cho đồ thị có hướng G=(V,E) với tập đỉnh V = {1,2,...,n} và ma trận giá C, với cij
độ dài giữa hai đỉnh i và j: cij=0 nếu i=j và cij>0 nếu i j, cij= nếu không có cạnh
(i,j). Không mất tính tổng quát ta giả sử đi từ đỉnh 1, đi qua mỗi đỉnh đúng một lần, về
lại
Cấu trúc dữ liệu – trang 49
đỉnh 1, sao cho tổng độ dài đường đi là ngắn nhất. Điều đó có nghĩa là nó gồm cạnh
(1,j), j 1, sau đó là đường đi từ j đến 1 qua mỗi đỉnh trong tập đỉnh còn lại V\{1,j}
đúng 1 lần. Nếu hành trình là tối ưu thì đường đi từ j đến 1 cũng tối ưu (theo nguyên
lý tối ưu).
Ta xét các đỉnh S V \ {1} và đỉnh i V\S, nếu i = 1 thì chỉ có S = V\ {1}. Đặt
g(i,S) là độ dài đường đi ngắn nhất từ i, đi qua mỗi đỉnh trong S đúng một lần, về lại đỉnh
1. Theo định nghĩa này thì g(1 , V\{1}) là độ dài cuộc hành trình tối ưu, ta có :
G (1, V\{1}) = min (cij + g (j,V\{1,j})) (1)
2 j n
Tổng quát hơn :
nếu: i 1, S , S V \ {1} và i S
G(i,S) = min (cij + g(j,S\{j})) , j S (2)
Và : g(i,) = ci1, i = 1,2,3...n
Biết g(i,S) khi tập S trống, ta có thể áp dụng công thức (2) để tính hàm g với tất
cả các tập S có đúng một đỉnh (khác đỉnh 1) rồi sau đó tính g với mọi tập S có đúng 2
đỉnh (khác đỉnh 1)... Khi đã tính được giá trị của g (j,V\{1,j}) với mọi đỉnh j 1 ta áp
dụng công thức (5.6) để tính g (1,V\{1}) là nghiệm bài toán.
Ví dụ : Cho G = (V,E) với C và độ thị tương ứng ở hình 5.19
5
1 14 2
15 6 9
13 10 7 16
11
4 3
12
8
Cây bao trùm tối thiểu của đồ thị G=(V,E) là đồ thị G’=(V,T) với T là tập con
của E, T nối hết các đỉnh trong G mà có tổng độ dài ngắn nhất.
Ta xét hai giải thuật : Kruskal và Prim.
1. Giải thuật Kruskal:
Các cạnh trong E xếp theo độ dài tăng dần. Lúc đầu cho T trống. Ban đầu tập
đỉnh V gồm n thành phần liên thông một phần tử riêng biệt. Quá trình của giải thuật
sẽ bổ sung dần các cạnh vào T. Mỗi bước của giải thuật là:
28 22 42 20 26
4 38 5 24 6
40 30 34
7
30
7
G’=(V, T)
Không mất tính tổng quát, cần tìm các đường đi ngắn nhất từ đỉnh 1 đến từng
đỉnh còn lại.
Ta ký hiệu D là mảng một chiều (vector) với D[i] là độ dài đường đi ngắn nhất
hiện tại đi từ đỉnh xuất phát 1 đến đỉnh i. S là tập các đỉnh mà các đường đi ngắn nhất
đó chỉ được đi ngang qua những đỉnh có trong tập S.
- Đầu tiên cho tập S chỉ gồm đỉnh 1. 1 w u
- Lặp nhiều lần cho đến khi S=V, mỗi lần lặp:
. Chọn đỉnh w mà D[w] là min với w ∉ S.
Ví dụ: Tìm các đường đi ngắn nhất từ đỉnh 1 đến từng đỉnh còn lại.
50 10 60
3 20 4
2
b
1 -2
S a
3
c
P1 J3
8
P2 J2 J1
5 2
P3 J5 J4 J6
5 1 1
Tổng thời gian thực hiện là T=8. Đây cũng là lịch tối ưu.
Dạng 2 : (Tối ưu thiết bị)
Cho m công việc J1, J2,..., Jm tương ứng thời gian thực hiện t1, t2..., tm và tập các
thiết bị P1 , P2 , P3 , ...
Với thời gian cho trước T0 - cố định để hoàn thành m công việc, cần bố trí các
công việc trên các thiết bị sao cho số thiết bị đạt min (giả thiết T 0 thời gian thực hiện
công việc dài nhất).
Bài toán này tương đương với bài toán Đóng gói sau đây: Mỗi thiết bị P i ta coi
như một thùng Bi, có kích thước T0 như nhau cho mọi thùng. Mỗi công việc tj ta coi
như một sản phẩm có kích thước tj bằng thời gian tj (j = 1, 2, ... , m) ta cần sắp các sản
phẩm này vào các thùng sao cho số thùng sử dụng ít nhất. Tất nhiên các sản phẩm
không thể tách ra nhỏ hơn.
Ví dụ : Có 5 sản phẩm, 2 sản phẩm có kích thước 3 và 3 sản phẩm có kích thước 2.
Mỗi thùng có kích thước 4, khi đó ta có thể dùng 4 thùng để xếp như sau:
2
3 3 4
Thường ta có bốn phương pháp kinh nghiệm Tham lam sau đây:
1. FF - (First Fit) :
Cho L là một thứ tự nào đó các sản phẩm.
Sản phẩm thứ nhất cho vào thùng B1, sản phẩm thứ 2 cho vào B1 nếu được,
ngược lại cho vào B2. Nói chung một sản phẩm kế tiếp sẽ cho vào Bi nếu được với i là
chỉ số thùng nhỏ nhất trong các thùng có thể. Nếu sản phẩm không chứa được vào một
trong k thùng đã chứa một phần thì chứa nó vào thùng thứ k+1 (thùng này có thể đã
chứa một phần hoặc còn trống), lặp bước cơ bản này khi nào L chưa chứa hết.
2. FFD - (First Fit Decreasing) :
FFD chỉ khác FF là L - xếp các sản phẩm theo kích thước giảm dần.
3. BF (Besty Fit) :
Cho L là một thứ tự nào đó các sản phẩm. Bước cơ bản giống FF. Nhưng sản
phẩm kế tiếp cho vào thùng còn trống ít nhất nếu được. Ví dụ kích thước thùng là 6, và
có bốn thùng đã chứa một phần còn trống là 4, 3, 2, 1 thì sản phẩm kế tiếp có kích
thước 3 sẽ cho vào thùng thứ 2 cho đầy luôn.
4. BFD (Best Fit Decreasing) :
Giống BF nhưng L - xếp các sản phẩm theo kích thước giảm dần.
Có nhiều đánh giá cận trên giữa nghiệm gần đúng theo một trong các phương
pháp trên và nghiệm đúng :
Ví dụ : Theo Graham R.L trong "Proc. Spring Joint Comput Conf" 205-217
(1972), nếu ta ký hiệu N0 là số thùng tối thiểu cho bởi Giải thuật đúng, và NFFD - là
số thùng thu được theo giải thuật FFD thì với > 0 bất kỳ và N0 đủ lớn, ta có đánh giá:
N 11
FFD
N0 9
Ta có :
N N 11 1 2 0.22
FFD 0
0.23
N0 9 9
Nghĩa là sai số của FFD so với nghiệm đúng là không vượt 23%. Nói chung giải
thuật FFD làm việc khá tốt, và khó tìm ra một ví dụ mà giải thuật này làm việc một
cách kém hiệu quả nhất. Ta thử đưa ra một trường hợp xấu như vậy. Cho > 0 khá
nhỏ. Giả sử các thùng đều có kích thước 1, và cần xếp các sản phẩm có kích thước
tương ứng sau :
* 6 sản phẩm kích thước 1/2 +
* 6 sản phẩm kích thước 1/4 +
Cấu trúc dữ liệu – trang 62
* 12 sản phẩm kích thước 1/4 - 2
* 6 sản phẩm kích thước 1/4 + 2
Nếu sắp xếp các sản phẩm theo kích thước giảm dần ta được:
* 6 sản phẩm kích thước 1/2 +
* 6 sản phẩm kích thước 1/4 + 2
* 6 sản phẩm kích thước 1/4 +
* 12 sản phẩm kích thước 1/4 - 2
Cách xếp tối ưu là dùng 9 thùng vì cả 9 thùng đều đầy (hình 6.25 (a)) và theo giải
thuật FFD là 11 thùng (hình 6.25 (b)).
L =
1 9
(7,9,7,1,6,2,4,3)
7 NEF=3
7
Nhưng bỏ sản phẩm kích thước 1, L' = (7, 9, 7, 6, 2, 4, 3) thì : NFF = 4 thùng.
Dư Dư
2 Dư
2 6
6
2 10
4
L' =
7 7
7 (7,9,7,6,2,4,3)
9
NEF=4
3
Nguyên lý của giải thuật là xây dựng vector nghiệm dần từng bước. Bắt đầu từ
vector rỗng. Thành phần đầu tiên x1 được chọn từ tập S1=A1
Giả sử đã có được nghiệm một phần (x 1, x2, . . . , xi-1). Từ các thành phần x1,
x2, . . . , xi-1 ta có thể xác định được tập S i các giá trị có thể chọn làm thành phần x i , với
Si là tập con của tập Ai
Chọn một thành phần xi từ tập Si ta mở rộng nghiệm một phần (x1, x2, . . . , xi-1)
để được nghiệm một phần (x1, x2, . . . , xi-1, xi). Nếu không chọn được thành phần xi
(khi Si là rỗng) thì ta quay lui chọn một thành phần xi-1 khác của Si-1
Lược đồ tổng quát của kỹ thuật Quay lui có thể được biểu diễn như sau:
Void Quaylui()
{S1=A1;
i=1;
while (i>=1)
{ while ( Si != ∅)
{chọn x ∈ S S = S \ {x }
i i i i i
{ x[i] = x[i]+1;
ok=0;
while ( (x[i]<=n) && (!ok) )
{ j=1;
ok=1;
while ( (j<=i-1) && ok )
{ if ( (x[i]!=x[j]) && (i-j) != (x[j]-x[i])
& (i-j) != (x[i]-x[j]) )
j++;
else ok=0;
}
if (!ok) x[i] = x[i]+1;
}
if (ok)
{ if (i==n)
{ d++; printf("\n Nghiem %2d :", d);
for ( t=1 ; t<=n ; t++)
printf("%6d", x[t]);
if (d%20==0) { printf("\n"); getch(); }
}
else { i++;
x[i]=0;
}
}
else i--; // quay lui
}
Cấu trúc dữ liệu – trang 68
}
main()
{ tamhau();
getch();
}
7.2.2. Bài toán: Các tập con có tổng cho trước:
Cho trước tập A gồm n số thực dương và cho trước M là một số thực dương.
Hãy tìm tất cả các tập con các số trong A sao cho tổng của chúng bằng M.
Để giải quyết bài toán này, ta biểu diễn tập A dưới dạng mảng (a1, a2, . . . an). Ta
cần tìm dãy con (ax1, ax2, . . . , axi) gồm i phần tử , với 1 x1 < x2 < . . . < xi n sao
cho ax1+ax2+ . . . + axi=M . Như vậy, nghiệm của bài toán là dãy (x1 , x2 , . . . , xi) sao
cho 1
x1 < x2 < . . . < xi n và ax1 + ax2 + . . . + axi = M.
Đương nhiên có thể chọn x1 là một trong các chỉ số 1, 2, . . . , n mà a x1 M . Khi
đã chọn được x1 , x2 , . . . , xi và S = ax1+ax2+ . . . + axi < M thì xi+1 có thể chọn là một
trong các chỉ số từ xi+1 tới n mà S + axi+1 M
Trong hàm con dưới đây, ta sử dụng mảng A[1 . . n] để lưu các số thực dương
thuộc tập đã cho. Mảng x[1..n] lưu chỉ số các thành phần thuộc tập con cần tìm. Biến S
lưu tổng các số của tập con trong quá trình hình thành.
int n, j, i, x[20];
float S, M, A[20];
void tongcon()
{ i=1; x[i]=0;
S=0; while
(i>0)
{ x[i]=x[i]+1;
if (x[i]<=n)
{ if (S+A[x[i]]<=M)
{ if (S+A[x[i]]==M)
{ for (j=1; j<=i; j++)
printf("%7.0f",
A[x[j]]);
printf("\n");
}
else
Cấu trúc dữ liệu – trang 69
{ S=S+A[x[i]];
x[i+1]=x[i];
i++;
}
}
}
else
{ i--;// quay lui
S=S-A[x[i]];
}
}
}
main()
{ n=5;
A[1]=50; A[2]=17; A[3]=73; A[4]=40; A[5]=33;
M=90;
tongcon();
getch();
}
Khi thực hiện sẽ có 3 nghiệm là:
50 40 1
x =(x1 , x2)=(1,4)
17 73 2
x =(x1 , x2)=(2,3)
17 40 33 3
x =(x1, x2, x3)=(2, 4, 5)
7.2.3. Bài toán Hoán vị:
Một hoán vị là một dãy có thứ tự gồm n thành phần khác nhau của tập hợp gồm
n số {1, 2, 3, . . . , n}.
Ví dụ với n=3 thì tập là {1, 2, 3} , thì có tất cả 1*2*3 = 6 hoán vị là:
123
132
213
231
312
Cấu trúc dữ liệu – trang 70
321
Có thứ tự (12 khác với 21) + Các thành phần không bắt buộc khác nhau:
111,112,113,121,122,123,131,132,133,211,212,213,221,…
Không có thứ tự + Các thành phần phải khác nhau:
123
Trong 3 vật A,B,C (đt,máy tính,máy ảnh) . Tôi cho anh lấy 2 vật:
AB,AC,BC
Không có thứ tự (12 cũng là 21) + các thành phần không bắt buộc khác nhau:
111 , 112 , 113 , 121
Viết chương trình nhập số nguyên dương n, rồi in ra tất cả các n hoán vị.
int s[1000]; int n;
int trienvong(int k)
{ int i;
for (i=1; i<=k-1; i++)
if (s[k]==s[i]) return 0;
return 1;
}
void hoanvi(int k)
{ int i;
if (k-1==n)
{ for (i=1; i<=n; i++)
printf("%d", s[i]);
printf("\n");
}
else for (i=1; i<=n; i++)
{ s[k]=i;
if (trienvong(k)) hoanvi(k+1);
}
}
---o-O-o--
KIỂM TRA GIỮA KỲ (02/08/2021-08/08/2021): 05/08/2021
- Thi viết.
Cấu trúc dữ liệu – trang 71
- Không dùng tài liệu.
- Nội dung:
o Viết chương trình.
o Rồi tính độ phức tạp.
o Giải thuật Đệ quy.
o Giải thuật Chia để trị.
ĐIỂM BÀI TẬP:
Điểm kiểm tra giữa kỳ + 1
THI CUỐI KỲ (30/08/2021-05/09/2021):
- Thi viết.
- Không dùng tài liệu.
- Nội dung:
o Độ phức tạp.
o Giải thuật Đệ quy.
o Giải thuật Chia để trị.
o Giải thuật Quy hoạch động.
o Giải thuật Tham lam.
o Giải thuật Quay lui.
CÁCH TÍNH ĐIỂM HỌC PHẦN:
= Điểm giữa kỳ * 0.2 + Điểm bài tập * 0.2 + Điểm cuối kỳ * 0.6
Thầy: Phan Chí Tùng , phanchitung@gmail.com , 0989.078.034
ế > 100
3
Hãy chỉ ra từng cặp i, j khác nhau mà fi(n) là 0 (fj (n)) và fi (n) là (fj (n)).
8. Xét các hàm của n sau :
2
1( ) = { 3 ớ ℎẵ ≥ 0
ớ ẻ≥1
( )={ ớ 0≤ ≤100
2
3
ớ > 100
2.5
3( )=
Hãy chỉ ra từng cặp i, j khác nhau để gi (n) là 0 (gi(n)) và gi(n) là (gj(n)).
9. Hãy sử dụng ký hiệu "0 lớn" để xác định thời gian chạy của các thủ tục sau đây
như một hàm của n trong trường hợp xấu nhất :
a. Procedure matmpy (n : integer);
Var i, j, k : integer :
begin
for i : = 1 to n do
for j : = 1 to n do
begin
C [i,j] : = 0;
for k : = 1 to n to
C [i,j] : = C[i,j] + A[i,K] * B [k,j]
end
end;
b. Proceduremystery (n: integer);
Vari, j, k : integer;
begin
for i:=1 to n - 1 do
for j : = i + 1 to n do
for k : = 1 to j do
{phát biểu nào đó cần 0 (1) thời gian}
Cấu trúc dữ liệu – trang 74
end;
c. Procedure vervodd (n: integer);
Var x, i, j, y : integer;
Begin
for i ; = 1 to n do
if odd(f) then
begin
for j : = 1 to n do x : = x + 1;
for j : = 1 to i do y : = y + 1;
end;
end;
D. Function recursive (n : integer). Integer;
Begin
If n < = 1 then return (1)
else
return (recursive (n-1) + recursive (n-1))
end;
10. Chứng minh các kết luận sau là đúng :
a. 17 là 0 (1)
2
b. n (n-1) /2 là 0 (n );
3 2 3
c. max (n .10n ) là 0 (n );
n
k+1 k+1
d. i k là 0 (n ) và (n ) với k - nguyên.
i1
k k
e. Nếu p(x) là đa thức bậc K với các hệ số dương thì p(n) là 0 (n ) và (n ).
11. Giả sử T1 (n) là (f(n)). Và T2 (n) là (g(n)). Kết luận nào sau đây là
đúng?
a. T1 (n) + T2 (n) là (max (f(n), g(n))).
b. T1 (n) T2(n) là (f(n), g(n)).
12. Một số tác giả định nghĩa ômega lớn () như sau :
f(n) là (g(n)) nếu n0 và c > 0 nào đó sao cho n n0 ta có f(n) c g(n).
a. Định nghĩa f(n) là (g(n)) nếu và chỉ nếu g(n) là ( ) (f(n)) có đúng không?
b. Định nghĩa (a) có đúng với định nghĩa trong mục 1.4 không?
c. Bài tập 12 a) hoặc b) có đúng với định nghĩa không?
Cấu trúc dữ liệu – trang 75
13. Sắp xếp các hàm sau đây theo tỉ lệ phát triển :
a) n b) c) logn 2
n
d) loglogn e) log n f) n/logn h)
g) n log2 n n n
(1/3) i) (3/2) j) 17
14. Giả sử thông số n trong thủ tục sau là lũy thừa dương của 2 nghĩa là n =
2,4,8,16... Hãy lập công thức biểu diễn giá trị của biến coum qua giá trị của n khi thủ
tục kết thúc.
Procedure mystery (n : integer);
Var x, count : integer ;
begin
count : = 0;
x : = 2;
While x < n do
Begin
x : = 2 * x;
count : = count + 1
end;
writeln (count)
end;
15. Cho hàm max (i, n) là hàm trả phần tử lớn nhất từ vị trí i đến i + n - i của
mảng nguyên (integer array) A. Để cho tiện ta có thể giả thiết n là lũy thừa 2.
a.T(n) = 3T (n/2) + n.
b. T(n) = 3T (n/2) + n2
c.T(n) = 8T (n/2) + n3
3. Giải các phương trình truy hồi sau đây với
T(1) = 1 và T(n) thỏa khi n 2.
a. T(n) = 4T (n/3) + n.
2
b. T(n) = 4T (n/3) + n
3
c. T(n) = 9T (n/3) + n
4. Tìm cận 0 lớn và của T(n) xác định bởi các phương trình truy hồi sau. Giả
thiết T(1) = 1
a. T(n) = T (n/2) + 1
Cấu trúc dữ liệu – trang 80
b. T(n) = 2T (n/2) + logn
c. T(n) = 2T (n/2) + n
2
d. T(n) = 2T (n/2) + n .
5. Giải phương trình truy hồi :
a. T (1) = 2
T(n) = 2T(n-1) + 1 khi n 2
b. T (1) = 1
T(n) = 2T(n-1) + n khi n 2
6. Giải bài tập 5 bằng phương pháp thay thế.
7. Tổng quát hóa bài 6 bằng cách giải tất cả các dạng truy hồi
T(1) = 1
T (n) = aT(n-1) + d(n) n 1
Theo a và d (n).
n
8. Giả thíêt trong bài 7, d(n) = c với c 1 nào đó. Khi đó T(n) phụ thuộc vào a
và c như thế nào? Tìm dạng T(n)?
9. Giải phương trình sau để tìm dạng T(n).
T(n) = 1
T(n) = n T( n ) n khi n 2
(Hướng dẫn : xem cách giải trong cuốn [3]) (trang 75)
i 0 i 0 i 0 i i
0
11. Chứng minh rằng số thứ tự khác nhau trong việc nhân n ma trận. được cho i
phương trình truy hồi.
T(1)=1
n1
T(n) T(I) T n 1
i1
Chứng minh : 1 2n (gọi là số calatan)
T(n 1)
n n
1
n
a. Hãy xây dựng hàm đệ qui tính
m
b. Tính thời gian chạy trong trường hợp xấu nhất là hàm của n?
n
c. Hãy viết Thuật toán qui hoạch động để tính
m
Gợi ý : Thuật toán xây dựng bảng như dạng tam giác Pascal.
d. Tính thời gian chạy của Thuật toán của bạn ở c) như hàm của n.
n
2. Cách tính khác là tính (n) (n-1) (n-2)...(n-m+1)/(1)(2)...(m).
m
Tính thời gian chạy trong trường hợp xấu nhất như một hàm của n?
2
3. Thuật toán ở hình 6.9 tốn bộ nhớ 0 (n ). Hãy viết lại Thuật toán này sao cho
chỉ tốn bộ nhớ 0(n).
4. Xây dựng cây tìm kiếm nhị phân tối ưu nếu cho các nút từ c1 đến c3 tương ứng
xác suất như sau :
Ci 1 2 3 4 5
Pi 0.50 0.05 0.08 0.45 0.12
---o-O-o---
Cấu trúc dữ liệu – trang 82
TÀI LIỆU THAM KHẢO
[1] Data structures and Algoritluns (1983)
Alfred V. Ano - John E.Hoperofi - Jeffrey D. Uliman.
[2] The design and analysis of computer Algorthms (cùng tác giả [1]) (1976)
[3] Algoritthmies - Theory and Practice. Gilles Brassard, Paul Braley
[4] Introduction to the design and analyssis of
Algorithans S.E Goodman. S.T.
Hedetniemi (1997).
---o-O-o---
Cấu trúc dữ liệu – trang 83