1. Trang chủ
  2. » Giáo Dục - Đào Tạo

Giúp học sinh rèn luyện và nâng cao kĩ năng lập trình qua việc lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán

47 8 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Giúp học sinh rèn luyện và nâng cao kỹ năng lập trình qua việc lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán
Tác giả Nguyễn Thị Tú Anh
Trường học Trường THPT Nguyễn Duy Trinh
Chuyên ngành Tin học
Thể loại Sáng kiến kinh nghiệm
Năm xuất bản 2021
Thành phố Nghi Lộc
Định dạng
Số trang 47
Dung lượng 1,08 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Mục đích nghiên cứu của SKKN - SKKN nêu ra các định hướng giúp học sinh có thể lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán trong một số dạng bài toán quen thuộc trên ngôn ng

Trang 1

1

Së GIÁO DỤC VÀ ĐÀO TẠO NGHỆ AN TRƯỜNG THPT NGUYỄN DUY TRINH

-*** -

SÁNG KIẾN KINH NGHIỆM

“GIÚP HỌC SINH RÈN LUYỆN VÀ NÂNG CAO

KỸ NĂNG LẬP TRÌNH QUA VIỆC LỰA CHỌN THUẬT TOÁN TỐI ƯU PHÙ HỢP VỚI DỮ LIỆU

NGHI LỘC, THÁNG 3/2021

Trang 2

2

MỤC LỤC

I ĐẶT VẤN ĐỀ 3

1 Lý do chọn đề tài 3

2 Mục đích nghiên cứu của SKKN 3

3 Nhiệm vụ nghiên cứu của SKKN 4

4 Đối tượng nghiên cứu của SKKN 4

6 Phương pháp thực hiện 4

7 Đóng góp của SKKN 4

II NỘI DUNG 5

1 Cơ sở lí luận của đề tài 5

2 Thực trạng của vấn đề trước khi áp dụng SKKN 5

2.1 Đặc điểm tình hình 5

2.2 Thực trạng trước khi nghiên cứu 6

3 Các giải pháp giải quyết vấn đề 6

3.1 Cơ sở lý thuyết 7

3.1.1 Độ phức tạp thuật toán 7

3.1.1.1 Tính hiệu quả của thuật toán 7

3.1.1.2 Tại sao cần thuật toán có tính hiệu quả? 7

3.1.1.3 Đánh giá thời gian thực hiện thuật toán 8

3.1.1.4 Các quy tắc đánh giá thời gian thực hiện thuật toán 9

3.1.1.5 Ước lượng độ phức tạp thuật toán tương ứng với độ lớn dữ liệu 10

3.1.2 Lựa chọn thuật toán 11

3.2 Lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán 11

3.2.1 Dạng 1: Các bài toán liên quan đến số học 12

3.2.2 Dạng 2: Sử dụng thuật toán sắp xếp 20

3.2.2.1 Bài toán sắp xếp 20

3.2.2.2 Bài tập ví dụ 23

3.2.3 Dạng 3: Sử dụng thuật toán tìm kiếm 32

3.2.3.1 Bài toán tìm kiếm 32

3.2.3.2 Bài tập ví dụ 34

3.2.4 Bài tập luyện tập 40

4 Tính mới của SKKN 43

5 Hiệu quả của SKKN 44

6 Những hướng phát triển của đề tài 44

III KẾT LUẬN VÀ KIẾN NGHỊ 45

1 Kết luận 45

2 Kiến nghị 45

TÀI LIỆU THAM KHẢO 46

Trang 3

Ở trường phổ thông hàng năm thường diễn ra các cuộc thi học sinh giỏi các môn học trong đó có môn Tin học Bồi dưỡng học sinh giỏi là một nhiệm vụ rất cần thiết và quan trọng đối với mỗi giáo viên Do đó, việc nghiên cứu, tìm tòi, tích lũy kiến thức là công việc thường nhật của mỗi giáo viên nhằm nâng cao trình độ chuyên môn nghiệp vụ, tích lũy kinh nghiệm cho bản thân.Trong những năm gần đây mức

độ yêu cầu của đề thi học sinh giỏi ngày càng nâng cao, môn Tin học cũng vậy Các bài toán trong đề thi thường yêu cầu giải quyết dữ liệu vào lớn trong khi đòi hỏi thời gian thực hiện nhanh (thường không quá 1 giây) Những bài toán này thường khó đối với học sinh, chương trình của các em thường không thực hiện được hết các test yêu cầu, đặc biệt là đối với học sinh không chuyên Bởi vì vừa giải quyết vấn đề dữ liệu, vừa phải giải quyết vấn đề thời gian Do đó giáo viên bồi dưỡng thường phải giúp học sinh rèn luyện kĩ năng lập trình Nghĩa là không chỉ dừng lại ở việc hướng dẫn học sinh giải được bài toán, mà còn phải giúp học sinh rèn luyện được thói quen

tư duy, cải tiến thuật toán để chương trình tối ưu nhất có thể Trong lập trình tin học

đã có rất nhiều phương pháp giải các bài toán đã được nêu ra Tuy nhiên rất ít tài liệu trình bày cụ thể về cách lựa chọn thuật toán thế nào sao cho phù hợp với dữ liệu bài toán để đạt độ tối ưu, đảm bảo giải quyết được yêu cầu của bài toán đặt ra Qua quá trình giảng dạy, học tập, tìm tòi và đặc biệt là tham gia bồi dưỡng học sinh giỏi nhiều năm qua, tôi đã tích lũy được một số kinh nghiệm về vấn đề này Do

