tong quan ki thuat lap trinh
TRANSCRIPT
Tổng quan Kỹ thuật lập trình Trang 1
MỘT SỐ VẤN ĐỀ CỦA KỸ THUẬT LẬP TRÌNH ............................................................. 2
1.1 Phân tích và đặc tả vấn đề bài toán ................................................................................... 2
1.2 Chọn lựa cấu trúc dữ liệu và phát triển thuật toán.......................................................... 2
1.3 Mã hóa chƣơng trình ........................................................................................................... 2
1.3.1 Giới thiệu .......................................................................................................................... 2
1.3.2 Cách đặt tên biến và tên hằng......................................................................................... 2
1.3.3 Trình bày tổng quan của chƣơng trình ......................................................................... 3
1.4 Nguyên lý lập trình .............................................................................................................. 4
1.4.1 Nguyên lý tối thiểu ........................................................................................................... 4
1.4.2 Nguyên lý địa phƣơng ...................................................................................................... 4
1.4.3 Nguyên lý nhất quán ........................................................................................................ 5
1.4.4 Nguyên lý an toàn ............................................................................................................ 6
1.4.5 Phƣơng pháp TOP-DOWN ............................................................................................. 8
1.4.6 Phƣơng pháp BOTTOM-UP ......................................................................................... 11
1.5 Kiểm thử chƣơng trình ..................................................................................................... 14
1.6 Bảo trì chƣơng trình .......................................................................................................... 14
KỸ THUẬT TỐI ƢU HÓA CHƢƠNG TRÌNH .................................................................. 15
1.7 Ví dụ bài toán ..................................................................................................................... 15
1.7.1 Cách giải thứ nhất .......................................................................................................... 15
1.7.2 Cách giải thứ hai ............................................................................................................ 15
1.7.3 Cách giải thứ ba ............................................................................................................. 16
1.7.4 Cách giải thứ tƣ .............................................................................................................. 17
BÀI TẬP .......................................................................................................................... 19
1.8 Kỹ thuật lính canh ............................................................................................................. 19
1.9 Kỹ thuật đặt cờ hiệu .......................................................................................................... 19
1.10 Kỹ thuật đếm ...................................................................................................................... 19
1.11 Kỹ thuật thêm/xóa phần tử ............................................................................................... 19
1.12 Kỹ thuật sắp xếp ................................................................................................................ 19
1.13 Tối ƣu hóa ........................................................................................................................... 20
TÀI LIỆU THAM KHẢO ................................................................................................... 20
Tổng quan Kỹ thuật lập trình Trang 2
MỘT SỐ VẤN ĐỀ CỦA KỸ THUẬT LẬP TRÌNH
1.1 Phân tích và đặc tả vấn đề bài toán
Xác định rõ thông tin đầu vào (nếu có), thông tin đầu ra. Mô hình hóa bài toán.
1.2 Chọn lựa cấu trúc dữ liệu và phát triển thuật toán
Sau khi việc đặc tả vấn đề hoàn tất, các cấu trúc dữ liệu thích hợp cần được chọn lựa để tổ
chức dữ liệu vào và các thuật toán phải được thiết kế để xử lý dữ liệu đó và để tạo đầu ra theo yêu
cầu của vấn đề bài toán. Giai đoạn này đòi hỏi sự khéo léo và óc sáng tạo và là giai đoạn khó khăn
nhất. Các kỹ thuật lập trình thường gặp, các chiến lược thiết kế thuật tóan quan trọng như: chia để
trị, quay lui, nhánh cận, tham lam, quy hoạch động, … sẽ được tìm hiểu trong các chuơng tiếp theo.
1.3 Mã hóa chƣơng trình
1.3.1 Giới thiệu
Phong cách lập trình của một lập trình viên cũng giống như phong cách sống của một con
người. Có lẽ song song với việc học các kiến thức phục vụ cho công việc lập trình sinh viên phải
học và rèn luyện cho bản thân mình một phong cách lập trình tốt. Việc học và rèn luyện một
phong cách lập trình tốt nên tiến hành ngay từ khi mỗi sinh viên mới bắt tay vào học lập trình.
Một phong cách lập trình tốt sẽ giúp sinh viên tạo ra một chương trình dễ đọc, dễ hiểu, dễ tìm và
sửa lỗi, dễ nhận thấy cấu trúc bậc cao của chương trình. Những điều này sẽ thuận lợi cho cả
người viết chương trình, người đánh giá chương trình, và người bảo trì chương trình. Sau đây là
một số những lời khuyên về một phong cách lập trình tốt được đúc kết từ kinh nghiệm của các
chuyên gia lập trình trên thế giới.
1.3.2 Cách đặt tên biến và tên hằng
Khi mới học lập trình, chúng ta thường cảm thấy việc đặt tên biến và tên hằng rất dễ dàng,
bởi vì những chương trình đầu tiên thường ngắn, đơn giản, dễ hiểu và được lập trình trong một
khoảng thời gian ngắn, liên tục. Nhưng sau khi lượng kiến thức đã nhiều, các chương trình bắt
đầu dài ra, phức tạp hơn, số lượng biến và hằng cần dùng ngày càng nhiều, đặc biệt là một
chương trình có thể được lập trình trong một khoảng thời gian dài, có thể là vài ngày, hoặc vài
tháng, thậm chí có những chương trình mà cả một công ty phần mềm có thể phải viết trên mười
năm, khi đó việc đặt tên biến và tên hằng sẽ là vấn đề không thể không quan tâm. Sinh viên phải
biết cách đặt tên biến và tên hằng sao cho chúng mang ý nghĩa rõ ràng và có tính gợi nhớ (lập
trình viên có thể nhìn vào tên biến và tên hằng và hiểu được chức năng và tác dụng của chúng).
Sau đây là một số lời khuyển về cách đặt tên biến và tên hằng:
1.3.2.1 Tên biến, tên hằng phải đƣợc đặt bởi một dãy kí tự có nghĩa
Ví dụ: char pp;
Cách đặt tên biến như vậy là không tốt, biến pp không mang ý nghĩa và không gợi nhớ cho
lập trình viên.
Nên đặt là char person_position;
Cách đặt tên biến như vậy là tốt, tên biến mang ý nghĩa rõ ràng đồng thời gợi nhớ cho lập
trình viên.
1.3.2.2 Tên biến không nên đặt bằng toàn kí tự in hoa
Ví dụ: char PERSON_POSITION;
Việc đặt tên biến như thế này là không tốt, bởi nếu ta coi một chương trình như một văn bản
thì trong một văn bản có quá nhiều chữ cái in hoa sẽ làm cho người đọc khó chịu và không nhận
ra những chỗ quan trọng khác trong văn bản.
1.3.2.3 Tên hằng nên đặt bằng một chuỗi kí tự in hoa
Trong một chương trình, số lượng các hằng xuất hiện không nhiều, vậy nếu chúng ta đặt tên
Tổng quan Kỹ thuật lập trình Trang 3
hằng là chuỗi kí tự in hoa sẽ phân biệt được chúng với tên biến. tên kiểu dữ liệu cũng nên đặt
bằng chuỗi ký tự hoa.
1.3.2.4 Các từ khoá nên dùng toàn chữ cái thƣờng
Như chúng ta đã biết trong một số ngôn ngữ thì bộ chương trình dịch không phân biệt chữ
cái in hoa và chữ cái in thường (ví dụ trong ngôn ngữ lập trình Pascal). Nhưng trong một số ngôn
ngữ lập trình khác thì chữ các in hoa và chữ cái in thường được coi là khác nhau. Trong hầu hết
các ngôn ngữ lập trình này, các từ khoá được viết dưới dạng chữ cái in thường (ví dụ trong ngôn
ngữ lập trình C++). Vậy một thói quen về việc dùng các từ khoá bằng toàn chữ cái in thường thì
sẽ dễ dàng cho chúng ta hơn rất nhiều khi chúng ta học một ngôn ngữ lập trình mới.
1.3.2.5 Cách đặt tên biến dài
Khi một chương trình dài và phức tạp, đặc biệt là nhiều người viết thì lượng biến cần phải
sử dụng là rất lớn. Để tránh sự nhầm lẫn giữa các biến khác nhau thì các lập trình viên bắt buộc
phải đặt các tên biến dài (thường từ 2 từ trở lên). Đối với các tên biến dài, ta có thể dùng chữ cái
in hoa hoặc dấu gạch chân dưới để phân biệt giữa các từ khác nhau.
Ví dụ: person_position hoặc PersonPosition: Là cách đặt tên biến tốt.
personposition: Là cách đặt tên biến không tốt.
1.3.2.6 Cách đặt tên hàm
Có nhiều cách đặt tên hàm khác nhau. Cách đặt tên hàm cũng giống như cách đặt tên biến.
Nhưng khi đặt tên cho một hàm thì cần có thêm một vài chú ý về cấu trúc ngữ pháp của tên hàm.
Một trong những cách đặt phổ biến trong việc đặt tên hàm là sử dụng cấu trúc ngữ pháp: Động từ
+ Cụm danh từ.
1.3.3 Trình bày tổng quan của chƣơng trình
1.3.3.1 Tổng quan chƣơng trình
Chương trình phải được lùi đầu dòng một cách thích hợp. Các câu lệnh bên trong các vòng
lặp (ví dụ: for, while, do while), các lệnh điều kiện rẽ nhánh (ví dụ: if, switch ) và trong cặp { }
phải được lùi thêm một bậc. Bên cạnh đó các câu lệnh khai báo biến cũng nên lùi vào một bậc.
Việc lùi vào một bậc có thể được thực hiện bằng một số dấu cách (space) hoặc dấu tab. Việc lùi
vào một cách thích hợp các dòng lệnh sẽ cho lập trình viên thấy được cấu trúc khối của chương
trình, điều đó giúp cho việc tìm lỗi và sửa lỗi dễ dàng hơn.
1.3.3.2 Đoạn chƣơng trình và dòng lệnh
Trong một file chương trình thường gồm nhiều hàm, trong một hàm có thể lại gồm nhiều
đoạn nhỏ. Mỗi đoạn nhỏ nên được tách nhau ra bởi các dòng trống. Trong một đoạn nhỏ ý nghĩa
của các dòng lệnh nên tương đối giống nhau và phù hợp với nhau.
Trong chương trình mỗi dòng chỉ nên viết một dòng lệnh (các bộ chương trình dịch của các
ngôn ngữ thường bỏ qua tất cả các dấu trống, vì vậy việc bạn cố viết một chương trình với số
lượng dòng ít bằng cách đẩy nhiều dòng lệnh trên cùng một dòng là vô nghĩa). Mỗi dòng lệnh
trên một dòng sẽ giúp bạn dễ dàng trong việc gỡ rối chương trình.
Đối với mỗi chương trình con nên liệt kê điều kiện trước và điều kiện sau của chúng.
Nên có ít nhất một dòng trống (hoặc một dòng kẻ) trước mỗi chương trình con để tách biệt
nhưng chương trình con, điều này sẽ giúp cho ta phân biệt được các chương trình con khác nhau.
1.3.3.3 Chƣơng trình phải có cấu trúc tốt
Sử dụng lối tiếp cận: “từ trên xuống dưới” để viết một chương trình cho một vấn đề phức tạp
nên chia chúng thành các vấn đề đơn giản hơn và viết thành từng hàm để giải quyết nó. Những hàm
này nên ngắn và có tính độc lập tương đối: mỗi hàm nên giải quyết trọn vẹn một vấn đề cụ thể.
1.3.3.4 Sử dụng tham trị, tham biến đúng lúc
Sử dụng các biến cục trong các chương trình con. Các biến chỉ dùng trong một chương trình
con nên được khai báo trong chương trình con đó.
Tổng quan Kỹ thuật lập trình Trang 4
Sử dụng các tham số để chuyển thông tin đến và ra khỏi chương trình con. Nên tránh dùng
biến toàn cục để chuyển giao thông tin giữa các chương trình con bởi vì điều đó sẽ phá vỡ tính
độc lập của chúng. Việc xác định giá trị của một biến toàn cục nào đó tại một điểm trong chương
trình có thể rất khó khăn bởi vì nó có thể đã bị thay thế bởi các chương trình con khác.
Để bảo vệ các tham số không bị thay đổi bởi các chương trình con khác, nên khai báo nó
kiểu tham trị hơn là tham biến. Nếu không, chương trình con có thể thay đổi giá trị của các biến
một cách không định trước trong các chương trình con khác.
1.3.3.5 Chú thích
Mỗi chương trình phải có một phần chú thích chung giới thiệu mục tiêu của chương trình.
Điều đó sẽ giúp bạn dễ dàng nâng cấp và bảo trì chương trình trong thời gian sau này. Phần chú
thích của chương trình như một phát biểu ngắn ngọn về bài toán cần giải quyết.
Mỗi chương trình con cần phải có chú thích về mục đích của chương trình con đó, những
chú thích làm cho người đọc hiểu được mục đích của chương trình con chứ không phải giúp
người đọc hiểu được chương trình con đó được thực hiện như thế nào.
Đối với các cấu trúc dữ liệu quan trọng phải có chú thích và về mục đích của cấu trúc đó và
mỗi liên hệ của nó với các phần dữ liệu khác. Vấn đề chú thích cho những cấu trúc dữ liệu đặc
biệt cần thiết cho lập trình hướng chức năng.
Những đoạn chương trình phức tạp và khó hiểu, cần chú thích cẩn thận. Lời chú thích sẽ
liên quan đến nội dung của đoạn chương trình đó.
Mỗi chương trình con cần có chú thích về ý nghĩa cho từng tham số của chương trình.
1.4 Nguyên lý lập trình
1.4.1 Nguyên lý tối thiểu
Hãy bắt đầu từ một tập nguyên tắc và tối thiểu các phương tiện là các cấu trúc lệnh, kiểu dữ
liệu cùng các phép toán trên nó và thực hiện viết chương trình. Sau khi nắm chắc những công cụ
vòng đầu mới đặt vấn đề mở rộng sang hệ thống thư viện tiện ích của ngôn ngữ.
Khi làm quen với một ngôn ngữ lập trình nào đó, không nhất thiết phải lệ thuộc quá nhiều
vào hệ thống thư viện hàm của ngôn ngữ, mà điều quan trọng hơn là trước một bài toán cụ thể,
chúng ta sử dụng ngôn ngữ để giải quyết nó thế nào, và phương án tốt nhất là lập trình bằng chính
hệ thống thư viện hàm của riêng mình. Do vậy, đối với các ngôn ngữ lập trình, chúng ta chỉ cần
nắm vững một số các công cụ tối thiểu. Các phép toán số học: +; -; *; %; /, các phép toán số học
mở rộng: ++, --, +=, -=, *=, /=, %=, các phép toán so sánh: >, <, >=, <=, ==, các phép toán logic:
&&, ||, !, các toán tử thao tác bít &, |, ^, <<, >>, ~, các lệnh vào ra cơ bản, thao tác trên các kiểu
dữ liệu có cấu trúc, thao tác trên con trỏ, thao tác trên file, …
1.4.2 Nguyên lý địa phƣơng
Các biến địa phương trong hàm, thủ tục hoặc chu trình cho dù có trùng tên với biến toàn cục
thì khi xử lý biến đó trong hàm hoặc thủ tục vẫn không làm thay đổi giá trị của biến toàn cục.
Tên của các biến trong đối của hàm hoặc thủ tục đều là hình thức.
Mọi biến hình thức truyền theo trị cho hàm hoặc thủ tục đều là các biến địa phương.
Các biến khai báo bên trong các chương trình con, hàm hoặc thủ tục đều là biến địa phương.
Khi phải sử dụng biến phụ nên dùng biến địa phương và hạn chế tối đa việc sử dụng biến
toàn cục để tránh xảy ra các hiệu ứng phụ.
Ví dụ hoán đổi giá trị của hai số a và b sau đây sẽ minh họa rõ hơn về nguyên lý địa phương.
Ví dụ: Hoán đổi giá trị của hai biến a và b
#include <stdio.h>
#include <conio.h>
int a, b; // khai báo a, b là hai biến toàn cục.
void Swap(void)
{
Tổng quan Kỹ thuật lập trình Trang 5
int a,b, temp; // khai báo a, b là hai biến địa phương
a= 3; b=5; // gán giá trị cho a và b
temp=a;
a=b;
b=temp; // đổi giá trị của a và b
printf(“\n Kết quả thực hiện trong thủ tục a=%5d b=%5d”, a, b);
}
void main(void)
{
a=1; b=8; // khởi đầu giá trị cho biến toàn cục a, b.
Swap();
printf(“\n Kết quả sau khi thực hiện thủ tục a =%5d b=%5d”, a, b);
getch();
}
Kết quả thực hiện chương trình:
Kết quả thực hiện trong thủ tục a = 5, b=3
Kết quả sau khi thực hiện thủ tục a = 1, b =8
Trong ví dụ trên a, b là hai biến toàn cục, hai biến a, b trong thủ tục Swap là hai biến cục bộ.
Các thao tác trong thủ tục Swap gán cho a giá trị 3 và b giá trị 5 sau đó thực hiện đổi giá trị của
a=5 và b=3 là công việc xử lý nội bộ của thủ tục mà không làm thay đổi giá trị của biến toàn cục
của a, b sau thi thực hiện xong thủ tục Swap. Do vậy, kết quả sau khi thực hiện Swap a = 1, b =8;
Điều đó chứng tỏ trong thủ tục Swap chưa bao giờ sử dụng tới hai biến toàn cục a và b. Tuy
nhiên, nếu ta thay 2 dòng int a, b, temp; và dòng a = 3; b = 5; bằng dòng int temp; thì thủ tục
Swap lại làm thay đổi giá trị của biến toàn cục a và b vì nó thao tác trực tiếp trên biến toàn cục.
1.4.3 Nguyên lý nhất quán
Dữ liệu thế nào thì phải thao tác thế ấy. Cần sớm phát hiện những mâu thuẫn giữa cấu trúc
dữ liệu và thao tác để kịp thời khắc phục.
Như chúng ta đã biết, kiểu là một tên chỉ tập các đối tượng thuộc miền xác định cùng với
những thao tác trên nó. Một biến khi định nghĩa bao giờ cũng thuộc một kiểu xác định nào đó
hoặc là kiểu cơ bản hoặc kiểu do người dùng định nghĩa. Thao tác với biến phụ thuộc vào những
thao tác được phép của kiểu. Hai kiểu khác nhau được phân biệt bởi tên, miền xác định và các
phép toán trên kiểu dữ liệu. Tuy nhiên, trên thực tế có nhiều lỗi nhập nhằng giữa phép toán và cấu
trúc dữ liệu mà chúng ta cần hiểu rõ.
Đối với kiểu ký tự, về nguyên tắc chúng ta không được phép thực hiện các phép toán số học
trên nó, nhưng ngôn ngữ C luôn đồng nhất giữa ký tự với số nguyên có độ lớn 1 byte. Do vậy,
những phép toán số học trên các ký tự thực chất là những phép toán số học trên các số nguyên.
Chẳng hạn, những thao tác như trong khai báo dưới đây là được phép:
char x1 = ‟A‟, x2 = ‟z‟;
x1 = (x1 + 100) % 255;
x2 = (x2 - x1) %255;
Mặc dù x1, x2 được khai báo là hai biến kiểu char, nhưng trong thao tác
x1 = (x1 + 100) % 255;
x2 = (x2 + x1) % 255;
Chương trình dịch sẽ tự động chuyển đổi x1 thành mã của ký tự „A‟ là 65, x2 thành mã ký
tự „z‟ là 122 để thực hiện phép toán. Kết quả nhận được x1 là một ký tự có mã là (65+100)%255
= 165; x2 là ký tự có mã là 32 ứng với mã của ký tự space.
Chúng ta có thể thực hiện được các phép toán số học trên kiểu int, long, float, double.
Nhưng đối với int và long, chúng ta cần đặc biệt chú ý phép chia hai số nguyên cho ta một số
nguyên, tích hai số nguyên cho ta một số nguyên, tổng hai số nguyên cho ta một số nguyên mặc
Tổng quan Kỹ thuật lập trình Trang 6
dù thương hai số nguyên là một số thực, tích hai số nguyên hoặc tổng hai số nguyên có thể là một
số long int. Do vậy, muốn nhận được kết quả đúng, chúng ta cần phải chuyển đổi các biến thuộc
cùng một kiểu trước khi thực hiện phép toán. Ngược lại, ta không thể lấy modulo của hai số thực
hoặc thực hiện các thao tác dịch chuyển bít trên nó, vì những thao tác đó không nằm trong định
nghĩa của kiểu.
Điều tương tự cũng xảy ra với các string. Trong Pascal, phép toán so sánh hai string hoặc
gán trực tiếp hai Record cùng kiểu với nhau là được phép, ví dụ : Str1 > Str2, Str1 := Str2; Nhưng
trong C thì các phép toán trên lại không được định nghĩa, nếu muốn thực hiện nó, chúng ta chỉ có
cách định nghĩa lại hoặc thực hiện nó thông qua các lời gọi hàm.
1.4.4 Nguyên lý an toàn
Lỗi nặng nhất nằm ở mức cao nhất (mức ý đồ thiết kế) và ở mức thấp nhất thủ tục phải chịu
tải lớn nhất.
Mọi lỗi, dù là nhỏ nhất cũng phải được phát hiện ở một bước nào đó của chương trình. Quá
trình kiểm tra và phát hiện lỗi phải được thực hiện trước khi lỗi đó hoành hành.
Các loại lỗi thường xảy ra trong khi viết chương trình có thể được tổng kết lại như sau:
Lỗi được thông báo bởi từ khoá error (lỗi cú pháp): loại lỗi này thường xảy ra trong khi soạn
thảo chương trình, chúng ta có thể viết sai các từ khoá ví dụ thay vì viết là int chúng ta soạn thảo
sai thành Int (lỗi chữ in thường thành in hoa), hoặc viết sai cú pháp các biểu thức như thiếu các
dấu ngoặc đơn, ngoặc kép hoặc dấu chấm phẩy khi kết thúc một lệnh, hoặc chưa khai báo nguyên
mẫu cho hàm .
Lỗi được thông báo bởi từ khoá Warning (lỗi cảnh báo): lỗi này thường xảy ra khi ta khai
báo biến trong chương trình nhưng lại không sử dụng tới chúng, hoặc lỗi trong các biểu thức
kiểm tra khi biến được kiểm tra không xác định được giá trị của nó, hoặc lỗi do thứ tự ưu tiên các
phép toán trong biểu thức. Hai loại lỗi error và warning được thông báo ngay khi dịch chương
trình thành file *.OBJ. Quá trình liên kết (linker) các file *.OBJ để tạo nên file chương trình mã
máy *.EXE chỉ được tiếp tục khi chúng ta hiệu đính và khử bỏ mọi lỗi error.
Lỗi xảy ra trong quá trình liên kết: lỗi này thường xuất hiện khi ta sử dụng tới các lời gọi
hàm, nhưng những hàm đó mới chỉ tồn tại dưới dạng nguyên mẫu (function prototype) mà chưa
được mô tả chi tiết các hàm, hoặc những lời hàm gọi chưa đúng với tên của nó. Lỗi này được
khắc phục khi ta bổ sung đoạn chương trình con mô tả chi tiết cho hàm hoặc sửa đổi lại những lời
gọi hàm tương ứng.
Ta quan niệm, lỗi cú pháp (error), lỗi cảnh báo (warning) và lỗi liên kết (linker) là lỗi tầm
thường vì những lỗi này đã được Compiler của các ngôn ngữ lập trình phát hiện được. Để khắc
phục các lỗi loại này, chúng ta chỉ cần phải đọc và hiểu được những thông báo lỗi thường được
viết bằng tiếng Anh. Cũng cần phải lưu ý rằng, do mức độ phức tạp của chương trình dịch nên
không phải lỗi nào cũng được chỉ ra một cách tường minh và chính xác hoàn toàn tại nơi xuất
hiện lỗi.
Loại lỗi cuối cùng mà các compiler không thể phát hiện nổi đó là lỗi do chính lập trình viên
gây nên trong khi thiết kế chương trình và xử lý dữ liệu. Những lỗi này không được compiler
thông báo mà nó phải trả giá bằng quá trình tự test hoặc chứng minh được tính đúng đắn của
chương trình. Lỗi có thể nằm ở chính ý đồ thiết kế, hoặc lỗi do không lường trước được tính chất
của mỗi loại thông tin vào. Ví dụ sau minh họa cho lỗi thường xảy ra thuộc loại này.
Ví dụ: Tính tổng hai đa thức A bậc n, đa thức B bậc m.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#define MAX 100
typedef float DaThuc[MAX];
Tổng quan Kỹ thuật lập trình Trang 7
void Init(DaThuc A, int *n, DaThuc B, int *m)
{
int i, j;
float temp;
printf("\n Nhap n=");
scanf("%d", n);
printf("\n Nhap m=");
scanf("%d",m);
printf("\n Nhap he so da thuc A:");
for(i=0; i<*n;i++)
{
printf("\n A[%d]=", i);
scanf("%f", &A[i]);
}
printf("\n Nhap he so da thuc B:");
for(i=0; i<*m;i++)
{
printf("\n B[%d]=",i);
scanf("%f", &B[i]);
}
In(A,*n,'A');
In(B,*m,'B');
}
void Tong(DaThuc A, int n, DaThuc B, int m, DaThuc C)
{
int i, k;
if (n>= m )
{
k =n;
for(i=0; i<m; i++)
C[i] = A[i]+B[i];
for (i=m; i<n; i++)
C[i]=A[i];
In(C,k,'C');
}
else
{
k = m;
for(i=0; i<n; i++)
C[i] = A[i]+B[i];
for (i=n; i<m; i++)
C[i]=B[i];
In(C, k, „C‟);
}
}
void In(DaThuc A, int n, char c)
{
int i;
printf("\n Da thuc %c:", c);
Tổng quan Kỹ thuật lập trình Trang 8
for(i=0;i<n; i++)
printf("%6.2f", A[i]);
}
void main(void)
{
DaThuc A, B, C;
int n, m;
Init(A, &n, B, &m);
Tong(A, n, B, m, C);
}
Trong ví dụ trên, chúng ta sử dụng định nghĩa MAX =100 để giải quyết bài toán. MAX
được hiểu là bậc của đa thức lớn nhất mà chúng ta cần xử lý. Như vậy, bản thân việc định nghĩa
MAX đã hạn chế tới phạm vi bài toán, hạn chế đó cũng có thể xuất phát từ ý đồ thiết kế. Do vậy,
nếu người sử dụng nhập n>MAX thì chương trình sẽ gặp lỗi. Nếu chúng ta khắc phục bằng cách
định nghĩa BẬC đủ lớn thì trong trường hợp xử lý các đa thức có bậc n nhỏ sẽ gây nên hiện tượng
lãng phí bộ nhớ, và trong nhiều trường hợp không đủ bộ nhớ để định nghĩa đa thức. Giải pháp
khắc phục các lỗi loại này là chúng ta sử dụng con trỏ thay cho các hằng.
1.4.5 Phƣơng pháp TOP-DOWN
Quá trình phân tích bài toán được thực hiện từ trên xuống dưới. Từ vấn đề chung nhất đến
vấn đề cụ thể nhất. Từ mức trừu tượng mang tính chất tổng quan tới mức đơn giản nhất là đơn vị
chương trình.
Một trong những nguyên lý quan trọng của lập trình cấu trúc là phương pháp phân tích từ
trên xuống (Top - Down) với quan điểm “thấy cây không bằng thấy rừng”, phải đứng cao hơn để
quan sát tổng thể khu rừng chứ không thể đứng trong rừng quan sát chính nó.
Quá trình phân rã bài toán được thực hiện theo từng mức khác nhau. Mức thấp nhất được
gọi là mức tổng quan (level 0), mức tổng quan cho phép ta nhìn tổng thể hệ thống thông qua các
chức năng của nó, nói cách khác mức 0 sẽ trả lời thay cho câu hỏi “Hệ thống có thể thực hiện
được những gì ?”. Mức tiếp theo là mức các chức năng chính. Ở mức này, những chức năng cụ
thể được mô tả. Một hệ thống có thể được phân tích thành nhiều mức khác nhau, mức thấp được
phép sử dụng các dịch vụ của mức cao. Quá trình phân tích tiếp tục phân rã hệ thống theo từng
chức năng phụ cho tới khi nào nhận được mức các đơn thể (UNIT, Function, Procedure), khi đó
chúng ta tiến hành cài đặt hệ thống.
Chúng ta sẽ làm rõ hơn từng mức của quá trình Top-Down thông qua bài toán sau:
Bài toán: Cho hai số nguyên có biểu diễn nhị phân là a=(a1, a2, . . ., an), b = (b1, b2,.., bn); ai,
bi =0, 1, i=1, 2, . . .n. Hãy xây dựng tập các thao tác trên hai số nguyên đó.
Mức tổng quan (level 0):
Hình dung toàn bộ những thao tác trên hai số nguyên a=(a1, a2, . . ., an), b=(b1,b2,..,bn) với
đầy đủ những chức năng chính của nó. Giả sử những thao tác đó bao gồm:
F1- Chuyển đổi a, b thành các số nhị phân;
F2- Tính tổng hai số nguyên: a + b;
F3- Tính hiệu hai số nguyên: a - b;
F4 Tính tích hai số nguyên: a *b;
F5- Thương hai số nguyên : a/b;
F6- Phần dư hai số nguyên: a % b;
F7- Ước số chung lớn nhất của hai số nguyên.
Mức 1: Mức các chức năng chính: mỗi chức năng cần mô tả đầy đủ thông tin vào (Input),
thông tin ra (Output), khuôn dạng (Format) và các hành động (Actions).
Chức năng F1: Chuyển đổi a, b thành các số ở hệ nhị phân
Input: a : integer;
Tổng quan Kỹ thuật lập trình Trang 9
Output: a=(a1, a2, . . ., an)b; (*khai triển cơ số bất kỳ*)
Format: Binary(a);
Actions
{
Q = n;
k=0;
While ( Q≠ 0 )
{
ak = q mod b;
q = q div b;
k = k +1;
}
< Khai triển cơ số b của a là (ak-1, ak-2, . ., a1, a0) >;
}
Chức năng F2: Tính tổng hai số nguyên a, b.
Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn);
Output: c = a + b;
Format: Addition(a, b);
Actions
{
c = 0;
for (j = 0; j< n; j++)
{
d = (aj + bj + c) div 2;
sj = aj + bj + c - 2d;
c = d;
}
sn = c;
< Khai triển nhị phân của tổng là (sn,sn-1. . .s1,s0) >
}
Chức năng F3: Hiệu hai số nguyên a, b.
Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn);
Output: c = a – b;
Format: Subtraction(a, b);
Actions
{
b = -b;
c = Addition(a, b);
return(c);
}
Chức năng F4: Tích hai số nguyên a, b.
Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn);
Output: c = a * b;
Format: Multual(a, b);
Actions
{
for (j =0; j< n; j++)
{
if ( bj =1)
cj = a<<j;
Tổng quan Kỹ thuật lập trình Trang 10
else
cj = 0;
}
(* c0, c1, . . ., cn-1 là các tích riêng*)
p=0;
for(j=0; j< n; j++)
{
p = Addition(p, cj);
}
return(p);
}
Chức năng F5: Thương hai số nguyên a, b.
Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn);
Output: c = a div b;
Format: Division(a, b);
Actions
{
c = 0;
while ( a>= b )
{
c = c +1;
a = Subtraction(a, b);
}
return(c);
}
Chức năng F6: Modulo hai số nguyên a, b.
Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn);
Output: c = a mod b;
Format: Modulation(a, b);
Actions
{
while ( a>= b )
a = Subtraction(a, b);
return(a);
}
Chức năng F7: Ước số chung lớn nhất hai số nguyên a, b.
Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn);
Output: c = USCLN(a,b);
Format: USCLN(a, b);
Actions
{
while ( a≠ b )
{
if (a > b)
a=Subtraction(a, b)
else
b = Subtraction(b, a);
}
return(a);
}
Tổng quan Kỹ thuật lập trình Trang 11
Để ý rằng, sau khi phân rã bài toán ở mức 1, chúng ta chỉ cần xây dựng hai phép toán cộng
và phép tính nhân các số nhị phân của a, b. Vì hiệu hai số a và b chính là tổng số của (a,-b).
Tương tự như vậy, tích hai số a và b được biểu diễn bằng tổng của một số lần phép nhân một bít
nhị phân của với a. Phép chia và lấy phần dư hai số a và b chính là phép trừ nhiều lần số a. Phép
tìm USCLN cũng tương tự như vậy.
Đối với các hệ thống lớn, quá trình còn được mô tả tiếp tục cho tới khi nhận được mức đơn
vị chương trình. Trong ví dụ đơn giản này, mức đơn vị chương trình xuất hiện ngay tại mức 1 nên
chúng ta không cần phân rã tiếp nữa mà dừng lại để cài đặt hệ thống.
1.4.6 Phƣơng pháp BOTTOM-UP
Đi từ cái riêng tới cái chung, từ các đối tượng thành phần ở mức cao tới các đối tượng thành
phần ở mức thấp, từ mức đơn vị chương trình tới mức tổng thể, từ những đơn vị đã biết lắp đặt
thành những đơn vị mới.
Nếu như phương pháp Top-Down là phương pháp phân rã vấn đề một cách có hệ thống từ
trên xuống, được ứng dụng chủ yếu cho quá trình phân tích và thiết hệ thống, thì phương pháp
Bottom- Up thường được sử dụng cho quá trình cài đặt hệ thống. Trong ví dụ trên, chúng ta sẽ
không thể xây dựng được chương trình một cách hoàn chỉnh nếu như ta chưa xây dựng được các
hàm Binary(a), Addition(a,b), Subtraction(a,b), Multial(a,b), Division(a,b), Modulation(a,b),
USCLN(a,b). Chương trình sau thể hiện quá trình cài đặt chương trình theo nguyên lý Botton-Up:
#include <stdio.h>
#include <math.h>
#include <conio.h>
#include <stdlib.h>
#include <alloc.h>
#include <dos.h>
void Init(int *a, int *b)
{
printf("\n Nhap a=");
scanf("%d", a);
printf("\n Nhap b=");
scanf("%d", b);
}
void Binary(int a)
{
int i, k=1;
for(i=15; i>=0; i--)
{
if ( a & (k<<i))
printf("%2d",1);
else
printf("%2d",0);
}
printf("\n");delay(500);
}
int bit(int a, int k)
{
int j=1;
if (a & (j<<k))
return(1);
Tổng quan Kỹ thuật lập trình Trang 12
return(0);
}
int Addition(int a, int b)
{
int du, d, s, j, c=0;
du=0;
for ( j=0; j<=15; j++)
{
d =( bit(a,j) + bit(b, j) +du)/2;
s = bit(a,j)+bit(b,j)+ du - 2*d;
c = c | (s <<j);
du = d;
}
return(c);
}
int Multiply(int a, int b)
{
int c,j, p=0;
for(j=0; j<=15; j++)
{
c = bit(b, j);
if (c==1)
{
c = a<<j;
p= Addition(p, c);
}
else
c=0;
}
return(p);
}
int Subtraction(int a, int b)
{
int c;
b=-b;
c=Addition(a,b);
return(c);
}
int Modulo(int a, int b)
{
while(a>=b)
a = Subtraction(a,b);
return(a);
}
int Division(int a, int b)
Tổng quan Kỹ thuật lập trình Trang 13
{
int d=0;
while(a>=b)
{
a= Subtraction(a,b);
d++;
}
return(d);
}
int USCLN(int a, int b)
{
while(a!=b)
{
if(a>b)
a = Subtraction(a,b);
else
b = Subtraction(b,a);
}
return(a);
}
void main(void)
{
int a, b, key, control=0;
do
{
clrscr();
printf("\n Tap thao tac voi so nguyen");
printf("\n 1 - Nhap hai so a,b");
printf("\n 2 - So nhi phan cua a, b");
printf("\n 3 - Tong hai so a,b");
printf("\n 4 - Hieu hai so a,b");
printf("\n 5 - Tich hai so a,b");
printf("\n 6 - Thuong hai so a,b");
printf("\n 7 - Phan du hai so a,b");
printf("\n 8 - USCLN hai so a,b");
printf("\n 0 - Tro ve: ");
key=getch();
switch(key)
{
case '1': Init(&a, &b);
control=1;
break;
case '2': if (control)
{
Binary(a);
Binary(b);
}
break;
Tổng quan Kỹ thuật lập trình Trang 14
case‟3‟: if(control)
printf("\n Tong a+b = %d", Addition(a, b));
break;
case '4': if (control)
printf("\n Hieu a-b =%d", Subtraction(a, b));
break;
case '5': if(control)
printf("\n Tich a*b =%d", Multiply(a,b));
break;
case '6': if(control)
printf("\n Chia nguyen a div b=%d",Division(a,b));
break;
case '7': if(control)
printf("\n Phan du a mod b =%d", Modulo(a,b));
break;
case '8': if(control)
printf("\n Uoc so chung lon nhat:%d",USCLN(a,b));
break;
}
} while(key!='0');
delay(1000);
}
1.5 Kiểm thử chƣơng trình
Cần đưa ra các bộ test để kiểm thử các trường hợp có thể xảy ra của chương trình. Phần lớn
khi một chương trình viết đều có lỗi: các lỗi về cú pháp ngôn ngữ thì dễ sửa, tuy nhiên các lỗi
logíc thì khó sửa hơn nhiều. Lỗi này là do mã hóa không chính xác thuật toán hoặc cũng có thể là
do chính bản thân việc thiết kế thuật toán.
1.6 Bảo trì chƣơng trình
Khi một chương trình đã được mã hóa và kiểm thử thành công. Nó bắt đầu cuộc sống có ích
của nó. Nó có thể được sử dụng trong nhiều năm hoặc cũng có thể là trong một chu kỳ sống ngắn.
Phần lớn các chương trình luôn cần được cập nhật, thay đổi.
Việc bảo trì phần mềm là một phận quan trọng trong chu kỳ sống của một phần mềm. đôi
lúc thời gian để bảo trì phần mềm còn nhiều hơn cả thời gian viết chương trình khi chuyển giao
cho khách hàng. Do đó, mỗi lập trình viên phải làm hết sức để có được một chương trình dễ đọc,
có cấu trúc rõ ràng, dễ thay đổi, dễ bảo trì.
Tổng quan Kỹ thuật lập trình Trang 15
KỸ THUẬT TỐI ƢU HÓA CHƢƠNG TRÌNH
Tối ưu hoá là một đòi hỏi thường xuyên không chỉ trong quá trình giải quyết các bài toán tin
học, mà ngay trong giải quyết các công việc thường ngày của chúng ta. Tối ưu hoá thuật toán là
một công việc yêu cầu tư duy 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.
Trong bài viết này, tôi xin được chia sẻ với các bạn về tối ưu hoá thuật toán trong giải quyết một
bài toán tưởng chừng rất đơn giản, nhưng việc tìm ra thuật toán tối ưu cho nó lại không dễ chút
nào. Tối ưu hoá thường được tiến hành theo hai 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ả hai đ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.
1.7 Ví dụ bài toán
Cho dãy số a0, a1, …, an-1. Hãy tìm dãy con có tổng lớn nhất.
Chúng ta thấy rằng bài toán khá đơn giản, song nếu không được giải quyết tốt sẽ không đáp
ứng được yêu cầu về thời gian. Với các bài toán có hạn chế về thời gian như thế, đòi hỏi ta phải
đưa ra được thuật toán tối ưu.
1.7.1 Cách giải thứ nhất
Cách làm sơ khai nhất là xét tất cả các đoạn con có thể theo đoạn mã sau:
int Findmax(int a[], int &dau, int &cuoi)
{
max=a[0];
dau=cuoi=0;
for(i=0;i<N;i++)
{
for(j=i;j<N;j++)
{
s=0;
for(k=i;k<=j;k++)
s+=a[k];
if (max<s)
{
max=s;
dau=i;
cuoi=j;
}
}
}
return max;
}
Ta dễ thấy rằng cách làm trên có độ phức tạp O(n3) với 3 vòng for lồng nhau. Đoạn mã trên
có quá nhiều cái để ta có thể tối ưu, ví dụ như khi tính tổng từ i đến j đã không tận dụng được
tổng từ i đến (j-1) đã được tính trước đó. Do vậy đây là một đoạn mã khó có thể chấp nhận được
về mặt kỹ thuật lập trình lẫn kỹ năng viết chương trình.
1.7.2 Cách giải thứ hai
Với một cải tiến nhỏ ta sẽ xét hết tất cả các dãy con có thể của dãy số bắt đầu từ vị trí thứ i
(i = 0,1,2…n-1), với việc tận dụng lại các giá trị đã được tính trước đó. Chúng ta cùng theo dõi
đoạn mã sau:
Tổng quan Kỹ thuật lập trình Trang 16
int Findmax(int a[], int &dau, int &cuoi)
{
max=a[0];
dau=cuoi=0;
for(i=0;i<N-1;i++)
{
s=0;
for(j=i;j<N;j++)
{
s+=a[j];
if (s>max)
{
max=s;
dau=i;
cuoi=j;
}
}
}
return max;
}
Thuật toán trên sử dụng hai vòng lặp lồng nhau, vòng đầu tiên lặp n-1 lần, vòng thứ hai lặp
tối đa n lần, nên dễ thấy độ phức tạp của thuật toán này là O(n2). Chúng tôi tin rằng nhiều người
sẽ đồng ý sử dụng cách làm trên, nhưng chắc chắn một điều là nó không đáp ứng được đòi hỏi về
giới hạn thời gian.
1.7.3 Cách giải thứ ba
Ta cùng tìm hiểu một cải tiến khác chắc chắn sẽ tốt hơn. Ta sử dụng thêm hai mảng k,c mỗi
mảng gồm n phần tử. Trong đó k[i] sẽ lưu giá trị lớn nhất của tổng dãy con mà giá trị a[i] là cuối
dãy, c[i] sẽ lưu chỉ số đầu của dãy đó. Như vậy k[i] =max(a[i],a[i]+k[i-1]). Hãy theo dõi đoạn mã
khá lý thú sau, nó được xây dựng trên tư tưởng quy hoạch động:
int Findmax(int a[], int &dau, int &cuoi)
{
k[0]=max=a[0];
c[0]=dau=cuoi=0;
for(i=1;i<N;i++)
{
s=k[i-1]+a[i];
k[i]=a[i];
c[i]=i;
if (s>k[i])
{
k[i]=s;
c[i]=c[i-1];
}
//tìm tổng lớn nhất
if (k[i]>max)
{
max=k[i];
cuoi=i;
}
Tổng quan Kỹ thuật lập trình Trang 17
}
dau=c[cuoi];
return max;
}
Với thuật toán trên độ phức tạp là O(n) với chỉ một vòng lặp n lần duy nhất. Như vậy, có thể
nói ta đã tối ưu xong về mặt thời gian từ độ phức tạp O(n3) xuống còn O(n). Về không gian bộ
nhớ, ở cách làm trên ta đã sử dụng thêm hai mảng k và c mỗi mảng n phần tử, nếu không gian
đầu vào không phải hạn chế 10000 mà là 20000 thậm chí 30000 thì sao? Chắc chắn sẽ không đủ
không gian bộ nhớ để sử dụng hai mảng k và c như thế. Vậy giải quyết thế nào? Ta sẽ bỏ mảng k
đi khi sử dụng tính toán hoàn toàn trên mảng a, vậy có thể bỏ nốt mảng c đi không nếu bỏ đi thì ta
sẽ xác định giá trị đầu của mỗi dãy như thế nào đây?
1.7.4 Cách giải thứ tƣ
Tại mỗi vị trí đang xét ta sẽ so sánh để tìm ra dãy có tổng lớn nhất ngay như trên, và như
vậy ta sẽ sử dụng một biến để lưu giá trị đầu của dãy tìm được! Ý tưởng đó thể hiện qua đoạn mã
sau:
int Findmax(int a[], int &dau, int &cuoi)
{
dau=cuoi=luu=0;
max=a[0];
for(i=1;i<N;i++)
{
a[i]+=a[i-1];
if (a[i]<a[i]-a[i-1])
{
a[i]-=a[i-1];
dau=i;
}
//tìm tổng lớn nhất
if (a[i]>max)
{
max=a[i];
cuoi=i;
luu=dau;
}
}
dau=luu;
return max;
}
Cách làm này quả thật hiệu quả, tuy nhiên nó đã làm biến đổi dãy số ban đầu (mảng a). Làm
sao có thể giữ nguyên dãy số, không dùng thêm mảng phụ, vẫn đáp ứng được yêu cầu đề bài. Với
một cải tiến nhỏ ta sẽ thấy ở đoạn mã sau thật tuyệt vời:
int Findmax(int a[], int &dau, int &cuoi)
{
dau=luu=cuoi=0;
max=s=a[0];
for(i=1;i<N;i++)
{
s+=a[i];
if (s<a[i])
Tổng quan Kỹ thuật lập trình Trang 18
{
s=a[i];
dau=i;
}
//tìm tổng lớn nhất
if (s>max)
{
max=s;
cuoi=i;
luu=dau;
}
}
dau=luu;
return max;
}
Với cải tiến cuối cùng này mọi yêu cầu của bài toán đã được giải quyết triệt để.
Tổng quan Kỹ thuật lập trình Trang 19
BÀI TẬP
1.8 Kỹ thuật lính canh
1. Cho mảng một chiều a chứa n số nguyên.
Viết hàm kiểm tra xem x có thuộc mảng a hay không ? nếu có thì trả về giá trị i nhỏ nhất
sao chi a[i] bằng x, nếu không có thì trả về giá trị -1.
2. Tìm số nguyên tố nhỏ nhất lớn hơn mọi giá trị có trong mảng.
1.9 Kỹ thuật đặt cờ hiệu
3. Cho mảng một chiều a chứa n số nguyên.
a.Viết hàm kiểm tra mảng có tăng dần hay không ? Nếu có thì trả về giá trị 1 nếu không có
thì trả về giá trị 0.
b.Viết hàm kiểm tra trong mảng các số nguyên có tồn tại giá trị chẵn nhỏ hơn 2010 hay
không ? Nếu có thì trả về giá trị 1 nếu không có thì trả về giá trị 0.
c.Viết hàm kiểm tra các phần tử trong mảng có thành lập nên cấp số cộng không ? Nếu có
hãy chỉ ra công sai d.
d.Viết hàm tìm giá trị chẵn đầu tiên, nếu không có thì trả về giá trị -1.
1.10 Kỹ thuật đếm
4. Cho mảng một chiều a chứa n số nguyên.
a.Viết hàm đếm số lượng số chính phương, số lượng số hoàn hảo, số lượng số nguyên tố.
b.Viết hàm đếm số lượng các giá trị lớn nhất.
5. Viết hàm đếm số lượng các giá trị phân biệt.
1.11 Kỹ thuật thêm/xóa phần tử
6. Cho mảng một chiều a chứa n số nguyên.
a.Viết hàm chèn thêm một phần tử có giá trị x vào mảng tại vị trí k.
b.Viết hàm xóa phần tử tại vị trí k trong mảng.
c.Viết hàm xóa tất cả các phần tử có giá trị bằng x trong mảng.
d.Viết hàm xóa tất cả các số nguyên tố trong mảng.
7. Viết hàm chèn một phần tử x vào mảng a (giả sử là mảng a đã được sắp tăng dần) để tính tăng
dần của mảng a là không vi phạm sau khi chèn thêm x.
1.12 Kỹ thuật sắp xếp
8. Viết hàm tìm k giá trị khác nhau lớn nhất của mảng.
9. Viết hàm hãy đưa các số chẵn về đầu mảng, các số lẻ về cuối mảng, còn các số 0 ở giữa.
10. Viết hàm sắp xếp các các số trong mảng sao cho các số dương được sắp xếp tăng dần, các số
âm được sắp xếp giảm dần, còn các số 0 thì giữ nguyên vị trí.
11. Cho mảng 1 chiều chứa n số nguyên dương. Hãy tìm các cặp số nguyên tố cùng nhau.
12. Cho mảng một chiều a chứa các số nguyên dương. Hãy xuất các số nguyên tố trong mảng a
theo chiều tăng dần.
13. Cho mảng hai chiều (ma trận) m dòng, n cột các phần tử là các số nguyên. Hãy thực hiện các
công việc sau đây:
a. Tính tổng các phần tử nằm trên các biên của mảng.
b. Tính tổng các giá trị lẻ trên mỗi cột.
c. Liệt kê chỉ số dòng có chứa giá trị nguyên tố.
d. Kiểm tra xem mảng có tồn tại số hoàn thiện nào không ?
Tổng quan Kỹ thuật lập trình Trang 20
e. Hoán vị hai cột k, l của mảng.
f. Dịch xuống xoay vòng các dòng.
g. Sắp xếp các dòng tăng dần theo tổng của từng dòng.
h*. Tìm giá trị xuất hiện nhiều lần nhất trong mảng.
14. Cho mảng hai chiều (ma trận) n dòng, n cột. Hãy thực hiện các công việc sau đây:
a. Tính tổng các phần tử nằm trên đường chéo phụ.
b. Kiểm tra ma trận có đối xứng qua đường chéo chính hay không ?
c. Kiểm tra xem đường chéo phụ có tăng dần từ trên xuống dưới hay không ?
d. Đếm số lượng giá trị chẵn trong ma trận tam giác trên.
e. Sắp xếp các phần tử trên đường chéo chính tăng dần.
f. Sắp xếp các phần tử trong ma trận tam giác trên tăng dần theo chiều từ trên xuống dưới và
từ trái qua phải.
g*. Ma trận vuông cấp n có 2n-1 đường chéo song song với đường chéo chính. - được đặt
tên là -(n-1) đến (n-1). Hãy tìm các đường chéo có tổng lớn nhất.
1.13 Tối ƣu hóa
15. Tính tổng !...
!3!2!11),(
32
n
xxxxxnS
n
16. Cho dãy n phần tử. hãy chuyển k phần tử đầu mảng về cuối mảng
17. Cho một bảng vuông gồm có n dòng, n cột (n<20). Trên mỗi ô của bảng chứa một phân số
(các phân số có cùng giá trị phải được xem là như nhau, chẳng hạn phân số 1/2 và 3/6 được xem
là như nhau). Hãy đếm tần số xuất hiện của các phân số trong bảng.
18. Cho hai chuỗi S1 và S2. Hãy tìm chuỗi con chung dài nhất.
19. Để quản lý nhân sự ở một tỉnh nọ có khoảng 1 triệu người, hãy sắp xếp tuổi của dân cư ở đây
theo thứ tự từ nhỏ đến lớn. Biết rằng tuổi ở đây chỉ nằm trong khoảng từ 1 đến 132. Tuổi của dân
cư được cho vào từ file AGE.INP viết liên tiếp nhau, cách nhau ít nhất một dấu cách hoặc một
dấu xuống dòng. Kết quả đưa ra file AGE.OUT cấu trúc như file vào nhưng đã được sắp xếp và
hãy cho biết tần suất xuất hiện của các độ tuổi.
TÀI LIỆU THAM KHẢO
1. Trần Tuấn Minh, Thiết kế và đánh giá thuật toán, Đà Lạt, 2002.
2. Nguyễn Đức Nghĩa, Nguyễn Tô Thành, Toán rời rạc, NXB Đại học Quốc gia Hà Nội, 2009
3. Học viện Công nghệ Bưu chính Viễn thông, Cấu trúc dữ liệu và giải thuật, Hà Nội, 2007
4. Đinh Mạnh Tường, Cấu trúc dữ liệu và giải thuật, NXB Đại Học Quốc Gia Hà Nội, 2008.
5. Huỳnh Minh Trí – Phan Tấn Quốc, Trường Đại học Sài Gòn, Giáo trình bài tập kỹ thuật lập
trình, 2009.
6. Học viện công nghệ bưu chính viễn thông, Giáo trình Kỹ thuật lập trình,, NXB Bưu điện,
2002.
7. Kỹ thuật lập trình C, Phạm Văn Ất, NXB Giáo dục, 2003.
8. www.google.com