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

Cấu trúc dữ liệu và giải thuật ĐH saigon

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 (611.57 KB, 128 trang )

Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 0


TRƯỜNG ĐẠI HỌC SÀI GÒN
KHOA CÔNG NGHỆ THÔNG TIN
BỘ MÔN KHOA HỌC MÁY TÍNH
o0o








BÀI TẬP
CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
(lưu hành nội bộ)









Năm 2010

Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 1





Lời giới thiệu

Cấu trúc dữ liệu và giải thuật là học phần bắt buộc thuộc khối kiến thức cơ
sở ngành của sinh viên các chuyên ngành công nghệ thông tin và cũng là nội dung
quan trọng ở các kỳ thi tốt nghiệp, thi hoàn chỉnh đại học các chuyên ngành công
nghệ thông tin.
Giáo trình này trình bày các chủ đề bài tập về:Tổng quan về cấu trúc dữ liệu
và giải thuật, tìm kiếm, sắp xếp, cấu trúc danh sách liên kết và cấu trúc cây theo
ngôn ngữ C/C++. Mỗi chủ đề được thiết kế gồm các phần: Tóm tắt lý thuyết, một số
dạng bài tập điển hình và một số đề bài tập chọn lọc. Phần cuối của giáo trình có
hướng dẫn giải cho một số bài tập tiêu biểu, đồng thời bổ sung một số đề thi mẫu để
sinh viên tự rèn luyện kỹ năng phân tích vấn đề bài toán. Giáo trình chỉ trình bày vấn
đề bài tập, còn các vấn đề lý thuyết liên quan thì bạn đọc có thể tham khảo chi tiết ở
các quyển sách đã được chỉ ra ở phần tài liệu tham khảo.
Quyển giáo trình này được biên soạn để làm tài liệu tham khảo khi giảng các
học phần cấu trúc dữ liệu và giải thuật ở hệ đại học và cao đẳng. Chúng tôi xin trân
trọng giới thiệu với bạn đọc quyển giáo trình này và hy vọng rằng nó sẽ giúp cho
việc giảng dạy và học tập học phần cấu trúc dữ liệu và giải thuật được thuận lợi
hơn.

Thành phố Hồ Chí Minh, ngày 06 tháng 09 năm 2010
CÁC TÁC GIẢ




Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 2



Chương 1
TỔNG QUAN VỀ CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT

A.Tóm tắt lý thuyết
1.Cấu trúc dữ liệu và giải thuật
Xuyên suốt trong giáo trình này, chúng tôi muốn đề cập đến hai mặt quan
trọng của một vấn đề bài toán là cách thức tổ chức dữ liệu của bài toán và các phép
xử lý trên các dữ liệu đó.
1.1.Cấu trúc dữ liệu
Cấu trúc dữ liệu của bài toán là cách thức tổ chức dữ liệu sao cho phản ánh
chính xác dữ liệu của bài toán và có thể dùng máy tính để xử lý các dữ liệu đó một
cách hiệu quả.
Một cấu trúc dữ liệu được đánh giá là tốt nếu nó thỏa mãn được các yêu cầu
như: Phản ánh đúng thực tế bài toán, phù hợp với các thao tác xử lý trên đó, tiết
kiệm được tài nguyên hệ thống,…
1.2.Giải thuật (trong giáo trình này chúng tôi đồng nhất khái niệm thuật toán
và giải thuật)
Giải thuật là một bảng liệt kê các chỉ dẫn (hay các qui tắc) cần thực hiện theo
từng bước xác định nhằm giải quyết một vấn đề bài toán.
Các đặc trưng của giải thuật
Tính xác định: Ở mỗi bước các chỉ dẫn phải rõ ràng.
Tính kết thúc: Giải thuật phải dừng sau một số hữu hạn bước.
Tính đúng đắn: Giải thuật phải cho ra kết quả đúng theo yêu cầu của
bài toán.
Tính tổng quát: Giải thuật phải áp dụng được cho các bài toán cùng
loại.
1.3.Sự liên hệ giữa giải thuật và cấu trúc dữ liệu
Giải thuật và cấu trúc dữ liệu có mối liên hệ chặt chẽ với nhau. Giải thuật

phản ánh các phép xử lý, còn đối tượng xử lý của giải thuật là các dữ liệu; dữ liệu
chứa đựng các thông tin cần thiết để thực hiện giải thuật. Để xác định được giải thuật
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 3


