You are on page 1of 40

THIẾT KẾ

THUẬT TOÁN 1
Một số kỹ thuật thiết kế

1. Kỹ thuật thiết kế tuần tự


2. Kỹ thuật thiết kế theo cấu trúc
3. Kỹ thuật đệ quy
4. Kỹ thuật tham lam
5. Kỹ thuật chia để trị
6. Kỹ thuật liệt kê
7. Lựa chọn cấu trúc dữ liệu thích hợp
8. Bài tập
Một số kỹ thuật thiết kế

1. Kỹ thuật thiết kế tuần tự


2. Kỹ thuật thiết kế theo cấu trúc
3. Kỹ thuật đệ quy
4. Kỹ thuật tham lam
5. Kỹ thuật chia để trị
6. Kỹ thuật liệt kê
7. Lựa chọn cấu trúc dữ liệu thích hợp
8. Bài tập
1. Kỹ thuật thiết kế tuần tự (1)

Ý tưởng:
Thực hiện các công việc một cách tuần tự
cho đến khi kết thúc (ra kết quả).

Phù hợp các bài toán nhỏ, đơn giản.


Còn gọi là thiết kế tuyến tính

4
1. Kỹ thuật thiết kế tuần tự (2)

Ví dụ 2.1:
Cho số nguyên a có không quá 100 chữ số và
số nguyên b, 1  b  9.
Tính c = ab.

5
1. Kỹ thuật thiết kế tuần tự (3)

Minh họa b = 6 với a = 6789:


Bước 1:
b= 6
a= 6 7 8 9
c= 4
nhớ 5 0

6 9 = 54 + 0 = 54

6
1. Kỹ thuật thiết kế tuần tự (4)

Bước 2:
b= 6

a= 6 7 8 9
c= 3 4
nhớ 5 5 0

6 8 = 48 + 5 = 53

7
1. Kỹ thuật thiết kế tuần tự (5)

Bước 3:
b= 6
a= 6 7 8 9
c= 7 3 4
nhớ 4 5 5 0

6 7 = 42 + 5 = 47

8
1. Kỹ thuật thiết kế tuần tự (6)

Bước 4:
b= 6
a= 6 7 8 9
c= 0 7 3 4
nhớ 4 4 5 5 0

6 7 = 42 + 5 = 47

9
1. Kỹ thuật thiết kế tuần tự (7)

Bước 5:
b= 6
a= 6 7 8 9
c= 4 0 7 3 4
nhớ 4 4 5 5 0

4+0=4
Kết quả:
c = 40734

10
1. Kỹ thuật thiết kế tuần tự (8)

Thuật toán:
input: a = (a[n], ...., a[1])  N, n ≤ 100; b {0, 1, ..., 9}
output: c=ab

1) nhớ = 0;
2) Nhân tuần tự từ a[1] đến a[n], mỗi lần cộng với nhớ
cho kết quả tg. Mỗi lần nhân, phần dư (tg mod 10)
đưa vào c, thương tg/10 đưa vào nhớ cho lần sau.
3) Cuối cùng, nếu nhớ > 0 thêm nhớ vào chữ số thứ
n+1 cho c
4) output c = (c[n+1] c[n], ...c[1]);

11
1. Kỹ thuật thiết kế tuần tự (9)

Thuật toán:
input: a = (a[n], ...., a[1])  N, n ≤ 100; b {0, 1, ..., 9}
output: c=ab

1) nhớ = 0;
2) for (i = 1; i ≤ n; i++)
a) tg = b*a[i] + nhớ;
b) c[i] = tg % 10;
c) nhớ = tg/10;
3) if (nhớ > 0)
{n++; c[n] = nhớ;}
4) output c = (c[n]...c[1]);

12
1. Kỹ thuật thiết kế tuần tự (10)

1) nhớ = 0;
2) for (i = 1; i ≤ n; i++)
a) tg = b*a[i] + nhớ;
b) c[i] = tg % 10;
c) nhớ = tg/10;
3) if (nhớ > 0)
{n++; c[n] = nhớ;}
4) output c = (c[n]...c[1]);

Độ phức tạp của thuật toán:


Các bước 1), 3), 4) đều có độ phức tạp O(1),
bước 2) có độ phức tạp O(n).
Tổng hợp: độ phức tạp của thuật toán là O(n).

13
1. Kỹ thuật thiết kế tuần tự (11)