đó, tôi quyết định viết sáng kiến kinh nghiệm: “Giúp học sinh rèn luyện và nâng

cao kĩ năng lập trình qua việc lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán”

2 Mục đích nghiên cứu của SKKN

- SKKN nêu ra các định hướng giúp học sinh có thể lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán trong một số dạng bài toán quen thuộc trên ngôn ngữ lập trình C++

- Từ đó bồi dưỡng học sinh năng lực giải quyết vấn đề trong giải toán Tin học, đồng thời rèn luyện và nâng cao kĩ năng lập trình cho các em Đặc biệt là học sinh tham gia dự thi học sinh giỏi cấp tỉnh THCS, THPT hoặc thi vào các trường chuyên

Trang 4

4

3 Nhiệm vụ nghiên cứu của SKKN

- SKKN phân tích các thuật toán trong các dạng toán quen thuộc, so sánh độ phức tạp thuật toán và định hướng lựa chọn thuật toán tối ưu trong các trường hợp

dữ liệu cụ thể nhằm giải bài toán hiệu quả nhất

- Minh họa bằng các ví dụ cụ thể, liên hệ các đề thi vào trường chuyên, đề thi học sinh giỏi tỉnh thời gian qua

4 Đối tượng nghiên cứu của SKKN

- Độ phức tạp thuật toán và giải pháp lựa chọn thuật toán tối ưu trong các dạng bài toán quen thuộc trên ngôn ngữ lập trình C++

- Phương pháp bồi dưỡng năng lực giải quyết vấn đề cho học sinh

5 Phạm vi nghiên cứu của SKKN

- Chương trình Tin học THCS, THPT để bồi dưỡng học sinh giỏi Tin học và thi vào trường chuyên THPT

- Cách giải quyết vấn đề của học sinh

6 Phương pháp thực hiện

- Nghiên cứu tài liệu

- Thực hiện bồi dưỡng học sinh giỏi

- Trao đổi chuyên môn với bạn bè, đồng nghiệp để giải quyết vấn đề

- Thực nghiệm sư phạm

7 Đóng góp của SKKN

- Sáng kiến trình bày được một số kinh nghiệm giúp học sinh rèn luyện và nâng cao kĩ năng lập trình qua việc lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán yêu cầu của các dạng bài toán quen thuộc, thường có trong các đề thi học sinh giỏi Tin học

- Là tài liệu tham khảo để bồi dưỡng học sinh giỏi Tin học, học sinh thi trường chuyên có hiệu quả

- Giúp bồi dưỡng học sinh năng lực giải quyết vấn đề trong giải toán Tin học, rèn luyện và nâng cao kỹ năng lập trình cho học sinh

Trang 5

II NỘI DUNG

1 Cơ sở lí luận của đề tài

Nghị quyết hội nghị Trung ương VIII khóa XI đã nêu: “Đối với giáo dục phổ thông tập trung phát triển trí tuệ, thể chất, hình thành phẩm chất, năng lực công dân, phát hiện và bồi dưỡng năng khiếu, định hướng nghề nghiệp cho học sinh Nâng cao chất lượng giáo dục toàn diện, chú trọng giáo dục lý tưởng truyền thống đạo đức, lối sống, ngoại ngữ, tin học, năng lực và kỹ năng thực hành, vận dụng kiến thức vào thực tiễn, phát triển khả năng sáng tạo và tự học, khuyến khích học tập suốt đời" Toàn ngành đang ra sức phấn đấu xây dựng chương trình sách giáo khoa mới, đổi mới phương pháp dạy học theo định hướng hình thành và phát triển năng lực

Mục tiêu của môn Tin học trong chương trình giáo dục phổ thông là giúp học sinh hình thành và phát triển năng lực Tin học, đổi mới phương pháp dạy học nhằm góp phần thực hiện tốt mục tiêu của ngành giáo dục trong giai đoạn mới Môn Tin học là môn học đặc thù có nhiều kiến thức khó Đặc biệt là phần học lập trình ở lớp

11 Đây cũng là phần kiến thức đề thi học sinh giỏi tỉnh môn Tin học Trong một số năm gần đây do sự phát triển nhanh chóng của khoa học kỹ thuật, tốc độ xử lí của máy tính ngày càng cao Các đề thi trong các cuộc thi lập trình cũng ngày càng đòi hỏi cao hơn về thời gian thực hiện, về độ lớn dữ liệu…Nên gây rất nhiều khó khăn trong việc ôn luyện cho thầy và trò Một trong những vấn đề luôn đặt ra là làm thế nào để lựa chọn được thuật toán tối ưu đảm bảo đáp ứng toàn bộ yêu cầu dữ liệu vào của bài toán Do đó đòi hỏi giáo viên cần có giải pháp để giúp học sinh giải quyết vấn đề này nhằm nâng cao chất lượng công tác bồi dưỡng học sinh giỏi bộ môn Tin học hiện nay

2 Thực trạng của vấn đề trước khi áp dụng SKKN

2.1 Đặc điểm tình hình

* Thuận lợi:

- Với sự phát triển nhanh của ngành công nghệ thông tin, máy tính ngày càng

có tốc độ xử lý cao đáp ứng được yêu cầu xử lý các bài toán có dữ liệu lớn trong thời gian thực hiện ngắn

- Học sinh và giáo viên có thể dễ dàng tìm hiểu nguồn tài liệu để học tập tham khảo

* Khó khăn:

