Tải bản đầy đủ (.pdf) (134 trang)

Baigiang dung chung kythuatlaptrinh

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.32 MB, 134 trang )

TRƯỜNG ĐẠI HỌC KIẾN TRÚC HÀ NỘI

KHOA CÔNG NGHỆ THÔNG TIN

BÀI GIẢNG
KỸ THUẬT LẬP TRÌNH

GIẢNG VIÊN THỰC HIỆN: ThS. Nguyễn Bá Quảng
ThS. Bùi Hải Phong

Số tiết giảng: 60 tiết
Lý thuyết: 30 tiết
Thực hành: 30 tiết

Hà Nội, 6-2016


TRƯỜNG ĐẠI HỌC KIẾN TRÚC HÀ NỘI

KHOA CÔNG NGHỆ THÔNG TIN

BÀI GIẢNG
KỸ THUẬT LẬP TRÌNH
GIẢNG VIÊN THỰC HIỆN: ThS. Nguyễn Bá Quảng
ThS. Bùi Hải Phong

Kết quả đề tài gồm: 01 quyển bài giảng gồm 130 trang

HĐKH Khoa
Công nghệ thông tin


Bộ môn
MMT & HTTT

Hà Nội, 6-2016

G.V Thực hiện


MỤC LỤC
GIỚI THIỆU MÔN HỌC .................................................................................................... 1
CHƯƠNG 1: ĐẠI CƯƠNG VỀ KỸ THUẬT LẬP TRÌNH CẤU TRÚC ......................... 3
1.1 Sơ lược về lịch sử lập trình cấu trúc .......................................................................... 3
1.2. Cấu trúc lệnh, lệnh có cấu trúc và cấu trúc dữ liệu ................................................... 4
1.3. Nguyên lý tối thiểu .................................................................................................... 6
1.4. Nguyên lý địa phương ............................................................................................. 12
1.5. Nguyên lý nhất quán ............................................................................................... 13
1.6. Nguyên lý an toàn ................................................................................................... 13
1.7. Phương pháp Top-Down ......................................................................................... 15
1.8. Phương pháp Bottom-up ......................................................................................... 15
CHƯƠNG 2: DUYỆT VÀ ĐỆ QUY ................................................................................ 16
2.1. Định nghĩa đệ qui .................................................................................................... 16
2.2. Giải thuật đệ qui ..................................................................................................... 16
2.3. Thuật toán sinh kế tiếp ............................................................................................ 18
2.4. Thuật toán quay lui (Back Tracking) ...................................................................... 22
2.5. Thuật toán nhánh cận (Branch and bound) ............................................................. 28
CHƯƠNG 3: NGĂN XẾP, HÀNG ĐỢI VÀ DANH SÁCH LIÊN KẾT ......................... 35
3.1. Ngăn xếp và ứng dụng ........................................................................................... 35
3.2. Hàng đợi và ứng dụng ............................................................................................. 44
3.3. Danh sách liên kết và các thao tác làm việc với danh sách liên kết........................ 49
CHƯƠNG 4: CẤU TRÚC DỮ LIỆU CÂY (TREE) ........................................................ 60

4.1. Định nghĩa và các khái niệm ................................................................................... 60
4.2. Cây nhị phân ........................................................................................................... 61
4.3. Cây tổng quát .......................................................................................................... 69
4.4. Ứng dụng và một số cài đặt minh họa .................................................................... 70
CHƯƠNG 5: ĐỒ THỊ (GRAPH) ...................................................................................... 77
5.1. Định nghĩa và các khái niệm cơ bản của đồ thị ...................................................... 77
5.2. Biểu diễn đồ thị trên máy tính ................................................................................. 80


5.3. Các thuật tốn tìm kiếm trên đồ thị ......................................................................... 82
5.4. Cây khung (cây bao trùm) (spanning tree) của đồ thị ............................................. 90
5.5. Bài tốn tìm đường đi ngắn nhất ............................................................................. 97
CHƯƠNG 6: SẮP XẾP ................................................................................................... 104
6.1. Đặt vấn đề ............................................................................................................. 104
6.2. Các giải thuật sắp xếp cơ bản ................................................................................ 104
6.3. Cài đặt minh họa ................................................................................................... 110
CHƯƠNG 7: TÌM KIẾM ................................................................................................ 121
7.1. Phát biểu bài toán .................................................................................................. 121
7.2. Các giải thuật tìm kiếm cơ bản ............................................................................. 121
TÀI LIỆU THAM KHẢO ............................................................................................... 130