phù hợp cần phải biết nó tác động đến những loại dữ liệu nào và khi chọn lựa một
cấu trúc dữ liệu cũng cần phải hiểu rõ những thao tác nào sẽ được tác động lên nó.
2.Độ phức tạp của một giải thuật
2.1.Phân tích thời gian thực hiện giải thuật
Với một bài toán chỉ có một giải thuật. Việc chọn lựa giải thuật đưa đến kết
quả nhanh là một đòi hỏi quan trọng. Vấn đề là căn cứ vào những yêu tố nào để biết
giải thuật này nhanh hơn giải thuật kia?
Rõ ràng thời gian thực hiện một giải thuật (hay chương trình để thực hiện giải
thuật đó) phụ thuộc vào nhiều yếu tố. Một yếu tố cần chú ý đầu tiên tiên chính là kích
thước của dữ liệu đưa vào. Chẳng hạn thời gian để sắp xếp một dãy số chịu ảnh
hưởng bởi số lượng số của dãy số đó. Nếu gọi n là số lượng này, thì thời gian thực
hiện T của một giải thuật được biểu diễn như một hàm của n: T(n).
Các kiểu lệnh và tốc độ xử lý của máy tính, ngôn ngữ viết chương trình và
chương trình dịch ngôn ngữ ấy đều ảnh hưởng đến thời gian thực hiện chương trình;
nhưng những yếu tố này không đồng đều với mỗi loại máy tính. Vì vậy không thể
dựa vào chúng khi xác lập T(n). Điều đó cũng có nghĩa là T(n) không thể được biểu
diễn thành đơn vị thời gian bằng giây, bằng phút được. Tuy nhiên không phải vì thế
mà không thể so sánh được các giải thuật về mặt tốc độ. Nếu như thời gian thực hiện
của một giải thuật là T
1
(n)=Cn
2
và thời gian thực hiện giải thuật khác là T
2
(n)= kn

(C, n, k là các hằng số nào đó), thì khi n khá lớn, thời gian thực hiện giải thuật t
2
ít
hơn so với giải thuật T
1
, như vậy nếu nói thời gian thực hiện giải thuật T(n) tỉ lệ với
với n
2
hay tỉ lệ với n cũng cho ta ý niệm về tốc độ thực hiện giải thuật đó khi n khá
lớn (với n nhỏ thì việc xét T(n) không có ý nghĩa). Cách đánh giá thời gian thực hiện
giải thuật độc lập với máy tính và các yếu tố liên quan tới máy như vậy sẽ dẫn tới
khái niệm về “cấp độ lớn của thời gian thực hiện giải thuật” hay còn gọi là “độ phức
tạp tính toán của giải thuật”.
2.2.Thời gian chạy của các lệnh
Lệnh gán
Lệnh gán có dạng
X = <biểu thức>
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 4


Thời gian chạy của lệnh gán là thời gian thực hiện biểu thức. Trường hợp hay
gặp nhất là biểu thức chỉ chứa các phép toán sơ cấp, và thời gian thực hiện nó là
O(1). Nếu biểu thức chứa các lời gọi hàm thì ta phải tính đến thời gian thực hiện
hàm, và do đó trong trường hợp này thời gian thực hiện biểu thức có thể không còn
phải O(1).
Lệnh lựa chọn
if (<điều kiện>)
<lệnh 1>;
else
<lệnh 2>;

Giả sử thời gian đánh giá điều kiện là T
0
(n), thời gian thực hiện <lệnh 1> là
T
1
(n), thời gian thực hiện <lệnh 2> là T
2
(n). Thời gian thực hiện lệnh lựa chọn if-else
sẽ là thời gian lớn nhất trong các thời gian T
0
(n) + T
1
(n) và T
0
(n) + T
1
(n).
Trường hợp hay gặp là kiểm tra điều kiện chỉ cần O(1). Khi đó nếu T
1
(n) =
O(f(n)), T
2
(n) = O(g(n)) và f(n) tăng nhanh hơn g(n) thì thời gian chạy của lệnh if-
else là O(f(n)); còn nếu g(n) tăng nhanh hơn f(n) thì lệnh if-else cần thời gian
O(g(n)).
Thời gian chạy của lệnh lựa chọn switch được đánh giá tương tự như lệnh if-
else, chỉ cần lưu ý rằng, lệnh if-else có hai khả năng lựa chọn, còn lệnh switch có thể
có nhiều hơn hai khả năng lựa chọn.
Các lệnh lặp
for, while, do-while

Để đánh giá thời gian thực hiện một lệnh lặp, trước hết ta cần đánh giá số tối
đa các lần lặp, giả sử đó là L(n). Sau đó đánh giá thời gian chạy của mỗi lần lặp, chú
ý rằng thời gian thực hiện thân của một lệnh lặp ở các lần lặp khác nhau có thể khác
nhau, giả sử thời gian thực hiện thân lệnh lặp ở lần thứ i (i=1,2, , L(n)) là T
i
(n). Mỗi
lần lặp, chúng ta cần kiểm tra điều kiện lặp, giả sử thời gian kiểm tra là T
0
(n). Như
vậy thời gian chạy của lệnh lặp là:
() ()()

