Tải bản đầy đủ (.doc) (91 trang)

Tài liệu cấu trúc dữ liệu cơ bản

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 (856.21 KB, 91 trang )

CHƯƠNG 1 : CÁC KHÁI NIỆM CƠ BẢN
I. TỪ BÀI TOÁN ĐẾN CHƯƠNG TRÌNH
1. Giải thuật
2. Ngôn ngữ già và tinh chế từng bước
3. Tóm tắt ba giai đoạn để giải một bài toán
II. CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG
1. Đ ịnh nghĩa
2. Ví dụ
III. CÁC KIỂU DỮ LIỆU - CÁC CẤU TRÚC DỮ LIỆU - CÁC KIỂU DỮ LIỆU TRỪU
TƯỢNG
IV. THỜI GIAN CHẠY MỘT CHƯƠNG TRÌNH
1. Đ o một thời gian chạy của một chương trình
2. Đ ộ phức tạp của giải thuật
3. Cách tính thời gian chạy của chương trình
I. TỪ BÀI TOÁN ÐẾN CHƯƠNG TRÌNH
1. Giải thuật
TOP
Giải thuật la ìmột chuỗi các chỉ thị, mỗi chỉ thị có ý nghĩa rõ ràng để có thể giải quyết
bài toán trong một khoảng thời gian hữu hạn.
Nói cách khác, giải thuật là một cách để giải bài toán nào đó, nhưng nó phải áp dụng
được cho mọi bài toán cùng loại. Nếu cách giải chỉ đúng cho một vài trường hợp đặc biệt thì
nó không phải là giải thuật.
Có nhiều cách để thể hiện giải thuật như dùng lời, dùng lưu đồ, có một cách diễn đạt
giải thuật được dùng rất phổ biến đó là dùng ngôn ngữ giả.
Ngôn ngữ giả là sự kết hợp của ngôn ngữ tự nhiên và các cấu trúc của một ngôn ngữ
lập trình nào đó.
Ví dụ: Cho một ngã 5 như hình vẽ
Giải: Chúng ta có thể mô hình hóa bài toán nói trên theo mô hình toán học đã biết đó
là đồ thị (Graph). Ðồ thị là tập hợp các điểm gọi là các đỉnh của đồ thị và một tập hợp các
cạnh nối một số các cặp đỉnh với nhau. 2 đỉnh có ít nhất 1 cạnh nối được gọi là 2 đỉnh kề
nhau. Tại ngả 5 này có 13 lối đi (AB, AC, AD, BA, BC, BD, DA, DB, DC, EA, EB, EC, ED).


Ta sẽ biểu diễn mỗi lối đi là một đỉnh của đồ thị và 2 lối đi không thể cùng đi đồng thời ta nối
chúng bằng 1 cạnh. Tóm lại, ta có:
• Ðỉnh là các tuyến đường đi cho phép.
• Cạnh nối 2 đỉnh dùng để chỉ tuyến đường không thể lưu thông đồng thời.
Ta có đồ thị hình 2 như sau :
Bài toán của chúng ta rõ ràng là bài toán tô màu cho đồ thị hình 2. Bài toán tô màu cho
đồ thị được phát biểu như sau: "Tô màu cho các đỉnh của đồ thị sao cho số màu được dùng là
ít nhất và 2 đỉnh kề nhau (có cung nối) không được tô cùng 1 màu.
Tuy nhiên, bài toán tô màu cho đồ thị không có giải thuật tốt. Nói cách khác, giải thuật
của bài toán tô màu là: "Thử tất cả các khả năng". Thực tế cách giải này khó có thể áp dụng
đưọc vì vậy ta cần suy nghĩ cách khác để giải quyết vấn đề. Nếu bài toán nhỏ ta có thể vét cạn
các khả năng để tìm ra lời giải tối ưu.
Một cách giải quyết dùng cho bài toán chúng ta là: "Cố gắng tô màu cho đồ thị bằng ít
màu nhất" một cách nhanh chóng. Một cách giải quyết như vậy gọi là một Heuricstic. Một
Heuricstic hợp lý cho bài toán tô màu đồ thị được gọi là giải thuật Greedy.
Chọn 1 đỉnh chưa tô màu và tô màu cho nó. Với các đỉnh còn lại mà không có
cạnh chung với đỉnh đang xét thì tô các đỉnh đó cùng 1 màu với đỉnh đang xét.
Duyệt danh sách các đỉnh chưa tô màu, lấy 1 đỉnh trong số chúng và tô bằng
màu mới rồi quay lại bước 1.
Ðây là giải thuật hiển nhiên và luôn đạt kết quả tốt (mặc dù có thể không là lời giải tối ưu).
1
2
3
4
5
Têm
Xanh
Xanh
Âoí
Âoí

Xanh
Xanh
Xanh
Âoí
Âoí
Ví dụ : Xét đồ thị dưới đây và cách tô màu cho nó
Dùng Greedy: Tối ưu:
+ Xanh : 1, 2 + Xanh: 1, 3, 4
+ Ðỏ : 3, 4 + Ðỏ : 2, 5
+ Tím : 5
Áp dụng giải thuật Greedy cho bài toán của đồ thị hình 2, ta được kết quả:
+ Xanh : AB, AC, AD, BA, DC, ED
+ Ðỏ : BC, BD, EA
+ Tím : DA, DB
+ Vàng : EB, EC
Nhận xét:
Một đồ thị có k đỉnh mà mỗi cặp đỉnh bất kỳ đều được nối với nhau thì phải
dùng k màu để tô.
Một đồ thị trong đó có k đỉnh mà mỗi cặp đỉnh bất kỳ trong k đỉnh này đều
được nối với nhau thì không thể dùng ít hơn k màu để tô cho đồ thị.
Ðồ thị hình 2 có 4 đỉnh AC, DA, EB, BD mà mỗi cặp đỉnh bất kỳ trong số
chúng đều được nối với nhau. Vậy đồ thị hình 2 không thể tô với ít hơn 4 màu. Ðều này
khẳng định lời giải trên là lời giải tối ưu.
2. Ngôn ngữ giả và tinh chế từng bước
TOP
Một khi ta đã có mô hình thích hợp cho bài toán ta cần hình thức hóa một giải thuật
trong thuật ngữ của mô hình đó. Mở đầu ta viết những mệnh đề tổng quát rồi tinh chế dần
thành những mệnh đề cụ thể hơn và cứ tiếp tục như thế. Ðến cuối cùng ta được những chỉ thị
thích hợp trong một ngôn ngữ lập trình.
Ví dụ: Xét giải thuật Greedy áp dụng cho bài toán tô màu

