Đệ quy (Cơ bản) - Nguyễn Trung Thành

Mục lục

1. Các vấn đề mở đầu .1

1a. Bài toán lớn bắt đầu từ bài toán nhỏ.1

1b. Định nghĩa cơ bản.2

2. Định nghĩa đệ quy.4

2a. Định nghĩa.4

2b. Tính chất.4

2c. Các ví dụ minh họa.6

3. Luyện tập viết code.9

3a. Trình tự phân tích một bài toán theo kiểu đệ quy.9

3b. Những bài toán đơn giản .10

3c. Những bài toán phức tạp hơn .16

4. Bài tập.22

5. Lời kết .24

pdf27 trang | Chuyên mục: Giải Tích | Chia sẻ: tuando | Lượt xem: 530 | Lượt tải: 0download
Tóm tắt nội dung Đệ quy (Cơ bản) - Nguyễn Trung Thành, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
 . 
Phân tích: Ta có R(2) = √ √ và R(1) = √ . 
 R(n) = √ ( ). Kiểm tra lại thì ta thấy công thức đúng. 
Và cuối cùng là bước 7: R(4) = √ ≈ 2.485 
R(4) = √ 𝑅( ) 
R(2) = √ 𝑅( ) 
R(3) = √ 𝑅( ) 
R(1) = 1 
Bước 1. Gọi R(3) 
Bước 2. Gọi R(2) 
Bước 3. Gọi R(1) 
Bước 4. Trả về 1 
Bước 5. Trả về √ 
Bắt đầu khó hiểu 
Đệ quy (cơ bản) – Trang 9 
3. Luyện tập viết code 
3a. Trình tự phân tích một bài toán theo kiểu đệ quy 
Bước 4. Viết code 
+ Điểm dừng trước. 
+ Gọi đệ quy sau. 
LƯU Ý : trên đây chỉ là các bước luyện tập với những bài toán đệ quy đơn giản. Đối 
với những bài toán phức tạp hơn thì cần nhiều bước hơn, cần phải có sự tư duy 
cao. 
Luyện tập thử 8 bài toán ví dụ luôn nhé !!! 
Bước 1. Viết S(n) và S(n-1). 
Bước 2. Tìm mối liên hệ giữa S(n) và S(n-1). Biểu diễn S(n) theo S(n-1). 
Bước 3. Tìm điểm dừng (bài toán đơn giản nhất). Thông thường điểm 
dừng là S(1), S(0), 
Đệ quy (cơ bản) – Trang 10 
3b. Những bài toán đơn giản 
Bài toán 1 : vẫn là bài toán quen thuộc . 
Tính S(n) = 1 + 2 + 3 + .. + n. 
Bước 1. Viết S(n) và S(n-1). 
S(n) = 1 + 2 + 3 +  + (n-1) + n 
S(n-1) = 1 + 2 + 3 +  + (n-1) 
Bước 2. Biểu diễn S(n) theo S(n-1). 
S(n) = S(n-1) + n 
Bước 3. Tìm điểm dừng (bài toán đơn giản nhất). 
S(1) = 1 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
int S (int n) 
{ 
 if (n == 1) return 1; 
 return S(n-1) + n; 
} 
Đệ quy (cơ bản) – Trang 11 
Chạy thử nhé 
Kết quả S(4) = 1 + 2 + 3 + 4 = 10. Chính xác. 
Đệ quy (cơ bản) – Trang 12 
Bài toán 2 : Tính P(n) = 2n. 
Bước 1. 
P(n) = 2 * 2 * 2 *  * 2 * 2 
P(n-1) = 2 * 2 * 2 * ... * 2 
Bước 2. 
P(n) = P(n-1) * 2 
Bước 3. 
P(0) = 1 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
int P (int n) 
{ 
 if (n == 0) return 1; 
 return P(n-1) * 2; 
} 
n số 2 
n-1 số 2 
Đệ quy (cơ bản) – Trang 13 
Bài toán 3 : Tính P(x,n) = xn (x và n là số nguyên, n ≥ 0). 
Đây là bài toán mở rộng của bài toán số 2 ở trên. 
Hãy tự suy nghĩ 3 bước đầu nhé. 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
int P (int x, int n) 
{ 
 if (n == 0) return 1; 
 return P(x, n-1) * x; 
} 
Đệ quy (cơ bản) – Trang 14 
Bài toán 4 : 
Tính tổng ( ) 
Bài toán này cũng không quá khó, bạn xem thử code rồi suy nghĩ xem nhé. 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
float T (int n) 
{ 
 if (n == 1) return 0.5; 
 return T(n-1) + (float)n/(n+1); 
} 
Đệ quy (cơ bản) – Trang 15 
Bài toán 5 : 
Viết hàm kiểm tra xem mảng a có toàn số dương hay không. 
a[0] a[1] a[2]  a[n-2] a[n-1] 
Bước 1 và 2. 
Ta gộp chung cả 2 bước vì ta khó tưởng tượng được. 
- Giả sử ta muốn mảng a có 4 phần tử đều là số dương. 
Tức là từ a*0+ đến a[3+ đều là số dương. 
Hay nói cách khác: từ a*0+ đến a[2] là số dương, và a*3] là số dương. 
- Muốn a*0+ đến a[2] là số dương, 
Thì a*0+ đến a[1] là số dương, và a*2] là số dương. 
 ToànSốDương từ a*0+ đến a[n-1] = ToànSốDương từ a*0+ đến a[n-2+, đồng thời 
a[n-1+ cũng phải là số dương. 
Bước 3. 
Khi mảng a chỉ có 1 phần tử a[0], ta dễ dàng kiểm tra được a[0] là số dương hay 
không. 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
int ToanSoDuong (int a[], int n) 
{ 
 if (n == 1) return (a[0] % 2 == 0); 
 return ToanSoDuong(a,n-1) && (a[n-1] % 2 == 0); 
} 
Đệ quy (cơ bản) – Trang 16 
3c. Những bài toán phức tạp hơn 
Bài toán 6 : tính R(n) = √ √( ) √( ) √ √ (n dấu căn) 
Bước 1 và bước 2. 
Rõ ràng ta thấy nếu viết R(n) và R(n-1) sẽ rất khó tìm thấy mối liên hệ giữa chúng. 
Đây là bài toán khó hơn :D Ta cứ thử viết đại đi. 
R(2) = √ √ 
R(1) = √ 
 R(n) = √ ( ) 
Đây chỉ là ta dự đoán, ta cần kiểm tra lại bằng máy tính bỏ túi. Kiểm tra lại thì ta 
thấy dự đoán đúng (khỏi cần chứng minh = Toán học :v). 
Bước 3. 
R(1) = √ = 1 
Bước 4. (nhớ khai báo thư viện math.h nhé) 
Dòng Mã 
1 
2 
3 
4 
5 
6 
float R (int n) 
{ 
 if (n == 1) return 1; 
 return sqrt(n + R(n-1)); 
} 
Bạn nên xem lại ví dụ số 3 trong mục “2c. Các ví dụ minh họa” để xem lược đồ gọi 
đệ quy nhé. 
Đệ quy (cơ bản) – Trang 17 
Bài toán 7 : Viết hàm kiểm tra xem N có phải là số có toàn chữ số lẻ hay không. 
Đụng tới các bài toán về chữ số, ta liền nhớ về các chiêu thức div 10 và mod 10. 
 Nếu N có dạng ̅̅ ̅̅ ̅̅ ̅ thì 
+ N / 10 = ̅̅ ̅̅ ̅ 
+ N % 10 = d 
Bước 1 và 2. 
Giả sử số N có dạng ̅̅ ̅̅ ̅̅ ̅ 
Và cuối cùng muốn ̅ có toàn chữ số lẻ thì a là số lẻ, ta dễ dàng xét được. 
Đến đây ta rút ra được : N toàn chữ số lẻ  (N%10 lẻ) và (N/10 toàn chữ số lẻ). 
toàn chữ số lẻ 
số lẻ 
𝑎𝑏𝑐𝑑̅̅ ̅̅ ̅̅ ̅ 
toàn chữ số lẻ 
số lẻ 
𝑎𝑏𝑐̅̅ ̅̅ ̅ 
Muốn 𝑎𝑏𝑐̅̅ ̅̅ ̅ có toàn chữ số lẻ thì 
Muốn 𝑎𝑏𝑐𝑑̅̅ ̅̅ ̅̅ ̅ có toàn chữ số lẻ thì 
toàn chữ số lẻ 
số lẻ 
𝑎𝑏̅̅ ̅ 
Muốn 𝑎𝑏̅̅ ̅ có toàn chữ số lẻ thì 
Đệ quy (cơ bản) – Trang 18 
Bước 3. 
Bài toán đơn giản nhất là khi N có 1 chữ số, ta dễ dàng xét được N là số lẻ hay 
không. 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
7 
int ToanChuSoLe (int N) 
{ 
 if (N < 10) return (N % 2 == 1); 
 int HangDonVi = N % 10; 
 return (HangDonVi % 2 == 1) && ToanChuSoLe(N/10); 
} 
Đệ quy (cơ bản) – Trang 19 
Lược đồ phác họa các bước đi của bài toán 7 như sau : 
Giả sử N = 1297. Để cho ngắn gọn, ta hiểu hàm ToanChuSoLe là hàm S. 
Và cuối cùng là bước 7: S(1297) = true và false = false. 
Kết luận : 1297 không phải là số có toàn chữ số lẻ. 
S(1297) = (7 lẻ) và S(129) 
Bước 1. Gọi S(129) 
Bước 2. Gọi S(12) 
Bước 3. Gọi S(1) 
Bước 4. Trả về true 
Bước 5. Trả về false 
(vì 2 chẳn) 
false và true = false 
S(129) = (9 lẻ) và S(12) 
S(12) = (2 lẻ) và S(1) 
S(1) = 1 lẻ = true 
7 lẻ S(129) 
Đệ quy (cơ bản) – Trang 20 
Bài toán 8 : Tìm số lớn nhất mảng 1 chiều có n phần tử. 
Ban đầu mình định để cho các bạn suy nghĩ bài này, nhưng mình thấy không ổn 
nên mình làm luôn. 
Mảng a như sau 
a[0] a[1] a[2]  a[n-2] a[n-1] 
Bước 1. 
max (n) = số lớn nhất từ a*0+ đến a[n-1] 
max(n-1) = số lớn nhất từ a*0+ đến a[n-2] 
Bước 2. 
Để tìm max từ a*0+ đến a[n-1], thì ta tìm số lớn nhất từ a*0+ đến a[n-2+, sau đó so 
sánh với a[n-1]. 
Công thức truy hồi sẽ là 
max(n) = số lớn nhất trong 2 số a[n-1] và max(n-1) 
Bước 3. 
Khi n = 1, mảng a chỉ có 1 phần tử duy nhất là a*0+, nên a*0+ cũng là max. 
max(1) = a[0] 
max (n) 
max (n-1) 
Đệ quy (cơ bản) – Trang 21 
Bước 4. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
int max (int a[], int n) 
{ 
 if (n == 1) return a[0]; 
 int max2 = max (a, n-1); 
 if (a[n-1] > max2) 
 return a[n-1]; 
 else 
 return max2; 
} 
Nói thêm : thật ra các bạn có thể code ngắn gọn hơn như sau 
Dòng Mã ngắn gọn hơn (không khuyến khích sử dụng) 
1 
2 
3 
4 
5 
6 
7 
8 
9 
int max (int a[], int n) 
{ 
 if (n == 1) return a[0]; 
 if (a[n-1] > max(a,n-1)) 
 return a[n-1]; 
 else 
 return max(a,n-1); 
} 
Nhưng chúng ta phải nhớ 1 khuyết điểm của đệ quy là sự bùng nổ tổ hợp. Nếu viết 
code ngắn gọn như trên, ta sẽ gọi hàm max đến 2 lần nếu a[n-1] <= max(a,n-1). 
Tưởng tượng mảng có 50 phần tử = ,49, 48, , 1, 0-. Như vậy trường hợp xui xẻo 
nhất đã xuất hiện: hàm max luôn được gọi 2 lần vì a[i] luôn > a[i+1]. 
Tổng số lần hàm max được gọi là 250 – 1 lần, một con số rất lớn(treo máy). 
Trong khi đó, nếu bạn sử dụng code mẫu của mình ở trên, thì chỉ mất 50 lần gọi 
hàm max mà thôi. 
“Sự chênh lệch vô cùng lớn sẽ tạo ra đẳng cấp khác biệt.” 
Đệ quy (cơ bản) – Trang 22 
4. Bài tập 
Tất cả 8 ví dụ mình làm ở trên, đều thuộc dạng đệ quy tuyến tính. 
Nếu có thời gian, bạn nên luyện tập với bài toán sau : 
a) Tính tổng T(n) = 12 + 22 + 32 +  + n2. 
b) Tính tổng ( ) 
c) Tìm số Fibonacci thứ n. Đây là loại đệ quy nhị phân. 
d) Tìm ước chung lớn nhất của 2 số (2 cách : trừ liên tiếp và chia Euclide). 
e) Đếm các chữ số lẻ của số N. 
f) Ta đã quen thuộc với bài toán S(n) = 1 + 2 + 3 +  + n. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
int S (int n) 
{ 
 if (n == 1) return 1; 
 return S(n-1) + n; 
} 
Có một cách giải khác bằng đệ quy. Để tính tổng 1+2++10 ta gọi hàm S(1,10). 
Dòng Mã 
1 
2 
3 
4 
5 
6 
int S (int i, int n) 
{ 
 if (i == n) return i; 
 return i + S(i+1,n); 
} 
Cách giải này có “bình thường” như những gì ta đã làm ? Khi nào ta mới giải 
được bằng cách “quái lạ” này ? 
Đệ quy (cơ bản) – Trang 23 
g) (nâng cao) Mảng tăng dần. 
Cho mảng a có 3 phần tử. Giá trị mỗi phần tử nằm từ 0 đến 9 (0 ≤ a*i+ ≤ 9). 
Mảng a được gọi là mảng tăng khi a*i+ ≤ a[i+1] với 0 ≤ i < n. 
Ví dụ a = {1,2,3}, a = {2,5,8}, 
Yêu cầu : đếm số lượng mảng a tăng dần (có 3 phần tử). 
Đây là bài giải bình thường. Code chưa được tốt, chưa được tối ưu. 
u chính là a[0], v là a[1] và t là a[2]. 
Dòng Mã 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
void Dem_Mang_Tang() 
{ 
 int dem = 0; 
 for (int u = 0; u <= 9; u++) 
 for (int v = 0; v <= 9; v++) 
 for (int t = 0; t <= 9; t++) 
 if (u <= v && v <= t) 
 { 
 dem++; 
 } 
 printf("So luong mang tang = %d", dem); 
} 
Giả sử mảng a không có 3 phần tử mà có n phần tử thì cách làm bình thường 
có giải được không ? 
Viết hàm đệ quy giải bài toán trên. 
Đệ quy (cơ bản) – Trang 24 
5. Lời kết 
Trên đây chỉ là những điều cơ bản nhất về đệ quy. 
8 bài tập ví dụ đều đi kèm với đầy đủ source code. Mình còn viết một số demo cho 
vài bài tập (các bạn xem trong thư mục demo). 
Đệ quy (cơ bản) – Trang 25 
Mình cũng mất rất là nhiều thời gian để làm tài liệu này trong khi mình có thể vui vẻ 
đi ăn chơi Tết thoải mái. Chỉ có điều mình sợ rằng tài liệu chưa được tốt, có khi đệ 
quy quá khó hiểu sẽ làm cho các bạn dễ bị chết. Sau này các bạn sẽ còn tiếp cận với 
những bài toán đệ quy đẳng cấp cao hơn. Hi vọng nếu bạn vững được gần hết các ý 
trong tài liệu này thì bạn sẽ có một cái nền vững chắc. 
Mình cũng mong rằng nếu nhiệt tình hơn, bạn có thể đóng góp ý kiến về cách trình 
bày của tài liệu, các ví dụ,để cho tài liệu ngày một hoàn thiện hơn. 
Cảm ơn các bạn đã sử dụng tài liệu này. 
Nguyễn Trung Thành. 
Đại học Khoa học Tự nhiên (HCMUS). 
https://www.facebook.com/abcxyztcit 
Email: itga.trial1@yahoo.com.vn 

File đính kèm:

  • pdfde_quy_co_ban_nguyen_trung_thanh.pdf