Ví dụ 2.2:
Cho dãy X có n số nguyên X = {X1, X2, ..., Xn}. Tìm
dãy con A không giảm dài nhất gồm các phần tử liên
tiếp của X.
Ví dụ, dãy X có 8 phần tử
X = { 3, 2, 4, 6, 9, 12, 1, 4 }
thì dãy con liên tiếp không giảm dài nhất là dãy con
A = {2, 4, 6, 9, 12}
gồm các phần tử X2, X3, X4, X5, X6.

14
1. Kỹ thuật thiết kế tuần tự (12)

Ý tưởng:
1. kí hiệu đoạn không giảm dài nhất ban đầu là {Xio,...,Xjo}, io=1, jo = 1.
2. xét từ i =1
3. while (i ≤ n)
a) Tìm đoạn không giảm liên tiếp dài nhất kể từ i, kí hiệu chỉ số
cuối là j, nghĩa là Xj-1 ≤ Xj, Xj > Xj+1
b) Nếu dài hơn đoạn (io, jo) thì lưu lại đoạn mới (io, jo) = (i,j);
c) Đặt lại i = j+1;

4. output cặp chỉ số đầu cuối (io, jo) và đoạn {Xio, ..., Xjo}

15
1. Kỹ thuật thiết kế tuần tự (13)

Chi tiết:
input: X1, X2, ..., Xn; Xi R
output: {io, jo} sao cho Xio ≤ ... ≤ Xjo, jo-io lớn nhất

1) io=1; jo = 1;
2) i = 1;
3) while (i ≤ n)
a) while (Xj ≤ Xj+1) j++;
b) if (j-i > jo – io) {io = i; jo = j;}
c) i = j+1; j = I;
4) output io, jo;
Độ phức tạp của thuật toán là O(n).

16
Một số kỹ thuật thiết kế

1. Kỹ thuật thiết kế tuần tự


2. Kỹ thuật thiết kế theo cấu trúc
3. Kỹ thuật đệ quy
4. Kỹ thuật tham lam
5. Kỹ thuật chia để trị
6. Kỹ thuật liệt kê
7. Lựa chọn cấu trúc dữ liệu thích hợp
8. Bài tập
2. Thiết kế theo cấu trúc (1)

Ý tưởng:
Phân chia lời giải bài toán thành các nhiệm vụ có kích thước dữ
liệu nhỏ hơn hoặc có thiết kế đơn giản hơn nhằm
 dễ kiểm tra hoặc
 sử dụng lại các mô-đun sẵn có.

Nếu các nhiệm vụ đó là mới thì thiết kế các nhiệm vụ đó. Trong
trường hợp các nhiệm vụ này là phức tạp thì có thể phân chia
tiếp.
Còn gọi là kiểu thiết kế top – down: từ ý tưởng đến chi tiết.

18
2. Thiết kế theo cấu trúc (2)

Ví dụ 2.4:
Cho 2 số nguyên a và b có không quá 100 chữ số. Tính c = ab.

input: a1a2...am và b1b2...bn;


output: c1c2...ck = ab;
1) c = 0;
2) for (i=1; im; i++)
a) tg = nhân_1(ai, b);
b) tg = nhân_2(tg, 10i-1);
c) c = cộng(c, tg);
3) output c;

19
2. Thiết kế theo cấu trúc (3)
input: a1a2...am và b1b2...bn;
output: c1c2...ck = ab;
1) c = 0;
2) for (i=1; im; i++)
a) tg = nhân_1(ai, b);
b) tg = nhân_2(tg, 10i-1);
c) c = cộng(c, tg);
3) output c;

Thiết kế chi tiết các nhiệm vụ:


Nhân một số nguyên lớn b với số nguyên có 1 chữ số ai:
nhân_1(ai, b)
Nhiệm vụ này đã được thiết kế chi tiết trong ví dụ 2.1