Giả sử đồ thị đang xét là G.
Giải thuật Greedy xác định một tập hợp NewClr các đỉnh của G được tô cùng
một màu. Giải thuật Greedy được lập cho đến khi tất cả các đỉnh của G đều được tô màu.
Procedure Greedy ( Var G : Graph ; Var NewClr : Set );
Begin
{1} NewClr : = 0 ;
{2} For mỗi đỉnh của V chưa tô màu Do
{3} If V không được nối với đỉnh nào trong NewClr Then
Begin
{4} Ðánh dấu V đã được tô màu ;
{5} Thêm V vào tập NewClr ;
End ;
End ;
Chú ý : Trong giải thuật bằng ngôn ngữ giả chúng ta đã dùng một số từ
khóa của Pascal (các từ được in đậm).
Graph và Set là các kiểu dữ liệu trừu tượng, chúng sẽ được xác định bằng phép
định nghĩa kiểu của Pascal.
Câu lệnh If {3} có thể được tinh chế một bước nữa như sau:
Procedure Greedy ( Var G : Graph ; Var NewClr : Set );
Begin
{1} NewClr : = 0 ;
{2} For mỗi đỉnh của V chưa tô màu Do
Begin
{3.1} Found : = False ;
{3.2} For mỗi đỉnh của W trong NewClr Do
{3.3} If có cạnh nối giữa V với W Then
{3.4} Found : = True ;
{3.5} If Found = False Then
Begin
{4} Ðánh dấu V đã được tô màu ;

{5} Thêm V vào tập NewClr ;
End;
End;
End ;
Ta có thể coi Graph và Set như các tập hợp. Có nhiều cách để biểu diễn 1 tập hợp
trong ngôn ngữ lập trình. Ðể đơn giản ta xem tập hợp như một danh sách (List) các số nguyên
là chỉ số của các đỉnh và kết thúc bằng 1 giá trị đặc biệt Null. Giải thuật Greedy được tinh chế
1 bước nữa như sau:
Procedure Greedy ( Var G:Graph ; Var NewClr: Set );
Var Found : Boolean ;
V, W : Integer ;
Begin
NewClr : = 0 ;
V : = Ðỉnh đầu tiên trong G ;
While V < > Null Do
Begin
Found : = False ;
W : = Ðỉnh đầu tiên trong NewClr ;
While W < > Null Do
Begin
If có cạnh nối giữa V với W Then
Found : = True ;
W : = Ðỉnh kế tiếp trong NewClr ;
End;
If Found = False Then
Begin
Ðánh dấu V đã được tô màu ;
Thêm V vào tập NewClr ;
End;
V : = Ðỉnh kế trong G ;

End;
End ; { Greedy}
3. Tóm tắt ba giai đoạn để giải 1 bài toán
TOP
Giai đoạn 1: Xây dựng mô hình toán thích hợp cho bài toán và tìm một giải thuật
giải quyết bài toán trên mô hình đó.
Giai đoạn 2: Giải thuật được trình bày bằng ngôn ngữ giả dựa trên các kiểu dữ liệu
trừu tượng.
Giai đoạn 3: Chọn một cách cài đặt một kiểu dữ liệu trừu tượng và thay ngôn ngữ giả
bằng các mã lệnh của 1 ngôn ngữ lập trình . Kết quả là ta được 1 chương trình hoàn chỉnh có
thể giải quyết được vấn đề đặt ra.
II. CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG (Abstract Data Type)
1. Ðịnh nghĩa
TOP
Một kiểu dữ liệu trừu tượng (ADT) là một mô hình toán học cùng với một tập hợp các
phép toán tác động trên mô hình đó.
Thông thường ta sẽ thiết kế giải thuật theo thuật ngữ của kiểu dữ liệu trừu tượng,
nhưng để cài đặt được giải thuật trong một ngôn ngữ lập trình, ta phải tìm cách biểu diễn kiểu
dữ liệu trừu tượng qua các kiểu dữ liệu của ngôn ngữ.
2. Ví dụ
TOP
Kiểu Set có thể được biểu diễn qua danh sách (List) và được quản lý bằng mảng
(Array) hay con trỏ (Pointer).
Kiểu Graph là một danh sách các số nguyên và các phép toán tác động trên nó là:
MakeNull (G): Tạo đồ thị rỗng.
W= First (G): Hàm trả về phần tử đầu danh sách của G ( = Null nếu G rỗng).
W= Next (G): Hàm trả về phần tử kế tiếp trong G ( = Null nếu là phần tử cuối).
Insert (V ,G): Xen phần tử V vào đồ thị G.
III. CÁC KIỂU DỮ LIỆU (DATA TYPE) - CÁC CẤU TRÚC DỮ LIỆU (DATA
STRUCTURE) - CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG (ABSTRACT DATA

TYPE - ADT)
TOP
Trong một ngôn ngữ lập trình, kiểu dữ liệu của một biến là tập hợp các giá trị mà biến
đó có thể nhận được. Mỗi một ngôn ngữ lập trình đều có một số kiểu dữ liệu cơ sở khác nhau.
Một kiểu dữ liệu được thành lập bằng sự tập hợp các kiểu dữ liệu khác nhau gọi là
kiểu dữ liệu hợp thành hay kiểu dữ liệu có cấu trúc hay cấu trúc dữ liệu.
Ví dụ: A: Array [1 N] of Record
Field1: < Type1>;
Field2: < Type2>;