GIỚI THIỆU MƠN HỌC
I. Giới thiệu chung
Kỹ năng lập trình là kỹ năng bắt buộc và không thể thiếu đối với sinh viên công
nghệ thông tin. Đặc biệt với sinh viên năm thứ 2 chuyên ngành Công nghệ thông tin, việc
thành thạo một (một số) ngơn ngữ lập trình và có kỹ thuật lập trình tốt giúp sinh viên có
thể nắm bắt tốt các môn học tiếp theo cũng như phát triển nghề nghiệp sau này. Môn học
“Kỹ thuật lập trình” là mơn học bắt buộc đối với sinh viên ngành Công nghệ thông tin
những năm đầu. Để giảng dạy, học tập tốt môn môn học, tài liệu giảng dạy và các bài lập

trình minh họa là khơng thể thiếu. Do đó, Khoa Cơng nghệ thơng tin tổ chức biên soạn
bài giảng dùng chung cho môn học “Kỹ thuật lập trình”. Thơng qua bài giảng này, chúng
tơi muốn giới thiệu cho sinh viên về kiến thức và kỹ năng lập trình cấu trúc và một số
thuật tốn quan trọng: Đại cương về lập trình cấu trúc , duyệt và đệ qui, các thao tác làm
việc với ngăn xếp, hàng đợi, danh sách, cây, đồ thị, sắp xếp và tìm kiếm.
II. Mục tiêu môn học
Môn học cung cấp cho sinh viên những kỹ thuật lập trình trên các cấu trúc dữ liệu
quan trọng thường gặp như danh sách, ngăn xếp, hàng đợi, cây, đồ thị cùng với các
phương pháp phân tích, thiết kế thuật tốn.
Kết thúc mơn học sinh viên có thể lập trình với ngơn ngữ C/C++ để giải quyết các
bài toán khoa học và thực tế.
III. Phương pháp nghiên cứu
Bài giảng cho mơn học “Kỹ thuật lập trình” được biên soạn dựa trên các cơ sở như sau:
 Nghiên cứu, tham khảo các giáo trình, tài liệu liên quan đến bài giảng.
 Tổng hợp kiến thức cần thiết liên quan tới bài giảng.
 Lập trình minh họa các kỹ thuật, thuật tốn trên ngơn ngữ C/C++
Để học tốt học phần, sinh viên cần lưu ý các vấn đề sau:
Môn học được thiết kế cho sinh viên ngành Công nghệ thơng tin năm thứ 2 sau khi
đã hồn thành các học phần: Tin học đại cương, Nhập môn công nghệ thơng tin, Cấu trúc
dữ liệu và giải thuật, Tốn rời rạc. Do vậy, sinh viên cần nắm chắc các kiến thức và kỹ
năng của các học phần trước khi học học phần này. Các kiến thức đã học chỉ được nhắc
lại một cách ngắn gọn.

1


Kết hợp học lý thuyết và thực hành. Học tới đâu thực hành tới đó, đặc biệt chú
trọng vào kỹ năng lập trình giải quyết các bài tốn trong học phần.
Có ý thức tự giác học tập, tham gia đầy đủ các buổi học trên lớp.



CHƯƠNG 1: ĐẠI CƯƠNG VỀ KỸ THUẬT LẬP TRÌNH CẤU TRÚC
Nội dung chính của chương này là tổng hợp và làm rõ những nguyên lý cơ bản của lập
trình cấu trúc. Đây là những nguyên lý nền tảng của lập trình cấu trúc được tích hợp sẵn
trong các ngơn ngữ lập trình.
Các nguyên lý cơ bản của lập trình cấu trúc xem xét trong chương này bao gồm:








Nguyên lý lệnh - lệnh có cấu trúc - cấu trúc dữ liệu
Nguyên lý tối thiểu
Nguyên lý địa phương
Nguyên lý nhất quán
Nguyên lý an toàn
Phương pháp Top-Down
Phương pháp Bottom-up