20
2. Thiết kế theo cấu trúc (4)
input: a1a2...am và b1b2...bn; Thiết kế chi tiết các nhiệm vụ:
output: c1c2...ck = ab; Nhân một số nguyên lớn tg với 10t:
1) c = 0; nhân_2(tg, 10t );
2) for (i=1; im; i++) Chữ số hàng đơn vị lưu trữ ở vị trí 1 của
a) tg = nhân_1(ai, b); mảng, chữ số hàng cao hơn được lưu trữ
b) tg = nhân_2(tg, 10i-1); ở các vị trí cao hơn của mảng.
c) c = cộng(c, tg);
Vì vậy, nhân với 10t thực chất là thêm t chữ số
3) output c; 0 vào phía chỉ số thấp.
Như vậy, phải kéo các chữ số ở hàng cao hơn
về phía sau t vị trí, sau đó thêm t chữ số 0
vào các vị trí từ 1 đến t.
Kí hiệu n là chữ số ở hàng cao nhất của 2 4 5 1 7
tg;
t=3
n = n + t; 2 4 5 1 7
for (i = n; i > t; i--) tg[i] = tg[i-t];
0 0 0 2 4 5 1 7
for (i = 1; i ≤ t; i++) tg[i] = 0;
21
2. Thiết kế theo cấu trúc (5)
input: a1a2...am và b1b2...bn;
output: c1c2...ck = ab;
1) c = 0;
2) for (i=1; im; i++)
a) tg = nhân_1(ai, b); Cộng 2 số nguyên lớn c và tg: cộng(c, tg)
b) tg = nhân_2(tg, 10i-1);
m = length (c);
c) c = cộng(c, tg);
n = lenght(tg);
3) output c; k = max { m, n);

1) Thêm các số 0 vô nghĩa vào sau c, tg để có đủ k phần


tử.
2) nhớ = 0;
3) for (i = 1; i ≤ k; i++)
a) s = c[i] + tg[i] + nhớ;
b) c[i] = s mod 10;
c) nhớ = s/10;
4) if (nhớ >0)
k ++; c[k] = nhớ;
5) output c[k, k-1, ..., 2, 1] 22
2. Thiết kế theo cấu trúc (6)
input: a1a2...am và b1b2...bn;
output: c1c2...ck = ab;
Chi tiết:
1) c = 0;
input c, tg
2) for (i=1; im; i++) output c = c[k, ..., 1] = c + tg;
a) tg = nhân_1(ai, b);
b) tg = nhân_2(tg, 10i-1); 1) m = length (c);
c) c = cộng(c, tg); 2) n = lenght(tg);
3) k = m; if (n> m) k = n;
3) output c; while (m<k) {c[m+1] = 0; m++;}
while (n<k) {tg[n+1] = 0; n++;}
4) nhớ = 0;
Bài tập: 5) for (i = 1; i ≤ k; i++)
Chi tiết thuật toán a) s = c[i] + tg[i] + nhớ;
nhân hai số nguyên b) c[i] = s % 10;
lớn a và b (sinh viên tự c) nhớ = s/10;
thực hiện). 6) if (nhớ >0)
{ k ++; c[k] = nhớ; }
7) output c[k, ..., 1];

23
2. Thiết kế theo cấu trúc (7)
Bài tập:
1) Cho hàm f(x) liên tục trong khoảng [a, b] và có f(a)f(b)<0. Xây dựng
thuật toán không đệ quy giải phương trình f(x) = 0.
Phân tích:
Hàm f(x) liên tục sẽ lấp đầy giá trị các khỏang
[min f(x), max f(x)] và
[f(a), f(b)].
Nếu
f(a).f(b) <0,
do sự lấp đầy, suy ra
 ít nhất một xo (a,b) sao cho f(xo) = 0.
Điều này đảm bảo cho tính đúng đắn của phương pháp chia đôi.

24
2. Thiết kế theo cấu trúc (8)

Ý tưởng:
while ! stop
chia đôi [a,b] thành [a, g] và [g, b]
nếu [a, g] chắc chắn chứa nghiệm, đặt [a,b] = [a, g]
ngược lại, đặt [a,b] = [g, b]
chọn xo  [a,b];
output xo;

25
2. Thiết kế theo cấu trúc (9)

Chi tiết từng bước:


stop (b-a> )
chia đôi [a,b] thành [a, g] và [g, b] g = (a+b)/2
nếu [a, g] chắc chắn chứa nghiệm, f(a)f(g) < 0
đặt [a,b] = [g, b] b=g
ngược lại, đặt [a,b] = [g, b] a=g
chọn xo  [a,b]; x o = (a+b)/2
output xo;

26
2. Thiết kế theo cấu trúc (10)
while ! stop
chia đôi [a,b] thành [a, g] và [g, b]
nếu [a, g] chắc chắn chứa nghiệm, đặt [a,b] = [a, g]
ngược lại, đặt [a,b] = [g, b]
chọn xo  [a,b];
output xo; input f(x), a, b;
output xo: f(xo) = 0;

Chi tiết từng bước: while (b-a> )