FieldN: < TypeN>;
End;
IV. THỜI GIAN CHẠY CỦA MỘT CHƯƠNG TRÌNH
TOP
Trong khi giải bài toán ta có thể có một vài giải thuật, vấn đề là chọn giải thuật nào,
tiêu chuẩn như thế nào, thường có 2 yêu cầu chính:
Dễ hiểu, dễ viết chương trình.
Giải thuật dùng các tài nguyên hiệu quả như dùng ít bộ nhớ, chạy càng nhanh
càng tốt,
1. Ðo thời gian chạy của một chương trình
TOP
Thời gian chạy của một chương trình phụ thuộc vào các yếu tố:
Dữ liệu đầu vào.
Tốc độ của máy được dùng.
Tính chất của trình biên dich được dùng.
Ðộ phức tạp tính toán của giải thuật.
Trong thực tế thời gian chạy của một chương trình có thể xem như là một hàm của dữ
liệu vào. Cụ thể, đó là kích thước của dữ liệu vào. Vì vậy, ta gọi T(n) là thời gian chạy
chương trình với dữ liệu vào có độ dài là n.
Ðối với nhiều chương trình thì thời gian chạy chương trình còn phụ thuộc vào đặc tính

của dữ liệu vào. Nghĩa là dữ liệu vào có cùng độ dài nhưng thời gian chạy chương trình là
khác nhau. Vì vậy, ta có thể xem T(n) là thời gian chạy chương trình trong trường hợp xấu
nhất trên dữ liệu vào có độ dài là n. Hay nói cách khác T(n) là thời gian chạy chương trình với
mọi dữ liệu vào có độ dài là n.
Ðo thời gian chạy chương trình như thế nào ?
2. Ðộ phức tạp của giải thuật
TOP
3. Cách tính thời gian chạy chương trình
TOP
Cách tính thời gian chạy của một chương trình bất kỳ là một vấn đề không đơn giản.
Nhưng đối với nhiều chương trình ta có thể tính thời gian chạy của nó theo 2 quy tắc sau: ( áp
dụng được cho tất cả các chương trình không đệ quy).
Chú ý :
Thời gian chạy của các lện gán, Read, Write là O(1).
Thời gian chạy của một chuỗi tuần tự các lệnh được xác định theo quy tắc
cộng. Như vậy thời gian này là thời gian thi hành một lệnh nào đó lâu nhất trong chuỗi lệnh
(câu lệnh đó được gọi là Câu lệnh tích cực).
Thời gian chạy của cấu trúc If là thời gian lớn nhất để thực hiện các lệnh sau
Then hoặc sau Else cộng với thời gian kiểm tra điều kiện. Thường thời gian kiểm tra điều kiện
là O(1).
Thời gian thực hiện vòng lặp là tổng (trên tất cả các lần lặp) thời gian thực hiện
từng vòng lặp. Nói cách khác thời gian này được tính theo quy tắc nhân tức là tích của số lần
lặp với thời gian thực hiện của thân vòng lặp.
Thời gian gọi thủ tục hay hàm là thời gian thực hiện đoạn chương trình tương
ứng với hàm hay thủ tục đó.
Ví dụ 1: Câu lệnh: for i: = 1 to n do
for j: = 1 to n do
x:= x + 1;
Ví dụ 2: Procedure BubbleSort ( Var a: array [ 1 n ] of Integer);
Var i, j, Temp : Integer;

Begin
for i: = 1 to n do
for j: = 1 to n do
If a[i] > a[j] then
Begin
Temp:= a[i]; a[i]: = a[j]; (1)
a[j]:= Temp;
End;
End;
Trong đó 5 dạng hàm đầu tiên gọi là hàm đa thức, còn 3 dạng hàm cuối gọi là hàm
mũ. Một giải thuật mà thời gian thực hiện có dạng là một hàm đa thức thì có thể chấp nhận
được.

BÀI TẬP CUỐI CHƯƠNG I
Bài 1 : Viết các thủ tục thực hiện các phép toán cộng và nhân 2 ma trận, rồi tính thời gian chạy
của các thủ tục này.

Bài 2 : Hãy viết chương trình tính gần đúngĠ theo công thức :

Sau đó hãy tính thời gian chạy của chương trình.
Bài 3 : Viết chương trình nhập vào một ma trận vuông cấp n. Sau đó in ra các phần tử
thuộc các đường chéo song song với đường chéo chính.
Phác họa giải thuật của chương trình và tính độ phức tạp của giải thuật.
Bài 4 : Hãy tìm một giải thuật để tínhĠ sao cho độ phức tạp của giải thuật sẽ là O(n) và
viết chương trình thể hiện giải thuật đã đề ra bằng ngôn ngữ Pascal.
Bài 5 : Hãy viết chương trình đổi một số nguyên dương ở hệ thập phân (cơ số 10) sang hệ
nhị phân (cơ số 2) và đánh giá thời gian thực hiện của giải thuật.
CHƯƠNG 2 : CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG CƠ BẢN
(BASIC ABSTRACT DATA TYPE)
I. KHÁI NIỆM VỀ CON TRỎ