1.1 Sơ lược về lịch sử lập trình cấu trúc
Lập trình được coi là một trong những công việc nặng nhọc nhất của khoa học
máy tính. Có thể nói, năng suất xây dựng các sản phẩm phần mềm là rất thấp so với các
hoạt động trí tuệ khác. Một sản phẩm phần mềm có thể được thiết kế và cài đặt trong
vòng 6 tháng với 3 lao động chính. Nhưng để kiểm tra tìm lỗi và tiếp tục hồn thiện sản
phẩm đó phải mất thêm chừng 3 năm. Đây là hiện tượng phổ biến trong tin học của
những năm 1960 khi xây dựng các sản phẩm phần mềm bằng kỹ thuật lập trình tuyến tính.
Để khắc phục tình trạng lỗi của sản phẩm, người ta che chắn nó bởi một mành che mang

tính chất thương mại được gọi là Version. Thực chất, Version là việc thay thế sản phẩm
cũ bằng cách sửa đổi nó rồi công bố dưới dạng một Version mới, giống như: MS-DOS
4.0 chỉ tồn tại trong thời gian vài tháng rồi thay đổi thành MS-DOS 5.0, MS-DOS 5.5,
MS-DOS 6.0 . . . Đây không phải là một sản phẩm mới như ta tưởng mà trong nó cịn tồn
tại những lỗi khơng thể bỏ qua được, vì ngay MS-DOS 6.0 cũng chỉ là sự khắc phục hạn
chế của MS-DOS 3.3 ban đầu.
Trong thời kỳ đầu của tin học, lập trình viên xây dựng các chương trình bằng ngơn
ngữ lập trình bậc thấp, chương trình được nạp và hoạt động trên chế độ trực tuyến, từng
dịng lệnh. Việc tìm và sửa lỗi tự động như hiện nay là chưa thực hiện được. Do đó, việc
lập trình phụ thuộc rất nhiều vào cá nhân lập trình viên.
Các hệ thống máy tính trong thời kỳ này có cấu hình tương đối thấp, dung lượng
bộ nhớ nhỏ, tốc độ xử lý chậm. Các chương trình được xây dựng bằng kỹ thuật lập trình


tuyến tính với các ngơn ngữ như Assembler hay Fortran. Với phương pháp lập trình
tuyến tính, lập trình viên chỉ có thể thực hiện hai cấu trúc lệnh là tuần tự và nhảy khơng
điều kiện (goto). Thư viện lập trình nghèo nàn khiến cơng việc lập trình trở nên hết sức
nặng nề. Việc xây dựng các hệ thống tin học gặp nhiều khó khăn, chi phí lớn, độ tin cậy
thấp.
Để giải quyết các vướng mắc trong lập trình, các nhà nghiên cứu lý thuyết tin học
đã đi sâu tìm hiểu bản chất của ngơn ngữ, thuật tốn, hoạt động lập trình và nâng lên
thành lý thuyết khoa học máy tính như ngày nay.
Để triển khai các nguyên lý của lập trình cấu trúc L. Wirth đã thiết kế và triển khai
ngôn ngữ ALGOL W là biến thể của ngôn ngữ ALGOL 60. Sau này, ơng tiếp tục hồn
thiện để trở thành ngôn ngữ Pascal. Đây được coi là một ngôn ngữ giản dị, trong sáng, dễ
minh họa các vấn đề phức tạp của lập trình và được coi là chuẩn mực trong giảng dạy lập
trình.
Năm 1978, Brian Barninghan cung Denit Ritche thiết kế ngơn ngữ lập trình C. Kể
từ đó, C trở thành ngôn ngữ rất thông dụng trong giảng dạy, nghiên cứu và được sử dụng
rộng rãi trong nhiều lĩnh vực khác nhau. Hai tác giả cũng phát hành phiên bản hệ điều

hành Unix viết bằng ngôn ngữ C, qua đó cho thấy vị thế của ngơn ngữ C trong việc phát
triển hệ thống.

1.2. Cấu trúc lệnh, lệnh có cấu trúc và cấu trúc dữ liệu
Sinh viên đã được làm quen cấu trúc lệnh và cấu trúc dữ liệu trong các học phần
trước đây (Tin học đại cương; Cấu trúc dữ liệu và giải thuật). Phần này tổng hợp các kiến
thức về các cấu trúc lệnh và cấu trúc dữ liệu
Cấu trúc lệnh
Những cấu trúc lệnh sau đây được sử dụng trong các ngơn ngữ lập trình có cấu
trúc. Ở đây, chúng ta không bàn tới cấu trúc nhảy vơ điều kiện (goto) mặc dù các ngơn
ngữ lập trình có cấu trúc đều trang bị cấu trúc này.


