1. danh sách móc nối đơn là gì? trường info chứa giá trị...

12
1 1. 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

Upload: others

Post on 30-Aug-2019

16 views

Category:

Documents


0 download

TRANSCRIPT

1

1. 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

2

nhiê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;

3

3. 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);

NewNode^.info := v;

NewNode^.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;

4

Việ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;

5

Xó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>);

6

Var 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

7

tó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

8

New(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;

9

var 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;

10

end;

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;

11

var 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

12

1: 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.

------------------------------