1. Đ ịnh nghĩa
2. Khai báo
3. Một số hàm thường sử dụng ở biến con trỏ
II. DANH SÁCH
1. Khái niệm
2. Các phép toán trên danh sách
3. Cài đặt danh sách
III. CẤU TRÚC CHỒNG/NGĂN XẾP (STACK)
1. Đ ịnh nghĩa
2. Các phép toán trên Stack
3. Cài đặt Stack bằng mảng
4. Cài đặt stack bằng con trỏ
IV. CẤU TRÚC HÀNG ĐỢI
1. Đ ịnh nghĩa
2. Các phép toán cơ bản trên hàng
3. Cài đặt hàng
V. DANH SÁCH LIÊN KẾT KÉP
1. Nhận xét
2. Khai báo
3. Thủ tục khởi tạo danh sách liên kết kép rổng
4. Hàm kiểm tra danh sách liên kết kép rổng
5. Thủ tục xen một phần tử vào danh sách liên kết kép
6. Thủ tục xóa một phần tử khỏi danh sách liên kết kép
I. KHÁI NIỆM VỀ CON TRỎ (Pointer)
1. Ðịnh nghĩa
TOP
Con trỏ là một kiểu dữ liệu (dành 4 bytes trong bộ nhớ) mà các giá trị của nó là địa chỉ
của các phần tử của kiểu dữ liệu mà nó trỏ đến.
Các giá trị của biến thuộc kiểu T được phát sinh bất cứ lúc nào khi biến đó được cấp
phát bằng lệnh New.

2. Khai báo
TOP
Gián tiếp :
Type Tên kiểu = ^ Kiểu dữ liệu;
Var Tên biến : Tên kiểu;
Khai báo trực tiếp :
Var Tên biến : ^ Kiểu dữ liệu;
Ví dụ 1: Type Ref = ^ Nhansu;
Nhansu = Record
Data : DataType;
Next : Ref;
End;
Var p: ^Nhansu;
Ví dụ 2: Var N : ^ Integer; {N là con trỏ chỉ đến số nguyên}
Ta xét một khai báo có dạng :
Type P = ^T;
• P là biến kiểu con trỏ.
• T là kiểu dữ liệu mà con trỏ trỏ tới.
• P^ : Cho kết quả là giá trị kiểu T mà nó đang trỏ tới.
• @ Chỉ địa chỉ của giá trị mà nó đang trỏ tới.
Vậy muốn truy xuất nội dung tại vị trí mà p đang trỏ tới ta dùng P^
Ngoài ra, còn có một con trỏ tổng quát, nó không chỉ đến một kiểu dữ liệu xác định
nào cả.
Var p : Pointer;
Kiểu dữ liệu con trỏ chiếm 4 bytes trong bộ nhớ, 4 bytes này sẽ chứa địa chỉ
của kiểu dữ liệu mà nó trỏ tới còn bản thân biến mà nó trỏ tới thì chưa có trong bộ nhớ.
Ví dụ : Var p : ^ Integer;
i : Integer;
Begin
i : = 5;

p : = @ i; {Hàm lấy địa chỉ của i và gán cho p}
Writeln ('Nội dung mà p đang trỏ tới :', p^);
Writeln ('Giá trị của biến i : ', i);
End.
3. Một số hàm thường sử dụng ở biến con trỏ :
TOP
a. Cấp phát vùng nhớ : New (p)
Thủ tục này sẽ cấp phát vùng nhớ động do con trỏ p quản lý và đồng thời gán cho
p địa chỉ đầu của vùng nhớ này.
b. Thu hồi vùng nhớ đã cấp phát : Dispose (p)
Thủ tục này sẽ hủy bỏ (giải phóng) vùng nhớ do p trỏ tới (quản lý).
c. Cấp phát / thu hồi hàng loạt vùng nhớ :
Mark (HeapTop) Realease (HeapTop)
Thủ tục Mark (HeapTop) sẽ gán giá trị của địa chỉ của vùng Heap cho một con trỏ p
nào đó. Con trỏ p này được dùng như để đánh dấu vị trí đầu của vùng ô nhớ sẽ được giải
phóng sau này. Sau thủ tục Mark ta có thể dùng hàng loạt thủ tục New để tạo các biến động
khác nhau và chúng sẽ chiếm ô nhớ bắt đầu từ vị trí đã được đánh dấu. Nếu muốn lấy lại toàn
bộ vùng nhớ đã cấp phát ta chỉ cần dùng thủ tục Realease (HeapTop). Tất nhiên là sau lệnh
Realease ta không còn có thể sử dụng các biến động được tạo ra bằng thủ tục New nằm trong
vùng ô nhớ vừa được giải phóng.
d. Thủ tục GetMem (p, k)
Thủ tục này sẽ cấp phát k bytes vùng nhớ do con trỏ p quản lý.
e. Thủ tục FreeMem (p, k)
Thủ tục này sẽ thu hồi vùng nhớ đã được cấp phát bởi thủ tục GetMem.
f. Các hàm cho biết tình trạng của vùng nhớ :
Hàm MaxAvail : Longint : Hàm cho biết vùng nhớ lớn nhất còn trống trong
Heap.
Hàm MemAvail : Longint : Hàm cho biết tổng số bytes còn lại trên Heap.
Hàm SizeOf (Biến ) : Longint : Cho biết số bytes được cấp phát / thu hồi bởi
biến.

Ví dụ : P là con trỏ trỏ đến kiểu RecordType nào đó.
If MaxAvail >= SizeOf (RecType) then New(p)
Else Writeln('Không đủ vùng nhớ cấp phát');
g. Các hàm cho biết địa chỉ của một đối tượng trong bộ nhớ :
Hàm Add (x) : Pointer : Cho biết địa chỉ tổng quát của biến x.
Hàm Seg (x) : Word : Cho biết địa chỉ segment của biến x.
Hàm Ofs (x) : Word : Cho biết địa chỉ Offset của biến x.
Hàm Ptr (Seg, Ofs) : Pointer : Trỏ tới địa chỉ seg : Ofs.
Ví dụ 1 :
Var a: Integer;
b : String;
p : ^ Integer;
q : ^ String;
r : Pointer;
Begin
a : = 1234;
p : = @a;
Writeln (' Nội dung của a là : ', a);
Writeln (' Nội dung p đang trỏ tới là : ', p^);
r : = @b; q : = r;
b : = 'Khoa su pham truong dai hoc can tho';
Writeln (q^);
End.
Ví dụ 2 :
Type Name = String [30];
PtrName = ^ Name;
Var p, q : ^ Name;
k : Word;
Begin
Writeln ('Vùng nhớ ban đầu',MemAvail,' Bytes trống');

