Cấu trúc nút của danh sách móc nối đơn Code Type PNode=^Node; {Kiểu con trỏ tới một nút} Node=Record {Kiểu biến động chứa thông tin trong một nút} info: ; link: PNode; End; Nút
Trang 11 Danh sách móc nối đơn là gì?
1.1 Khái niệm
Danh sách móc nối đơn (Singly-linked list) gồm các nút được nối với nhau theo một chiều Mỗi nút là một bản ghi (record) gồm hai trường:
- Trường info chứa giá trị lưu trong nút đó
- Trường link chứa liên kết (con trỏ) tới nút kế tiếp, tức là chứa một thông tin đủ để biết nút kế tiếp nút đó trong danh sách là nút nào Với nút cuối cùng (không có nút
kế tiếp), trường liên kết này được gắn với một giá trị đặc biệt, chẳng hạn con trỏ nil
Cấu trúc nút của danh sách móc nối đơn
Code
Type
PNode=^Node; {Kiểu con trỏ tới một nút}
Node=Record {Kiểu biến động chứa thông tin
trong một nút}
info: <Kiểu dữ liệu>;
link: PNode;
End;
Nút đầu tiên (head) đóng vai trò quan trọng trong danh sách nối đơn Để duyệt danh sách nối đơn Để duyệt danh sách nối đơn, ta bắt đầu từ nút đầu tiên, dựa vào trường liên kết để đi sang nút kế tiếp, đến khi gặp giá trị đặc biệt (duyệt qua nút cuối) thì dừng lại
Danh sách nối đơn
1.2 Truy cập các phần tử trong danh sách móc nối đơn
Bản thân danh sách móc nối đơn đã là một kiểu dữ liệu trừu tượng Để cài đặt
kiểu dữ liệu trừu tượng này, có thể dùng mảng các nút (trường link chứa chỉ số của nút
kế tiếp) hoặc biến cấp phát động (trường link chứa con trỏ trỏ tới nút kế tiếp) Tuy
Trang 2nhiên, việc xác định phần tử đứng thứ p trong danh sách bắt buộc phải duyệt từ đầu
danh sách qua p nút, việc này mất thời gian trung bình O(n) và tỏ ra không hiệu quả
như thao tác trên mảng Nói cách khác, danh sách nối đơn tiện cho việc truy cập tuần
tự nhưng không hiệu quả nếu chúng ta thực hiện nhiều phép truy cập ngẫu nhiên
2 Tạo danh sách móc nối đơn
2.1 Ý tưởng
Chúng ta có thể bắt đầu tạo dần danh sách từ trái hoặc phải qua đều được, cách được đề cập tới đây là từ trái qua phải:
- Đầu tiên ta tạo một nút đầu tiên, nút này đóng vai trò vừa là nút Head
- Sau đó từ nút thứ 2 trở đi, mỗi lần tạo thêm 1 nút ta gán nút trung gian bằng nút vừa tạo Gán trường Link hợp lí theo định nghĩa danh sách nối đơn
2.2 Code
{Thủ tục tạo mới danh sách}
Procedure Readf;
Var P:PNode;
Begin
Assign(f,fi);Reset(f);
Readln(f,N,K);
New(P);
Read(f,P^.info);
Head:=P;
Q:=P;
For i:=2 to n do Begin
New(P);
P^.Next:=Nil;
Read(f,P^.Info);
Q^.Next:=P;
Q:=P;
End;
Close(f);
End;
Trang 33 Chèn phần tử vào danh sách nối đơn
3.1 Ý tưởng
Để chèn thêm một nút chứa giá trị v vào vị trí của nút p trong danh sách nối đơn, trước hết ta tạo ra một nút mới Newnode chứa giá trị v và cho nút này liên kết tới p Nếu p đang là nút đầu tiên của danh sách (head) thì cập nhật head bằng NewNode, còn nếu p không phải là nút đầu tiên của danh sách, ta tìm nút q là nút đứng liền trước nút p và chỉnh lại liên kết: q liên kết tới NewNode thay vì liên kết thẳng tới p
Chèn phần tử vào danh sách nối đơn
3.2 Code
{Thủ tục chèn phần tử V vào vị trí nút P}
Procedure Insert(p: Pnode; const v: <Kiểu dữ liệu>); Var NewNode, q, head: Pnode;
Begin
New(NewNode);
NewNodệinfo := v;
NewNodệlink := p;
If head = p then head := NewNode
Else
Begin
q := head;
while q^.link <> q do q:=q^.link;
q^.link := NewNode;
End;
End;
Trang 4Việc chỉnh lại liên kết trong thủ tục chèn phần tử vào danh sách nối đơn mất thời gian O(1) Tuy nhiên, việc tìm ra nút đứng liền trước nút p yêu cầu phải duyệt từ đầu danh sách, việc này mất thời gian trung bình O(n) Vậy thủ tục chèn một phần tử vào danh sách nối đơn mất thời gian thực hiện trung bình O(n)
4 Xóa phần tử khỏi danh sách móc nối đơn
4.1 Ý tưởng
Để xóa nút p khỏi danh sách nối đơn, gọi next là nút đứng liền sau p trong danh
sách Xét hai trường hợp:
- Nếu p là nút đầu tiên trong danh sách head = p thì ta đặt lại head = next
- Nếu p không phải nút đầu tiên trong danh sách, tìm q là nút đứng liền trước nút p
và chính lại liên kết: q liên kết tới next thay vì liên kết tới p
Việc cuối cùng là hủy nút p
4.2 Code
{Thủ tục xóa nút P}
Procedure Delete(p:PNode);
Var next, q, head: PNode
Begin
Next := p^.link;
If p = head then head := next
Else
Begin
Q:= head;
While q^.link <> p do q := q^.link;
Q^.link:= next;
End;
Dispose(P);
End;
Trang 5Xóa phần tử khỏi danh sách nối đơn Cũng giống như thủ tục chèn, thủ tục xóa một phần tử khỏi danh sách nối đơn
cũng mất thời gian thực hiện trung bình là O(n)
5 Biểu diễn ngăn xếp (Stack) bằng danh sách nối đơn kiểu LIFO
5.1 Khái niệm:
Ngăn xếp là một dạng đặc biệt của danh sách mà việc bổ sung hay loại bỏ một phần tử đều được thực hiện ở 1 đầu của danh sách gọi là đỉnh Nói cách khác, ngăn xếp là 1 cấu trúc dữ liệu có 2 thao tác cơ bản: bổ sung (push) và loại bỏ phần tử (pop), trong đó việc loại bỏ sẽ tiến hành loại phần tử mới nhất được đưa vào danh sách Chính vì tính chất này mà ngăn xếp còn được gọi là kiểu dữ liệu có nguyên tắc LIFO (Last In First Out - Vào sau ra trước)
Ta sẽ trình bày cách cài đặt ngăn xếp bằng danh sách nối đơn bằng biến động và con trỏ
- Lối vào và lối ra đều là đỉnh của Stack được quản lí bởi nút Top
- Không xét trường hợp tràn Stack bởi nó còn phụ thuộc vào nhiều thứ khác
- Stack rỗng khi nút Top trống (nil)
5.2 Code
Type
PNode=^Node;
Node=Record
Info: <Kiểu dữ liệu>;
Link: PNode;
End;
Var Top:PNode;
Procedure StackInit;
Begin
Top:=Nil;
End;
{Nộp phần tử vào Stack ở đỉnh danh sách}
Procedure SPUSH(V: <Kiểu dữ liệu>);
Trang 6Var P:PNode;
Begin
New(P);
P^.Info:= V;
P^.Link:= top;
Top := p;
End;
{Lấy phần tử ra khỏi stack ở đỉnh danh sách}
Procedure SPOP: <Kiểu dữ liệu>;
Var P:PNode;
Begin
If Top=Nil then Writeln(‘Stack is empty’)
Else
Begin
SPOP:=Top^.Info;
P:=Top^.Next;
Dispose(Top);
Top:=P;
End;
End;
BEGIN
StackInit;
<Một vài lệnh để kiểm tra hoạt động của Stack>;
END
6 Biểu diễn hàng đợi (Queue) bằng danh sách nối đơn kiểu FIFO:
6.1 Khái niệm:
Hàng đợi là một cấu trúc dữ liệu gần giống với ngăn xếp, nhưng khác với ngăn xếp ở nguyên tắc chọn phần tử cần lấy ra khỏi tập phần tử Trái ngược với ngăn xếp, phần tử được lấy ra khỏi hàng đợi không phải là phần tử mới nhất được đưa vào mà là phần tử đã được lưu trong hàng đợi lâu nhất
Điều này nghe có vẻ hợp với quy luật thực tế hơn là ngăn xếp ! Quy luật này của hàng đợi còn được gọi là Vào trước ra trước (FIFO - First In First Out) Ví dụ về hàng đợi có rất nhiều trong thực tế Một dòng người xếp hàng chờ cắt tóc ở 1 tiệm hớt
Trang 7tóc, chờ vào rạp chiếu phim, hay siêu thị là nhưng ví dụ về hàng đợi Trong lĩnh vực máy tính cũng có rất nhiều ví dụ về hàng đợi Một tập các tác vụ chờ phục vụ bởi hệ điều hành máy tính cũng tuân theo nguyên tắc hàng đợi
Hàng đợi còn khác với ngăn xếp ở chỗ: phần tử mới được đưa vào hàng đợi sẽ nằm ở phía cuối hàng, trong khi phần tử mới đưa vào ngăn xếp lại nằm ở đỉnh ngăn xếp
Như vậy, ta có thể định nghĩa hàng đợi là một dạng đặc biệt của danh sách mà việc lấy ra một phần tử, get, được thực hiện ở 1 đầu (gọi là đầu hàng), còn việc bổ sung 1 phần tử, put, được thực hiện ở đầu còn lại (gọi là cuối hàng)
- Lối vào là phía bên phải Queue được quản lí bởi nút L
- Lối ra là phía bên trái Queue được quản lí bởi nút R
- Không xét trường hợp tràn Queue bởi nó còn phụ thuộc vào nhiều thứ khác
- Queue rỗng khi nút L và R đều trống
6.2 Code
Type
Pnode = ^Node;
Node = Record
Info: <Kiểu dữ liệu>;
Link: PNode;
End;
Var L,R: PNode;
Procedure QueueInit;
Begin
L := Nil;
End;
{Lấy giá trị của phần tử ở vị trí đầu danh sách}
Function Get: <Kiểu dữ liệu>;
Begin
If (L=R) and (R=Nil) then Writeln(‘Queue is empty’) Else Get := L^.info;
End;
{Nộp phần tử vào Queue ở vị trí cuối cùng}
Procedure QPUSH(V: <Kiểu dữ liệu>);
Var P:PNode;
Begin
Trang 8New(P);
P^.Info := V;
P^.Link := Nil;
If L=Nil then L := P
Else R^.link := P;
R := P;
End;
{Lấy phần tử ra khỏi Queue ở đầu danh sách}
Procedure QPOP: <Kiểu dữ liệu>;
Var P:PNode;
Begin
If (L=R) and (R=Nil) then Writeln(‘Queue is empty’) Else Begin
QPOP:=L^.Info;
P:=L^.Link;
L^.Link:=Nil;
Dispose(L);
L:=P;
End;
End;
BEGIN
QueueInit;
<Một vài lệnh để kiểm tra hoạt động của Queue>;
END
7 Bài tập ứng dụng
7.1 Bài tập minh họa
Viết chương trình Dslk.pas minh hoạ các cách làm việc với danh sách liên kết thuận Phần dữ liệu của một phần tử slà các thông tin về một sinh viên
Type SVienPtr = ^SVien;
SVien = record
maso: string[6];
hoten: string[23];
next : SvienPtr;
end;
Trang 9var L, R: SVienPtr; chon: byte; traloi: char;
Procedure Bosung;
var p: SVienPtr; ans: integer;
Begin
while true do begin
new(p);
with p^ do begin
write('Ma sinh vien : '); readln(maso);
write('Ho va ten : ');
readln(Hoten);
write('Diem trung binh: '); readln(dtb);
end;
p^.next:=NIL;
if L=NIL then begin L:= p; R:= p end else begin
R^.next:= p; R:= p
end;
write('Co tiep tuc khong 1/0 ? '); readln(ans);
if ans=0 then break;
end;
end;
procedure DuyetXuoi;
var p: SVienPtr;
begin
if L = NIL then begin
writeln('D.sach rong'); exit;
end;
p := L;
while p <> NIL do
begin
with p^ do writeln(maso:5,' ',Hoten:25,'
',dtb:5:2);
p := p^.Next;
Trang 10end;
End;
procedure ChenSau;
var q,p: SVienPtr; found: boolean; masv: string[6];
begin
if L <> NIL then begin
write('Ma so SV can tim de chen :
');
readln(masv);
p:= L; found:= false;
while (p<>NIL) and (not found) do
if p^.maso=masv then found :=
true
else p:=p^.next;
if found then begin
new(q);
with q^ do
begin
write('Ma so : ');
readln(maso); write('Ho ten : ');
readln(Hoten);
write('Diem TB :');
readln(dtb);
end;
q^.next:= p^.next;
p^.next:= q;
end
else begin
write('Khong tim thay ');
readln;
end;
end;
end;
Procedure TimXoa;
Trang 11var masv: string[6]; found: boolean; p,q: SVienPtr; begin
write('Ma so SV can loai khoi danh sach : ');
readln(masv);
p:= L;
if p<>NIL then begin
found:= false;
while (p<>NIL) and (not found) do
if p^.maso=masv then found := true
else begin
q:=p; p:=p^.next
end;
if found then
begin
if p=L then L :=p^.next
else q^.next:=p^.next;
if p^.next=NIL then R:=q; dispose(p)
end
else begin
write('Khong tim thay '); readln;
end;
end;
End;
BEGIN
L := NIL;
repeat
writeln; writeln('1 Bo sung mot sinh vien');
writeln('2 Duyet danh sach sinh vien');
writeln('3 Tim kiem mot phan tu va chen vao sau'); writeln('4 Tim kiem mot phan tu va xoa');
writeln('5 Ket thuc chuong trinh');
writeln; write('Chon chuc nang: '); readln(chon); writeln;
case chon of
Trang 121: Bosung;
2: DuyetXuoi;
3: ChenSau;
4: TimXoa;
End;
Until chon=5;
while L<>NIL do Begin
R:= L; L:= L^.next;
Dispose(R);
End;
End
7.2 Bài tập tự giải
Bài tập 1: Viết một hàm để xác định xem một danh sách liên kết đã cho có thứ tự tăng
dần hay không theo 2 cách: Không đệ qui và đệ qui
Bài tập 2: Cho 2 danh sách liên kết đơn đại diện cho 2 tập hợp được trỏ bởi L1 và L2 Viết chương trình để hiển thị:
1 Phần giao của 2 danh sách trên
2 Phần hợp của 2 danh sách trên
3 Phần hiệu của 2 danh sách trên
Bài tập 3: Cho 2 danh sách liên kết L1 và L2
1 Sắp xếp lại 2 danh sách đó theo thứ tự tăng dần
Trộn 2 danh sách đó lại thành danh sách L3 sao cho L3 vẫn có thứ tự tăng dần
- -