1.1. Mở đầu
Chia để trị là một kỹ thuật thiết kế thuật tốn bằng cách chia bài tốn đã cho thành một số bài tốn con hồn tồn tương tự nhưng với kích thước đầu vào nhỏ hơn, giải
quyết lần lượt và độc lập các bài tốn con này (cĩ thể áp dụng kỹ thuật này đối với các
bài tốn con), sau đĩ kết hợp các lời giải con để nhận được lời giải cho bài tốn ban đầu. Cĩ thể mơ tả một cách tổng quát kỹ thuật này như sau:
Function DQ(x);
{trả lại một lời giải với đầu vào x}
If x đủ nhỏ hoặc đơn giản then return ADHOC(x) Else
Tách x thành các đầu vào nhỏ hơn x1, …, xk; For i:=1 to k do yi:=DQ(xi);
Kết hợp các yi để thu được y Return y;
Trong đĩ ADHOC(x) là một thuật tốn cơ sở được dùng để giải quyết bài tốn với những đầu vào nhỏ.
thuật này được gọi là kỹ thuật đơn giản hố.
Để áp dụng được kỹ thuật chia để trị cần cĩ một số điều kiện: phải cĩ khả năng tách đầu vào thành những đầu vào nhỏ hơn và cĩ khả năng kết hợp các lời giải con lại một cách hiệu quả. Phải quyết định khi nào thì sử dụng thuật tốn cơ sở.
Các thuật tốn chia để trị, do cách tiếp cận, thường cĩ mơ tả tự nhiên dạng đệ qui.
1.2. Tìm nhị phân.
Bài tốn: Giả sử T[1..n] là mảng sắp thứ tự tăng và x là phần tử nào đĩ. Tìm vị trí để chèn x vào T sao cho T vẫn là mảng sắp thứ tự.
Cụ thể là ta muốn tìm chỉ số i sao cho 0 ≤ i ≤ n và T[i] < x ≤ T[i+1] với qui ước logic là T[0]=- ∞ và T[n+1]=+ ∞. Khi đĩ vị trí để chèn x vào T là i+1.
Theo cách tiếp cận chia để trị ta sẽ chia phạm vi tìm kiếm thành 2 phần và xác định sẽ tiếp tục tìm kiếm ở phần nào. Do tính chất sắp thứ tự tăng của mảng nên ta thấy:
- Nếu với chỉ số i nào đĩ mà T[i] < x thì với mọi chỉ số j < i ta cũng cĩ T[j] < x. Khi đĩ nếu k là vị trí chèn x thì k > i
- Nếu với chỉ số i nào đĩ mà T[i] ≥ x thì với mọi chỉ số j > i ta cũng cĩ T[j] ≥ x. Khi đĩ nếu k là vị trí chèn x thì k < i
Quá trình tìm kiếm sẽ được thực hiện bằng cách chia phạm vi đang tìm kiếm là [d,c] bởi vị trí g thành 2 phần cĩ kích thước gần bằng nhau.
Nếu như x > T[g] thì phạm vi tìm kiếm tiếp tục sẽ là [g,c]. Ngược lại, nếu x ≤ T[g] thì phạm vi tìm kiếm tiếp tục sẽ là [d,g]. Thuật tốn cĩ thể mơ tả như sau:
Procedure TimNP2(T,i,j,x);
g:=(i+j+1) div 2;
if x ≤ T[g] then TimNP2(T,i,g,x) else TimNP2(T,g,j,x);
Return vị trí
Lưu ý rằng trong thuật tốn này lời giải của bài tốn nhỏ cũng là lời giải của bài tốn ban đầu vì vậy ta khơng cần kết hợp các lời giải của các bài tốn con. Nếu như phần tử x cĩ trong mảng thì vị trí chèn x vào mảng tìm được theo thuật tốn này sẽ là vị trí xuất hiện đầu tiên của x trong mảng.
1.3. Sắp xếp trộn.
Giả sử T[1..n] là mảng, ta đã biết một số thuật tốn sơ cấp sắp xếp mảng với độ phức tạp là O(n2). Theo cách tiếp cận chia để trị ta sẽ chia T thành hai phần cĩ kích thước gần bằng nhau và sắp xếp các phần này bằng lời gọi đệ qui, sau đĩ trộn các kết quả, lưu ý bảo tồn thứ tự. Cơng việc chủ yếu chỉ là trộn 2 mảng sắp thứ tự thành 1 mảng sắp thứ
tự. Mơ tả thuật tốn như sau:
Procedure SXTron(T[i..j]); If j-i nhỏû then Insert(T) Else
U:= T[i..(i+j)div 2];V:= T[1+(i+j)div 2 ,,n] SXTron(U); SXTron(V);
Tron(T,U,V);
Trong đĩ Insert(T) là thuật tốn sắp xếp chèn đã biết, cịn Tron(T,U,V) là thủ tục trộn 2 mảng U, V đã sắp thứ tự thành mảng sắp thứ tự T.
Thuật tốn này phù hợp với mơ tả tổng quát của kỹ thuật chia để trị, trong đĩ thao tác Tron chính là việc kết hợp các lời giải của bài tốn con để được lời giải của bài tốn ban đầu.
1.4. Quicksort.
Khác với sắp xếp trộn, phần khơng đệ qui của thuật tốn này khơng kết hợp các lời giải con mà là đi xây dựng các bài tốn con.
Bước đầu tiên thuật tốn chọn một phần tử trong mảng làm mốc (pivot). Sau đĩ mảng được chia thành hai phần ở hai phía của mốc: các phần tử lớn hơn mốc được đưa sang phải và các phần tử khơng lớn hơn mốc được đưa sang trái mốc. Nếu bây giờ mỗi phần được sắp thứ tự nhờ lời gọi đệ qui thì tồn bộ mảng sẽ được sắp thứ tự mà khơng cần trộn.
Lưu ý rằng cĩ nhiều biến thể của thuật tốn QuickSort, chủ yếu khác nhau ở phương pháp chọn phần tử để đặt mốc. Ở đây ta sẽ chọn phần tử đầu tiên của mảng làm mốc.
.Mơ tả thuật tốn:
Procedure QuickSort(T[i..j]); If j-i nhỏ then insert(T[i..j]) Else
Datmoc(T[i..j],m);
{sau khi đặt mốc thì T[k] ≤ T[m] với i≤k<m và T[k] > T[m] với m<k≤j } QuickSort(T[i..m-1]); QuickSort(T[m+1..j]);
Trong đĩ thủ tục Datmoc sẽ chia mảng T thành 2 phần như mong muốn. Ta sẽ thực hiện điều này bằng cách duyệt mảng T đồng thời từ hai đầu. Dùng 2 biến k và h để duy trì 2 vị trí duyệt, khởi tạo k:=i và h:=j+1. Tăng k cho đến khi T[k] > p và giảm h cho tới khi T[h] ≤ p. Khi đĩ đổi chỗ T[k] và T[h]. Quá trình này cịn tiếp tục khi nào mà k<h. Cuối cùng đổi chỗ T[i] và T[h] để đặt mốc đúng chỗ.
Trong thuật tốn này ta đã chọn thuật tốn sắp xếp chèn làm thuật tốn sắp xếp cơ sở.
p:=T[i]; k:=i; h:=j+1; repeat k:=k+1 until (T[k]>p) or (j≤k); repeat h:=h-1 until T[h] ≤ p; while k<h do Đổi chỗ T[k] và T[h]; repeat k:=k+1 until T[k]>p; repeat h:=h-1 until T[h] ≤ p; Đổi chỗ T[i] và T[h];
Đánh giá độ phức tạp của thuật tốn QuickSort: Giả thiết mảng đã cho cĩ kích thước n = 2k và giả sử ở mỗi lần đặt mốc mảng được chia làm hai phần cĩ kích thước gần bằng nhau. Dễ thấy độ phức tạp của thủ tục Datmoc là n. Gọi thời gian chạy của thuật tốn QuickSort với mảng kích thước n là h(n).
Khi đĩ theo mơ tả của thuật tốn ta cĩ h(n) = 2h(n/2)+n
Ta cĩ h(n) = 2(2h(n/4)+n/2)+n = 4h(n/4) + 2n = ... = nh(1)+kn =kn Do đĩ ta cĩ h(n) = O(n.logn).
1.5. Tính luỹ thừa.
Nhập mơn về Mật mã: vì một lý do nào đĩ An và Bình muốn thiết lập một bí mật
chung. Họ chỉ cĩ thể giao tiếp với nhau qua điện thoại. Tuy nhiên họ biết chắc rằng mọi cuộc điện thoại của họ đều bị C nghe lén (ta nĩi An và Bình giao tiếp qua một kênh khơng an tồn).
Vấn đề đặt ra là làm thế nào An và Bình cĩ thể thiết lập được một bí mật chung mà C khơng thể biết được bí mật này mặc dù C cĩ thể biết hết nội dung mọi cuộc trao đổi giữa An và Bình? Nĩi khác đi là cĩ hay khơng một nghi thức để An và Bình cĩ thể cơng
khai tạo ra một khố bí mật an tồn. Một khố bí mật được gọi là an tồn nếu thời gian
Trong Mật mã học cĩ một yêu cầu khác liên quan tới việc tạo ra khố bí mật là việc tạo ra khố phải tương đối đơn giản và khơng mất nhiều thời gian.
Vấn đề này lần đầu tiên được Diffie và Hellman giải quyết vào năm 1976. Cách làm như sau: Đầu tiên An và Bình thoả thuận chọn một số nguyên p nào đĩ cĩ hàng trăm
chữ số và một số nguyên g, 2<g<p-1. Tất nhiên là C biết rõ hai số này. Tiếp theo An và
Bình, độc lập với nhau, mỗi người chọn ngẫu nhiên một số là A và B tương ứng.
Sau đĩ An tính số a=gA mod p và gửi số a cho Bình, tương tự Bình tính và gửi cho An số b=gB mod p. Cuối cùng An tính x= bA mod p và Bình tính y=aB mod p.
Dễ thấy x=y và khơng ai cĩ thể kiểm sốt trước được giá trị này. Đây cĩ thể coi là bí mật chung của An và Bình mà họ hy vọng là C sẽ khơng biết được.
Dễ dàng chứng minh được nếu A’ khác với A mà gA mod p = gA’ mod p thì bA’ mod p = bA mod p với b=gB mod p. Việc tính A’ từ p, g và a được gọi là vấn đề logarithm rời rạc.
Cĩ một thuật tốn hiển nhiên để tính A’ như sau:
Function Logrr(g,a,p); A:=0; x:=1;
Repeat
A:=A+1; x:=x*g;
Until (A=x mod p) or (A=p); Return A;
Thuật tốn trả lại giá trị 0 nếu khơng cĩ A sao cho a=gA mod p. Chẳng hạn khơng cĩ A sao cho 3=2A mod 7.
Tuy nhiên thuật tốn này địi hỏi một thời gian khơng chấp nhận được vì trung bình nĩ địi hỏi khoảng p/2 lần thực hiện vịng lặp. Nếu mỗi lần lặp được thực hiện trong 1
và b mà khơng phải tính logarithm rời rạc.
Vì vậy hiện nay C vẫn khơng thể biết được bí mật của An và Bình mặc dù người ta vẫn chưa thể chứng minh được điều này. Nĩi cách khác độ an tồn của mật mã khố cơng khai vẫn cịn được đảm bảo chừng nào người ta cịn chưa tìm ra được thuật tốn hiệu quả hơn để tính x từ p, g, a và b mà khơng phải tính logarithm rời rạc.
Nếu C sử dụng thuật tốn nĩi trên để tìm A thì An và Bình cũng khơng thể sử dụng thuật tốn tương tự như vậy để tính a, b, x và y. Chẳng hạn như thuật tốn sau
Function Lt1(g,A,p) a:=1;
For i:=1 to A do a:=a*g mod p; Return a;
May mắn là cĩ một cách khác hiệu quả hơn để tính luỹ thừa. Ta mơ tả ý tưởng này qua một ví dụ: x25 = (((x2x)2)2)2x. Như vậy x25 cĩ thể tính được nhờ 2 phép nhân và 4 phép bình phương. Cách tính này cĩ được vì x25=x24x, x24=(x12)2, v.v. Tổng quát ý tưởng này ta được một thuật tốn chia để trị như sau:
Function LtCdt(g,A,p); If A=0 then return 1 If A mod 2 = 1 then x:= LtCdt(g,A-1,p); Return (x*g mod p) Else x:= LtCdt(g,A div 2,p); Return (x*x mod p);
Thay vì đệ qui, thuật tốn trên cĩ thể đưa về dạng lặp như sau:
Function LtCdt2(g,A,p) n:=A; y:=g; x:=1;
While n>0 do
If n mod 2 =1 then x:=x*y mod p; y:= y*y mod p;
n:= n div 2; Return x;
Gọi h(A) là số phép nhân được thực hiện khi thực hiện thuật tốn trên, theo thuật tốn ta cĩ 0 khác chẵn A nếu lẻ A nếu 0 A nếu = + − + = ) 2 ( 1 ) 1 ( 1 0 ) ( div A h A h A h
Gọi s là độ dài của dãy nhị phân biểu diễn số A với A>1, khi đĩ dễ dàng chứng minh được s ≤ h(A) ≤ 2s. Như vậy với p, A, B cĩ 200 chữ số An chỉ phải thực hiện khơng quá 3000 phép nhân các số cĩ 200 chữ số và khơng quá 3000 phép nhân các số cĩ 400 chữ số. Điều này là hồn tồn cĩ thể chấp nhận được.
Ta minh họa qua một ví dụ: tính g35
Bước lặp 0 1 2 3 4 5 6
n 35 17 8 4 2 1 0
y g g2 g4 g8 g16 g32 g64
1.6. BÀI TẬP
1. Mơ tả thuật tốn trộn hai mảng sắp thứ tự thành một mảng sắp thứ tự. Đánh giá độ phức tạp của thuật tốn. Từ đĩ đánh giá độ phức tạp của thuật tốn sắp xếp trộn.
2. Mơ tả thuật tốn chèn một phần tử T vào một mảng A sắp thứ tự tăng cho trước sao cho mảng vẫn cịn sắp thứ tự.
3. Trong thuật tốn sắp xếp trộn, thay vì chia mảng T thành 2 phần, ta sẽ chia nĩ thành 3 phần. Hãy mơ tả thuật tốn tương ứng.
4. Dãy Fibbonacci. Xét ma trận F=10 11. Giả sử i, j là hai số nguyên. Cái gì là tích của vectơ (i,j) và ma trận F? Điều gì xảy ra nếu i, j là hai số Fibbonacci liên tiếp? Dùng kỹ thuật chia để trị tính số Fibbonacci thứ n.
Gợi ý: áp dụng kỹ thuật chia để trị để tính luỹ thừa.
5. Giả sử T[1..n] là mảng n phần tử. Dễ dàng tìm phần tử lớn nhất của T bằng cách thực hiện n-1 phép so sánh như sau
max:=T[1]; index:=1; For i:=2 to n do
If max < T[i] then
Max:=T[i]; index:=i;
Ở đây ta chỉ quan tâm tới số phép so sánh giữa các phần tử. Ta cĩ thể tiếp tục tìm phần tử nhỏ nhất của T bằng cách thực hiện n-2 phép so sánh nữa như sau
T[index]:=T[1]; min:=T[2]; For i:=3 to n do
If min > T[i] then min:=T[i];
Hãy đưa ra một thuật tốn cĩ thể tìm cả phần tử lớn nhất và phần tử nhỏ nhất của mảng gồm n phần tử bằng cách thực hiện ít hơn 2n-3 phép so sánh. Cĩ thể giả thiết n là luỹ thừa của 2
Giải quyết trường hợp n khơng là luỹ thừa của 2.
6. Giả sử T[1..n] là mảng sắp thứ tự tăng các số nguyên khác nhau, một vài số trong mảng cĩ thể là số âm.
Hãy đưa ra một thuật tốn tìm một chỉ số i sao cho 1≤ i≤ n và T[i] = i với giả thiết tồn tại một chỉ số như vậy.
7. Tìm cơng thức tường minh cho h(A) trong mục Tính luỹ thừa (xét với A được biểu điễn đưới dạng nhị phân).
8. Lịch thi đấu. Cần tổ chức cuộc thi đấu cĩ n người tham gia. Mỗi người tham gia thi đấu một lần với mỗi đối thủ. Hơn nữa mỗi đấu thủ phải thi đấu đúng một trận trong một ngày ngoại trừ là cĩ thể nghỉ thi đấu một ngày.
Nếu n là luỹ thừa của 2, hãy đưa ra một thuật tốn xây dựng một lịch thi đấu cho phép cuộc thi kết thúc sau n-1 ngày
9. *Với mỗi số nguyên n >1 hãy đưa ra thuật tốn xây dựng một lịch thi đấu cho phép cuộc thi kết thúc sau n-1 ngày nếu n chẵn hoặc sau n ngày nếu n lẻ. Chẳng hạn với n=5 và n=6 ta cĩ lịch như sau n=5 Người Ngày 1 2 3 4 5 n=6 Người Ngày 1 2 3 4 5 6 1 2 1 - 5 4 1 2 1 6 5 4 3 2 3 5 1 - 2 2 3 5 1 6 2 4 3 4 3 2 1 - 3 4 3 2 1 6 5 4 5 - 4 3 1 4 5 6 4 3 1 2 5 - 4 5 3 2 5 6 4 5 2 3 1
10.Phần tử đa số. Giả sử T[1..n] là mảng n phần tử. Một phần tử x được gọi là phần tử đa số trong T nếu tập hợp {i | T[i]=x } cĩ nhiều hơn n/2 phần tử. Hãy đưa ra một thuật tốn chia để trị xác định xem mảng T cĩ phần tử đa số hay khơng và tìm phần tử đĩ nếu nĩ tồn tại.