- Những kiến thức trong chương trình Tin học phổ thông còn hạn chế hoặc không đủ đáp ứng cho việc giải một số bài toán trong các kỳ thi học sinh giỏi Tỉnh khi có yêu cầu dữ liệu lớn cùng thời gian thực hiện ngắn

- Các tài liệu tổng hợp các cách để giải quyết các bài toán yêu cầu cao như vậy chưa có nhiều để học sinh tham khảo, ôn luyện

Trang 6

6

2.2 Thực trạng trước khi nghiên cứu

Với sự phát triển nhanh về tốc độ của máy tính hiện nay thì đề thi học sinh giỏi Tin học cũng đòi hỏi ngày càng nâng cao hơn Đặc biệt về mặt thời gian thực hiện

và độ lớn của dữ liệu đầu vào Đa số giáo viên gặp khó khăn trong việc hướng dẫn học sinh giải bài toán thế nào để đạt được trọn vẹn yêu cầu của bài toán Những năm trước đây khi bồi dưỡng học sinh thi học sinh giỏi tỉnh tôi cũng đã chú trọng nhiều hơn đến cải tiến chương trình tối ưu, tuy nhiên hiệu quả mang lại chưa cao

Nguyên nhân chủ yếu của thực trạng này là giáo viên chưa có phương pháp giảng dạy phù hợp với vấn đề này Cụ thể giáo viên thường giúp học sinh cải tiến, làm mịn dần thuật toán của từng bài cụ thể Chưa phân loại thành các dạng bài, định hướng học sinh đánh giá thuật toán và cải thiện thuật toán nhưng chưa ước lượng với mỗi mức dữ liệu thì cần phải xây dựng thuật toán có độ phức tạp tương ứng như thế nào để có thể đáp ứng được…Chính vì vậy, học sinh thường cố gắng tinh chỉnh, tìm cách giải tối ưu nhưng lại không chắc chắn rằng bài giải của mình đã đáp ứng trọn vẹn dữ liệu yêu cầu của bài toán hay chưa Cho nên học sinh sẽ gặp khó khăn trong việc vận dụng vào các bài toán tương tự, cũng như không linh hoạt trong các bài toán mới Kết quả là học sinh vẫn giải được các bài toán nhưng không đạt điểm

số cao nên thường chỉ đạt giải khuyến khích trở xuống

Vì vậy, tôi vẫn luôn duy trì hướng dẫn, uốn nắn các em biết cách giải bài, rồi rèn luyện kĩ năng tinh chỉnh, làm mịn dần thuật toán, kết hợp biết cách nhận biết độ lớn dữ liệu bài toán và lựa chọn thuật toán phù hợp để đạt hiệu quả nhất Trong quá trình giảng dạy tôi còn cho học sinh kiểm chứng qua đánh giá của phần mềm Themis

để dễ dàng so sánh và ghi nhớ Từ đó đạt hiệu quả cao hơn

3 Các giải pháp giải quyết vấn đề

Có những bài toán nhiều khi có vẻ đơn giản, đa số học sinh có thể giải được nhưng khi thực hiện với dữ liệu lớn thì không đáp ứng được thời gian yêu cầu hoặc không thể thực hiện được tất cả các test yêu cầu Lúc này đòi hỏi phải tìm được thuật toán tối ưu nhất

Tối ưu hoá là một đòi hỏi thường xuyên trong quá trình giải quyết các bài toán tin học Tối ưu hoá thuật toán là một công việc yêu cầu tư duy thuật toán rất cao, cùng với khả năng sử dụng thuần thục các cấu trúc dữ liệu Vì vậy, việc tìm ra thuật toán tối ưu là không dễ chút nào Tối ưu hoá thường được tiến hành theo 2 góc độ

đó là tối ưu theo không gian có nghĩa là tối ưu không gian lưu trữ (bộ nhớ), và tối

ưu theo thời gian có nghĩa là giảm độ phức tạp thuật toán, giảm các bước xử lý trong chương trình…Tuy nhiên không phải lúc nào ta cũng có thể đạt được đồng thời cả 2 điều đó, trong nhiều trường hợp tối ưu về thời gian sẽ làm tăng không gian lưu trữ,

và ngược lại Bài toán trong Tin học thì đa dạng, phong phú Đa số giáo viên gặp khó khăn trong việc hướng dẫn học sinh thiết kế thuật toán hoặc lựa chọn thuật toán nào để có thể giảm độ phức tạp của thuật toán, đồng thời phù hợp với dữ liệu và yêu cầu của đề bài Đa số khi chấm bài, dùng test chấm mới biết được chương trình đáp

Trang 7

ứng được bao nhiêu test so với yêu cầu của đề ra Điều này chỉ thực hiện được khi

ôn luyện cho các em, còn khi các em đi thi thì không tự ước lượng được bài làm đạt kết quả thế nào Có một cách để ước lượng được chương trình giải được có thể đáp ứng được yêu cầu của đề hay không (nghĩa là đáp ứng được khoảng dữ liệu vào bao nhiêu), đó là đánh giá độ phức tạp của thuật toán trong chương trình Như vậy để có thể tối ưu hóa thuật toán trước tiên chúng ta phải hiểu rõ về độ phức tạp của thuật toán và cách đánh giá ước lượng thuật toán so với dữ liệu đầu vào yêu cầu

3.1 Cơ sở lý thuyết

3.1.1 Độ phức tạp thuật toán

3.1.1.1 Tính hiệu quả của thuật toán

Khi giải một bài toán, chúng ta cần chọn trong số các thuật toán một thuật toán mà chúng ta cho là “tốt” nhất Vậy dựa trên cơ sở nào để đánh giá thuật toán này “tốt” hơn thuật toán kia? Thông thường ta dựa trên hai tiêu chuẩn sau:

1 Thuật toán đơn giản, dễ hiểu, dễ cài đặt (dễ viết chương trình)

2 Thuật toán hiệu quả: Chúng ta thường đặc biệt quan tâm đến thời gian thực hiện của thuật toán (gọi là độ phức tạp tính toán), bên cạnh đó chúng ta cũng quan tâm tới dung lượng không gian nhớ cần thiết để lưu giữ các dữ liệu vào, ra và các kết quả trung gian trong quá trình tính toán

Khi viết chương trình chỉ để sử dụng một số ít lần thì tiêu chuẩn (1) là quan trọng, nhưng nếu viết chương trình để sử dụng nhiều lần, cho nhiều người sử dụng thì tiêu chuẩn (2) lại quan trọng hơn Trong trường hợp này, dù thuật toán có thể phải cài đặt phức tạp, nhưng ta vẫn sẽ lựa chọn để nhận được chương trình chạy nhanh hơn, hiệu quả hơn

3.1.1.2 Tại sao cần thuật toán có tính hiệu quả?

Kĩ thuật máy tính tiến bộ rất nhanh, ngày nay các máy tính lớn có thể đạt tốc

độ tính toán hàng nghìn tỉ phép tính trong một giây Vậy có cần phải tìm thuật toán hiệu quả hay không? Chúng ta xem ví dụ bài toán kiểm tra tính nguyên tố của một số nguyên dương n (n ≥ 2)

Trang 8

8

Dễ dàng nhận thấy rằng, nếu n là một số nguyên tố chúng ta phải mất n- 2

phép toán chia lấy dư (%) Giả sử một siêu máy tính có thể tính được trăm nghìn tỉ

(1014) phép % trong một giây, như vậy để kiểm tra một số khoảng 25 chữ số mất

Như vậy để kiểm tra một số khoảng 25 chữ số mất khoảng

3.1.1.3 Đánh giá thời gian thực hiện thuật toán

Có hai cách tiếp cận để đánh giá thời gian thực hiện của một thuật toán Cách

thứ nhất bằng thực nghiệm, chúng ta viết chương trình và cho chạy chương trình với

các dữ liệu vào khác nhau trên một máy tính Cách thứ hai bằng phương pháp lí

thuyết, chúng ta coi thời gian thực hiện thuật toán như hàm số của cỡ dữ liệu vào

(cỡ của dữ liệu vào là một tham số đặc trưng cho dữ liệu vào, nó có ảnh hưởng quyết

định đến thời gian thực hiện chương trình Ví dụ đối với bài toán kiểm tra số nguyên

tố thì cỡ của dữ liệu vào là số cần kiểm tra; hay với bài toán sắp xếp dãy số, cỡ của

dữ liệu vào là số phần tử của dãy) Thông thường cỡ của dữ liệu vào là một số nguyên

dương n , ta sử dụng hàm số T(n) trong đó n là cỡ của dữ liệu vào để biểu diễn thời

gian thực hiện của một thuật toán

Xét ví dụ bài toán kiểm tra tính nguyên tố của một số nguyên dương n (cỡ dữ

liệu vào là n ), nếu n là một số chẵn (n>2) chỉ cần một lần thử chia 2 để kết luận n

không phải là số nguyên tố Nếu n (n>3) không chia hết cho 2 nhưng lại chia hết

cho 3 thì cần 2 lần thử (chia 2 và chia 3) để kết luận n không nguyên tố Còn nếu n

là một số nguyên tố thì thuật toán phải thực hiện nhiều lần thử nhất

Trong tài liệu này, chúng ta hiểu hàm số T(n) là thời gian nhiều nhất cần thiết

để thực hiện thuật toán với mọi bộ dữ liệu đầu vào cỡ n

Sử dụng kí hiệu toán học ô lớn để mô tả độ lớn của hàm Giả sử n là một số

nguyên dương, T(n) và f(n) là hai hàm thực không âm Ta viết T(n)= O(f(n)) nếu và

chỉ nếu tồn tại các hằng số dương c và n0 , sao cho T(n)≤ c x f(n), mọi n ≥ n 0

Nếu một thuật toán có thời gian thực hiện T(n)= O(f(n)) chúng ta nói rằng

thuật toán có thời gian thực hiện cấp f(n)

25 14

10

Trang 9

Ví dụ: Giả sử T(n) = n 2 + 2n, ta có n 2 + 2n ≤ 3n 2 với mọi n ≥ 1

Vậy T(n) = O(n 2) trong trường hợp này ta nói thuật toán có thời gian thực hiện

cấp n 2

3.1.1.4 Các quy tắc đánh giá thời gian thực hiện thuật toán

Để đánh giá thời gian thực hiện thuật toán được trình bày bằng ngôn ngữ C++, ta cần biết cách đánh giá thời gian thực hiện các câu lệnh của C++

Trước tiên, chúng ta hãy xem xét các câu lệnh chính trong C++ Các câu lệnh trong C++ được định nghĩa như sau:

1 Các phép gán, đọc, viết là các câu lệnh (được gọi là lệnh đơn)

2 Nếu S1, S2, , Sm là câu lệnh thì

{ S1; S2; …; Sm; }

là câu lệnh (được gọi là lệnh hợp thành hay khối lệnh)

3 Nếu S1 và S2 là các câu lệnh và E là biểu thức lôgic thì

If (E) S1; else S2;

là câu lệnh (được gọi là lệnh rẽ nhánh hay lệnh If)