Hình 1.1. Cấu trúc tuần tự

Hình 1.2. Cấu trúc điều kiện, rẽ nhánh


Hình 1.3. Cấu trúc lặp

Cấu trúc dữ liệu
Cấu trúc dữ liệu được chia làm hai loại: cấu trúc dữ liệu cơ bản và cấu trúc dữ liệu
do người dùng định nghĩa. Kiểu dữ liệu cơ bản bao gồm: Kiểu ký tự (char), kiểu số
ngun có dấu và số ngun khơng dấu (signed int và unsigned int), kiểu số thực (float)
và kiểu số thực có độ chính xác kép (double).
Kiểu dữ liệu do người dùng tự định nghĩa: kiểu mảng (array), kiểu tập hợp (union),
kiểu cấu trúc (struct),…Các kiểu dữ liệu do người dùng tự định nghĩa như kiểu danh sách
móc nối (linked list), cấu trúc cây (tree)
Kích thước của kiểu cơ bản đồng nghĩa với miền xác định của kiểu với biểu diễn
nhị phân của nó, phụ thuộc vào từng hệ thống máy tính cụ thể.

1.3. Nguyên lý tối thiểu
Khi làm quen với một ngơn ngữ lập trình nào đó, người lập trình khơng nhất thiết
q phụ thuộc vào hệ thống thư viện của ngơn ngữ đó. Điều quan trọng là trước một bài
tốn cụ thể, người lập trình có thể tự lập trình giải quyết bằng hệ thống các chương trình
của riêng mình. Do vậy, đối với mỗi ngơn ngữ lập trình thủ tục cụ thể, chúng ta chỉ cần
nắm các công cụ tối thiểu như sau.
Tập các phép toán số học:
Toán tử

Ý nghĩa

+

Phép cộng

-

Phép trừ


*

Phép nhân

/

Phép chia

%


Phép chia lấy phần dư

Tập các phép toán so sánh:
Toán
tử

Tên gọi

==
!=
>
<
>=

Bằng
Khác
Lớn hơn
Nhỏ hơn
Lớn hơn hoặc bằng

<=

Nhỏ hơn hoặc bằng

Giá trị biểu thức “a toán tử b”
Đúng
Sai
Nếu a bằng b
Nếu a khác b
Nếu a khác b

Nếu a bằng b
Nếu a lớn hơn b
Nếu a nhỏ hơn b
Nếu a nhỏ hơn b
Nếu a lớn hơn b
Nếu a lớn hơn hoặc bằng Nếu a nhỏ hơn hoặc
b
bằng b
Nếu a nhỏ hơn hoặc bằng Nếu a lớn hơn hoặc
b
bằng b

Tập các phép tốn logic:
Tên
Tốn tử
Tốn tử phủ Phép tốn 1 ngơi !a
định
Tốn tử hội
&&

Phép tốn 2 ngơi
a && b

Tốn tử tuyển
||

Phép tốn 2 ngơi
a || b

A

True
False
True
True
False
False
True
True
False
False

b
true
false
true
false
true
false
true
false

Kết quả
False
True
True
False
False
False
True
True

True
False

Tốn tử chuyển đổi kiểu:
Chúng ta sử dụng một trong các cú pháp sau để chuyển đổi kiểu dữ liệu:
(kiểu_dữ_liệu)biến hoặc (kiểu_dữ_liệu)(biến) hoặc kiểu_dữ_liệu(biến).


Ví dụ:
Tính giá trị của phép chia 2 số nguyên:
#include <cstdlib>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int a=3,b=5;
float c;
c= (float) a/b;
cout<<"Ket qua phep chia"<system("PAUSE");
return EXIT_SUCCESS;
}
Thứ tự ưu tiên các phép toán: trong khi thực hiện phép toán, chúng ta cần chú ý tới thứ tự
thực hiện các phép toán. Bảng sau đây cho thấy thứ tự thực hiện các phép toán:
Mức ưu tiên
1
2
3

4

5
6
7
8
9
10
11
12

Toán tử
::
() [] . -> ++ -- (hậu tố) dynamic_cast
static_cast reinterpret_cast const_cast
++ -- (tiền tố) ~ ! sizeof new delete
*&
+ - (dấu dương âm)
(type) (chuyển đổi kiểu)
.* ->*
*/%
+ - (phép tốn cơng, trừ)
<< >>
< > <= >=
== !=
&
^

