ÔN THI OLYMPIC CẤP KHOA 2012 Tập Tin Một Số Phương Thức và Toán Tử Thao Tác Đọc Định Nghĩa Đệ Qui y Một định nghĩa đệ qui phải có hai phần: 1. Phần neo hay cơ sở: Được định nghĩa không đệ qui. 2. Phần qui nạp hay đệ qui: Được định nghĩa dựa trên giá trị đã được định nghĩa trước đó. Thuật Toán Quay Lui
Trang 1ÔN THI OLYMPIC CẤP KHOA 2012
Trang 2ThS.GVC Tô Oai Hùng 2
Nội Dung
y Tập tin: Mở, tạo mới, đọc/ghi, đóng tập tin.
y Đệ qui: Định nghĩa và xây dựng hàm đệ qui, backtracking.
y Bài tập.
Trang 3Tập Tin
y Các bước thao tác trên tập tin:
1 Thêm chỉ dẫn #include <fstream.h>.
2 Đọc hoặc gán tên tập tin vào biến kiểu
chuỗi, ví dụ là fileName.
3 Tạo các đối tượng iFile/oFile thuộc
lớp ifstream/ofstream tùy theo tập tin dùng để đọc hay ghi (nếu đọc và ghi thì phải thêm tham số thứ 2 của phương thức open()).
4 Kết nối chương trình với tập tin fileName thông qua các đối tượng này bằng phương thức open().
Trang 5Lớp ifstream & ofstream
y Khi thao tác với tập tin, các luồng không được tạo tự động mà phải mở luồng:
- Tạo kết nối giữa chương trình trong bộ nhớ với tập tin trên đĩa bằng cách sử dụng phương thức open() của các đối tượng thuộc lớp ifstream và ofstream.
Trang 6ThS.GVC Tô Oai Hùng 6
Lớp ifstream & ofstream
y Khai báo luồng tập tin để đọc:
Trang 7y >> Toán tử, đọc giá trị đơn vào biến
y getline() Đọc dòng văn bản vào chuỗi
ký tự.
y get() Đọc một hay nhiều ký tự.
y << Toán tử, ghi giá trị đến tập tin.
y eof() Trả về true nếu đến cuối tập tin.
Trang 9Phương Thức open()
y Khi tập tin được mở để ghi, con trỏ tập tin
sẽ được đặt tại ký hiệu kết thúc tập tin.
Trang 11Mở Tập Tin Không Dùng open()
y Có thể mở tập tin lúc khai báo đối tượng:
ifstream iFile("pressure.in");
ofstream oFile("pressure.out");
Trang 12ThS.GVC Tô Oai Hùng 12
Phương Thức is_open()
y Thao tác mở tập tin phải luôn được kiểm tra
có thành công không bằng is_open():
iFile.open(fileName);
assert(iFile.is_open());
Hoặc oFile.open(fileName);
assert(oFile.is_open());
Có thể viết:
if(!iFile) { cerr << “File not found!\n“;
exit(1); }
Trang 13Thao Tác Đọc
y Chúng ta đã sử dụng toán tử >>:
cin >> x;
để nhập giá trị từ bàn phím vào biến.
y C++ sử dụng cùng toán tử này để nhập giá trị vào biến từ đối tượng của ifstream:
iFile >> x;
y Sau mỗi lần đọc, con trỏ tập tin di chuyển đến phần tử kế tiếp
Trang 14- Đọc cho đến khi gặp ký tự ‘\n’.
- Ký tự ‘\n’ được bỏ đi.
Trang 15Trong đó: iFile là đối tượng lớp ifstream.
- Dạng (1) và (2): Đọc một ký tự từ luồng nhập (dù là ký tự trắng).
- Dạng (3): Giống như cin.getline()
Trang 17Thao Tác Ghi
y Tương tự như toán tử >> được sử dụng với ifstream, toán tử << được sử dụng với ofstream để xuất dữ liệu từ luồng này.
outStream<<"\n > There were a total of“
<< count << "values.";
Sau mỗi lần ghi, con trỏ tập tin di chuyển về
Trang 18ThS.GVC Tô Oai Hùng 18
Phương Thức close()
y Luồng tập tin sẽ không được kết nối khi:
- Chương trình thoát khỏi tầm vực của đối tượng fstream (mặc định).
- Hoặc khi phương thức close()được thực thi.
y Trong lập trình nên đóng tập tin bằng phương thức close() khi không còn dùng đến.
y Bài tập: Lấy ví dụ về đọc nội dung tập tin văn bản vào mảng một chiều.
Trang 19Định Nghĩa Đệ Qui
chính khái niệm đó.
y Ví dụ 1: Định nghĩa đệ qui “Số tự nhiên”:
- 0 là một số tự nhiên.
- n là số tự nhiên nếu n - 1 là số tự nhiên.
y Ví dụ 2: Định nghĩa đệ qui giai thừa (ký hiệu
là n!) của số nguyên n không âm.
Trang 20ThS.GVC Tô Oai Hùng 20
Định Nghĩa Đệ Qui
y Một định nghĩa đệ qui phải có hai phần:
1 Phần neo hay cơ sở: Được định nghĩa
không đệ qui.
2 Phần qui nạp hay đệ qui: Được định
nghĩa dựa trên giá trị đã được định nghĩa trước đó.
Trang 21Hàm Đệ Qui
y Để viết được hàm đệ qui, chúng ta tìm
cách định nghĩa bài toán dạng đệ qui và sau đó viết hàm đệ qui từ định nghĩa trên:
- Đầu tiên tìm phần cơ sở (không đệ qui).
- Kế đến tìm phần đệ qui sao cho những lần đệ qui tiếp theo phải hội tụ về phần
Trang 22ThS.GVC Tô Oai Hùng 22
Hàm Đệ Qui
y Ví dụ hàm đệ qui:
int factorial(int n) {
Trang 23Sự Thực Thi Của Hàm Đệ Qui
y Quan sát các lời gọi đệ qui khi n = 4:
Trang 24ThS.GVC Tô Oai Hùng 24
Sự Thực Thi Của Hàm Đệ Qui
Lời gọi factorial(n-1) cuối cùng, khi này là factorial(0) sẽ làm cho câu lệnh ở phần neo được thực thi Không còn lời gọi
đệ qui nào khác.
Trang 25Sự Thực Thi Của Hàm Đệ Qui
y Bây giờ những giá trị đã tính được trả về:
1 2
24
Trang 26ThS.GVC Tô Oai Hùng 26
Các Loại Đệ Qui
1 Đệ qui tuyến tính (Linear recursion): Mỗi
lần hàm thực thi chỉ gọi đệ qui một lần.
Ví dụ 1: Tính n!.
int factorial(int n) {
qui (tuyến tính) mySqrt() như sau: Để tính
Trang 27Các Loại Đệ Qui
giá trị gần đúng với căn bậc hai của một số
dương bất kỳ a được thực hiện như sau: Lấy
số dương xấp xỉ khởi đầu x và tìm số xấp xỉ
mới bằng cách tính trung bình của x và a/x,
đó là (x + a/x)/2 Lặp lại quá trình này với x
được thay bằng giá trị xấp xỉ mới cho đến
khi hiệu giữa x và a/x nhỏ hơn hay bằng độ
chính xác khá bé cho trước.
2 Đệ qui đuôi (Tail recursion):
- Là một dạng của đệ qui tuyến tính.
- Trong đệ qui đuôi, lời gọi đệ qui là lệnh
Trang 28- Lưu ý: Hàm factorial() tính n! trong ví
dụ trước không phải là đệ qui đuôi Vì lời gọi đệ qui trong hàm
return n * factorial(n–1);
Trang 29Các Loại Đệ Qui
không phải là lệnh cuối cùng mà hàm thực thi (kết quả lời gọi đệ qui phải được nhân với n).
- Bài tập: Hãy sửa lại hàm factorial()
thành hàm đệ qui đuôi.
Trang 30return facto(n, 1);
}
Trang 31Các Loại Đệ Qui
3 Đệ qui nhị phân (Binary recursion): Mỗi lần
hàm thực thi thì gọi đệ qui hai lần
- Ví dụ 1: Tính số tổ hợp không lặp chập k
từ n phần tử (tập con k phần tử của n phần tử).
Định nghĩa đệ qui:
Trang 33Các Loại Đệ Qui
4 Đệ qui nhánh: Mỗi lần hàm thực thi, lời gọi
đệ qui được thực hiện nhiều hơn một lần Bài toán Tháp Hà Nội là một ví dụ về đệ qui nhánh (có 3 lời gọi đệ qui).
void move(int n, char source, char
spare, char dest) { if(n == 1)
cout << "Chuyển đĩa từ " << source
<< " sang " << dest << endl;
else {
move(n-1, source, dest, spare);
move(1, source, ' ', dest);
Trang 36ThS.GVC Tô Oai Hùng 36
6 Đệ qui hỗ tương (Mutual recursion): Các
hàm đệ qui thực hiện lời gọi lẫn nhau Một trong các ứng dụng của nó là vẽ đường Hilbert và Sierpinski:
y Ví dụ: Xác định số chẵn/lẻ.
bool is_odd(int);
Trang 37Các Loại Đệ Qui
bool is_even(int n) {
if(n == 0)
return true;
return is_odd(n-1);
} bool is_odd(int n) {
return !is_even(n);
}
Trang 38ThS.GVC Tô Oai Hùng 38
Các Loại Đệ Qui
7 Đệ qui chồng/lồng (Implicated/nested
gọi đệ qui Ví dụ, tính hàm Ackermann cho
mọi số nguyên không âm m, n.
int ackerman(int m, int n)
{
if(m == 0)
return n + 1;
Trang 40ThS.GVC Tô Oai Hùng 40
Thuật Toán Quay Lui
y Thuật toán quay lui (backtracking) được
sử dụng trong phương pháp "thử và sai" Nét đặc trưng của thuật toán này là để có được lời giải, ta phải đi từng bước bằng phép thử Giả sử bài toán có n trường hợp
x 1 , x 2 , , x n và giả sử đã chúng ta đã xây dựng xong i - 1 trường hợp (x 1 , x 2 , , x i-
1 ), bây giờ đang xây dựng trường hợp thứ
i (x i ):
- Nếu tồn tại một khả năng chấp nhận được, chọn khả năng này và nếu i = n
Trang 41Thuật Toán Quay Lui
thì x i là một lời giải Ngược lại nếu i < n thì
ghi nhớ trường hợp này và tiến hành xây dựng trường hợp kế tiếp i + 1.
- Nếu không tồn tại khả năng nào cho x i thì
xoá ghi nhớ và lùi lại bước trước đó x i - 2
để thử các khả năng còn lại cho x i - 1.
y Thuật toán quay lui có dạng như sau:
void Try(int i)
{
if(<x là trường hợp kết thúc>)// i > n
Trang 42ThS.GVC Tô Oai Hùng 42
Thuật Toán Quay Lui
else
for(<j thuộc tập các lựa chọn>)
if(<khả năng thứ j chọn được>) {
<xác định x i theo khả năng j>
<ghi nhớ trạng thái mới>
Try(i + 1) // thử bước kế tiếp
<xoá bỏ ghi nhớ, về trạng thái cũ > }
}
Trang 43Thuật Toán Quay Lui
y Để minh họa cho thuật toán quay lui, ta xét
bài toán mã đi tuần (Knight’s tour):
Cho một bàn cờ có kích thước là n × n (5
≤ n ≤ 8) Một con mã di chuyển theo luật cờ
vua được đặt trong một ô với tọa độ ban đầu
là (x 0 , y 0 ) Hãy tìm một đường đi với n 2 - 1
bước đi còn lại sao cho mọi ô trên bàn cờ
đều được con mã nhảy đến đúng một lần Ví
dụ, với n = 5, tọa độ ban đầu là (0, 0) thì một
trong các lời giải là:
Trang 44ThS.GVC Tô Oai Hùng 44
Thuật Toán Quay Lui
Trang 45Thuật Toán Quay Lui
for(<j thuộc tập các lựa chọn>)
// 0 ≤ j < 8 (có tối đa 8 vị trí mới) {
u = x + dx[j];
v = y + dy[j];
if(<có khả năng thứ j>) // ((u >= 0 && u < n) &&
Trang 46<xoá bỏ ghi nhớ, trở về trạng thái cũ>
//(board[u][v] = 0;
// x = x - dx[j];
// y = y - dy[j];) }
}
Trang 47Thuật Toán Quay Lui
y Bài tập