stop (b-a> ) g = (a+b)/2;
chia đôi [a,b] thành [a, g] và [g, b] g = (a+b)/2 if (f(a)f(g) <0) b=
g;
nếu [a, g] chắc chắn chứa nghiệm,
f(a)f(g) < 0 else
a = g;
đặt [a,b] = [g, b] b=g
output a;
ngược lại, đặt [a,b] = [g, b] a=g
chọn xo  [a,b]; a, (a+b)/2, b

27
2. Thiết kế theo cấu trúc (11)
Bài tập:
2) Cho a>0, tính gần đúng giá trị của a với sai số  cho trước.
Phân tích:

Phương pháp 1:
Đặt f(x) = x2 – a.
f(xo) = 0  xo2 = a
 xo = a
Sử dụng chương trình giải phương trình trong ví dụ trước.

28
2. Thiết kế theo cấu trúc (11)
Cho hàm a>0, tính gần đúng giá trị của a với sai số  cho trước.

Phương pháp 2:
Dãy xn+1 = (xn + a/xn) /2 có giới hạn là a.

input a, ;
output a
x = 1;
while !stop
x = (x + a/x) /2;
output x;

29
2. Thiết kế theo cấu trúc (12)
Cho hàm a>0, tính gần đúng giá trị của a với sai số  cho
trước.
Dãy xn+1 = (xn + a/xn) /2 có giới hạn là a. input a, ;
output a
x = 1;
while !stop
điều kiện stop: x = (x + a/x) /2;
|xn - a | <  output x;
có thể thay bằng điều kiện:
|xn+1 – xn| < 

30
2. Thiết kế theo cấu trúc (13)
Cho hàm a>0, tính gần đúng giá trị của a với sai số  cho
trước.
Dãy xn+1 = (xn + a/xn) /2 có giới hạn là a.

điều kiện stop:


|xn - a | < 
có thể thay bằng điều kiện: input a, ;
|xn+1 – xn| <  output a
x = 1; y = x +2*;
while (|y-x| > )
y = x;
x = (y + a/y) /2;
output x;

31
2. Thiết kế theo cấu trúc (14)
Cho hàm a>0, tính gần đúng giá trị của a với sai số  cho
trước.
Dãy xn+1 = (xn + a/xn) /2, với xo>0 bất kỳ có giới hạn là a.

Tính đúng đắn


Xét cho trường hợp a>1. Trường hợp 0< a <1 được chứng minh tương tự.
Mệnh đề 1: Nếu a < xo thì a < .... xn < ... < x0
Chứng minh: (bằng phương pháp quy nạp)

Giả thiết a < xn xn+1 < xn


ta cần chứng minh a < xn+1  xn + a /xn < 2xn
Thật vậy,  a /xn < xn
a < xn+1  a < (xn + a/xn) /2  a < xn2
 0 < xn2 + a - 2xna  a < xn (đã có cm
 0 < (xn - a)2 trước)

Vậy {xn} bị chặn Vậy, {xn} đơn điệu giảm. Suy ra, {xn} có giới
hạn, kí hiệu là b. Cần chứng minh a =32 b2.
2. Thiết kế theo cấu trúc (15)
Cho hàm a>0, tính gần đúng giá trị của a với sai số  cho
trước.
Dãy xn+1 = (xn + a/xn) /2, với xo>0 bất kỳ có giới hạn là a.
Tính đúng đắn
Mệnh đề 2: lim xn = a
Chứng minh:
Do a < .... xn < ... < x0,
nghĩa là {xn} đơn điệu giảm. bị chặn. Suy ra {xn} có giới hạn, kí hiệu là b.
Cần chứng minh a = b2.
Lấy giới hạn 2 vế của
xn+1 = (xn + a/xn)/2
được
b = (b + a/b)/2
hay
2b = b + a/ b  b = a/b  b2 = a
33
2. Thiết kế theo cấu trúc (16)
Ví dụ 3.10.
Đồ thị G = {V, E} có n đỉnh, đánh số từ 1 đến n, được cho bởi danh sách cạnh
edge G[m]
struct edge
{ int d, c;
double ts;
}
Tính số màu và đưa ra phương án tô màu của đồ thị.

Lược đồ 1:
Số màu dự kiến = n;
Xét từng phương án (đề nghị)
a) Nếu là PA đúng, tính số màu;
b) Nếu số màu ít hơn số màu đã có thì lưu số màu và PA;