Độ ưu tiên cùng loại
Trái sang phải
Trái sang phải
Từ phải sang trái


Từ phải sang trái
Trái sang phải
Trái sang phải
Trái sang phải
Trái sang phải
Trái sang phải
Trái sang phải
Trái sang phải
Trái sang phải


13
14
15
16
17
18

|
&&
||
?:
= *= /= %= += -= >>= <<= &= ^= |=
,

Trái sang phải
Trái sang phải
Trái sang phải
Từ phải sang trái

Từ phải sang trái
Trái sang phải

Các tập lệnh vào ra cơ bản với màn hình và bàn phím:
Các thao tác nhập, xuất cơ bản trong C++:
cout<cin>>biến_1>>…>>biến_n;
Chú ý: Các thao tác nhập, xuất cơ bản trong C như scanf, printf vẫn sử dụng được trong
C++.
Các thao tác làm việc với file cơ bản trong C++ sử dụng thư viện fstream
Cách đọc và ghi ra file sử dụng thư viện fstream trong C++ tương đối đơn giản. Các ví dụ
sau minh họa thao tác đọc, ghi file
Chương trình sau thực hiện mở một file và ghi dữ liệu vào file sử dùng thư viện fstream
#include <iostream>
#include <fstream>
#include <iostream>
using namespace std;

int main()
{
ofstream FileDemo ("File Demo.txt");
FileDemo<<"Day la file demo su dung cach doc va ghi file su dung fstream";
FileDemo.close();
system("pause");


return 0;
}
Chương trình sau thực hiện mở file và đọc dữ liệu từ file. File có tên “So Chan.txt” có
chứa các số chẵn:

#include <fstream>
#include <iostream>
using namespace std;
int main()
{
int a[501];
ifstream SoChan ("So Chan.txt");
if(! SoChan.is_open())
{
cout<<"Khong the mo file.\n";
return 0;
}
else {
for(int i = 1; i <= 500; i++)
{
SoChan>>a[i];
}
}
for(int i =1; i <= 500; i++)
{
cout<}


SoChan.close();
system("pause");
return 0;
}

Các thao tác làm việc với con trỏ

Thao tác lấy địa chỉ của biến: ¶meter_name;
Thao tác lấy giá trị của biến: *pointer_name;
Thao tác cấp phát bộ nhớ: pointer = new type;
pointer =new type [number of elements];
Thao tác giải phóng bộ nhớ: delete pointer;
delete[] pointer;
Ví dụ:
#include <cstdlib>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{ int a=5;
int b=6;
int *pa=&a;
cout<<"Gia tri con tro pa: "<<*pa<cout<<"Dia chi cua bien b: "<<&b<system("PAUSE");
return EXIT_SUCCESS;
}


Các thao tác làm việc cơ bản với cấu trúc:
Định nghĩa cấu trúc:
struct struct_name{
type_1 parameter1;
type_2 parameter2;

type_k parameterk;
}struct_name;
Ví dụ: định nghĩa cấu trúc có tên là sinhvien có hai trường họ tên và điểm:

typedef struct{
string ten;
float diem;
}sinhvien;
Truy nhập tới thành phần của cấu trúc:
Struct_name.parameter_name
Phép gán hai cấu trúc cùng kiểu:
Struc_name1=struct_name2
Phép tham trỏ tới thành phần của con trỏ cấu trúc:
Pointer_struct_name->struct_parameter_name

1.4. Nguyên lý địa phương
Các nguyên lý địa phương trong kỹ thuật lập trình có cấu trúc bao gồm:
Tên của các biến địa phương trong hàm, thủ tục, mặc dù có trùng tên với biến tồn cục
thì khi xử lý biến đó trong hàm hoặc thủ tục vẫn không làm ảnh hưởng tới biến toàn cục.
Tên của biến trong tham số của hàm hay thủ tục đều là hình thức
Mọi biến hình thức truyền theo tham trị cho hàm đều là biến địa phương


Các biến khai báo bên trong hàm đều là biến địa phương
Khi sử dụng biến, nên sử dụng biến địa phương, hạn chế sử dụng biến toàn cục để tránh
xảy ra hiệu ứng phụ.
1.5. Nguyên lý nhất quán
Dữ liệu thế nào thì 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ờ bao giờ cũng thuộc một
kiểu xác định nào đó hoặc kiểu cơ bản hoặc kiểu do người dùng định nghĩa. Thao tác của
biến phụ thuộc vào các thao tác được phép của kiểu. Hai kiểu phân biệt với nhau bởi tên,
miền giá trị và các phép tốn trên kiểu đó.