4 Nếu S là câu lệnh và E là biểu thức lôgic thì

While (E) S;

là câu lệnh (được gọi là lệnh lặp điều kiện trước hay lệnh While)

5 Nếu S1, S2,…,Sm là các câu lệnh và E là biểu thức lôgic thì

Do

S1; S2; …; Sm;

While (E);

là câu lệnh (được gọi là lệnh lặp điều kiện sau hay lệnh Do While)

6 Nếu S là lệnh, E1 và E2 là các biểu thức cùng một kiểu thứ tự đếm được Thì For (i=E1; i<= E2;i++) S;

là câu lệnh (được gọi là lệnh lặp với số lần xác định hay lệnh For)

Để đánh giá, chúng ta phân tích chương trình xuất phát từ các lệnh đơn, rồi đánh giá các lệnh phức tạp hơn, cuối cùng đánh giá được thời gian thực hiện của chương trình, cụ thể:

1 Thời gian thực hiện các lệnh đơn: gán, đọc, viết là O(1)

2 Lệnh hợp thành: giả sử thời gian thực hiện của S1, S2,…,Sm tương ứng là

Trang 10

10

O((f 1 (n)), O((f 2 (n)), …, O((f m (n)) Khi đó thời gian thực hiện của lệnh hợp

thành là O(max(f 1 (n), f 2 (n), …, f m (n)))

3 Lệnh If: giả sử thời gian thực hiện của S1, S2 tương ứng là

Khi đó thời gian thực hiện của lệnh If là: O((f 1 (n)), O((f 2 (n)) Khi đó thời gian

thực hiện của lệnh If là O(max(f 1 (n), f 2 (n)))

4 Lệnh lặp While: giả sử thời gian thực hiện lệnh S (thân của lệnh While) là

O(f(n)) và g(n) là số lần lặp tối đa thực hiện lệnh S Khi đó thời gian thực hiện

lệnh While là O(f(n)g(n))

5 Lệnh lặp Repeat: giả sử thời gian thực hiện khối lệnh { S1; S2;…; Sm; }

là O(f(n)) và g(n) là số lần lặp tối đa Khi đó thời gian thực hiện lệnh Do While

là O(f(n)g(n))

6 Lệnh lặp For: giả sử thời gian thực hiện lệnh S là O(f(n)) và g(n) là số lần lặp tối đa Khi đó thời gian thực hiện lệnh For là O(f(n)g(n))

3.1.1.5 Ước lượng độ phức tạp thuật toán tương ứng với độ lớn dữ liệu

Độ phức tạp thuật toán là một hàm phụ thuộc đầu vào Tuy nhiên trong những ứng dụng thực tiễn, chúng ta không cần biết chính xác hàm này mà chỉ cần biết một ước lượng đủ tốt của chúng Việc ước lượng rất quan trọng trong việc giải quyết bài toán lập trình thi đấu, đòi hỏi nhanh nhạy và có quyết định chính xác ngay từ đầu

Độ phức tạp của thuật toán có thể có những giá trị là O(1), O(log n), O(n), O(nlogn), O(n2), O(n3)… Và thời gian thực hiện được so sánh như sau:

O(1)< O(log n) < O(n) < O(nlogn) < O(n2) < O(n3)…

Sau đây là bảng ước lượng (*) giới hạn dữ liệu vào tương ứng với độ phức tạp của thuật toán đảm bảo thực hiện trong thời gian tối đa 1 giây

Độ phức tạp O(1) O(logn) O(n) O(nlogn) O(n2) O(n3)

Ước lượng n

tối đa

Không ảnh hưởng thời gian, thường chỉ phụ thuộc giới hạn kiểu dữ liệu

108 4.107 104 500

Với bảng ước lượng1 này giáo viên có thể hướng dẫn học sinh xác định độ phức tạp của thuật toán phải đạt được để đảm bảo thời gian thực hiện Từ đó yêu cầu học sinh phải suy luận, tư duy để đạt được yêu cầu của bài toán Hơn nữa, sau khi hoàn thành chương trình học sinh dù chưa chấm bằng test vẫn có thể dự đoán được kết quả hoàn thành bao nhiêu so với giới hạn yêu cầu của bài toán

1 Theo https://vnoi.info/wiki/translate/topcoder/Computational-Complexity-Section-1.md

Trang 11

3.1.2 Lựa chọn thuật toán

Các bước để giải một bài toán trên máy tính:

Bước 1: Xác định bài toán

Bước 2: Lựa chọn hoặc thiết kế thuật toán

Bước 3: Viết chương trình

Bước 4: Kiểm thử

Bước 5: Viết tài liệu

Để có chương trình đúng được thực hiện ở bước 3, 4, 5 thì người lập trình phải làm tốt ở bước 1, 2

Bước 1: Xác định bài toán

Là xác định Input, Output của bài toán Khi xác định Input của bài toán ta quan tâm đến độ lớn của dữ liệu vào vì nó ảnh hưởng nhiều đến việc lựa chọn hoặc thiết kế thuật toán phù hợp ở bước 2

Bước 2: Lựa chọn hoặc thiết kế thuật toán

- Trường hợp 1: Trong trường hợp bài toán chưa có cách giải thì ta thiết kế thuật toán Khi thiết kế thuật toán ngoài yêu cầu đảm bảo tính đúng đắn, còn phải phân tích dữ liệu vào để ước lượng độ phức tạp thuật toán cho phép phù hợp, có thể đảm bảo giải quyết yêu cầu của bài toán

Ví dụ: Dữ liệu vào yêu cầu cỡ 108 thì độ phức tạp của chương trình cho phép

tối đa là O(n)

- Trường hợp 2: Trong trường hợp bài toán có nhiều cách giải thì lựa chọn thuật toán tối ưu hơn về thời gian thực hiện, bộ nhớ, độ phức tạp…

3.2 Lựa chọn thuật toán tối ưu phù hợp với dữ liệu bài toán

Trong giới hạn sáng kiến này tôi muốn trình bày kinh nghiệm về việc hướng dẫn học sinh lựa chọn một thuật toán của những dạng bài toán đã có nhiều thuật toán

để giải Sao cho sự lựa chọn đó phù hợp với độ lớn dữ liệu vào của bài toán để đảm bảo giải quyết bài toán tối ưu, hiệu quả nhất Từ đó giúp học sinh rèn luyện và nâng cao kĩ năng lập trình

Khi hướng dẫn học sinh làm bài toán mới tôi thường yêu cầu học sinh làm theo các trình tự sau:

Bước 1: Xác định bài toán (xác định Input, Output)

Bước 2: Xác định đặc điểm dữ liệu (như số lượng phần tử, kiểu dữ liệu của phần tử, độ lớn của phần tử )

Bước 3: So sánh giữa các thuật toán có thể áp dụng cho bài toán (gồm đánh giá độ phức tạp, thời gian thực hiện, khả năng đảm bảo yêu cầu phù hợp với dữ liệu của bài toán)

Trang 12

12

Bước 4: Đánh giá và lựa chọn thuật toán hiệu quả nhất

Bước 5: Cài đặt thuật toán đã lựa chọn bằng ngôn ngữ C++

3.2.1 Dạng 1: Các bài toán liên quan đến số học

Trong nhiều bài toán tin học nếu biết suy luận dựa theo các định lí, công thức, kết quả… của toán học có thể cho ta cách giải rất tối ưu Những cách giải đó có thể đáp ứng với dữ liệu lớn Chúng ta xét các ví dụ sau:

Ví dụ 1:

TỔNG BÌNH PHƯƠNG 2

Lại là tính tổng! Cô giáo yêu cầu Thắng phải hoàn thành một bài toán về tính tổng, nhưng do nghỉ phòng chống dịch Covid-19 thời gian dài, Thắng quên kiến thức

về lĩnh vực này nên đành nhờ đến tài năng của các bạn với bài toán như sau:

Cho số nguyên dương N (N ≤ 105)

Yêu cầu: Tính tổng S(N) = 12 + 22 +…+ N2

Dữ liệu vào: Từ tệp văn bản TONGBP.INP gồm:

Dòng đầu chứa số nguyên dương T là số lượng test (T ≤ 105)

T dòng tiếp theo, mỗi dòng là một số nguyên dương N

Kết quả: Ghi ra tệp văn bản TONGBP.OUT gồm T dòng, với mỗi dòng là giá trị tổng

Bước 1: Xác định bài toán

Input: số nguyên dương T là số lượng test (T ≤ 105),số nguyên dương N (N ≤ 105)

Output: T dòng, với mỗi dòng là giá trị tổng S(N) tương ứng

2 Đề thi vào lớp 10 Chuyên Phan Bội Châu –Năm học 2020-2021

Trang 13

Bước 2: Đặc điểm dữ liệu

Dữ liệu vào yêu cầu tính tổng S(N) với N ≤ 105, hơn nữa lại có T ≤ 105 test Kiểu dữ liệu T, N nguyên dương

Bước 3: Thuật toán có thể giải bài toán

Cách 1: -Tính lần lượt mỗi test, với mỗi test tính tổng bình phương các số từ 1

Bước 4: Đánh giá, lựa chọn thuật toán

Với cách 1, độ phức tạp là O(n2) Đối chiếu theo bảng ước lượng (*) đã nêu với

thời gian thực hiện tối đa 1 giây thì đáp ứng được giới hạn với 1 ≤ N, T ≤ 103 chiếm

80% số test theo yêu cầu của đề

Với cách 2, độ phức tạp O(n) đáp ứng được thời gian thực hiện tối đa 1 giây

cho cả 20% số test còn lại với 10 3 < N, T ≤ 105

Kết luận: Lựa chọn cách 2 để giải được 100% số test theo yêu cầu của đề bài

Bước 5: Cài đặt chương trình

Code tham khảo

#include <bits/stdc++.h>

using namespace std;

int main()

{

freopen ("tongbp.inp" , "r" , stdin);

freopen ("tongbp.out" , "w" , stdout);

Trang 14

14

cin >> t;

cout << (t * (t + 1) * (2 * t + 1)) / 6 << '\n'; }

return 0;

}

Kết luận: Đối với những bài toán dạng số học thế này, nếu dữ liệu nhỏ (cỡ

≤104) thì có thể giải theo cách thông thường Trường hợp dữ liệu lớn hơn thường đòi hỏi người giải phải linh hoạt vận dụng các suy luận, công thức…trong toán học để giải thì mới đáp ứng được toàn bộ yêu cầu của bài toán

Ví dụ 2:

Thuật toán: Kiểm tra tính nguyên tố của một số nguyên dương (N>0)

Đây là thuật toán cơ bản, quen thuộc và thường được sử dụng để giải các bài toán trong các đề thi Tin học Tuy nhiên, đa số học sinh chưa biết cách vận dụng linh hoạt thuật toán này sao cho phù hợp với đề ra

Thực tế có rất nhiều cách để có thể thực hiện kiểm tra tính nguyên tố của một

số nguyên dương Tuy nhiên, có 2 cách giải tối ưu thường được sử dụng là:

Cách 1: Kiểm tra nguyên tố theo cách thông thường

Nhận xét thấy nếu 𝑛 có một ước nguyên dương 𝑖 thì nó sẽ có ước nguyên dương 𝑛/𝑖 Từ đó ta chỉ cần kiểm tra xem nếu n không chia hết cho các phần tử từ

2 đến √𝑛 thì 𝑛 là số nguyên tố

Ta có hàm kiểm tra nguyên tố như sau:

bool check(long long x)

{if (x==1) return false;

for(long long i=2;i<=sqrt(x);i++)

if (x%i==0) return false;

return true;

}

Thuật toán này có độ phức tạp là O(√𝑛), nếu chỉ kiểm tra một số nguyên tố thì

có thể thực hiện với n cỡ 1016 trong 1 giây

Tuy nhiên, nếu bài toán yêu cầu liệt kê các số nguyên tố nhỏ hơn 𝑛, khi đó ta

có thể sử dụng hàm kiểm tra nguyên tố ở trong cách 1 như sau:

for(long long i=1; i<=n; i++)

if (check(i)) cout<<i<<endl;

Trang 15

Ta thấy độ phức tạp lúc này là O(n√𝑛), với 𝑛 cỡ 107 trường hợp trên đã có cỡ

1010 lệnh thực thi Do đó việc sử dụng thuật toán ở trên sẽ vượt quá tốc độ xử lý của máy tính hiện nay vì tốc độ máy tính hiện tại chỉ có thể thực hiện tối đa 108phép toán trên một giây

Theo bảng ước lượng đã nêu thì với dữ liệu vào 𝑛 cỡ 107 thì độ phức tạp phải

là O(n) Vì vậy trong trường hợp này ta cần sử dụng thuật toán khác đó là sàng nguyên tố Eratosthenes

Như vậy thuật toán kiểm tra nguyên tố theo cách này có:

Ưu điểm:

dùng để kiểm tra ít số

Nhược điểm:

- Hạn chế đối với bài toán liệt kê các số nguyên tố với số lượng phần tử lớn

Cách 2: Kiểm tra nguyên tố bằng sàng nguyên tố Eratosthenes

Sàng nguyên tố Eratosthenes là một thuật toán giúp nhanh chóng liệt kê các số

nguyên tố Đây là một thuật toán tìm số nguyên tố tối ưu khi muốn tìm tất cả các số nguyên tố nhỏ hơn một số N cho trước (N >=2)

Ý tưởng của thuật toán sàng nguyên tố Eratosthenes

Dựa theo lý thuyết về số nguyên tố: Một số nguyên tố là số chỉ có 2 ước là 1

và chính nó Do vậy, nếu ta xác định được số x là số nguyên tố, ta có thể kết luận mọi số chia hết cho x đều không phải số nguyên tố Do đó ta đã loại bỏ được rất nhiều số mà không cần kiểm tra

Ví dụ:

Số 2 là số nguyên tố => các số 4, 6, 8, 10…không phải số nguyên tố

Số 3 là số nguyên tố => các số 9, 15, 21…không phải số nguyên tố (Do 6, 12,

18 đã bị loại ở số 2)

Thuật toán sàng nguyên tố Eratosthenes

1 Tạo mảng đánh dấu cho tất cả các phần tử từ 2 đến N và mặc định tất cả đều

là số nguyên tố

2 Xét số đầu tiên tìm được là số nguyên tố – giả sử x, đánh dấu tất cả các ước của x: 2x, 3x, 4x,… trong đoạn [x, N] không phải số nguyên tố

3 Tìm số tiếp theo được đánh dấu là số nguyên tố trong [x, N] Nếu không còn

số nào, thoát chương trình Nếu còn, gán nó bằng x và lặp lại bước 2

4 Khi kết thúc giải thuật, các số không bị đánh dấu là các số nguyên tố

Ta có hàm kiểm tra nguyên tố dùng sàng nguyên tố Eratosthenes như sau:

Trang 16

- Khi i=2, vòng lặp trong lặp N/2 lần

- Khi i=3, vòng lặp trong lặp N/3 lần

- Khi i=5, vòng lặp trong lặp N/5 lần

Trang 17

So sánh Kiểm tra nguyên tố Sàng nguyên tố

(thời gian thực hiện ≤1s)

- Kiểm tra tính nguyên tố của ít số Hoặc kiểm tra

số lượng phần tử khoảng n≤ 105

- Kiểm tra và liệt kê nhiều số nguyên tố -Kiểm tra số lượng phần

tử khoảng n≤ 107

Xét một số ví dụ để so sánh việc sử dụng 2 thuật toán trên:

Bài 1: - Số lượng số nguyên tố3

Yêu cầu: Cho dãy số a1, a2, , an Hãy đếm số nguyên tố có mặt trong dãy đã cho

Dữ liệu:

- Dòng đầu tiên ghi số nguyên không âm n (0 < n ≤ 103)

- Dòng thứ 2 ghi n số nguyên không âm kiểu 64 – bit

Kết quả: in ra số lượng số nguyên tố có mặt trong dãy

- Output: Số lượng số nguyên tố trong dãy a1, a2, , an

Bước 2: Đặc điểm dữ liệu:

- Dữ liệu vào yêu cầu thực hiện đếm số nguyên tố với dãy số nguyên không âm

có giá trị ai ≤ 108 và n≤103 trong thời gian ≤ 1 giây

Bước 3: Các cách giải có thể lựa chọn:

- Cách 1: Với mỗi ai kiểm tra tính nguyên tố của nó bằng thuật toán kiểm tra nguyên tố thông thường Độ phức tạp tính được là O(𝑛√𝑛)

Trang 18

18

- Cách 2: Sử dụng sàng nguyên tố và kiểm tra từng ai có thuộc mảng đã sàng

hay không Độ phức tạp tính được O(max(nlogn,n) = O(nlogn)

Bước 4: Đánh giá, lựa chọn:

- Đối chiếu với bảng ước lượng (*) thì với độ phức tạp của cách 1 và cách 2 đều đáp ứng được thời gian thực hiện ≤ 1 giây Như vậy ta có thể sử dụng cách nào cũng được Tuy nhiên ta nên dùng thuật toán 1 vì tính đơn giản của nó

Bước 5: Viết chương trình:

Code tham khảo:

#include <bits/stdc++.h>

using namespace std;

#define nmax 1009

int a[nmax];

bool check(long long x)

{if (x==1) return false;

for(long long i=2;i<=sqrt(x);i++)

if (x%i==0) return false;

Trang 19

Bài 2: Số nguyên tố trong đoạn4

Yêu cầu: Đếm số lượng số nguyên tố trong đoạn [a; b]

Dữ liệu: Một dòng ghi hai số nguyên a, b với 0 < a, b ≤ 107

Kết quả: Một dòng là số lượng số nguyên tố trong đoạn [a; b]

Bước 1: Xác định bài toán

- Input: hai số nguyên a, b với 0 < a, b ≤ 107

- Output: số lượng số nguyên tố trong đoạn [a; b]

Bước 2: Đặc điểm dữ liệu:

- Dữ liệu vào a, b ≤ 107, trường hợp lớn nhất là phải xét 107 số, với mỗi số

- Cách 2: Sử dụng sàng nguyên tố và kiểm tra từng ai có thuộc mảng đã sàng

hay không Độ phức tạp tính được O(max(nlogn,n) = O(nlogn)

Bước 4: Đánh giá, lựa chọn

- Đối chiếu với bảng ước lượng (*) thì với độ phức tạp của cách 1 (trường hợp xấu nhất là cỡ 1010) nên không thể thực hiện hết các test của đề bài trong thời gian yêu cầu Cách 2 đáp ứng được thời gian thực hiện với mọi test Vì vậy lựa chọn cách

2 để giải bài toán

Bước 5: Viết chương trình

Code tham khảo

Trang 20

Cho dãy a gồm n số nguyên a 1 , a 2, , a n Hãy sắp xếp dãy a thành dãy không giảm

Có nhiều thuật toán sắp xếp, tuy nhiên có 3 thuật toán sắp xếp thường được sử dụng giảng dạy ôn luyện học sinh giỏi cấp tỉnh đó là : sắp xếp nổi bọt, sắp xếp nhanh

Trang 21

nhau Làm tương tự với các cặp số a1 với a3, , a1 với an Khi đó phần tử nhỏ nhất sẽ đưa lên đầu dãy (tức a1)

- Thực hiện lặp lại với các cặp số: a2 với a3; a2 với a4; ; a2 với an; ; an-1 với an Đoạn chương trình thực hiện thuật toán:

- Code đơn giản, dễ hiểu

- Sắp xếp được với phần tử có miền giá trị lớn |ai| ≤ 1018

Nhược điểm:

- Độ phức tạp của thuật toán lớn O(n 2)

- Chạy không đủ nhanh (quá 1 giây) với dữ liệu lớn, chỉ áp dụng với số phần

Trang 22

- Thời gian thực hiện nhanh Độ phức tạp O(nlogn) Áp dụng với số phần tử n ≤ 10 6

- Sắp xếp được các phần tử với miền giá trị lớn |a i | ≤ 10 18

*Thuật toán sắp xếp bằng phương pháp đếm phân phối (Distribution Counting)

Ý tưởng:

- Khởi tạo giá trị ban đầu cho mảng dem

- Dùng mảng dem để đếm số lần xuất hiện của số a[i] trong dãy

- Duyệt i từ giá trị nhỏ nhất (gtmin) của các a[i] đến giá trị lớn nhất (gtmax) của các a]i] Duyệt j từ 1 đến dem[i] rồi in ra i

Đoạn chương trình mô phỏng:

//khoi tao gia tri mang dem

for (i = 0; i <= max; i++)

dem[i] = 0;

Trang 23

//so lan xuat hien cua a[i]

- Code đơn giản

- Độ phức tạp O(max(n,gtmax)) phụ thuộc miền giá trị

- Áp dụng được với dãy có số phần tử lớn n ≤ 108

Nhược điểm:

- Phải biết miền giá trị của số nguyên

- Miền giá trị > 107 sẽ không thể tạo được mảng dem để lưu trữ (nghĩa là chỉ

áp dụng được với miền giá trị |ai| ≤ 107)

Kết luận:

So sánh Sắp xếp nổi bọt Sắp xếp nhanh Sắp xếp đếm phân phối

Độ phức tạp O(n 2) O(nlogn) trường

hợp xấu nhất có

thể là O(n 2 )

O(max(n,gtmax))

phụ thuộc miền giá trị

Đặc điểm Code đơn giản,

dễ hiểu

Thời gian thực hiện nhanh, trong thư viện C++ có sẵn hàm sort

Code đơn giản

|a i | ≤ 10 18

số phần tử lớn n ≤ 108

chỉ áp dụng được với miền giá trị |ai| ≤ 107

Ngày đăng: 21/05/2021, 22:12

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w