=
+
)(
1
0
nL
i
i
nTnT
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 5


Công đoạn khó nhất trong đánh giá thời gian chạy của một lệnh lặp là đánh
giá số lần lặp. Trong nhiều lệnh lặp, đặc biệt là trong các lệnh lặp for, ta có thể thấy
ngay số lần lặp tối đa là bao nhiêu. Nhưng cũng không ít các lệnh lặp, từ điều kiện
lặp để suy ra số tối đa các lần lặp, cần phải tiến hành các suy diễn không đơn giản.
Trường hợp hay gặp là: kiểm tra điều kiện lặp (thông thường là đánh giá một
biểu thức) chỉ cần thời gian O(1), thời gian thực hiện các lần lặp là như nhau và giả

sử ta đánh giá được là O(f(n)); khi đó, nếu đánh giá được số lần lặp là O(g(n)), thì
thời gian chạy của lệnh lặp là O(g(n)f(n)).
2.3.Độ phức tạp tính toán của giải thuật
Nếu thời gian thực hiện một giải thuật là T(n)=Cn
2
(với C là hằng số) thì ta
nói: độ phức tạp tính toán của giải thuật này có cấp là n
2
(hay cấp độ lớn của thời
gian thực hiện giải thuật là n
2
) và ta ký hiệu T(n) = O(n
2
) - ký hiệu chữ O lớn. Một
cách tổng quát ta có thể định nghĩa như sau:
Một hàm f(n) được xác định là O(g(n))
f(n)=O(g(n)) và được gọi là có cấp g(n) nếu tồn tại một hằng số C và n
o
sao
cho f(n) ≤ C.g(n) khi n ≥ n
o

Nghĩa là f(n) bị chặn trên bởi một hằng số nhân với g(n), với mọi giá trị của n
từ một điểm nào đó. Chú ý rằng O(C(f(n))=O(f(n))
Để xác định độ phức tạp tính toán của một giải thuật bất kỳ có thể dẫn tới
những bài toán phức tạp. Tuy nhiên trong thực tế, đối với một số giải thuật ta cũng
có thể phân tích được bằng một số quy tắc đơn giản.
Quy tắc cộng
Giả sử T
1

(n) và T
2
(n) là thời gian thực hiện của hai đoạn chương trình P
1

P
2
mà T
1
(n) = O(f(n)) và T
2
(n)=O(g(n)), thì thời gian thực hiện P
1
rồi P
2
tiếp theo sẽ
là T
1
(n)+ T
2
(n) = O( max(f(n), g(n))
Chẳng hạn đoạn lệnh
for (int i=1;i<=n;i++)
x=x+1;
có thời gian thực hiện là O(n.1) = O(n)
Quy tắc nhân
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 6


Nếu tương ứng với P

1
và P
2
là T
1
(n) và T
2
(n), T
1
(n) = O(f(n)) và
T
2
(n)=O(g(n)), thì thời gian thực hiện P
1
và P
2
lồng nhau là T
1
(n) * T
2
(n) =
O(f(n).g(n))
Chẳng hạn đoạn lệnh
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++ )
x=x+1;
có thời gian thực hiện là O(n.n)=O(n
2
).
Chú ý rằng có những trường hợp giải thuật không phải chỉ thuộc vào kích

thước của dữ liệu vào mà còn phụ thuộc vào chính tính trạng của dữ liệu đó nữa.
Chẳng hạn việc sắp xếp một dãy số theo thứ tự tăng dần nếu gặp dãy số đưa vào đã
có đúng thứ tự thì sẽ khác với trường hợp dãy số đưa vào chưa có thứ tự hoặc có thứ
tự ngược lại, lúc đó khi phân tích thời gian thực hiện giải thuật ta sẽ phải xét tới: đối
với mọi dữ liệu vào có kích thước n thì T(n) trong trường hợp thuật lợi nhất là thế
nào? rồi T(n) trong trường hợp xấu nhất và T(n) trung bình ? Việc xác định T(n)
trung bình thường khó vì sẽ phải dùng tới những công cụ toán phức tạp. Trong các
trường hợp mà T(n) trung bình khó xác định người ta thường đánh giá giải thuật qua
giá trị xấu nhất của T(n).
2.4.Sự phân lớp các giải thuật
Thông thường các hàm thể hiện độ phức tạp tính toán của giải thuật có dạng
hằng số, log
2
n, n, nlogn, n
2
, n
3
, 2
n
, n!, n
n
,…
Hằng số:Hầu hết các chỉ thị của các chương trình đều được thực hiện một lần
hay một số số lần nhất định không phụ thuộc vào n
Các hàm như 2
n
, n!, n
n
được gọi là hàm mũ. Một giải thuật mà thời gian thực
hiện của nó có cấp là các hàm loại mũ thì tốc độ rất chậm. Các hàm log

2
n, n, nlogn,
n
2
, n
3
được gọi là các hàm loại đa thức. Giải thuật với thời gian thực hiện có cấp hàm
đa thức thì thường là chấp nhận được.
Các cấp độ thời gian chạy của giải thuật và tên gọi của chúng được liệt kê
trong bảng sau:
Ký hiệu ô lớn Tên gọi
O(1) hằng
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 7


O(logn)
O(n)
O(nlogn)
O(n
2
)
O(n
3
)
O(2
n
)
logarit
tuyến tính
nlogn

bình phương
lập phương

B.Các dạng bài tập
Dạng 1: Bài toán với cấu trúc dữ liệu là mảng một chiều
Ví dụ 1.1: Cộng hai số nguyên lớn
Cho hai số nguyên lớn a và b; a có m chữ số và b có n chữ số. Hãy viết
chương trình tính tổng a+b.
Giải thuật
Số nguyên lớn ở đây là số có thể có đến vài nghìn chữ số. Để lưu trữ các số
nguyên lớn này ta có thể dùng chuỗi (mỗi ký tự của chuỗi là một chữ số) hoặc dùng
mảng một chiều (mỗi phần tử của mảng một chiều là một chữ số). Tuy nhiên trong
hai phương án này thì phương án dùng mảng một chiều để lưu trữ sẽ có giải thuật tốt
hơn.
Giải thuật này có thể trình bày ngắn gọn như sau:
Bước 1:Nhập hai số nguyên lớn a,b. Để có thể thực hiện được phép a+b một
cách tự nhiên thì khi nhập a,b thì a và b phải được giống hàng bên phải.
Ví dụ: Giả sử a có m=5 chữ số, b có n=4 chữ số như sau:
a = 97895
b = 6478
Thì việc lưu trữ hai số này là như sau:
a[1] = 9, a[2]=7, a[3]=8, a[4]=9, a[5]=5
b[2] = 6, b[3]=4, b[4]=7, b[5]=8
Việc giống hàng bên phải cho a và b có thể tiến hành bằng cách đặt max là số
lớn nhất trong hai số m và n.
for i=max-m+1 to max
cin>>a[i];
for i=max-n+1 to max
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 8



cin>>b[i];
Bước 2:
Thực hiện phép hai số a,b theo cách thông thường: Nghĩa là cộng từng cặp
chữ số a[i], b[i] bắt đầu từ phía bên phải và lưu kết quả cuối cùng lưu vào mảng c.
Lưu ý các mảng a,b bắt đầu từ chỉ số 1, còn mảng c bắt đầu từ chỉ số 0 và c[0] có thể
bằng 0 (khi phép cộng hai chữ số cuối cùng không có nhớ) và cũng có thể khác 0
(khi phép cộng hai chữ số cuối cùng có nhớ).
sonho=0;
for (i=max;i >0;i )
{
c[i]=(a[i]+b[i]+ sonho)%10;
sonho =(a[i]+b[i]+ sonho)/10;
}
c[0]= sonho;
Bước 3:
Xuất mảng c kết quả ra màn hình. Lưu ý là chỉ nên xuất c[0] khi c[0] khác 0.
Chương trình 1-1

Dạng 2: Bài toán với cấu trúc dữ liệu là mảng hai chiều
Ví dụ 1.2.Ma trận phân số
Cho ma trận hai chiều m dòng, n cột; trong đó mỗi phần tử là một phân số
(giả sử tử số và mẫu số của các phân số này là các số nguyên dương). Hãy thực hiện
các yêu cầu sau:
a.Tìm ma trận phân số tối giản
b.Tìm phân số có giá trị lớn nhất.
Giải thuật
Một cấu trúc dữ liệu tốt cho bài toán này là định nghĩa một kiểu dữ liệu mới
kiểu bản ghi để lưu trữ dữ liệu là các phân số như sau:
struct phanso

{
int tuso;
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 9


int mauso;
};
-Phân số tối giản là phân số mà ước số chung lớn nhất của tử số và mẫu số
bằng 1. Để tìm bảng phân số tối giản ta chỉ cần tối giản từng phân số.
Lưu ý là để so sánh phân số 1 có lớn phân số 2 hay không ta dùng điều kiện:
ps1.tu*ps2.mau>ps2.tu*ps1.mau
Chương trình 1-2
#include <conio.h>
#include <math.h>
#include <iostream.h>
#include <stdio.h>
#define maxm 50
#define maxn 50
struct phanso
{
int tu;
int mau;
};
void nhapmangps(phanso ps[maxm][maxn], int &m, int &n);
void xuatmangps(phanso ps[maxm][maxn], int m, int n);
int sosanhps(phanso ps1, phanso ps2);
void bangphansotoigian(phanso ps[maxm][maxn], int m, int n);
void timpslonnhat(phanso ps[maxm][maxn], int m, int n);
void main()
{

clrscr();
phanso ps[maxm][maxn];
int m,n;
nhapmangps(ps,m,n);
bangphansotoigian(ps,m,n);
xuatmangps(ps,m,n);
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 10


timpslonnhat(ps,m,n);
getch();
}
void nhapmangps(phanso ps[maxm][maxn], int &m, int &n)
{
cout<<"Nhap m = ";cin>>m;
cout<<"Nhap n = ";cin>>n;
for (int i=0; i<m;i++)
for (int j=0; j<n;j++)
{
cin>>ps[i][j].tu;
cin>>ps[i][j].mau;
}
}
void xuatmangps(phanso ps[maxm][maxn], int m, int n)
{
cout<<endl;
for (int i=0; i<m;i++)
{
for (int j=0; j<n;j++)
cout<<ps[i][j].tu<<"/"<<ps[i][j].mau<<";";

cout<<endl;
}
}
int sosanhps(phanso ps1, phanso ps2)
{
return ps1.tu*ps2.mau>ps2.tu*ps1.mau>0;
}

int uscln(int a, int b)
{
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 11


int r=a%b;
while (r!=0)
{
a=b;
b=r;
r=a%b;
}
return b;
}
void bangphansotoigian(phanso ps[maxm][maxn], int m, int n)
{
for (int i=0; i<m;i++)
for (int j=0; j<n;j++)
{
int uc=uscln(ps[i][j].tu,ps[i][j].mau);
ps[i][j].tu=ps[i][j].tu/uc;
ps[i][j].mau=ps[i][j].mau/uc;

}
}
void timpslonnhat(phanso ps[maxm][maxn], int m, int n)
{
phanso psmax;
psmax.tu=ps[0][0].tu;
psmax.mau=ps[0][0].mau;
for (int i=0; i<m;i++)
for (int j=0; j<n;j++)
if (sosanhps(ps[i][j],psmax)>0)
psmax=ps[i][j];
cout<<"\nPhan so lon nhat la : "<<psmax.tu<<"/"<<psmax.mau;
}
Dạng 3: Cấu trúc dữ liệu kiểu bản ghi
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 12


Ví dụ 1.2.Bài toán quản lý
Cho một danh sách lưu thông tin của các nhân viên trong một công ty, thông
tin gồm :
- Mã nhân viên (chuỗi, tối đa là 8 ký tự)
- Họ và tên (chuỗi, tối đa là 20 ký tự)
- Phòng ban (chuỗi, tối đa 10 ký tự)
- Lương cơ bản (số nguyên)
- Thưởng (số nguyên)
- Thực lãnh (số nguyên, trong đó thực lãnh = lương cơ bản + thưởng )
Hãy thực hiện các công việc sau:
a.Tính tổng thực lãnh tháng của tất cả nhân viên trong công ty.
b.In danh sách những nhân viên có mức lương cơ bản thấp nhất.
c.Đếm số lượng nhân viên có mức thưởng >= 1200000.

d.In danh sách các nhân viên tăng dần theo phòng ban, nếu phòng ban trùng
nhau thì giảm dần theo mã nhân viên.
Chương trình 1-3
#include<conio.h>
#include<iostream.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
struct nhanvien
{
char manv[8];
char hoten[20];
char phongban[16];
float luongcb;
float thuong;
float thuclanh;
};
int n;
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 13


struct nhanvien nv[100],temp;
void nhap(nhanvien nv[], int &n);
void xuat(nhanvien nv[], int n);
void tongthuclanh(nhanvien nv[], int n);
void luongcbthapnhat(nhanvien nv[], int n);
void mucthuong(nhanvien nv[], int n);
void sapxep(nhanvien nv[], int n);

void main()

{
clrscr();
nhap(nv,n);
tongthuclanh(nv,n);
luongcbthapnhat(nv,n);
mucthuong(nv,n);
sapxep(nv,n);
xuat(nv,n);
getch();
}
void nhap(nhanvien nv[], int &n)
{
cin>>n;
for(int i=0;i< n;i++)
{
cout<<"\nManv:"; fflush(stdin);
gets(nv[i].manv);
cout<<"Ho ten:"; fflush(stdin);
gets(nv[i].hoten);
cout<<"Phong Ban:"; fflush(stdin);
gets(nv[i].phongban);
cout<<"Luong CB:"; cin>>nv[i].luongcb;
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 14


cout<<"Thuong:"; cin>>nv[i].thuong;
nv[i].thuclanh = nv[i].luongcb + nv[i].thuong;
}
}


void xuat(nhanvien nv[], int n)
{
for(int i=0;i<n;i++)
cout<<nv[i].manv<<nv[i].hoten<<nv[i].phongban<<nv[i].luongcb
<<nv[i].thuong<<nv[i].thuclanh;
}

void tongthuclanh(nhanvien nv[], int n)
{
long tong=0;
for(int i=0;i<n;i++)
tong=tong +nv[i].thuclanh;
cout<<"\nTong thuc lanh la "<<tong;
}
void luongcbthapnhat(nhanvien nv[], int n)
{
long min=nv[0].luongcb;
for(int i=1;i<n;i++)
if (nv[i].luongcb<min)
min=nv[i].luongcb;
cout<<"\nLuong co ban thap nhat la:"<<min;
}
void mucthuong(nhanvien nv[], int n)
{
int dem=0;
for(int i=0;i<n;i++)
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 15


if (nv[i].thuong>=1200000)

dem++;
cout<<"\nSo luong nhan vien co muc thuong >=1200000 la: "<<dem;
}
void sapxep(nhanvien nv[], int n)
{
for(int i=0;i<n-1;i++)
for(int j=i+1;j<n;j++)
if ( strcmp(nv[i].phongban,nv[j].phongban) > 0 ||
(strcmp(nv[i].phongban,nv[j].phongban)==0 &&
strcmp(nv[i].manv,nv[j].manv)<0))
{
temp = nv[i];
nv[i] = nv[j];
nv[j] = temp;
}
}
Dạng 4: Cấu trúc dữ liệu dạng đồ thị
Ví dụ 1-4.Dãy con chung dài nhất.
Cho hai dãy số a, b. Hãy tìm dãy con chung dài nhất của hai dãy này. Dãy
con ở đây không nhất thiết phải là các phần tử liền nhau
Chẳng hạn cho hai dãy số sau:
Dãy a: 1, 5, 3, 7, 8
Dãy b: 6, 2, 5, 7, 1, 9, 8
Gợi ý:
Một cấu trúc dữ liệu cho bài toán này là chuyển bài toán này về cấu trúc đồ
thị bằng cách thành lập một ma trận kề, trong đó a[i][j] =1 nếu phần tử thứ i của dãy
a trùng vớ
i phần tử thứ j trong dãy b. Chẳng hạn với ví dụ trên thì ta có ma trận kề
như sau:
0 0 0 0 1 0 0

Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 16


0 0 1 0 0 0 0
0 0 0 0 0 0 0
0 0 0 1 0 0 0
0 0 0 0 0 0 1
Khi đó dãy cần tìm là dãy ứng với đường đi xuống dài nhất trong ma trận kề:
Là phần tử ở các vị trí 2, 4, 5 của dãy a hoặc là các vị trí 3, 4, 7 của dãy b.
Dạng 5: Tối ưu hóa cấu trúc
Ví dụ 1.5.Dãy con có tổng lớn nhất
Cho dãy n số nguyên {a} Dãy con liên tiếp là dãy mà thành phần của nó là các thành
phần liên tiếp nhau trong {a}, ta gọi tổng của dãy con là tổng tất cả các thành phần
của nó. Tìm tổng lớn nhất trong tất cả các tổng của các dãy con của {a}
Chẳng hạn n = 7 số sau:
4 –5 6 –4 2 3 -7
Thì kết quả tổng dãy con cần tìm là 7.
Giải thuật 1:
Giải thuật đơn giản nhất có thể viết ngay là: xét tất cả các cặp số nguyên L
và U thỏa mãn 1 ≤ L ≤ U ≤ n; đối với mỗi cặp như vậy ta tính tổng của dãy con
a[L U] và so sánh tổng này với giá trị lớn nhất hiện có:
for (L=1;L<=n;L++)
for (U=L;U<=n;U++)
{
sum=0;
for (int I=L;I<=U;I++)
sum=sum+a[I];
maxsofar=max(maxsofar,sum);
}
chương trình này tuy ngắn và dễ hiểu, tuy nhiên giải thuật này có độ phức tạp là

O(n
3
)
Giải thuật 2:
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 17


Ta có thể cải tiến giải thuật trên để có giải thuật với độ phức tạp là O(n
2
) bằng
cách sử dụng hệ thức :
Tổng a[L U]= Tổng a[L U-1]+a[U]
maxsofar=0;
for (L=1;L<=n;L++)
{
sum=0;
for (U=L;U<=n;U++)
{
sum=sum+a[U];
maxsofar=max(maxsofar,sum);
}
}
Giải thuật 3:
Tổng lớn nhất trong dãy con a[1 i] là tổng lớn nhất trong dãy con a[1 i-
1](gọi là maxsofar) hoặc tổng lớn nhất trong tất cả các tổng của các dãy con kết thúc
tại i (gọi là maxendinghere). Chúng ta có nhận xét rằng: Dãy con lớn nhất kết thúc tại
i là dãy con lớn nhất kết thúc tại vị trí i-1 được bổ sung thêm phần tử a[i] ở cuối hoặc
là dãy con rỗng trong trường hợp tổng của dãy con nhận được là số âm. Ta có giải
thuật như sau:
maxsofar=0;

maxendinghere=0;
for (i=1; i<=n;i++)
{
maxendinghere=max(maxendinghere+a[i],0);
maxsofar=max(maxsofar,maxendinghere);
}
Minh họa cho Giải thuật 3:
1 2 3 4 5 6 7
a[i] 4 -5 6 -4 2 3 -7
maxendinghere
4 0 6 2 4 7 0
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 18


maxsofar
4 4 6 6 6 7 7
Giải thuật 3 này có độ phức tạp là O(n).
#include <iostream.h>
#include <conio.h>

void algorithm1(int a[], int n);
void algorithm2(int a[], int n);
void algorithm3(int a[], int n);
int max(int a,int b);
void input(int a[],int &n);

void main()
{
int a[100],n;
input(a,n);

algorithm1(a,n);
getch();
}

void input(int a[],int &n)
{
clrscr();
cin>>n;
for (int i=1;i<=n;i++)
cin>>a[i];
}

void algorithm1(int a[], int n)
{
int maxsofar=0;
for (int L=1;L<=n;L++)
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 19


for (int U=L;U<=n;U++)
{
int sum=0;
for (int I=L;I<=U;I++)
sum=sum+a[I];
maxsofar=max(maxsofar,sum);
}
cout<<maxsofar;
}

void algorithm2(int a[], int n)

{
int maxsofar=0;
for (int L=1;L<=n;L++)
{
int sum=0;
for (int U=L;U<=n;U++)
{
sum=sum+a[U];
maxsofar=max(maxsofar,sum);
}
}
cout<<maxsofar;
}

void algorithm3(int a[], int n)
{ int maxsofar=0;
int maxendinghere=0;
for (int i=1; i<=n;i++)
{
maxendinghere=max(maxendinghere+a[i],0);
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 20


maxsofar=max(maxsofar,maxendinghere);
}
cout<<maxsofar;
}

int max(int a,int b)
{

return a>b?a:b;
}

Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 21


C.Bài tập
Viết chương trình hoàn chỉnh cho các bài toán sau đây
BT1-1.Cho dãy n số nguyên a
0
,a
1
, ,a
n-1
.
a.Tìm chiều dài của dãy con dài nhất chứa toàn số chẵn.
b.Tìm dãy con liên tiếp tăng dài nhất.
c.Tìm giá trị lớn thứ k của dãy.
d.Tìm dãy con tăng chứa nhiều số nguyên tố nhất.
e.Tìm tần số xuất hiện của các số.
Cho biết độ phức tạp tính toán của các thuật toán trên.
BT1-2.Cho dãy n số nguyên a
0
,a
1
, ,a
n-1
. Hãy chuyển k phần tử đầu tiên của dãy về
cuối dãy.
BT1-3.Cho hai số nguyên lớn a và b; trong đó a có m chữ số và số b có n chữ số.

Viết chương trình thực hiện các phép cộng, trừ, nhân hai số nguyên lớn.
BT1-4.Xây dựng cấu trúc dữ liệu để lưu trữ đa thức có bậc tự nhiên n (0 ≤ n ≤ 100)
trên trường số nguyên (a
i
, x ∈ Z).

=
=
n
i
i
in
xaxf
0
)(

Với cấu trúc dữ liệu được xây dựng, hãy viết chương trình thực hiện các công việc
sau:
a.Tính giá trị của đa thức tại giá trị x
0
nào đó.
b.Tính tổng, hiệu, tích, thương hai đa thức p và q.
BT1-5.Cho mảng một chiều gồm n tọa độ điểm (giả sử hoành độ và tung độ của các
điểm là các số nguyên).
a.Tìm một điểm trong mảng xa gốc tọa độ nhất.
b.Tìm tọa độ hai điểm gần nhau nhất.
c.Xác định tọa độ của hình chữ nhật nhỏ nhất (tọa độ góc trên bên trái và tọa
độ góc dưới bên phải của hình chữ nhật) bao hết cả n điểm trên.
Ví dụ n = 5 và tọa độ 5 điểm là: (0,0); (0,3); (3,3); (4,1); (4,4).
Thì kết quả câu a là điểm (4,4), kết quả câu b là (3,3) và (4,4), kết quả câu c

là (0,4); 4(,0).
Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 22


BT1-6.Cho ma trận hai chiều m dòng n cột; trong đó mỗi phần tử là một phân số (giả
sử các tử số và mẫu số của các phân số là các số nguyên).
a.Tính tổng giá trị của các phân số trong mảng (kết quả là phân số ở dạng tối
giản).
b.Tính tích giá trị của các phân số trong mảng (kết quả là phân số ở dạng tối
giản).
c.Sắp xếp các phân số theo chiều tăng dần từ trái qua phải (theo dòng) và từ
trên xuống dưới (theo cột).
BT1-7.Cho ma trận hai chiều m dòng n cột (m,n>=3); các phần tử là các số nguyên
dương.
a.Hãy chuyển các số của ma trận về số chính phương nhỏ nhất lớn hơn hoặc
bằng nó.
b.Hãy chuyển các số của ma trận về số nguyên tố gần nó nhất
c.Tìm một mặt nạ 3 dòng 3 cột chứa nhiều số nguyên tố nhất (mặt nạ di có
các cạnh song song với các cạnh của ma trận).
BT1-8.Cho một tập tin văn bản có tên là “BANGSO.INP” có cấu trúc như sau:
-Dòng đầu tiên ghi hai số m và n.
-Trong m dòng tiếp theo mỗi dòng ghi n số nguyên; các số cách nhau ít nhất một
khoảng cách.
a.Xoay vòng các cột qua phải một vị trí (cột 0 sẽ qua cột 1, cột 1 qua cột 2, cột cuối
cùng n-1 sẽ về vị trí của cột 0).
b.Xoay vòng các dòng xuống dưới một vị trí (dòng 0 sẽ qua dòng 1, dòng 1 qua dòng
2, dòng cuối cùng n-1 sẽ về vị trí của dòng 0).
c.Xoay vòng các dòng xuống dưới k vị trí (ví dụ khi k =2 thì nghĩa là dòng 0 chuyển
đến dòng 2, dòng 1 chuyển đến dòng 3,… dòng n-1 chuyển đến dòng 1).
Kết quả của các câu a,b,c này được ghi vào tập tin BANGSO.OUT

BT1-9.Khai báo kiểu cấu trúc dữ liệu mảng mà mỗi phần tử chứa thông tin về một
quyển sách bao gồm các trường: Mã số sách, tên sách, tác giả, năm xuất bản.
a.Đếm số sách xuất bản năm X.
b.Sắp xếp danh sách theo mã số sách tăng.
BT1-10.Trong mặt phẳng OXY cho đa giác lồi A
1
,A
2
,…,A
n

Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 23


a.Tính chu vi của đa giác.
b.Tính diện tích của đa giác.
BT1-11.Xét tập tất cả phân số tối giản (số hữu tỉ) giữa 0 và 1 với mẫu số nhỏ hơn
hoặc bằng N.
Ví dụ, với N = 5, ta có tập sau:
0/1 1/5 1/4 1/3 2/5 1/2 3/5 2/3 3/4 4/5 1/1
Cho số nguyên N (1 <= N <= 500), viết chương trình in ra số lượng các phân
số và in ra một phần các phân số đó để chứng tỏ bạn làm đúng.
BT1-12.Tìm độ phức tạp của các thuật toán sau:
a.Tìm giá trị lớn nhất của một dãy số.
b.Tìm ước số chung lớn nhất của 2 số nguyên dương a,b.
c.Kiểm tra xem n có phải là số nguyên tố hay không ?

Bài tập cấu trúc dữ liệu và giải thuật–SGU2010 Trang 24



Chương 2
TÌM KIẾM VÀ SẮP XẾP

A.Tóm tắt lý thuyết
1.Phương pháp tìm kiếm
1.1.Bài toán tìm kiếm
Cho dãy n số nguyên a
0
,a
2
, ,a
n-1
và một số nguyên x. Hãy tìm xem x có
thuộc vào dãy số trên hay không ? Nếu tìm được ở vị trí thứ i thì xuất kết quả là i,
ngược lại nếu không tìm thấy thì xuất kết quả là –1 (chú ý dãy bắt đầu từ chỉ số 0).
Sau đây là hai giải thuật tìm kiếm thường được sử dụng nhất.
1.2.Tìm kiếm tuyến tính
Bắt đầu từ phần tử thứ nhất a[0], ta lần lượt so sánh x với các giá trị a[i]. Nếu
có a[i] bằng x thì i chính là kết quả cần tìm và kết thúc giải thuật. Nếu trong dãy
không có số a[i] nào bằng x thì xuất kết quả là –1 và cũng kết thúc giải thuật.
int LinearSearch ( int a[], int n, int x )
{
int i = 0;
while ( i<n && a[i]!=x)
i++;
if( i==n)
return -1; // tìm hết nhưng không có x
return i; // tìm thấy a[i] là phần tử có khóa x
}
Hiệu quả của giải thuật được nâng cao bằng cách đặt thêm phần tử cầm canh

(sentinel) ở cuối mảng (a[n]=x) để bảo đảm rằng trong dãy a[i] lúc này luôn có phần
tử bằng x và vòng lặp while luôn kết thúc. Do đó không cần kiểm tra điều kiện (i<n)
nữa.
int LinearSearch ( int a[], int n, int x )
{
int i = 0; // mảng gồm n phần tử từ a[0] a[n-1]
a[n] = x; // thêm phần tử thứ n+1

×