Với kiểu ký tự, về ngun tắc chúng ta không được phép thực hiện các phép tốn
số học trên nó, tuy vậy, ngơn ngữ C ln đồng nhất ký tự với 1 số nguyên có độ lớn 1
byte. Do đó, các phép tốn số học thực hiện với ký tự thực chất là các phép toán số học
thực hiện với số nguyên.
Ví dụ:
char x1='A',x2='z';
x1=x1+3;
x2=x2+3;
Trong khai báo trên, x1,x2 là các ký tự. Tuy nhiên, khi thực hiện các phép tốn, chương
trình dịch sẽ tự động chuyển mã của ký tự ‘A’ là 65 và ‘z’ là 122 để thực hiện các phép
tốn.
Chúng ta có thể thực hiện các phép toán số học trên các kiểu int,long,float,double.
Đối với kiểu xâu ký tự string, ngơn ngữ lập trình Pascal thì cho phép so sánh trực tiếp hai
xâu ký tự, cịn ngơn ngữ C thì khơng cho phép. Muốn so sánh hai xâu ký tự trong C
chúng ta phải tự định nghĩa hoặc sử dụng các hàm xử lý xâu ký tự.
1.6. 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 chịu
lỗi cao nhất.


Mọi lỗi, dù là nhỏ nhất cũng cần được phát hiện ở bước nào đó của chương trình. Quy
trình kiểm tra và xử lý lỗi cần được thực hiện trước khi lỗi xảy ra.
Các lỗi thường xảy ra nhất khi viết chương trình có thể được tổng kết như sau:
Lỗi cú pháp: Lỗi xảy ra khi viết chương trình chúng ta viết sai cú pháp trong khi soạn
thảo chương trình, ví dụ viết “Int” thay vì “int” trong C (phân biệt chữ hoa, chữ thường)
hoặc viết sai cú pháp biểu thức, thiếu dấu ngoặc kép, dấu chấm phẩy khi kết thúc lệnh,
thiếu khai báo nguyên mẫu hàm.
Lỗi cảnh báo (Warning): một số lỗi cảnh báo có thể xảy ra trong q trình viết chương
trình như khai báo biến mà khơng sử dụng, lỗi sai thứ tự ưu tiên các phép tốn trong biểu
thức, lỗi khơng xác định được giá trị của biến.

Lỗi xảy ra trong quá trình liên kết: lỗi này thường xuất hiện khi chúng ta khai báo
nguyên mẫu hàm nhưng chưa định nghĩa, mô tả chi tiết hàm; hoặc lỗi xảy ra khi chúng ta
gọi sai tên hàm. Với các lỗi này, chúng ta có thể khắc phục bằng cách chỉnh sửa lại tên
hàm cho phù hợp, mô tả lại chi tiết hoạt động của các hàm.
Các loại lỗi trên (lỗi cú pháp, lỗi cảnh báo, lỗi xảy ra trong quá trình liên kết) là những lỗi
tầm thường vì các lỗi này được trình biên dịch Compiler của các ngơn ngữ lập trình phát
hiện. Người dùng cần đọc, hiểu các thơng báo lỗi của trình biên dịch và sửa lỗi cho phù
hợp.
Ngoài những lỗi kể trên, lỗi chương trình cịn có thể do người lập trình gây nên trong quá
trình thiết kế chương trình và xử lý dữ liệu, lỗi này khơng thể phát hiện ra bởi trình biên
dịch Compiler. Lỗi này chỉ có thể phát hiện trong quá trình kiểm thử chương trình. Để
sửa loại lỗi này, chương trình cần được kiểm tra và phân tích tỉ mỉ.
Ví dụ sau đây minh họa cho lỗi này:
#include <stdlib.h>
#include <stdio.h>
#define Max 100 //so phan tu toi da cua Stack
typedef int item; //kieu du lieu cua Stack
struct Stack
{
int Top; //Dinh Top
item Data[Max]; //Mang cac phan tu
};


