SỞ GIÁO DỤC VÀ ĐÀO TẠO THANH HOÁ TRƯỜNG THPT THỌ XUÂN 5 SÁNG KIẾN KINH NGHIỆM TÊN ĐỀ TÀI GIÚP HỌC SINH TẠO DỮ LIỆU ĐẦU VÀO, CÀI ĐẶT CHƯƠNG TRÌNH TỐI ƯU VÀ KHẮC PHỤC NHỮNG SAI LẦM THƯỜNG
Trang 1SỞ GIÁO DỤC VÀ ĐÀO TẠO THANH HOÁ
TRƯỜNG THPT THỌ XUÂN 5
SÁNG KIẾN KINH NGHIỆM
TÊN ĐỀ TÀI GIÚP HỌC SINH TẠO DỮ LIỆU ĐẦU VÀO, CÀI ĐẶT CHƯƠNG TRÌNH TỐI ƯU VÀ KHẮC PHỤC NHỮNG SAI LẦM THƯỜNG GẶP KHI LÀM VIỆC VỚI KIỂU DỮ LIỆU
XÂU TRONG NGÔN NGỮ LẬP TRÌNH C++.
Người thực hiện: Lê Thị Hạnh
Chức vụ: Giáo viên SKKN thuộc lĩnh vực (môn): Tin học
THANH HOÁ NĂM 2022
Trang 2Mục lục
Trang 3Ở ĐẦU
1.1 Lý do chọn đề tài
C++ là ngôn ngữ lập trình khá phổ biến, có kiểu dữ liệu tĩnh và hỗ trợ hầu hết các phương pháp lập trình Dữ liệu kiểu xâu trong C++ phức tạp hơn khá nhiều so với dữ liệu kiểu xâu trong Pascal, việc vận dụng linh hoạt các thao tác
xử lí kiểu dữ liệu này vào giải quyết các bài toán liên quan không phải dễ Bên cạnh đó một số thói quen sử dụng các thao tác xử lý kiểu xâu trong ngôn ngữ lập trình pascal đã làm cho các em vận dụng sai khi giải các bài tập trong C++ một cách đáng tiếc, cùng với đó là tài liệu tham khảo phổ thông chính thống về ngôn ngữ lập trình C++ còn khá hạn chế
Với mong muốn giúp học sinh giải quyết tốt hơn các bài tập về xâu và hiểu biết sâu sắc kiểu dữ liệu xâu trong ngôn ngữ lập trình C++, tôi mạnh dạn trình
bày sáng kiến kinh nghiệm: “ Giúp học sinh tạo dữ liệu đầu vào, cài đặt chương trình tối ưu và khắc phục những sai lầm thường gặp khi làm việc với kiểu dữ liệu xâu trong ngôn ngữ lập trình C++ ” để trao đổi cùng đồng nghiệp.
1.2 Mục đích nghiên cứu
Trong phạm vi đề tài của mình tôi muốn đưa ra ý tưởng giải quyết, cài đặt chương trình tối ưu giải quyết một số bài toán cụ thể, qua đó chỉ ra những sai lầm học sinh thường mắc phải khi sử dụng một số thao tác xử lý xâu và định hướng một số bài tập về kiểu xâu Các thao tác và bài tập tuy không phải mới nhưng khi hiểu đúng về kiểu xâu trong C++ thì hiệu quả thuật toán nâng lên rõ rệt Từ đó học sinh có thể phân dạng được các bài tập về xâu và sử dụng các thao tác xử lý xâu một cách nhuần nhuyễn Cài đặt được các chương trình tối ưu cho các bài tập xâu, rèn luyện tư duy và phong cách lập trình Cũng qua đề tài, tôi muốn cùng đồng nghiệp trao đổi, trau dồi chuyên môn nhằm góp phần nâng cao trình độ chuyên môn nghiệp vụ và khả năng mở rộng kiến thức
1.3 Đối tượng nghiên cứu
Đề tài này được áp dụng đối với học sinh khá và giỏi với nhiệm vụ chủ yếu
là ôn thi học sinh giỏi và bồi dưỡng kiến thức cho học sinh yêu thích môn tin
1.4 Phương pháp nghiên cứu.
Để hoàn thành đề tài này, tôi đã tiến hành áp dụng một số phương pháp nghiên cứu sau: Phương pháp đặt vấn đề - giải quyết vấn đề, Phương pháp phân tích tổng hợp,Phương pháp so sánh đối chiếu, Phương pháp thực nghiệm
2 NỘI DUNG CỦA SÁNG KIẾN KINH NGHIỆM
2.1 Cơ sở lí luận của sáng kiến kinh nghiệm.
Giáo dục tin học đóng vai trò chủ đạo trong việc chuẩn bị cho học sinh khả năng tìm kiếm, tiếp nhận, mở rộng tri thức và sáng tạo trong thời đại cách mạng công nghiệp lần thứ tư và toàn cầu hoá Tin học có ảnh hưởng lớn đến
Trang 4cách sống, cách suy nghĩ và hành động của con người, là công cụ hiệu quả hỗ trợ biến việc học thành tự học suốt đời.[1]
Căn cứ vào những quan điểm, định hướng trên mục tiêu của môn Tin học,
là phải cung cấp những tri thức cơ bản, làm nền tảng để học sinh có thể tiếp tục
đi sâu vào tìm hiểu và xây dựng khoa học Tin học hoặc tiếp thu những tri thức của các lĩnh vực kĩ thuật công nghệ tiên tiến, nhất là các lĩnh vực của công nghệ thông tin
2.2 Thực trạng vấn đề trước khi áp dụng sáng kiến kinh nghiệm.
2.2.1 Về phía Phụ huynh học sinh.
Môn Tin học là môn học không được đưa vào thi tốt nghiệp hoặc xét các trường Cao đẳng, Đại học Chính vì vậy đa phần không được phụ huynh đồng tình khi con mình được lựa chọn vào đội tuyển Tin học phải đầu tư thời gian để tìm hiểu nó
2.2.2 Về phía Giáo viên.
Tin học 11 là một lĩnh vực khó truyền tải, Không phải Giáo viên có chuyên môn tốt sẽ đào tạo được học sinh Giỏi, mà Giáo viên có chuyên môn tốt phải đồng hành với việc có phương pháp giảng dạy lập trình tốt Có sự đánh giá đúng
về điểm mạnh, điểm yếu lập trình của từng học sinh, những sai lầm về lập trình từng em hay mắc phải
2.2.3 Về phía học sinh.
Các em thường quen với việc giải toán đó là tìm cách giải sao cho ra được một đáp án theo đề là được Chính vì vậy nhiều em đi thi học sinh giỏi về tự tin
là mình làm bài tốt mà không kiểm tra cách giải của mình đã tối ưu chưa? Đã bao quát được tất cả trường hợp xảy ra chưa? Đã chạy được test với dữ liệu lớn hay chưa? Dẫn đến kết quả kỳ thi thấp
2.2.4 Một số tài liệu hiện có.
Trên thực tế đã có một số tài liệu đề cập đến các bài tập về xâu, nhưng các tài liệu này mới chỉ đưa ra thuật toán và chương trình giải một số bài tập cụ thể làm ví dụ minh họa cho một kỹ thuật lập trình nào đó khi nghiên cứu mà chưa khái quát, nhấn mạnh được những điểm cần lưu ý với kiểu xâu trong C++
2.3 Các sáng kiến kinh nghiệm hoặc các giải pháp đã sử dụng để giải quyết vấn đề.
Từ thực trạng trên, qua 5 năm đứng đội tuyển từ 2017- 2022, nhất là khi chuyển đổi ngôn ngữ lập trình từ pascal sang C++ , khi dạy về kiểu dữ liệu xâu tôi đã tập hợp những lỗi sai thường gặp của học sinh, đưa ra cách khắc phục, hướng dẫn các em tạo dữ liệu đầu vào để kiểm tra độ phức tạp, thời gian thuật
Trang 5toán, hướng các em tới thuật toán tối ưu khi giải quyết bài tập về kiểu dữ liệu xâu
2.3.1 Những sai lầm học sinh thường gặp
2.3.1.1 Vân dụng sai : cin và getline
a) Nhập xâu
Học sinh cần linh động trong cách nhập xâu tùy vào yêu cầu đề bài để tránh làm chậm chương trình hoặc mất test
+ Xâu không chứa dấu cách hoặc dấu cách ở cuối xâu chúng ta dùng cin:
cin>>tên biến xâu;
Với cách nhập này nó chỉ nhập được xâu từ đầu đến trước vị trí cách trống đầu tiên khi gặp các kí tự trắng (như dấu cách, kí tự xuống dòng, ….) sẽ coi là kết thúc việc nhập dữ liệu
+ xâu chứa dấu cách chúng ta dùng getline: getline(cin,tên biến xâu);
+ xâu có chứa dấu cách hoặc không có dấu cách ta có thể lựa chọn cách
đọc mảng kí tự
Lưu ý: khi dùng getline thời gian chạy chương trình sẽ lâu hơn cin
Ứng dụng linh hoạt cin, getline, mảng xâu, mảng kí tự
Để thấy được sự linh hoạt, hiệu quả khi hiểu rõ bản chất về cin, getline
ta giới thiệu cho học sinh ví dụ sau:
Cho file XAUDEP.INP gồm các dấu cách và các kí tự Một từ là các kí tự liên tiếp không chứa dấu cách, hãy tối ưu xâu trên để giữa hai từ chỉ có một dấu cách in kết quả xâu tìm được vào file XAUDEP.OUT
#include <iostream>
using namespace std;
string s,st;
int main()
{
freopen("xaudep.inp","r",stdin);
freopen("xaudep.out","w",stdout);
getline(cin,s); // sử dụng getline để đọc xâu đầu vào
s=s+' '; st="";
for(int i=0;i<s.size();i++)
if (s[i]!=' ' || s[i]==' ' &&s[i+1]!=' ') st=st+s[i];
if (st[0]==' ') st.erase(0,1);
cout<< st;
return 0;
}
Trang 6Ở ví dụ này nếu học sinh hiểu rõ về cin sẽ giải quyết bài toán nhanh hơn như sau:
#include <iostream>
using namespace std;
string s,st;
int main()
{
freopen("xaudep.inp","r",stdin);
freopen("xaudep.out","w",stdout);
st="";
while (cin>> s) {st=st+s+' ';}
cout<< st;
return 0;
}
Để giải quyết ví dụ trên, ngoài cách đọc xâu bằng cin hoặc getline chúng ta cũng có thể đọc vào theo kiểu mảng kí tự hoặc mảng xâu như sau:
#include <iostream>
using namespace std;
string s,st;
string a[10000];
int main()
{
freopen("xaudep.inp","r",stdin);
freopen("xaudep.out","w",stdout);
st=""; int i=0;
while (cin>> s) {i++; a[i]=s;}
int n=i;
for (i=1; i<=n; i++)
cout<< a[i]<< ' ';
return 0;
}
b) Hiện tượng trôi lệnh
Khi chúng ta nhập lệnh bằng cin>> biến 1 và câu lệnh nhập sau bằng lệnh getline(cin,biến 2) thì chương trình không dừng lại cho chúng ta nhập dữ liệu cho biến 2
Ta xét chương trình sau:
#include <iostream>
#include <string>
Trang 7using namespace std;
int main(){
int n;
string s2;
cout << " nhap so la=";
cin >> n; cout << n << endl;
cout << "nhap xau s2="; getline(cin,s2); cout << s2 << endl;
cout << "ket thuc" << endl;
return 0;
}
Bạn chạy chương trình trên sẽ thấy ngay Sau khi nhập dữ liệu cho biến
n, chương trình không dừng lại cho ta nhập dữ liệu cho s2 Mà in ngay ra thông báo “Ket thuc” Nguyên nhân là do sau khi nhập dữ liệu cho biến n ta
gõ phím Enter Mã của Enter được lưu trong bộ đệm (chính là ký tự xuống
dòng “\n”) và do đó khi chương trình gặp câu lệnh: getline(cin,s2); nó sẽ đọc
ngay ký tự này và nhận thấy đây là ký tự kết thúc nên nó sẽ loại bỏ ký tự này
ra khỏi bộ đệm mà không đọc vào xâu s2 Sau đó nó sẽ chạy thẳng đến câu lệnh tiếp theo là: cout << "ket thuc" << endl;
Để khắc phục hiện tượng trôi lệnh này thì trước mỗi lệnh getline ta nên
đặt câu lệnh: fflush(stdin);
Câu lệnh này có tác dụng xóa bộ đệm và do đó ta có thể yên tâm là sẽ không bị trôi lệnh nữa
#include <iostream>
#include <string>
using namespace std;
int main(){
int n;
string s2;
cout << " nhap so la=";
cin >> n; cout << n << endl; fflush(stdin);
cout << "nhap xau s2="; getline(cin,s2); cout << s2 << endl;
cout << "ket thuc" << endl;
return 0;
}
2.3.1.2 Sai lầm khi sử dụng các thao tác xử lý trong xâu
a) Lấy chỉ số: Dùng chỉ số của xâu mà không có trong xâu
Lưu ý: Chỉ số trong xâu được đánh từ 0
Trang 8Ví dụ: xâu có n kí tự khi tham chiếu tới phần tử s[n] // trong code block vẫn chạy nhưng khi đưa vào temis chương trình báo lỗi
b) Ghép xâu: Chúng ta có thể ghép 2 xâu hoặc kí tự vào xâu nhưng không được
ghép 2 hằng xâu với nhau
Ví dụ: string s= ‘b’; // kí tự b được gán cho xâu s, lúc này s là xâu “b”;
s1= “123”; s2= “456”; s3= s1+s2; // lúc này s3= “123456”
Lỗi thường gặp: s3= “123”+ “456”; // Báo lỗi
Do hằng xâu+ hằng xâu thì sẽ trả về hằng xâu mà hằng là giá trị không thay đổi
Khắc phục: ta có thể viết
s3= s3+ “123”+ “456”;
s3= “123”+ s3+ “456”;
c) Chèn xâu: s1.insert(vt,s2); // ý nghĩa: Chèn xâu s2 vào xâu s1 bắt đầu từ vị
trí vt( vị trí trong xâu bắt đầu được tính từ 0)
Lỗi thường gặp: s1.insert(0, 'a'); // lỗi vì 'a' là kí tự không phải xâu “a”
d) Tìm kiếm: s1.find(s2); // s2 có thể là xâu hoặc kí tự
Lưu ý: Nếu không tìm thấy thì kết quả trả về giá trị lớn nhất của kiểu unsigned
long long nếu máy tính 64 bit thì kết quả trả về 264 -1 (chú ý vị trí của xâu trong C++ bắt đầu từ 0)
Chính vì vậy trong một số bài toán học sinh viết trực tiếp s1.find(s2) // nếu không tìm thấy s2 trong s1 kết quả trả về rất lớn và số này dương dẫn đến kết quả biên dịch thành công nhưng không cho kết quả hoặc kết quả sai.
Ví dụ: Cho xâu ST chứa các kí tự liên tiếp có độ dài <= 255 ký tự gồm các chữ cái la tinh (gồm cả ký tự in hoa và thường) Yêu cầu hãy xóa tất cả các ký tự ‘a’ hoặc ‘A’ trong xâu ST rồi ghi kết quả xâu sau khi xóa ra màn hình
Khi làm bài toán này học sinh thường hay viết chương trình như sau
#include<bits/stdc++.h>
#include<string>
using namespace std;
string s,s2,s3;
int main()
{
cin>> s; //cout<<s.length();
while ((s.find('a')>=0)||(s.find('A'))>=0)
{s.erase(s.find('a'),1); s.erase(s.find('A'),1);}
cout<<s;
return 0;
}
Trang 9Chương trình cho kết quả như sau:
Khắc phục lỗi trên: ta nên ép kiểu
unsigned long long về int, trong kiểu int không có 264 -1 nên khi ép kiểu nó
sẽ trả về -1 Code minh họa
#include<bits/stdc++.h>
#include<string>
using namespace std;
string s,s2,s3;
int main()
{
cin>> s;
while ((int(s.find('a'))>=0)|| (int(s.find('A'))>=0))
{ s.erase(int(s.find('a')),1); s.erase(int(s.find('A')),1);
} cout<<s;
return 0;
}
2.3.1.3 Sai lầm trong lựa chọn và đánh giá độ phức tạp thuật toán.
Bài toán 1: Cho một xâu chỉ gồm các kí tự liên tiếp từ a đến z Hãy xóa tất cả
các kí tự a trong xâu, biết độ dài xâu <=106
Sai lầm thường gặp: Học sinh thường làm trực tiếp là duyệt tất cả các kí tự trong xâu, nếu gặp kí tự a thì xóa Với yêu cầu 1s cho ra output cần tìm thì cách làm này không hiệu quả, thí sinh chỉ lấy được 1/3 số điểm của bài
Lý do: int main()
{
cin>> s;
while ( int(s.find('a')) >= 0) // độ phức tạp O(n)
{ s.erase(int(s.find('a')),1); // độ phức tạp O(n) }
cout<<s;
return 0;
}
=> độ phức tạp thuật toán là O(n2)
Cách khắc phục:
Trang 10int main() {
cin >>s;
for (i=0; i< s.size(); i++)// độ phức tạp O(n)
if (s[i]!= 'a') st=st+s[i];
cout<< st;
return 0;
}
=> độ phức tạp thuật toán O(n)
Các bài toán tương tự:
Bài 1 Xoá Số (DELNUM.PAS | DELNUM.CPP) [2]
Các bạn nhỏ Trung tâm Anh ngữ ABC Smart đang làm quen với bảng chữ cái và chữ số Tiếng Anh Sau một ngày học về bảng chữ cái và chữ số tiếng Anh thì các bạn nhỏ đã thuộc vanh vách Các bạn liền nghĩ ngay ra một bài toán để đố
các anh chị: Cho một xâuST gồm các chữ cái Latin in thường (‘a’ đến ‘z’) hoặc các chữ số (‘0’ đến ‘9’) Hãy xoá đi tất cả các chữ số trong xâu ST
Dữ liệu: vào từ file DELNUM.INP gồm 1 dòng là xâu ST có độ dài không quá
105 ký tự
Kết quả: ghi ra file DELNUM.OUTlà xâu sau khi xoá tất cả các chữ số.
Ví dụ:
laptrinhccongcongtap1 2
laptrinhccongcongta p
Giới hạn:
+ Có 80% số điểm tương ứng với xâu có độ dài không qua 103
+ 20% số điểm còn lại, không có ràng buộc gì thêm
Code tham khảo
#include <bits/stdc++.h>
using namespace std;
string st, res;
int n;
int main(){
freopen("delnum.inp", "r",stdin);
freopen("delnum.out","w", stdout);
cin >> st;
n = st.size();
res = "";
for(int i = 0; i< n; i++)
if(st[i]>= 'a') res = res + st[i];
Trang 11cout << res;
return 0;
}
Bài 2: Đếm xâu (COUNTST.PAS |COUNTST.CPP)[2]
Các bạn nhỏ Trung tâm Anh ngữ ABC Smart đang làm quen với bảng chữ cái Tiếng Anh Sau một ngày học về bảng chữ cái tiếng Anh thì các bạn nhỏ đã thuộc vanh vách bảng chữ cái Các bạn liền nghĩ ngay ra một bài toán về đếm xâu, ngày mai đi học
để đố các bạn khác trên lớp: Cho một xâu ký tự ST chỉ chứa các chữ cái Latin in thường (‘a’ đến ‘z’), hãy đếm xem trong xâu trên có bao nhiêu xâu “abc” là xâu con của xâu trên (xâu con liên tiếp)
Dữ liệu: vào từ file COUNTST.INP gồm 1 dòng là xâu ST có độ
dài không quá 106 ký tự
Kết quả: ghi ra file COUNTST.OUT một số nguyên là kết quả
của bải toán
Ví dụ:
COUNTST.IN P
COUNTST.OU T
aabcbcabaabc 2
Lưu ý: với bài toán này nhiều học sinh đã nghĩ ngay đến hàm tìm kiếm xự xuất
hiện xâu “abc” nếu tìm thấy thì tăng biến đếm và dồng thời xóa khỏi xâu ban
đầu
Code tham khảo:
#include <bits/stdc++.h>
using namespace std;
string st;
int res, n;
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
freopen("countst.inp","r",stdin);
freopen("countst.out","w",stdout);
cin >> st;
n= st.length();
Trang 12res = 0;
for (int i=0; i<= n-3; i++)
if ((st[i]=='a')&&(st[i+1]=='b')&&(st[i+2]=='c')) res++;
cout << res;
return 0;
}
Bài 3 Biến đổi xâu [2]
Cho một xâu S có độ dài tối đa là 106 ký tự Trong xâu S người ta loại bỏ
sự xuất hiện của một xâu con T có độ dài ≤ 100 ký tự Để làm điều này, người ta tìm sự xuất hiện của T lần đầu tiên trong S và xóa nó Sau đó cứ lặp đi lặp lại quá trình này cho đến khi không còn sự xuất hiện của T trong S Lưu ý rằng việc xóa một lần xuất hiện có thể tạo ra một sự xuất hiện mới của T chưa từng tồn tại trước đó Hãy xác định nội dung cuối cùng của xâu S
Dữ liệu: Vào từ file văn bản BAI3.INP:
- Dòng đầu tiên chứa xâu S
- Dòng thứ hai chứa xâu T Chiều dài của xâu T bé hơn chiều dài của S, và tất cả các kí tự của S và T đều là ký tự thường (trong phạm vi từ a z)
Kết quả: Ghi ra file văn bản BAI3.OUT chỉ một dòng chứa xâu S sau khi đã
xóa bỏ hết T Đảm bảo rằng S sẽ không trở nên xâu rỗng trong quá trình xóa
Ví dụ
Whatthemomooofun Moo
Whatthefun
Ý tưởng: Tao một xâu mới (s1) lần lượt từ các kí tự từ xâu s Nếu
s1.length()>=t.length() thì so sánh s1.copy(s1.length() - t.length(), t.length()) với xâu t Nếu bằng nhau thì thực hiện xóa s1 ngược lại thì tiếp tục tao xâu s1 từ s Kết quả tìm được là xâu s1
Code tham khảo:
#include <iostream>
using namespace std;
string s,st,t,tg;
int main()
{
freopen("bai3.inp","r",stdin);
freopen("bai3.out","w",stdout);
cin>>s;
cin>>t;
st="";