34
2. Thiết kế theo cấu trúc (17)
Ví dụ 3.10.
Đồ thị G = {V, E} có n đỉnh, đánh số từ 1 đến n, được cho bởi danh sách cạnh
edge G[m]
struct edge
{ int d, c;
double ts;
}
Tính số màu và đưa ra phương án tô màu của đồ thị.

Lược đồ 2:
Sh_màu = 1;
for (d = 1; d ≤ n; d++)
a) m = 1;
b) while (m ≤ sh_màu) & (! hợp_lệ(d, m) m++;
c) Màu (d) = m;
d) if (m > sh_màu) sh_màu ++;

35
2. Thiết kế theo cấu trúc (18)
Xét lược đồ 2:
Sh_màu = 1;
màu[1] = 1;
for (d = 2; d ≤ n; d++)
a) m = 1;
b) while (m ≤ sh_màu) & (! hợp_lệ(d, m) m++;
c) màu [d] = m;
d) if (m > sh_màu) sh_màu ++;

Sh_màu: dùng để đếm số màu đã dùng


Màu[d] = m; đỉnh d được tô màu m
hợp_lệ(d,m): tính hợp lệ khi tô màu m cho d –
là không gây ra 2 đỉnh liền kề nào có cùng
hợp_lệ = true;
for all x  DS_kề(d)
if (màu[x] = m) hợp_lệ = false;

36
2. Thiết kế theo cấu trúc (19)

Sh_màu: dùng để đếm số màu đã dùng


Màu[d] = m; đỉnh d được tô màu m
hợp_lệ(d,m): tính hợp lệ khi tô màu m cho d –
là không gây ra 2 đỉnh liền kề nào có cùng
hợp_lệ = true;
for all x  DS_kề(d)
if (màu[x] = m) hợp_lệ = false;

for (i = 1; i ≤ m; i++) i = 1;
if (d = G[i]->d & màu[G[i]->c] = m) or while màu[G[i]->d] 
(d = G[i]->c & màu[G[i]->d] = m) màu[G[i]->c] i++
hợp_lệ = false; hợp_lệ = i>m;

Chi phí ít hơn và đơn giản hơn.

37
2. Thiết kế theo cấu trúc (20)

Câu hỏi ôn tập và bài tập tự làm:


1) Từ là một dãy liên tiếp các chữ cái Latin. Cho xâu kí tự S gồm các từ. Xây dựng
thuật toán tìm từ dài nhất trong S. Đánh giá độ phức tạp của thuật toán xây
dựng được. [~ Tìm dãy con không giảm có nhiều phần tử liên tiếp dài nhất]
2) Một xâu gọi là xâu đối xứng nếu đem đảo ngược xâu đó ta lại nhận được xâu
ban đầu. Cho xâu S, hãy tìm số kí tự ít nhất cần thêm vào S để S trở thành xâu
đối xứng. [~Tìm xâu con chung dài nhất của 2 xâu - ]
3) Cho hai xâu A và B. Xâu S được gọi là bao A và B nếu A và B là các xâu con liên
tiếp của S. Tìm xâu bao ngắn nhất của A và B. [~ Tìm xâu con chung dài nhất sau
đó chèn ký tự không thuộc dãy con chung dài nhất để đảm bảo thứ tự của xâu
A và xâu B].
4) Mỗi đoạn thẳng trên trục Ox được mô tả bới hai giá trị [a, b]. Kí hiệu S là tập
hợp n đoạn thẳng S = { [ai,bi], i = 1,2,...,n}. Xây dựng thuật toán tìm tập S* có
nhiều phần tử nhất thuộc S sao cho các đoạn thẳng trong S* đôi một không có
38
điểm chung.
Câu hỏi ôn tập và bài tập tự làm:
4. Mỗi đoạn thẳng trên trục Ox được mô tả bới hai giá trị [a, b]. Kí hiệu S là tập hợp n
đoạn thẳng S = { [ai,bi], i = 1,2,...,n}. Xây dựng thuật toán tìm tập S* có nhiều phần tử
nhất thuộc S sao cho các đoạn thẳng trong S* đôi một không có điểm chung. [Interval
scheduling]
Sắp xếp các đoạn theo thứ tự tăng dần của điểm kết thúc (bi), ký hiệu tập kết quả là S
While (S!=rỗng )
{ - Chọn đoạn i đầu tiên
- Thêm đoạn i vào tập kết quả
- Loại bỏ khỏi S các đoạn có giao với i
}

You might also like