Trong chương trình trên, chúng ta khai báo bậc của đa thức là hằng số MAX=100; do đó,
với bậc đa thức nhập vào lớn hơn 100, chương trình sẽ có lỗi (n>MAX hoặc m>MAX).
Nếu chúng ta khắc phục bằng cách khai báo hằng số MAX đủ lớn thì sẽ gây lãng phí bộ
nhớ trong trường hợp bậc n của đa thức nhỏ. Để khắc phục sự cố này, chúng ta sẽ sử
dụng con trỏ thay cho hằng số. Kỹ thuật này sẽ được đề cập tới ở chương 2.


1.7. Phương pháp Top-Down
Q trình phân tích bài tốn được thực hiện từ trên xuống dưới, từ vấn đề chung
nhất tới 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.
Phương pháp phân tích từ trên xuống (Top-Down) là một ngun lý quan trọng
của lập trình có cấu trúc.
Q trình phân rã bài tốn được thực hiện thao từng mức khác nhau. Mức thấp
nhất gọi là mức tổng quan, mức tổng quan cho phép chúng ta nhìn tổng thể hệ thống theo
chức năng của nó, nói theo cách khác, mức tổng quan cho biêt hệ thống có chức năng gì.
Mức tiếp theo là mức các chức năng chính của hệ thống. Q trình phân tích được thực
hiện cho tới mức đơn giản nhất có thể thực hiện được. Khi đó chúng ta tiến hành cài đặt
hệ thống.
1.8. Phương pháp Bottom-up
Nếu phương pháp Top-down là phương pháp phân tách vấn đề 1 cách có hệ thống
từ trên xuống, được ứng dụng chủ yếu cho q trình phân tích và thiết kế hệ thống, thì
phương pháp Bottom-up được sử dụng cho quá trình cài đặt hệ thống.


CHƯƠNG 2: DUYỆT VÀ ĐỆ QUY
Duyệt toàn bộ là phương pháp phổ biến để giải quyết các bài toán trên máy tính.
Các kỹ thuật duyệt cũng rất phong phú, đa dạng và có hiệu lực lớn trong việc hạn chế
khơng gian tìm kiếm của bài tốn nếu chúng ta áp dụng một số mẹo trong phép duyệt.
Giải thuật đệ qui được sử dụng nhiều trong các phép duyệt. Lời giải đệ qui thường ngắn
gọn nhưng cũng ẩn chứa nhiều yếu tố bí ẩn.Tuy vậy, đệ qui được coi là một mẫu hình để
vét cạn mọi lời giải của bài tốn.
Các kỹ thuật đệ qui được đề cập tới trong chương này bao gồm:
 Các định nghĩa đệ qui, cấu trúc dữ liệu được định nghĩa bằng đệ qui và giải thuật
đệ qui.
 Thuật toán sinh kế tiếp giải quyết bài toán duyệt
 Thuật toán quay lui giải quyết bài toán duyệt

 Thuật toán nhánh cận giải quyết bài toán duyệt

2.1. Định nghĩa đệ qui
Trong thực tế, nhiều trường hợp chúng ta rất khó định nghĩa đối tượng một cách
tường minh nhưng lại dễ dàng định nghĩa đối tượng qua chính nó.
Một đối tượng là đệ qui nếu nó được định nghĩa qua chính nó hoặc một đối tượng
khác cùng dạng với chính nó bằng quy nạp.
Ví dụ:
Giai thừa của n (n!): nếu n=0 thì n!=1; nếu n>0 thì n!=n*(n-1)!
2.2. Giải thuật đệ qui
Một thuật toán được gọi là đệ qui nếu nó giải bài tốn bằng cách rút gọn bài toán
ban đầu thành bài toán tương tự như vậy sau một số hữu hạn lần thực hiện. Trong mỗi lần
thực hiện, dữ liệu đầu vào tiệm cận tới dữ liệu dừng.
Nói cách khác lời giải của bài tốn P được thực hiện bằng lời giải của bài tốn P’
có dạng giống như P. Mấu chốt cần lưu ý là: lời giải của bài toán P’ phải đơn giản hơn
bài toán P và việc giải nó khơng cần đến P.
Định nghĩa một hàm đệ qui gồm 2 phần:


Phần neo (anchor): phần này được thực hiện trực tiếp vì lời giải đơn giản, khơng
cần nhờ tới lời giải của bài toán con nào khác.
Phần đệ qui: Trong trường hợp bài toán chưa giải quyết được bằng phần neo, ta
xác định những bài toán con và gọi đệ qui giải những bài tốn con đó. Khi đã có lời giải
của những bài toán con rồi, ta phối hợp chúng lại để giải bài toán đang quan tâm.
Phần đệ qui thể hiện tính quy nạp của lời giải, phần neo quyết định tính hữu hạn
và tính dừng của lời giải.
Một số ví dụ về giải thuật đệ qui
Ví dụ 1: Tính an với a là số thực, n là số tự nhiên

Ví dụ 2: Thuật tốn đệ qui tính ước chung lớn nhất của 2 số nguyên dương a,b:


Ví dụ 3: Giải thuật đệ qui tính n!


Ví dụ 4: Giải thuật đệ qui tính số Fibonacci
Dãy số Fibonacci được định nghĩa như sau:
F(n)=1 nếu n<2
F(n)=F(n-1)+F(n-2) nếu n>=2
int fibonacci(int n)
{
if (n==0) return (0);
else if (n==1) return (1);
return (fibonacci(n-1)+fibonacci(n-2));
}

2.3. Thuật toán sinh kế tiếp
Phương pháp sinh kế tiếp được dùng để giải quyết bài toán liệt kê của lý thuyết tổ hợp.
Phương pháp này chỉ thực hiện được với lớp các bài toán thỏa mãn các điều kiện sau:
 Có thể xác định được một thứ tự trên tập các cấu hình tổ hợp cần liệt kê, từ đó xác
định được cấu hình đầu tiên và cấu hình cuối cùng.
 Từ một cấu hình bất kỳ chưa phải là cuối cùng, đều có thuật tốn sinh cấu hình kế
tiếp.
Tổng quát, thủ tục sinh kế tiếp được mô tả bằng thủ tục “generate”, trong đó
“sinh_ke_tiep” là thuật tốn sinh cấu hình tiếp theo thuật tốn sinh đã được xây dựng.


Nếu cấu hình hiện tại là cấu hình cuối cùng thì thủ tục “sinh_ke_tiep” sẽ gán cho stop giá
trị true, ngược lại cấu hình kế tiếp sẽ được sinh ra.
Mơ tả thuật toán bằng mã giả như sau:
generate(){

<xây dựng cấu hình ban đầu>
stop=false;
while(!stop)
{
<Đưa ra cấu hình đang có>
sinh_ke_tiep;
}
}

Dưới đây là một số ví dụ điển hình minh họa thuật tốn sinh kế tiếp
Bài toán sinh xâu nhị phân độ dài n: Hãy liệt kê tất cả các xâu nhị phân có độ dài n cho
trước. Như vậy sẽ có tất cả 2n xâu nhị phân có độ dài n.
Bây giờ chúng ta xác định thứ tự các xâu nhị phân và phương pháp sinh kế tiếp các xâu
nhị phân.
Ví dụ với n=4, chúng ta có các xâu nhị phân sau:
b

p(b)

0000

0

0001

1


1111


15

Xâu nhị phân đầu tiên là “0000” và xâu nhị phân cuối cùng là “1111”. Xâu nhị phân kế
tiếp là biểu diễn của xâu nhị phân trước đó cộng thêm 1.
Phương pháp liệt kê như sau:
Tìm chỉ số i đầu tiên theo thứ tự i=n,n-1,…,1 sao cho bi=0


Gán lại bi=1 và bj=0 với j>i. Dãy nhị phân thu được là dãy cần tìm.
Cài đặt minh họa chương trình liệt kê các xâu nhị phân độ dài n
#include <cstdlib>
#include <iostream>

using namespace std;
#define MAX 100
#define TRUE 1
#define FALSE 0
int stop, dem;

void init(int *B,int n)//Khoi tao xau nhi phan
{
for(int i=0;i{
B[i]=0;
}
dem=0;
}
void result(int *B,int n)//Hien thi xau nhi phan
{
dem++;

cout<<"Hien thi xau nhi phan thu: "<for(int i=0;i<=n;i++)
{
cout<

}
}
void next_bit(int *B,int n)
{
int i=n;
while(i>0&&B[i]>0)
{
B[i]=0;
i--;
}
if(i==0) stop=true;
else B[i]=1;
}
void generate(int *B,int n)
{
stop=false;
while(stop==false)
{
result(B,n);
next_bit(B,n);
}
}

int main(int argc, char *argv[])

{
int *B;


×