Duyệt một danh sách lμ thao tác truy cập đến tất cả các phần tử của danh sách để thực hiện một xử lí nμo đó sao cho đảm bảo không sót vμ không lặp.. Một danh sách các hồ sơ được thực hi
Trang 1Duyệt một danh sách lμ thao tác truy cập đến tất cả các phần tử của
danh sách để thực hiện một xử lí nμo đó sao cho đảm bảo không sót vμ không
lặp Không sót nghĩa lμ mọi phần tử đều được xử lí, không lặp nghĩa lμ
không phần tử nμo bị xử lí quá một lần
Phép duyệt có thể thực hiện nhờ một vòng lặp For
For i:= 1 to L.kt do " xử lí L.PhanTu[i]" ;
Ví dụ
Thay toμn bộ tên bằng chữ in hoa
For i:= 1 to L.kt do L.PhanTu[i]:= upper(L.PhanTu[i]);
Tìm kiếm
Tìm kiếm một phần tử trong danh sách lμ nhằm phát hiện phần tử có chứa một thμnh phần dữ liệu trùng khớp với mẫu đã cho Mẫu nμy thường
được gọi lμ khóa tìm kiếm
Tuỳ theo danh sách có được sắp xếp thứ tự theo khoá đã cho hay
không mμ có các cách tìm kiếm khác nhau
Tìm kiếm tuần tự - Sequential Searching
Khi danh sách chưa được sắp xếp thì cách duy nhất để thực hiện tìm kiếm lμ duyệt từ đầu, cho đến khi tìm thấy hoặc phát hiện không có
Tìm kiếm nhị phân - Binary Searching
Khi danh sách đã được sắp xếp đúng thứ tự theo khoá cần tìm thì có thể tiến hμnh tìm kiếm theo cách chia đôi dần Đây lμ thủ tục tìm kiếm nhị phân đã xét đến trong phần thủ tục đệ quy
Thêm phần tử
- Hoặc nối vμo cuối, khi đó danh sách không được sắp theo thứ tự Thực hiện đơn giản nhưng sẽ phải chi phí nhiều khi tìm kiếm
- Hoặc chèn đúng chỗ, theo cách nμy buộc phải tìm đúng vị trí vμ sau
đó dịch chuyển cả phần đuôi Thực hiện phức tạp hơn nhưng bù lại khi tìm kiếm sẽ nhanh hơn
Gỡ bỏ phần tử
- Tìm đến phần tử cần gỡ bỏ vμ huỷ phần tử nμy bằng cách chép dồn lên
1.4 Các ưu, nhược điểm
Danh sách thể hiện bằng cấu trúc mảng có các ưu nhược điểm sau
Trang 2- Đơn giản, có thể cμi đặt đễ dμng bằng kiểu mảng đã quen biết
- Bất tiện khi thường xuyên có thao tác thêm bớt phần tử ở giữa danh sách
1.5 Ví dụ minh hoạ
Để thấy được các ưu nhược điểm của danh sách mảng hãy cải tiến chương trình quản lí hồ sơ của sinh viên đã phát triển trong chương 3
Chương trình nμy đã cho phép tiến hμnh các thao tác tạo lập, liệt kê, tìm kiếm
vμ thêm một hồ sơ vμo cuối danh sách Cần bổ xung thêm chức năng thứ 5
lμ cho phép huỷ một hồ sơ
1.5.1 Phân tích, thiết kế
Chức năng huỷ một hồ sơ trong danh sách sinh viên lμ thủ tục phức
tạp hơn cả vì bản ghi cần huỷ có thể nằm ở giữa tệp Cần có thao tác dồn lấp chỗ trống Nội dung tệp phải viết lại hoμn toμn Do đó cần đọc toμn bộ thông tin ra một mảng trong bộ nhớ để tiện xử lí Chỉ viết lại vμo tệp ở thời điểm cuối cùng, khi mọi thay đổi đã hoμn tất
Một danh sách các hồ sơ được thực hiện bằng mảng các bản ghi Ta cần khai báo một danh sách mảng như sau
Type DSHoso = Record
PhanTu: array [1 max_kt] of Hoso;
- Đầu vμo: tệp đã mở sẵn, mã số của hồ sơ cần huỷ gõ từ bμn phím
- Đầu ra: tệp không còn chứa bản ghi cần huỷ
Chi tiết dần từng bước
5.1 Chép toμn bộ nội dung tệp ra mảng các bản ghi
5.2.1 Đưa cửa sổ tệp về đầu tệp; gán chỉ số đầu tiên của mảng
5.2.2 Đọc từ tệp ra phần tử mảng 5.2.3 Tăng chỉ số mảng lên một 5.2.4 Dừng khi hết tệp
5.2 Nhập mã số của hồ sơ cần huỷ
5.3 Tìm kiếm phần tử mảng lμ bản ghi có mã số đã cho:
5.3.1 Gọi thủ tục Tìm kiếm
5.4 Xử lí kết quả tìm kiếm
5.4.1 Trường hợp không thấy, hiển thị thông báo không thấy
Trang 35.4.2 Trường hợp thấy, tiến hμnh gỡ bỏ bản ghi khỏi mảng:
5.4.2.1 Chép dồn các phần tử mảng lên một vị trí lấp vμo chỗ phần tử mảng bị gõ bỏ,
1.5.2 Triển khai chi tiết
Từ bảng phân tích chi tiết dần từng bước ở trên, dễ dμng chuyển thμnh một thủ tục Pascal như dưới đây Lưu ý rằng để nhấn mạnh cấu trúc danh
sách vμ dễ theo dõi ta không sử dụng câu lệnh with mμ viết đầy đủ các cấp
của kiểu bản ghi lồng nhau
{chep lai vao danh sach de xu ly thao tac huy}
while not eof(TepHoso) do
TimThay:= false; i:= 0;
while (not TimThay) do
Trang 4L.PhanTu[i].namsinh:= L.PhanTu[i+1].namsinh; end
Trang 5TenMC[2]:=' Tim kiem ';
TenMC[3]:=' Them vao ';
2 Danh sách kiểu ngăn xếp - Stack
Nếu các thao tác thêm bớt phần tử không xảy ra "ở giữa" danh sách thì hoμn toμn có thể thực hiện bằng mảng như đã phân tích ở trên Dưới đây xét hai loại danh sách đặc biệt, các phép thêm vμo, lấy ra thực hiện tại vị trí
đầu hoặc cuối
2.1 Định nghía danh sách kiểu ngăn xếp
Khi lμm việc với một chồng hồ sơ trên bμn, ta chỉ thấy được cái trên cùng, lấy ra lần lượt từ trên xuống dưới Nếu có thêm hồ sơ mới thì xếp
chồng lên trên Mô hình trừu tượng hoá của chồng hồ sơ chính lμ một danh sách với các thao tác thêm vμo, lấy ra chỉ thực hiện tại đầu danh sách Đó lμ
danh sách kiểu ngăn xếp hay Stack
Đinh nghĩa: Danh sách kiểu ngăn xếp (Stack) lμ một kiểu danh sách
tuyến tính đặc biệt mμ phép thêm vμo, lấy ra phần tử chỉ thực hiện ở một đầu
gọi lμ đỉnh ngăn xếp - Top
Trang 6Khi có thao tác lấy ra thì phần tử được xếp cuối cùng vμo ngăn xếp sẽ
được lấy ra đầu tiên Do đó ngăn xếp còn có tên gọi lμ danh sách kiểu last in,
first out hay danh sách LIFO
Stack có rất nhiều ứng dụng
Hình 13.1: Một ngăn xếp các hồ sơ
2.2 Biểu diễn danh sách kiểu ngăn xếp
Như đã phân tích ở cuối tiết trước, danh sách kiểu ngăn xếp có thể
đươc thực hiện dễ dμng bằng mảng Một ngăn xếp gồm một biến mảng các phần tử vμ một biến kiểu số nguyên để ghi số phần tử hiện có Nếu đánh số các phần tử mảng từ 1 thì số phần tử hiện có đồng thời cũng lμ chỉ số của phần tử trên cùng của mảng Kiểu ngăn xếp có thể được khai báo như sau
Type NganXep = Record
PhanTu: array [1 max_kt ] of kiểu phần tử ;
2.3 Các phép toán đối với kiểu ngăn xếp
1 - Khởi tạo ngăn xếp rỗng: Top:= 0;
2 - Thêm vμo một phần tử
Tên chuẩn lμ Push Các bước thực hiện lμ
Trang 7- Kiểm tra đầy ngăn xếp chưa Nếu đầy, báo lỗi “trμn ngăn xếp”
- Trái lại: sửa lại vị trí đỉnh vμ chép phần tử mới vμo
Procedure Push(x: kiểuphầntử, VAR S:NgănXếp);
begin if S.Top=maxSize then
Tên chuẩn lμ Pop Các bước thực hiện lμ
- Kiểm tra ngăn xếp có rỗng không Nếu rỗng, báo lỗi “ ngăn xếp
rỗng”
- Trái lại: lấy ra nội dung phần tử ở đỉnh vμ sửa lại vị trí đỉnh
Procedure Pop(S: NgănXếp; VAR x: kiểuphầntử) ;
begin if Top=0 then
3 Danh sách kiểu hàng đợi - Queue
3.1 Định nghĩa danh sách kiểu hàng đợi
Kiểu danh sách hμnh đợi lμ mô hình hóa một loại danh sách mμ hoạt
động của nó giống như hμng đợi trước cửa một quầy bán vé Phần tử mới chỉ
được thêm vμo cuối còn phần tử được lấy ra chỉ từ vị trí đầu tiên của danh sách
Định nghĩa: Danh sách kiểu hμng đợi (Queue) lμ một kiểu danh sách
mμ:
- Phép thêm phần tử vμo chỉ thực hiện ở một đầu mút, gọi lμ lối sau
(rear) hay đuôi hμng đợi;
- Phép lấy phần tử ra thực hiện ở đầu kia, gọi lμ lối trước (front) hay
đầu hμng đợi
Trang 8
←←←←←
front rear
Hình 13.2: Hμng đợi trước quầy bán vé
Danh sách kiểu hμng đợi sẽ hoạt động theo theo nguyên tắc, đến trước
phục vụ trước, đến sau phục vụ sau do đó còn có tên gọi lμ danh sách kiểu
first in first out hay danh sách FIFO
3.2 Biểu diễn danh sách kiểu hàng đợi bằng mảng
Hμng đợi Q gồm một biến mảng chứa các phần tử, hai biến kiểu số
nguyên để ghi chỉ số của phần tử đầu vμ phần tử cuối vμ một biến nguyên
khác để ghi số phần tử hiện có
Type HangDoi = Record
PhanTu: array [0 max_kt - 1] of kiểuphầntử ; r,f, kt: word;
end;
Var Q: HangDoi;
Trong đó, max_kt lμ kích thước lớn nhất (dự phòng) có thể của hμng đợi r =
chỉ số phần tử cuối hμng đợi, f = chỉ số phần tử đầu hμng đợi, kt = số phần
tử hiện có
Lưu ý việc đánh chỉ số các phần tử mảng bắt đầu bằng 0 để thuận tiện
hơn cho việc nối vòng tròn như sau nμy sẽ thấy
- Khi thêm một phần tử vμo hμng đợi cần tăng chỉ số phần tử cuối r
lên 1 vμ chép nội dung vμo Q(r):
Trang 9Thêm vμo: kt:= kt+ 1; r:=r+1; Q[r]:=x ;
- Khi lấy một phần tử ra khỏi hμng đợi cần đọc nội dung Q(f) vμ tăng
f lên 1:
Lấy ra: x:= Q[f]; f:= f+1; kt:= kt -1;
Tuy nhiên, nếu theo dõi hoạt động của hμng đợi sau một loạt thao tác
thêm vμo, lấy ra được triển khai đơn giản như trên sẽ có hiện tượng hμng đợi
“bò trườn”, di chuyển dần đi trong bộ nhớ
Để khắc phục hiện tượng nμy cần hạn chế một vùng nhớ, chỉ cho mảng
các phần tử xoay trở trong vùng nμy Giải pháp lμ nối đầu đuôi thμnh vòng
tròn Do đó phải sử dụng phép chia đồng dư modulo trong tính toán các chỉ
- Nối vòng tròn khi thêm vμo
Nếu r = max_kt -1 thì r+1 vượt ra ngoμi mảng Cần lộn trở lại đầu
mảng như hình vẽ, tức lμ cần có r+1:= 0 Công thức tính vị trí của phần tử
đuôi phải thay đổi lại lμ
r:= (r+1) Mod max_kt
- Nối vòng tròn khi lấy ra
Khi lấy ra, địa chỉ f của đầu hμng đợi cũng cộng thêm 1 Do đó, vấn
Trang 10Procedure Enqueue(x: kiểuphầntử; VAR Q: HangDoi );
begin if Q.kt = max_kt then “bao loi tran”
Muốn thực hiện một danh sách với thao tác thêm bớt phần tử một cách thuận tiện tại bất cứ vị trí nμo ta cần khắc phục nhược điểm của cách tổ chức xếp liên tiếp các phần tử bằng cách dùng con trỏ để móc nối, sử dụng liên kết
động Khi thêm vμo phần tử hoặc lấy ra phần tử chỉ cần sủa lại các liên kết
Một danh sách móc nối cấu tạo từ các nút - node Mỗi nút gồm tối
thiểu hai trường:
Trang 11- Data: chứa dữ liệu hoặc con trỏ đến biến động chứa dữ liệu;
Next: con trỏ trỏ tới nút tiếp theo
-
Nút cuối cùng của danh sách có trường Next = NIL
Như vậy, mỗi nút lμ một biến kiểu bản ghi - record, có một trường
kiểu con trỏ trỏ đến một nút để lμm mối nối
4.2.2 Khai báo kiểu danh sách móc nối
Có một điểm tế nhị cần lưu ý lμ khi khai báo trường kiểu con trỏ trỏ
đến một nút thì chính kiểu nút còn chưa được định nghĩa xong Theo quy
định chung thì mọi thứ như hằng, kiểu, biến, hμm, thủ tục đều phải được
định nghĩa trước khi sử dụng, nghĩa lμ sau dấu con trỏ (^) phải lμ kiểu dữ liệu
đã được định nghĩa
Pascal chấp nhận một biệt lệ cho trường hợp nμy Trong khai báo kiểu mới ta có thể sử dụng một kiểu chưa được định nghĩa rồi định nghĩa nó sau, miễn lμ cùng nằm trong phần khai báo kiểu
Khi xây dựng danh sách móc nối ta phải khai báo trước kiểu mối nối
nutPtr lμ con trỏ đến một nút vμ sau đó mới khai báo kiểu nút
Một danh sách L được truy cập bằng 1 con trỏ tới nút đầu tiên Danh sách rỗng thì L = NIL
Type nutPtr = ^nut;
Nut = Record
End;
Type DanhSachMocNoi = nutPtr;
Hình dưới đây minh hoạ một danh sách móc nối L
L
DC
B
A
Hình 13.5: Hình ảnh của một danh sách móc nối
Trang 12
4.3 Các phép toán
Tại một thời điểm chỉ có thể truy cập đến một nút trong danh sách Đó lμ
nút hiện hμnh hay View Trong các thao tác đối với danh sách, cần có con trỏ
không bỏ sót nút nμo gọi lμ duyệt danh sách
Procedure DS_Duyet(VAR L: DanhSachMocNoi);
Trang 13- Nếu L rỗng thì nút thêm vμo lμ đầu danh sách
- Trái lại, cần phải sửa hai mối nối như hình vẽ
L V
Hình 13.6: Móc nối lại để thêm nút vμo sau View
Procedure DS_ThemNut_SauView(x: kiểudữliệu;
VAR L: DanhSachMocNoi; V: nutPtr);
- Dò từ đầu danh sách đến nút ngay trước V, sau đó thêm vμo sau nút nμy
- Hoặc dùng thủ thuật, chép nội dung Data của V vμo N, chép nội dung
Data mới vμo V, sửa lại các mối nối giống như trong thủ tục trên (Hãy tự
thực hiện như một bμi tập)
N
Trang 14- Trái lại, có hai cách lμm
1 - Dò từ đầu danh sách đến nút ngay trước V, sửa mối nối của nút nμy, cho trỏ đến nút sau V
2 - Dùng thủ thuật sau: Nếu V không phải lμ nút cuối danh sách thì
chép nội dung Data của nút sau V vμo nút V, sửa mối nối của V cho trỏ đến
nút sau nữa Nếu V lμ nút cuối danh sách thì phức tạp hơn Phải dò tìm từ
đầu danh sách đến nút ngay trước V vμ sửa mối nối của nút nμy thμnh NIL
(Hãy tự thực hiện như một bμi tập)
Thủ tục dưới đây thực hiện theo cách 1
Procedure DS_HuyNut(VAR L:DanhSachMocNoi ; V:nutPtr);
con trỏ đến nút cuối danh sách T = Tail để tiện thao tác Trong trường hợp
nμy một danh sách móc nối lμ bộ 2 con trỏ: L trỏ đến đầu danh sách vμ T trỏ
đến nút cuối cùng
5 Danh sách nối kép
5.1 Cấu trúc danh sách nối kép
Danh sách nối đơn chỉ tiện khị duyệt theo một chiều Nếu cần duyệt theo chiều ngược lại thì chi phí lớn
Trang 15Cải tiến, thực hiện thêm mối nối theo chiều ngược lại Một nút có hai con trỏ, trỏ đến nút sau vμ đến nút trước nó
Left Data Right
L R
d c
Khai báo cấu trúc một nút nut vμ kiểu danh sách nối kép cũng tương
tự như trường hợp danh sách nối đơn
Type nutPtr = ^ nut;
Nut = Record
Data: kiểu dữ liệu;
Left, Right: nutPtr;
End;
Type DSNoiKep = nutPtr;
5.2 Các phép toán với danh sách nối kép
Phép khởi tạo danh sách rỗng lμ khởi tạo các con trỏ L, R bằng NIL Các thao tác duyệt vμ tìm kiếm có thể triển khai dễ dμng theo hai chiều
Ta xét các thao tác thêm nút vμ gỡ bỏ nút
1 -Thêm nút
Cần xét 3 trường hợp
- khi danh sách rỗng
- khi bổ sung vμo vi trí cực trái (cực phải);
- khi bổ xung vμo giữa danh sách
Trường hợp bổ xung vμo giữa danh sách cần chia thμnh thêm vμo bên trái nút hiện hμnh V vμ thêm vμo bên phải nút hiện hμnh V
Trang 16Procedure DS2_ThemNutVaoTraiView(x: kiÓud÷liÖu;
VAR L:DSNoiKep V: nutPtr);
else if V = L then { bæ xung vμo cùc tr¸i }
begin Nˆ.Left:= NIL; Nˆ.Right:=V; Vˆ.Left:=N; L:=N;
- khi lo¹i nót cùc tr¸i (cùc ph¶i);
- khi lo¹i nót ë gi÷a
Procedure DS2_HuyNut(VAR L: DSNoiKep; V: nutPtr);
begin if L= NIL then “danh s¸ch rçng ”
else if L=R then L:=R:=NIL
else if V=L then begin L:=Lˆ.Right; Lˆ.Left:= NIL end else if V=R then begin R:=Rˆ.Left; Rˆ.Right:= NIL end else
begin (Vˆ.Left)ˆ.Right:= Vˆ.Right;
Trang 176 Ví dụ ứng dụng của danh sách móc nối
6.1 Cải tiến chương trình quản lí hồ sơ
Lại xét bμi toán quản lí hồ sơ của sinh viên Ta đã xây dựng một
chương trình quản gồm có các chức năng tạo lập, liệt kê, tìm kiếm, thêm vμo
cuối vμ huỷ một hồ sơ khỏi danh sách Vì chưa sử dụng con trỏ vμ biến động,
thực hiện danh sách bằng mảng nên thao tác thêm bản ghi chỉ thuận tiện nếu thực hiện tại cuối danh sách Thao tác huỷ một bản ghi đòi hỏi phải chép dồn toμn bộ phần đuôi danh sách rất vất vả khi danh sách có nhiều phần tử
Để có thể tiến hμnh các thao tác chèn thêm, gỡ bỏ các bản ghi một cách thuận tiện thì cách tốt nhất lμ liên kết các bản ghi bằng một cấu trúc
danh sách động - danh sách móc nối
Ta hãy thử xây dựng một chương trình cải tiến hơn, không dùng danh sách mảng mμ sử dụng danh sách móc nối để giải quyết bμi toán trên
Chương trình có các chức năng mở tệp, liệt kê, tìm kiếm vμ sửa chữa thông tin, thêm một hồ sơ vμ huỷ một hồ sơ của danh sách Với tính linh hoạt của danh sách móc nối, có thể dễ dμng thực hiện chức năng chèn thêm hồ sơ vμ huỷ hồ sơ tại một vị trí bất kì trong danh sách Do đó có thể tổ chức danh sách được sắp theo thứ tự
6.2 Phân tích thiết kế
6.2.1
6.2.2
Cấu trúc dữ liệu
Sử dụng cấu trúc danh sách móc nối đơn như đã trình bμy trên Phần
dữ liệu trong mỗi nút lμ một bản ghi kiểu HoSo
Type nutPtr = ^nut;
nut = Record
Data: HoSo;
next: nutPtr;
end;
Type DanhSachMocNoi = nutPtr;
Để cho xác định, ta quy ước sắp xếp danh sách theo mã số sinh viên tăng dần
Thực thi các chức năng
Thủ tục mở tệp không có gì thay đổi
Các chương trình con thực hiện các chức năng khác của chương trình
như liệt kê, tìm kiếm, thêm vμo vμ gỡ bỏ hồ sơ sẽ gọi các phép toán tương ứng
trên danh sách móc nối Do đó phần đầu của chương trình lμ phần triền khai