New (p); p : = 'Khoa Su Pham';
k : = SizeOf (p);
Writeln ('Vùng nhớ sau khi khởi động',MemAvail,' Bytes');
GetMem(q, SizeOf(q));
P^ : = q^;
Writeln ('p có kích thưóc là : ',SizeOf(p^),');
Writeln ('q có kích thưóc là : ',SizeOf(q^),');
Writeln ('Nội dung của q là : ',q^,');
Dispose(p); FreeMem (q,k);
Writeln ('Vùng nhớ hiện tại là :',MemAvail,'Bytes' );
End.
II. DANH SÁCH (LIST)
1. Khái niệm
TOP
Mô hình toán học của danh sách là một tập hợp các phần tử có cùng một kiểu mà
ta gọi là kiểu phần tử (ElementType).
2. Các phép toán trên danh sách :
TOP
Một số phép toán trên danh sách :
a. Hàm MakeNullList ( Var L : List ) - Hàm tạo một danh sách L rỗng.
b. Hàm EmptyList (L : List) : Boolean - Hàm kiểm tra danh sách rỗng
( Hàm cho kết quả là True nếu danh sách rỗng.
( Hàm cho kết quả là False nếu ngược lại.
c. Hàm First ( L : List ) : Position - Hàm cho kết quả là vị trí của phần tử đứng
đầu danh sách L. Nếu danh sách rỗng thì ta có First (L) = EndList (L).
d. Hàm Next ( p : Position ; L : List ) : Position - Hàm cho kết quả là vị trí của
phần tử đứng sau phần tử thứ p trong danh sách L. Nếu p là phần tử cuối danh sách thì ta có
Next (p, L) = EndList (L).
e. Hàm Previous ( p : Position ; L : List ) : Position - Hàm cho kết quả là vị trí
của phần tử đứng trước phần tử thứ p trong danh sách L. Nếu p là phần tử đầu danh sách thì

hàm không xác định.
f. Hàm Retrieve (p : Position ; L : List ) : ElementType - Hàm cho kết quả là
nội dung của phần tử thứ p trong danh sách L. Nếu vị trí p không tồn tại thì hàm không xác
định.
g. Thủ tục InsertList (x : ElementType ; p : Position ; Var L : List ) - Thủ tục
thực hiện việc xen phần tử x vào vị trí p trong danh sách L
h. Thủ tục DeleteList (p : Position ; Var L : List ) - Thủ tục thực hiện việc xóa
phần tử tại vị trí p trong danh sách L
i. Hàm Locate (x : ElementType ; L : List ) : position - Hàm tìm phần
tử x trong danh sách L. Hàm trả về vị trí đầu tiên của phần tử x nếu tìm thấy. Nếu không tìm
thấy hàm trả về EndList (L).
j. Thủ tục PrintList (L : List ) - In danh sách các phần tử trong danh sách L
theo thứ tự xuất hiện của các phần tử.
Ví dụ 1 : Viết giải thuật sắp xếp một danh sách theo thứ tự tăng dần giả sử thủ tục
Swap ( p, q) thực hiện việc đổi chỗ 2 phần tử tại vị trí p và vị trí q.
Procedure BubbleSort ( Var L : List );
Var p, q : position;
Begin
P : = First(L);
While p <> EndList(L) do
Begin
q : = Next(p, L);
While q <> EndList(L) do
Begin
If Retrieve(p) > Retrieve(q) then
Swap(p, q)
Else
q : = Next(q, L);
End;
P : = Next(p, L);

End;
End;
Ví dụ 2 : Lặp danh sách các hồ sơ và loại bỏ các hồ sơ trùng lặp.
Procedure LoaiBo ( Var L : List );
Var p, q : position;
Begin
P : = First(L);
While p <> EndList(L) do
Begin
q : = Next(p, L);
While q <> EndList(L) do
Begin
If Retrieve(p) = Retrieve(q) then DeleteList(q, L)
Else
q : = Next(q, L); End;
P : = Next(p, L);
End;
End;
3. Cài đặt danh sách
TOP
a. Cài đặt danh sách bằng mảng (danh sách đặc ) :
Danh sách đặc là danh sách mà các phần tử của nó được sắp xếp trong các vùng nhớ
hay ô nhớ liên tiếp nhau.
Cài đặt danh sách bằng mảng nghĩa là ta dùng một mảng (array) để lưu trữ liên tiếp
các phần tử của danh sách bắt đầu từ vị trí đầu tiên của mảng. Dĩ nhiên là ta phải ước lượng
số phần tử của danh sách để khai báo số phần tử của mảng thích hợp và nói chung là còn thừa
chỗ trống trong mảng, vì vậy ta phải lưu giữ độ dài hiện tại của danh sách. Danh sách này cho
biết mảng có bao nhiêu phần tử và phần tử nào của mảng còn trống.
Khai báo :
Const Maxlength = ; {Ðộ dài tối đa của danh sách}

Type
ElementType = ; {Kiểu phần tử trong danh sách}
List = Record
Elements : array [1 Maxlength] of ElementType;
Last : integer; {Ðộ dài thật sự của danh sách}
End;
Thủ tục khởi tạo danh sách rỗng :
Procedure MakenullList ( Var L : List);
Begin
L.Last : = 0;
End;
Hàm kiểm tra danh sách rỗng :
Function EmptyList ( L : List ) : Boolean;
Begin
EmptyList : = ( L.Last = 0 );
End;
Hàm kiểm tra danh sách đầy :
Function FullList ( L : List ) : Boolean;
Begin
FullList : = ( L.Last > = Maxlenght );
End;
Thủ tục nhập giá trị cho các phần tử của danh sách :
Procedure ReadList (Var L : List);
Var i : position;
Begin
For i := 1 to L. Last do
Begin
Write (' Nhập phần tử thứ ', i ,' của danh sách : ');
Readln (L.Elements [i]);
End;

End;
Thủ tục xen một phần tử vào danh sách :
Khi xen một phần tử vào danh sách, ta có thể gặp các trường hợp sau đây :
Mảng đầy ( độ dài của danh sách = độ dài của mảng ) thì thủ tục sẽ bị lỗi.
Ngược lại nếu vị trí p không tồn tại (p < 1 hay p > L . Last + 1) thì thủ tục cũng
bị lỗi.
Ngược lại nếu vị trí p là hợp lệ thì ta thực hiện các bước sau :
Dời các phần tử từ vị trí p đến cuối danh sách xuống 1 đơn vị.
Ðộ dài của danh sách tăng lên 1.
Xen phần tử mới vào vị trí p của danh sách.
Procedure InsertList (x:ElementType; p:position; Var L:List);
Var i:position;
Begin
If (L.Last>=Maxlength) then
writeln('Mảng đầy')
Else
Begin
for i:=L.Last downto p do
L.Elements [i+1]:=L.Elements [i];
L.Last:= L.last + 1;
L.Elements[p]:=x;
End;
End;
Thủ tục xóa một phần tử khỏi danh sách :
Khi xóa một phần tử khỏi danh sách, ta có thể gặp các trường hợp sau đây :
Danh sách rỗng thì thủ tục sẽ bị lỗi.
Ngược lại nếu vị trí p không tồn tại (p < 1 hay p > L . Last + 1) thì thủ tục cũng
bị lỗi.
Ngược lại nếu vị trí p là hợp lệ thì ta thực hiện các bước sau :
Dời các phần tử từ vị trí p + 1 đến cuối danh sách lên 1 đơn vị.

Ðộ dài của danh sách giảm đi 1 đơn vị.
Procedure DeleteList(p : position; Var L : List);
Var i: position;
Begin
If EmptyList(L) then
Writeln ('Danh sách rỗng')
Else
Begin
for i : = p to L.Last do
L.Elements [i] : = L.Elements [i+1];
L.Last : = L. Last - 1;
End;
End;
Hàm tìm kiếm một phần tử trong danh sách :
Hàm Locate( x, L ) tìm phần tử x trong danh sách L và cho kết quả là phần tử
đầu tiên nếu tìm thấy, còn nếu không tìm gûặp thì hàm cho kết quả là EndList (L) (tức là Last + 1)
Giải thuật đơn giản như sau : Bắt đầu từ phần tử đầu tiên, rồi lần lượt duyệt qua các phần
tử tiếp theo cho đến khi tìm được phần tử cần tìm (đầu tiên). Trả kết quả về cho hàm là vị trí
của của phần tử được tìm thấy.
Function Locate ( x : ElementType; L : List ) : position;
Var i : position;
Begin
i : = 1;
While (i <= L. Last) and (L.Elements [i]<> x) do
i : = i + 1;
Locate : = i;
End;
Thủ tục hiển thị các phần tử của danh sách :
Procedure WriteList ( L : List);
Var i : position;

Begin
For i := 1 to L. Last do
Writeln (L.Elements [i]);
End;
Ðặc điểm của danh sách đặc :
• Ưu điểm :
Không lảng phí bộ nhớ.
Dễ dàng truy xuất đến các phần tử của danh sách.
• Khuyết điểm :
Phép xen, xóa tương đối phức tạp, nó phụ thuộc vào số phần tử của danh sách.
Ðòi hỏi phải xác định số phần tử của mảng, nếu không ước lượng được số phần
tử thì không thể cài đặt một cách hiệu quả. Do đó danh sách đặc được sử dụng phổ biến đối
với các danh sách ít biến động.
b. Cài đặt danh sách bằng con trỏ (danh sách liên kết ) :
Ðịnh nghĩa : Danh sách liên kết là danh sách mà các phần tử của nó được nối kết với
nhau nhờ vào vùng liên kết của chúng. Với danh sách này, khi cài đặt ta không cần dùng bộ
nhớ liên tục và không cần di chuyển các phần tử khi xen hoặc xóa.
Mô tả danh sách liên kết :
a
1

a
2

a
n


Nil


Header



Ðể cài đặt danh sách liên kết, ta dùng con trỏ để liên kết các phần tử của danh sách
theo phương thức ai chỉ đến ai+1. Ðể một phần tử có thể chỉ đến một phần tử khác ta xem mỗi
ô là một Record gồm có 2 trường :
Trường Elements để giữ nội dung của phần tử trong danh sách.
Trường Next là một con trỏ giữ địa chỉ của ô kế tiếp.
Phần tử cuối cùng của danh sách ta cho trỏ đến một giá trị đặc biệt Nil.
Ðể quản lý danh sách ta cần một biến chỉ đến phần tử đấu danh sách. Biến này được
gọi là chỉ điểm đầu danh sách Header.
Header là một ô có cùng kiểu với kiểu phần tử trong danh sách. Nhưng trường
Elementss của ô header là rỗng, còn trường Next của ô Header thì giữ địa chỉ của phần tử đầu
danh sách.
• Khai báo :
Type ElementType = ; {Kiểu phần tử của danh sách}
Position = ^ CellType;
CellType = Record
Elements: ElementType;
Next : Position;
End;
List = Position;
• Thủ tục khởi tạo danh sách rỗng :
Procedure MakenullList ( Var Header : List);
Begin
New (Header);
Header.Next : = Nil;
End;
• Hàm kiểm tra danh sách rỗng :

Function EmptyList( Header : List): Boolean;
Begin
EmptyList: = (Header.Next = Nil);
End;
• Thủ tục duyệt các phần tử trong danh sách liên kết :
Procedure Order ( L : List );
Var p : Position;
Begin
p : = L^.Next;
While p <> Nil do
Begin
Write (p^.Elements);
p:= p^.Next;
End;
End;




p

x (2)


Temp

(1)
(3)
(4)
p^.Next

p^.Next
• Thủ tục xen thêm một phần tử vào danh sách liên kết :
Muốn xen một phần tử x vào danh sách ta cần biết vị trí p trước chỗ xen, rồi ta xen x
vào vị trí sau p.
Procedure InsertList ( x: ElementType; p : Position );
Var Temp : Position;
Begin
New (Temp);
Temp^.Elements:=x;
Temp^.Next:= p^.Next;
p^.Next:= Temp;
End;




p




Temp

(2)
(3)
• Thủ tục xóa phần tử khỏi danh sách liên kết :
Muốn xóa một phần tử trong danh sách ta cũng cần biết vị trí p trước chỗ xóa.
Procedure DeleteList ( p : Position );
Var Temp : Position;
Begin

Temp:= p^.Next;
p^.Next:= Temp^.Next;
Dispose(Temp);
End;
• Thủ tục tìm kiếm một phần tử trong danh sách liên kết :
Function Locate (x: ElementType; L:List) : Position;
Var p: Position;
Found: Boolean;
Begin
p:= L^.Next; found:= False;
While ( p <> Nil ) and ( not found ) do
If p^.Elements = x then
Found:= True
Else
p:= p^.Next;
Locate:= p;
End;
• So sánh 02 phương pháp cài đặt :
c. Cài đặt danh sách liên kết bằng con nháy (trên cơ sở mảng ) :
Danh sách không phải là một cấu trúc dữ liệu tiền định trong đa số các ngôn ngữ lập
trình. Vì vậy, người lập trình phải cài đặt bằng các cấu trúc tiền định khác có sẳn trong ngôn
ngữ lập trình.
Một số ngôn ngữ lập trình không có định nghĩa biến kiểu con trỏ. Trong trường hợp
này ta sẽ giả con trỏ (dựa trên cơ sở cấu trúc tiền định mảng, được định nghĩa trong hầu hết
các ngôn ngữ lập trình) để cài đặt danh sách liên kết.
Ý tưởng: Là dùng một biến số nguyên (Integer) để lưu giữ chỉ số (vị trí) của các phần
tử kế tiếp trong mảng. Như vậy để cài đặt danh sách bằng con nháy ta cần một mảng mà mỗi
phần tử là một Record gồm 2 trường:
Trường Elements: giữ nội dung của phần tử trong danh sách.
Trường Next: Là một số nguyên chỉ tới vị trí của phần tử kế tiếp.Ðể quản lý

danh sách, ta dùng một con nháy (một biến số nguyên) để chỉ đến phần tử đầu danh sách.
Phần tử cuối danh sách ta cho chỉ đến một phần tử đặc biệt Null (Giá trị Null có thể chọn là 0,
nếu như mảng không có phần tử thứ 0).
Khi xen một phần tử vào danh sách, ta lấy một ô trống trong mảng để chứa phần tử
mới này và nối kết lại các con nháy.
Tương tự, khi xóa một phần tử khỏi danh sách, ta nối kết lại các con nháy để loại bỏ
phần tử đó. Ðiều này kéo theo số phần tử trong mảng tăng lên 1. Ðể quản lý các ô trống, ta
liên kết tất cả các ô trống vào một danh sách đặc biệt gọi là Available. Khi xen một phần tử
vào danh sách ta lấy ô trống đầu Available để chứa phần tử mới này. Khi xóa 1 phần tử, ta
cho ô bị xóa nối vào đầu Available.
• Khai báo :
Const Maxlenght = ; { Ðộ dài cực đại của mảng }
Type ElementType = ; {Kiểu phần tử của danh sách}
CursorType = 0 Maxlenght ; { Kiểu con nháy }
NodeType = Record
Elements: ElementType;
Next : CursorType;
End;
ArrayOfNodes = Array [1 Maxlenght] of NodeType;
Var Node: ArrayOfNode;
Available: CursorType; {Chỉ đến nút tự do đầu tiên}
List: CursorType; { Chỉ đến nút đầu danh sách }
• Thủ tục khởi tạo vùng lưu trữ các nút tự do :
Procedure Initialize;
Var i: Integer;
Begin
for i: = 1 to Maxlenght - 1 do
Node[i].Next := i + 1;
Node[Maxlenght].Next := 0;
Available := 1;

End;
• Thủ tục lấy nút tự do đầu tiên :
{ Thủ tục này trả con nháy p chỉ đến nút tự do, nếu không còn nút tự do, P trả về giá trị Null (0) }
Procedure Getnode (Var P:CursorType);
Begin
p := Available;
If p < > 0 then
Available := Node[p].Next
Else Writeln('Vung luu tru rong');
End;
• Thủ tục khởi tạo danh sách rỗng :
Procedure Create ( Var List: CursorType);
Begin
List : = 0; {Null}
End;
• Hàm kiểm tra danh sách rỗng :
Function EmptyList( List : cursorType): Boolean;
Begin
EmptyList: = (List = 0);
End;
• Thủ tục duyệt các phần tử trong danh sách liên kết :
Procedure Linker ( List: CursorType );
Var CrrPtr : CursorType;
Begin
CrrPtr := List;
While CrrPtr < > 0 do
Begin
Write(Node[CrrPtr].Elements);
CrrPtr := Node[CrrPtr].Next;
End;

End;







Temp
1
2
3
4
p
q
L
1
L
2
Node[p]
• Hàm chuyển 1 phần tử từ danh sách này sang danh sách khác :
Ta thấy thực chất của việc xen hay xóa một phần tử là thực hiện công việc chuyển
một ô từ danh sách này sang danh sách khác. Vậy ta cần phải viết một hàm Move thực hiện
thao tác này. Hàm cho kết quả kiểu Boolean tùy theo việc chuyển thành công hay thất bại. Ta
viết hàm Move thực hiện việc chuyển ô được chỉ bởi con nháy p vào ô trước ô được chỉ bởi
con nháy q.
Function Move ( var p, q: Integer) : Boolean;
Var Temp : Integer;
Begin
If p = 0 then

Move := false
Else
Begin
Temp := q;
q := p;
p := Node[p].Next;
Node[p].Next := Temp;
Move := True;
End;
End;
• Thủ tục xen một phần tử vào danh sách :
Muốn xen một phần tử vào danh sách, ta cần biết vị trí trước chỗ xen rồi ta chuyển ô
đầu Available vào vị trí cần xen (sau p). Tuy nhiên, nếu ta xen vào đầu danh sách thì không
có vị trí thực sự trước đó. Ta sẽ cho p một giá trị đặc biệt để thủ tục xử lý trong trường hợp
này. Ở đây ta cho p = 0
Procedure InsertList (x : ElementType; p : CursorType; Var L : CursorType);
Begin
If p = 0 then { Xen vào đầu danh sách }
Begin
If Move (Available, L) then
Node[L].Elements := x;
End
Else
If Move (Available, Node[p].Next) then
Node [Node [p].Next].Elements := x;
End;
• Thủ tục xóa một phần tử khỏi danh sách :
Muốn xóa một phần tử khỏi danh sách ta cũng cần biết vị trí trước chỗ xóa rồi
ta chuyển ô cần xóa này vào đầu Available. Tương tự như phép xen vào, muốn xóa phần tử
đầu danh sách ta cho p = 0.

Procedure DeleteList ( p : CursorType; Var L : CursorType);
Begin
If p = 0 then { Xóa phần tử đầu danh sách }
Begin
If Move (L, Available) then
Writeln ('Da xoa')
Else Writeln ('Loi')
Else
If Move (Node[p].Next, Available) then
Writeln ('Da xoa')
Else Writeln ('Loi')
End;
III. CẤU TRÚC CHỒNG / NGĂN XẾP (STACK)
1. Ðịnh nghĩa :
TOP
Stack là một danh sách đặc biệt mà phép thêm vào hoặc loại bỏ một phần tử chỉ thực hiện
tại một đầu gọi là đỉnh (Top) của Stack. Như vậy, Stack là một cấu trúc có tính chất vào
trước, ra sau (FILO - First In Last Out).
2. Các phép toán trên Stack
TOP
a. Thủ tục MakeNullStack (Var S : Stack) : Tạo một Stack rỗng.
b. Hàm/thủ tục Top (S : Stack) : Trả về kết quả là phần tử tại đỉnh của Stack
c. Thủ tục Pop (Var S : Stack) : Loại bỏ phần tử tại đỉnh của Stack.
d. Thủ tục Push (x : ElementsType; Var S : Stack) : Ðặt phần tử x vào đỉnh của
Stack.
e. Hàm EmptyS (S : Stack ) : Boolean : Kiểm tra xem một stack có rỗng hay
không? Hàm trả về giá trị True nếu Stack rỗng và False nếu ngược lại.
f. Hàm FullS (S : Stack ) : Boolean : Kiểm tra xem một stack có đầy hay
không? Hàm trả về giá trị True nếu Stack đầy và False nếu ngược lại.
Ví dụ : Viết thủ tục nhập từ bàn phím một dãy ký tự cho đến khi gặp ký tự '@' thì

ngưng. In dãy ký tự theo thứ tự ngược lại lúc nhập.
Ta sẽ trình bày giải thuật cho thủ tục này theo 2 cách sau:
Procedure Edit;
Var S : Stack;
C : Char;
Begin
MakeNullStack (S);
Repeat
Read (c);
Push (c, S);
Until c = '@';
Repeat
Write (Top(S));
Pop (S);
Until EmptyS (S);
End;
Procedure Edit1;
Var S : Stack;
C : Char;
Begin
MakeNullStack (S);
Repeat
Read (c);
If c < > '@' then
Push (c, S);
Until c = '@';
While not EmptyS (S) do
Begin
Write (Top(S));
Pop (S);

End;
End;
3. Cài đặt Stack bằng mảng :
TOP
• Khai báo :
Const Maxlength = ; { Ðộ dài mảng }
Type ElementType = ; { Kiểu phần tử }
Stack = Record
Elements : array[1 Maxlength] of ElementType;
Top : integer;
End;
• Thủ tục khởi tạo Stack rỗng :
Procedure MakeNullStack ( Var S : Stack);
Begin
S.Top :=Maxlenght + 1;
End;
• Hàm kiểm tra Stack rỗng :
Function EmptyS (S : Stack) : Boolean;
Begin
EmptyS := (S.Top = Maxlenght + 1);
End;
• Hàm kiểm tra Stack đầy :
Function FullS (S : Stack):Boolean;
Begin
FullS := ( S.Top = 1);
End;
• Hàm cho phần tử ở đầu Stack :
Function Top (S : Stack) : ElementType;
Begin
If not EmptyS (S) then

Top:=S.Elements[S.Top]
Else Writeln('Stack rong');
End;
Nếu Elements không thể là kiểu kết quả của hàm thì ta sẽ chuyển thành thủ tục như
sau :
Procedure Top (S : Stack; Var x : ElementType);
Begin
If not EmptyS (S) then
x:=S.Elements[S.Top]
Else Writeln('Stack rong');
End;
• Thủ tục xóa một phần tử khỏi Stack :
Procedure Pop ( Var S: Stack);
Begin
If not FullS (S) then
S.Top:=S.Top+1
Else Writeln('Stack rong');
End;
• Thủ tục thêm một phần tử vào Stack :
Procedure Push (x:ElementsType; Var S:Stack);
Begin
If not FullS (S) then
Begin
S.Top:=S.Top -1;
S.Elements[S.Top]:=x;
End
Else Writeln('Stack day');
End;

×