Tải bản đầy đủ (.doc) (7 trang)

Bài toán Fibo Môn Tin học

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (107.95 KB, 7 trang )

Tìm hiểu về dãy số Fibonacci

Võ Công Chương
Qua tìm hiểu và tổng hợp các nghiên cứu của các tác giả về dãy số Fibonacci từ internet,
tôi thấy có nhiều thông tin mà theo tôi là hấp dẫn như sự ra đời của số Fibonacci, nhà
toán học Fibonacci là ai, các giải thuật để tính số Fibonacci. Tôi xin chia sẻ cùng bạn
đọc.
1. Định nghĩa dãy số Fibonacci
Trong Toán học, các số Fibonacci là một dãy số, kí hiệu F(n) (n là số nguyên không âm),
được định nghĩa một cách đệ quy theo công thức sau:

Dãy bắt đầu từ hai số F(0)=0 và F(1)=1. Như vậy, các số đầu tiên của dãy Fibonacci là:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765.
2. Sự ra đời của số Fibonacci
Xuất phát từ bài toán về sự gia tăng số lượng của đàn thỏ, gọi là bài toán đàn thỏ: Bắt đầu
từ một cặp thỏ con, sau 12 tháng, ta sẽ có bao nhiêu cặp thỏ trong đàn nếu giả sử:
- Tháng đầu tiên, chỉ có một cặp thỏ vừa mới sinh (nói vui là thỏ Adam và Eve),
- Các cặp thỏ vừa mới sinh trở thành các cặp có khả năng sinh sản sau hai tháng,
- Mỗi tháng, mọi cặp thỏ trên hai tháng tuổi đều sinh ra một cặp mới, và - Những con thỏ
không bao giờ chết.
Điều giả sử cuối cùng là hơi vô lí nhưng nó đã làm cho bài toán trở nên đơn giản hơn, có
một số tài liệu đã thay đổi giả sử này thành 'các cặp thỏ sẽ chết sau k tháng nào đó',
nhưng bản chất của bài toán cơ bản là không thay đổi nhiều. Một số giả sử khác là hiển
nhiên nên không nêu ra như thỏ không chạy trốn khỏi đàn và không bị vô sinh.
Theo giả sử, mỗi cặp thỏ trên hai tháng tuổi đều cho ra đời một cặp thỏ con mỗi tháng,
điều đó có nghĩa rằng, tất cả 'á cặp thỏ đang sống ở tháng thứ n sẽ cho ra đời 'á cặp thỏ
con khác ở tháng thứ n+2. Điều đó lí giải tại sao ta tính số cặp thỏ ở tháng thứ n+2 chính
là số cặp thỏ ở tháng thứ n+1 (có 'b' cặp) cộng thêm số cặp thỏ ở tháng thứ n (có 'á cặp).
Ta biểu diễn số cặp thỏ trong đàn như một hàm theo thời gian, F(n) (n là số tháng):
F(1) = 1 - ta bắt đầu với một cặp thỏ mới sinh,
F(2) = 1 - chúng còn nhỏ nên chưa thể có con sau một tháng,


F(3) = 2 - đến tháng thứ ba, chúng sinh được một cặp thỏ con.
F(4) = 3 - đến tháng thứ tư, chúng lại sinh một cặp thỏ con nữa,
F(5) = 5 - đến tháng này, ngoài cặp thỏ con, chúng có thêm một cặp thỏ cháu nữa.
Tổng quát, F(n) = F(n - 1) + F(n - 2), tức là tất cả các cặp thỏ sống ở tháng trước vẫn còn
là F(n - 1) cộng thêm các cặp thỏ con của mỗi cặp thỏ sống ở tháng thứ n-2, tức F(n - 2).
Rõ ràng, công thức tính F(n) ở trên áp dụng cho bài toán đàn thỏ. Câu trả lời của bài toán
trên đã tạo ra dãy số: 1, 2, 3, 5, 8, 13, 21... mà sau này ta gọi là dãy số Fibonacci.


Đến đây, ta sẽ đặt ra câu hỏi là tại sao dãy số lại có tên là dãy Fibonacci?
3. Leonardo là người phát minh ra dãy số Fibonacci?
Theo giáo sư D.E.Knuth (trường đại học Stanford, )
dẫn chứng trong tập sách nổi tiếng The Art of Computer Programming, Volume 1,
Fundamental Algorithms , dãy số này lần đầu tiên được tranh luận bởi các nhà Toán học
ấn Độ, trong đó có Gopala và Hemachandra, những người đã mô tả một cách chính xác
dãy số 1, 2, 3, 5, 8, 13, 21... Còn ở Tây Âu, dãy số này lần đầu tiên được nghiên cứu một
cách đầy đủ bởi nhà Toán học Leonardo of Pisa , người có biệt danh là Fibonacci, và
cũng từ đó mà dãy số có tên là dãy Fibonacci.
Trong cuốn sách Liber Abaci, Fibonacci chủ yếu nghiên cứu về các con số (dạng hình
tượng) và việc tính toán số học của người ấn Độ đang được sử dụng ở nhiều quốc gia
khắp Địa Trung Hải. Vì vậy, bài toán về đàn thỏ được trình bày trong sách hoàn toàn chỉ
đơn thuần là một liên hệ của ông chứ không phải là một phát minh về bài toán hay về dãy
số Fibonacci.
4. Các giải thuật tính số Fibonacci
4.1. Giải thuật 1: Đệ quy nhị phân
Từ định nghĩa về số Fibonacci, một cách tự nhiên, ta có hàm Fib(n) để tính số Fibonacci
thứ n trong dãy như sau:
Function Fib(n);
Begin
If (n=1) or (n=0) then Fib:=n

Else Fib:=Fib(n-1)+Fib(n-2);
End;
Giải thuật đệ quy này rõ ràng là quá chậm, bởi lẽ nó sẽ tính toán lặp đi lặp lại nhiều lần
cùng một giá trị. Độ phức tạp về thời gian của giải thuật là theo hàm số mũ, cụ thể là
O(2n). Với n = 45, nó cần trên một tỉ bước tính. Vì thế mà giải thuật này thường chỉ được
dùng trong mục đích giảng dạy, đặc biệt là để minh họa cho việc phân tích hiệu quả của
một giải thuật. Nó cũng là một cách để đánh giá hiệu suất của một chương trình dịch.
4.2. Giải thuật 2: Độ phức tạp tuyến tính
Giải thuật này tính toán mỗi F(n) chỉ một lần bằng việc lưu giữ lại hai số Fibonacci kế
trước tại mỗi lần tính. Các giải thuật sau đây có độ phức tạp tương đương nhau, O(n):
Giải thuật 2A. Mảng một chiều
Ở đây, các số Fibonacci đã tính trước đó được lưu giữ trong mảng một chiều. Trên thực
tế, theo cách này, dữ liệu được lưu giữ đã vượt hơn nhu cầu (vì lưu giữ tất cả các số
Fibonacci trước đó chứ không chỉ là hai số kế trước). Vì vậy, nó hơi tốn bộ nhớ. Tuy
nhiên, nếu chương trình cần nhiều hơn một số Fibonacci và sẽ gọi hàm này lại nhiều lần
thì việc lưu giữ mọi thứ vừa được tính toán giúp giảm bớt thời gian ở các lần gọi sau:
(1). Xây dựng mảng F[1..n] với F[0]=0 và F[1]=1;
(2). Tính các phần tử F[i], với i=2..n theo công thức F[i]=F[i -1]+F[i-2];
(3).Trả về giá trị của F[n].
Đoạn mã lệnh tương ứng:
Function Fib(n);
Begin
F[0]:=0; F[1]:=1;
For i:=2 to n do F[i]:=F[i-1]+F[i-2];
Fib:=F[n];


End;
Giải thuật 2B. Đệ quy đơn
Trong giải thuật này, hai số Fibonacci trước được lưu giữ như là các đối số cho hàm đệ

quy λ(n1,n2, n):
(1). Ta định nghĩa một hàm đệ quy λ(n1, n2,n):
(1.1). λ(n1, n2,n) nhận giá trị n1 nếu n<=1;
(1.2). λ(n1, n2, n) nhận giá trị là kết quả trả về của λ(n1+ n2, n1,n-1) nếu n>1;
(2). Hàm Fib(n) nhận giá trị trả về của λ(0,1,n).
Đoạn mã lệnh tương ứng:
Function lamda (n1,n2,n);
Begin
If n=0 then lamda:=n1
Else lamda:=lamda (n1+n2,n1,n-1);
End;
Function Fib(n);
Begin
Fib:=lamda (0,1,n);
End;
Giải thuật 2C. Lặp không đệ quy
Cũng bắt đầu tính các số Fibonacci từ dưới lên, bắt đầu từ hai biến lưu hai giá trị 0 và 1
rồi lặp đi lặp lại n lần việc thay thế số thứ nhất bằng tổng của hai số và số thứ hai bằng
giá trị cũ của số thứ nhất:
Function Fib(n);
Begin
n1:=0; n2:=1;
For i:=1 to n do
Begin
t:=n1;
n1:=n1+n2;
n2:=t;
End;
Fib:=n1;
End;

Giải thuật này cũng có độ phức tạp O(n) nhưng trên thực tế, nó chậm hơn giải thuật 2A
một chút bởi phép hoán đổi, bù lại nó chiếm ít bộ nhớ hơn. Rõ ràng, ba giải thuật 2A, 2B
và 2C nhanh hơn khoảng 10 triệu lần so với giải thuật 1.
4.3. Giải thuật 3: Độ phức tạp logarit
Tất cả các giải thuật sau tương đương nhau về độ phức tạp thời gian, đều là O(log2n).
Giải thuật 3A: Phương trình ma trận
Dưới đây là công thức toán học tinh tế nhất để tính số Fibonacci:

Ta biết rằng công thức nhân hai ma trận đối xứng 2x2 là:


Ta có thể chứng minh công thức (*) bằng quy nạp. Cho
, bằng quy nạp, giả sử
rằng, công thức trên đúng với n nào đó, nhân cả hai vế với A (và rồi sử dụng công thức
nhân 2 ma trận ở trên) và kiểm tra lại để thấy rằng các số hạng ta có được tương tự như
trong công thức định nghĩa số Fibonacci. Nên nhớ rằng, bằng kĩ thuật bình phương, việc
tính lũy thừa n chỉ cần O(log2 n) phép nhân.
Giải thuật sau khởi tạo ma trận M là ma trận đơn vị và rồi lặp lại n lần việc nhân M với
A. Từ công thức trên, F(n) là phần tử ở góc trên trái của ma trận thu được.
A[0,0]:=1; A[0,1]:=1;
A[1,0]:=1; A[1,1]:=0;
Function M_Pow(M, n); {lũy thừa n của ma trận M}
Begin
If (n>1) then
Begin
T:=M_Pow(M,n div 2);
T:=M_Mult(T,T); {nhân 2 ma trận}
End;
If (n mod 2)=1 then T:=M_Mult(T,A);
M_Pow:=T;

End;
Function Fib(n);
Begin
If n<2 then Fib:=n
Else Begin A:=M_Pow(A, n); Fib:=A[0,0]; End;
End;
Giải thuật này mất O(log2 n) bước. Nếu n là một tỉ, log2n sẽ bằng khoảng 30. Vậy, giải
thuật này tốt hơn giải thuật 2 rất nhiều, chắc chắn tốt hơn giải thuật 1 rất rất nhiều.
Giải thuật 3B: Đệ quy nhanh
Giải thuật này xuất phát từ công thức F(n+m) = F(n). F(m) + F(n-1).F(m-1). Ta có thể
chức minh công thức này bằng phương pháp quy nạp theo m. Từ công thức ta có cách
tính F( n) nha sau: - Nếu n là số chẵn thì

- Nếu n là số lẻ thì

(1). Trước hết, ta định nghĩa thủ tục đệ quy λ( n1, n2,n) ( n1, n2 là các tham chiếu):
(1.1). Gán n1 = n và n2 = 1 nếu n<2;
(1.2). Gán n1 = 1 và n2 = 1 nếu n = 2;
(1.3). Nếu n > 2 và n là số chẵn, gọi λ(f1,f2, n/2-1) và gán n1</SUB>=(F1+f2)*(f1+f2) +


f1*f1; n2</SUB>=(F1+f2)*f1 + f1*f2.
(1.4). Nếu n>2 và n là số lẻ, gọi λ(f1,f2,(n-1)/2) và gán n1</SUB>=F1*(f1+f2) + f1*f2;
n2</SUB>=F1*f1 + f2*f2.
(2). Hàm Fib( n) nhận giá trị của phần tử đầu tiên trong việc gọi λ( n1, n2, n).
Lưu ý rằng giải thuật này nhanh hơn một chút so với giải thuật 3A bởi vì nó không lặp lại
việc tính các số bằng nhau (góc trên phải và góc dưới trái của ma trận trong giải thuật 3A
là luôn bằng nhau). Sau đây là đoạn mã lệnh cài đặt giải thuật:
Procedure lamda (n1,n2,n);
Begin

If n<2 then Begin n1:=n; n2:=1; End
Else If n=2 then Begin n1=1; n2=1; End
Else If (n mod 2)=1 then
Begin
lamda (f1,f2,(n-1)div 2);
n1:=f1*(f1+f2) + f1*f2;
n2:=f1*f1 + f2*f2;
End
Else
Begin
lamda (f1,f2,(n div 2)-1);
n1:=(f1+f2)*(f1+f2) + f1*f1;
n2:=(f1+f2)*f1 + f1*f2;
End;
End;
Function Fib(n);
Begin
lamda (n1,n2,n);
Fib:=n1;
End;
Giải thuật 3C: Công thức Binet
Vào năm 1843, Jacques Philippe Marie Binet đã tìm ra công thức sau:

Thật ra, giá trị của
là giá trị của hệ số Phi, một số có ứng dụng rất
nhiều trong Toán học. Ta sẽ bàn về hệ số này trong một dịp khác. Từ công thức trên, ta có
đoạn mã lệnh tương ứng để tính F(n) là:
Function Pow(x, n); {tính lũy thừa n của x}
Begin
If (n>1) then

Begin
M:=Pow(x,n div 2);
M:=M*M;


End;
If ođ(n) then M:=M*x;
End;
Function Fib(n);
Begin
If (n<2) Then Fib:=n
Else
Begin
Phi:=(1+sqrt(5))/2;
Fib:=(pow(phi,n+1)-pow(1-phi,n+1))/sqrt(5);
End;
End;
Việc tính công thức Binet bao gồm cả việc tính lũy thừa n nên cần O(log2 n) phép nhân.
Giải thuật này thực tế chậm hơn cả giải thuật 3A và 3B bởi lẽ nó đã sử dụng phép chia
các số vô tỉ, lỗi làm tròn số sẽ xảy ra và các số thực trong máy tính thường không đủ độ
chính xác. Tuy nhiên, công thức này trở nên vô giá nếu bạn muốn mở rộng các số
Fibonacci thành các số thuộc kiểu số thực.
5. Một số biến thể của số Fibonacci
5.1. Dãy số Tribonacci
Dãy số Tribonacci giống dãy số Fibonacci, nhưng thay vì bắt đầu với hai số cho trước,
dãy Tribonacci bắt đầu với ba số cho trước và mỗi số kế tiếp là tổng của ba số đứng trước
đó trong dãy. Dưới đây là các số đầu tiên trong dãy Tribonacci:
0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149, 274, 504, 927, 1705, 3136, 5768, 10609, 19513.
5.2. Dãy số Tetranacci
Cách thành lập dãy số Tetranacci giống dãy số Tribonacci, chỉ khác là nó bắt đầu với bốn

số cho trước và mỗi số kế tiếp là tổng của bốn số đứng trước trong dãy. Ví dụ:
0, 1, 1, 2, 4, 8, 15, 29, 56, 108, 208, 401, 773, 1490, 2872, 5536, 10671, 20569.
Dãy Pentanacci, Hexanacci và Heptanacci cũng có thể được thành lập theo cách trên
nhưng chúng không có nhiều giá trị trong nghiên cứu.
5.3. Số Repfigit
Một số nguyên dương có hai (hoặc ba, hoặc bốn) chữ số được gọi là số Repfigit hay số
Keith nếu trong dãy Fibonacci (hoặc Tribonacci, hoặc Tetranacci) tương ứng được thành
lập bắt đầu từ các chữ số của nó xuất hiện chính số đó.
Ví dụ, số 47 là số Keith, bởi vì dãy Fibonacci bắt đầu với 4 và 7 là 4, 7 , 11, 18, 29, 47.
Số 197 là số Keith, bởi vì dãy Tribonacci bắt đầu với 1, 9 và 7 là 1, 9, 7, 17, 33, 57, 107,
197. Dưới đây là một vài số Repfigit:
14, 19, 28, 47, 61, 75, 197, 742, 1104, 1537, 2208, 2580, 3684, 4788, 7385, 7647.
6. Tài liệu tham khảo
Bạn có thể tham khảo tại các địa chỉ:
; ;
; ; ;
; ;
; ... hoặc tìm kiếm trên Internet
với từ khóa?Fibonacci Numbers? để biết nhiều hơn về số Fibonacci, về lịch sử, về các
ứng dụng của nó, về các quy luật của dãy Fibonacci.




Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×