Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

1
LỜI NÓI ĐẦU

Để đáp ứng nhu cầu học tập của các bạn sinh viên, nhất là sinh viên
chuyên ngành công nghệ thông tin, chuyên ngành tin học kinh tế, chuyên
ngành Điện tử Viễn thông các tác giả Bộ môn Công nghệ phần mềm - Khoa
Công Nghệ Thông Tin - Trường Đại học Thái nguyên chúng tôi đã tiến
hành biên soạn tập các bài giảng chính trong chương trình học đào tạo theo
tín chỉ. Bài gi ảng môn Cấu Trúc Dữ Liệu và Thuật toán này được biên
soạn cơ bản dựa trên quyển: “Cấu trúc dữ liệu & thuật toán” của tác giả
Đinh Mạnh Tường, Nhà xuất bản Khoa học và Kỹ thuật. “Cấu trúc dữ liệu và
giải thuật”, của tác giả Đỗ Xuân Lôi, “ Cấu trúc dữ liệu + giải thuật= Chương
trình” của N. Wirth. Giáo trình này cũng được biên soạn dựa trên kinh
nghiệm giảng dạy nhiều năm môn Cấu Trúc Dữ Liệu và Giải Thuật của chúng
tôi.
Tài liệu này được soạn theo đề cương chi tiết môn Cấu Trúc Dữ Liệu
v à t huật t oán của sinh viên chuyên ngành Công nghệ thông tin của
Khoa Công Nghệ Thông Tin - Trường Đại Học Thái nguyên. Mục tiêu của nó
nhằm giúp các bạn sinh viên chuyên ngành có một tài liệu cô đọng dùng làm
tài liệu học tập, nhưng chúng tôi cũng không loại trừ toàn bộ các đối tượng
khác tham khảo. Chúng tôi nghĩ rằng các bạn sinh viên không chuyên tin và
những người quan tâm tới cấu trúc dữ liệu và giải thuật sẽ tìm được trong
này những điều hữu ích.
Mặc dù đã rất cố gắng nhiều trong quá trình biên soạn giáo trình nhưng
chắc chắn giáo trình sẽ còn nhiều thiếu sót và hạn chế. Rất mong nhận
được sự đóng góp ý kiến quý báu của sinh viên và các bạn đọc để tập bài
giảng ngày một hoàn thiện hơn và có thể trở thành một giáo trình thực sự
hữu ích.

Thái nguyên, 10 / 2009
Các tác giả

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
2
MỤC LỤC
PHẦN TỔNG QUAN .......................................................................................... 5
Chƣơng 1 MỞ ĐẦU ........................................................................................... 7
1. 1 Từ bài toán đến chƣơng trình ...................................................................................... 7
1.2 Các khái niệm cơ bản .................................................................................................. 11
1.2.1 Mô hình dữ liệu ( Data model ) ........................................................................... 11
1.2.2. Khái niệm trừu tƣợng hóa ................................................................................... 13
1.2.3 Kiểu dữ liệu trừu tƣợng ....................................................................................... 14
1.2.4 Dữ liệu .................................................................................................................... 14
1.2.5 Biểu diễn dữ liệu trên máy tính ........................................................................... 14
1.2.6 Kiểu dữ liệu ........................................................................................................... 14
1.2.7 Cấu trúc dữ liệu (Data Structures) ..................................................................... 16
1.2.8 Giải thuật ............................................................................................................... 18
1.2.6 Mối quan hệ giữa cấu trúc dữ liệu và giải thuật ................................................ 18
1.3. Phân tích giải thuật ................................................................................................... 19
1.3.1 Sự cần thiết phải phân tích giải thuật...................................................................... 19
1.3.2 Thời gian thực hiện của giải thuật .......................................................................... 20
1.3.3 Không gian của giải thuật .................................................................................... 23
1.4 Ngôn ngữ diễn đạt giải thuật ...................................................................................... 23
1.5. Đệ quy và giải thuật đệ ............................................................................................... 24
1.5.1 Khái niệm đệ quy .................................................................................................. 24
1.5.2 Giải thuật đệ quy và thủ tục đệ quy .................................................................... 24
Chƣơng 2 MÔ HÌNH DỮ LIỆU DANH SÁCH ........................................... 26
2.1 Danh sách (List) ........................................................................................................... 26
21.1 Khái niệm danh sách ............................................................................................. 26
2.1. 2. Các phép toán cơ bản trên danh sách ............................................................... 27
2.1.3 Biểu diễn (cài đặt) danh sách trên máy tính ...................................................... 28
2.1.3.1 Danh sách cài đặt bằng mảng ......................................................................... 28
2.1.3.2 Danh sách cài đặt bởi con trỏ ......................................................................... 33
2.2 Ngăn xếp (Stack) và ứng dụng..................................................................................... 41
2.2.1. Định nghĩa ngăn xếp ............................................................................................ 41
2.2.2 Các phép toán cơ bản trên ngăn xếp: .................................................................. 42
2.2.3. Cài đặt ngăn xếp ................................................................................................... 42

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
3
2.2.4. Ứng dụng ngăn xếp .............................................................................................. 43
2.3. Hàng đợi (QUEUE) ................................................................................................ 44
2.3.1. Định nghĩa hàng đợi ............................................................................................ 44
2.3.2 Các phép toán cơ bản trên hàng .......................................................................... 44
2.3.3 Cài đặt hàng đợi .................................................................................................... 45
2.3.3.1 Cài đặt hàng bằng mảng .................................................................................. 45
2.3.3.2. Cài đặt hàng bằng danh sách liên kết (cài đặt bằng con trỏ) ........................ 48
2.3.4. Một số ứng dụng của cấu trúc hàng .................................................................... 49
Chƣơng 3 Mô hình dữ liệu Cây ...................................................................... 50
3.1 Cây tổng quát ............................................................................................................... 50
3.1.1 Định nghĩa cây và các khái niệm cơ bản trên cây .............................................. 50
3.1.2 Các phép toán cơ bản trên cây ............................................................................ 51
3.1.3 Các cách thăm ( duyệt) cây .................................................................................. 51
3.1.4. Cài đặt cây ............................................................................................................ 55
3.1.4.1 Biểu diễn cây bằng danh sách các con của mỗi đỉnh .................................... 55
3.1.4.2 Biểu diễn cây bằng con trưởng và em liền kề của mỗi đỉnh .......................... 58
3.1.4.3 Biểu diễn cây bởi cha của mỗi đỉnh ................................................................ 60
3.2 Cây nhị phân (binary tree) ......................................................................................... 62
3.2.1 Định nghĩa ............................................................................................................. 62
3.2.2. Duyệt cây nhị phân .............................................................................................. 63
3.2.3 Cài đặt cây nhị phân ............................................................................................. 65
3.2.4 Các phép toán cơ bản trên cây nhị phân ............................................................ 65
3.3 Cây tìm kiếm nhị phân (binary search tree) ............................................................. 68
3.3.1 Định nghĩa cây TKNP .......................................................................................... 68
3.3.2 Cài đặt cây tìm kiếm nhị phân ............................................................................ 69
3.3.3 Các phép toán cơ bản trên cây tìm kiếm nhị phân ............................................ 69
CHƢƠNG 4 Mô hình dữ liệu Đồ thị (Graph) .......................................... 75
4.1 Định nghĩa đồ thị và các khái niệm ............................................................................ 75
4.2 Các phép toán cơ bản trên đồ thị ................................................................................ 76
4.3 Biểu diễn đồ thị ............................................................................................................ 77
4.3.1. Biểu diễn đồ thị bằng ma trận kề ........................................................................ 77
4.3.2. Biểu diễn đồ thị bằng danh sách các đỉnh kề ..................................................... 77
4.4 Các phép duyệt đồ thị (TRAVERSALS OF GRAPH) .......................................... 78
4.4.1. Duyệt theo chiều sâu (depth-first search) ........................................................... 78
4.4.2. Duyệt theo chiều rộng (breadth-first search) ..................................................... 78

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
4
4.4. Một số bài toán ứng dụng trên đồ thị: .................................................................. 78
Chƣơng 5 Mô hình dữ liệu Tập hợp ............................................................. 79
5.1. Khái niệm tập hợp....................................................................................................... 79
5. 2 Mô hình dữ liệu tập hợp ............................................................................................. 80
5.3 Cài đặt tập hợp và cài đặt các phép toán trên tập hợp ........................................... 80
5.3.1.Cài đặt tập hợp bởi vectơ bit ............................................................................... 81
5.3.2 Cài đặt tập hợp bởi mảng .................................................................................... 82
5.3.3 Cài đặt bởi danh sách liên kết hoặc danh sách đƣợc sắp .................................. 82
5.5 Từ điển (Dictionary) ................................................................................................... 85
5.5.1 Từ điển là gì? ......................................................................................................... 85
5.5.2 Các phƣơng pháp cài đặt từ điển ........................................................................ 85
5.5.3 Cấu trúc dữ liệu Bảng băm, cài đặt từ điển bởi bảng băm ............................... 85
5.5.3.1. Cài đặt từ điển bằng bảng băm mở .............................................................. 86
5.5.3.2. Cài đặt từ điển bằng bảng băm đóng ........................................................... 90
5.5.3.3 Các phương pháp xác định hàm băm ............................................................. 93


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
5
PHẦN TỔNG QUAN
1. Mục đích yêu cầu
Môn học cấu trúc dữ liệu cung cấp cho sinh viên một khối lượng lớn các kiến
thức cơ bản về các cấu trúc dữ liệu và các phép toán trên từng cấu trúc đó.
Sau khi học xong môn này, sinh viên cần phải:

- Nắm vững khái niệm kiểu dữ liệu, kiểu dữ liệu trừu tượng, mô hình dữ
liệu, giải thuật và cấu trúc dữ liệu

- Nắm vững và cài đặt được các mô hình dữ liệu, kiểu dữ liệu trừu
tượng cơ bản như danh sách, ngăn xếp, hàng đợi, cây, tập hợp, bảng
băm, đồ thị bằng một ngôn ngữ lập trình căn bản.

- Vận dụng được các kiểu dữ liệu trừu tượng, các mô hình dữ liệu để
giải quyết bài toán đơn giản trong thực tế.

2. Đối tƣợng sử dụng

Môn học cấu trúc dữ liệu được dùng để giảng dạy cho các sinh viên sau:

- Sinh viên chuyên ngành công nghệ thông tin (môn bắt buộc )

- Sinh viên chuyên ngành tin học kinh tế (môn bắt buộc)

- Sinh viên chuyên ngành Điện tử - Viễn thông và tự động hóa (môn bắt
buộc)

3. Nội dung cốt lõi
Nội dung giáo trình gồm 5 chương và đuợc trình bày trong 45 tiết cho
sinh viên, trong đó có khoảng 30 tiết lý thuyết và 15 tiết bài tập mà giáo viên sẽ
hướng dẫn cho sinh viên trên lớp. Bên cạnh tài liệu này còn có tài liệu thực
hành cấu trúc dữ liệu, do vậy nội dung giáo trình chú trọng về các cấu trúc dữ
liệu và các giải thuật trên các cấu trúc dữ liệu đó.
Chƣơng 1:
Trình bày cách tiếp cận từ một bài toán đến chương trình, nó bao gồm
mô hình hoá bài toán, thiết lập cấu trúc dữ liệu theo mô hình bài toán, viết
giải thuật giải quyết bài toán và các bước tinh chế giải thuật đưa đến cài đặt
cụ thể trong một ngôn ngữ lập trình
Chƣơng 2:
Trình bày mô hình dữ liệu danh sách, các cấu trúc dữ liệu để cài đặt
danh sách, chúng tôi tập trung trình bày cấu trúc danh sách liên kết đơn, và cấu
trúc danh sách liên kết kép cho những bài toán cần duyệt danh sách theo 2 chiều
xuôi, ngược một cách thuận lợi. Ngăn xếp và hàng đợi cũng được trình bày
trong chương này như là hai cấu trúc danh sách đăc biệt. Chương này có
nhiều cài đặt tương đối chi tiết để các bạn sinh viên mới tiếp cận với lập
trình có cơ hội nâng cao khả năng lập trình trong ngôn ngữ C đồng thời cũng
nhằm minh hoạ việc cài đặt một kiểu dữ liệu trừu tượng trong một ngôn ngữ
lập trình cụ thể.

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
6
Chƣơng 3:
Chương này giới thiệu về kiểu dữ liệu trừu tượng cây, khái niệm cây
tổng quát, các phép duyệt cây tổng quát và cài đặt cây tổng quát. Kế đến chúng
tôi trình bày về cây nhị phân, các cách cài đặt cây nhị phân. Cuối cùng, chúng
tôi trình bày cây tìm kiếm nhị phân như là một cấu trúc dùng để lưu trữ và tìm
kiếm dữ liệu hiệu quả.
Chƣơng 4:
Trình bày mô hình dữ liệu đồ thị, các cách biểu diễn đồ thị hay là cài đặt
đồ thị. Ở đây chúng tôi cũng trình bày các phép duyệt đồ thị bao gồm
duyệt theo chiều rộng và duyệt theo chiều sâu một đồ thị và đề cập một số
bài toán thường gặp trên đồ thị như là bài toán tìm đường đi ngắn
nhất, bài toán tìm cây khung tối thiểu.… . Do hạn chế về thời lượng lên
lớp nên chương này chúng tôi chỉ giới thiệu để sinh viên tham khảo thêm về
cách cài đặt đồ thị và các bài toán trên đồ thị.
Chƣơng 5:
. Do hạn chế về thời lượng lên lớp nên chúng tôi Chương này Chương
này dành để nói về mô hì nh dữ liệu t ập hợp, các cách đơn giản để cài đặt
tập hợp như cài đặt bằng vectơ bít hay bằng danh sách... Phần chính của
chương này trình bày kiểu dữ liệu trừu tượng tự điển, đó là tập hợp với ba phép
toán thêm, xoá và tìm kiếm phần tử, cùng với các cấu trúc lưu trữ thích hợp
cho nó là bảng băm.

4. Kiến thức tiên quyết

Để học tốt môn học cấu trúc dữ liệu này, sinh viên cần phải có các kiến thức cơ
bản sau:
- Kiến thức và kỹ năng lập trình căn bản.

- Kiến thức toán rời rạc.

5. Danh mục tài liệu tham khảo

[1] Aho, A. V. , J. E. Hopcroft, J. D. Ullman. "Data Structure and
Algorihtms", Addison– Wesley; 1983
[2] Đỗ Xuân Lôi . "Cấu trúc dữ liệu và giải thuật". Nhà xuất bản khoa học và
kỹ thuật. Hà nội, 1995.
[3] Đinh Mạnh Tƣờng, Cấu trúc dữ liệu & thuật toán, Nhà xuất bản Khoa
học và Kỹ thuật, 2003.
[4] Michel T. Goodrich, Roberto Tamassia, David Mount, “Data
Structures and Algorithms in C++”. Weley International Edition; 2004.
[5] N. Wirth " Cấu trúc dữ liệu + giải thuật= Chương trình", 1983.
[6] Nguyễn Trung Trực, "Cấu trúc dữ liệu". BK tp HCM, 1990.
[7] Lê Minh Trung; “Lập trình nâng cao bằng Pascal với các cấu trúc dữ
liệu”; 1997

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
7
Chƣơng 1 MỞ ĐẦU
Tổng quan:
1. Mục tiêu

Sau khi học xong chương này, sinh viên sẽ:

Nắm được các bước trong lập trình để giải quyết cho một bài toán.

Nắm vững khái niệm cơ bản liên quan đến cấu trúc dữ liệu và giải thuật như: Mô
hình dữ liệu, kiểu dữ liệu trừu tượng, kiểu dữ liệu, giải thuật, ....
Nắm vững các tiêu chuẩn để lựa chọn cấu trúc dữ liệu tốt cho bài toán


2. Kiến thức cơ bản cần thiết

Các kiến thức cơ bản cần thiết để học chương này bao gồm:

Khả năng nhận biết và giải quyết bài toán theo hướng tin học hóa.


3. Nội dung chính

Chương này chúng ta sẽ nghiên cứu các vấn đề sau:

- Cách tiếp cận từ bài toán đến chương trình

- Các khái niệm cơ bản liên quan đến cấu trúc dữ liệu và thuật toán
- Các tiêu chuẩn để lựa chọn cấu trúc dữ liệu tốt cho bài toán
- Kỹ thuật đệ quy trong cách giải bài toán
- Ngôn ngữ diễn đạt giải thuật
1. 1 Từ bài toán đến chƣơng trình
Để giải một bài toán trong thực tế bằng máy tính ta thường trải qua 2 giai đoạn:
Giai đoạn 1: Xác định bài toán cần giải quyết và xây dựng mô hình toán học cho
bài toán cần giải quyết. Mục đích trả lời hai câu hỏi: Bài toán cho cái gì và yêu cầu
làm những gì? sau đó trả lời câu hỏi “để thực hiện các yêu cầu của bài toán thì làm
như thế nào”
Giai đoạn 2: Cài đặt chương trình: Sử dụng ngôn ngữ lập trình để xây dựng
chương trình tương ứng với cách làm của giai đoạn trước nó
1.1.2 Xác định & mô hình hóa bài toán cần giải quyết
Khi giải quyết 1 bài toán thực tế, ta phải bắt đầu từ việc xác định bài toán. Nhiều
thời gian và công sức bỏ ra để xác định bài toán cần giải quyết, tức là phải trả lời rõ
ràng câu hỏi "phải làm gì?" sau đó là "làm như thế nào?". Thông thường, khi khởi
đầu, hầu hết các bài toán là không đơn giản, không rõ ràng. Để giảm bớt sự phức
tạp của bài toán thực tế, ta phải hình thức hóa nó, nghĩa là phát biểu lại bài toán
thực tế thành một bài toán hình thức (hay còn gọi là mô hình toán). Có thể có rất
nhiều bài toán thực tế có cùng một mô hình toán.
Ví dụ 1: Tô màu bản đồ thế giới.
Ta cần phải tô màu cho các nước trên bản đồ thế giới. Trong đó mỗi nước đều

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
8
được tô một màu và hai nước láng giềng (cùng biên giới) thì phải được tô bằng hai
màu khác nhau. Hãy tìm một phương án tô màu sao cho số màu sử dụng là ít nhất.
Ta có thể xem mỗi nước trên bản đồ thế giới là một đỉnh của đồ thị, hai nước
láng giềng của nhau thì hai đỉnh ứng với nó được nối với nhau bằng một cạnh.
Bài toán lúc này trở thành bài toán tô màu cho đồ thị như sau: Mỗi đỉnh đều phải
được tô màu, hai đỉnh có cạnh nối thì phải tô bằng hai màu khác nhau và ta cần tìm
một phương án tô màu sao cho số màu được sử dụng là ít nhất. Mô hình toán học
được sử dụng trong bài toán này là mô hình đồ thị.
Ví dụ 2: Đèn giao thông
Cho một ngã năm như hình 1.1, trong đó C và E là các đường một chiều theo
chiều mũi tên, các đường khác là hai chiều. Hãy thiết kế một bảng đèn hiệu điều
khiển giao thông tại ngã năm này một cách hợp lý, nghĩa là: phân chia các lối đi
tại ngã năm này thành các nhóm, mỗi nhóm gồm các lối đi có thể cùng đi đồng
thời nhưng không xảy ra tai nạn giao thông (các lối đi này có các hướng đi không cắt
nhau), và số lượng nhóm chia là ít nhất có thể được.












Ta có thể xem đầu vào của bài toán là tất cả các lối đi tại ngã năm này, đầu ra của
bài toán là các nhóm lối đi có thể đi đồng thời mà không xảy ra tai nạn giao thông,
mỗi nhóm sẽ tương ứng với một pha điều khiển của đèn hiệu, vì vậy ta phải tìm kiếm
lời giải với số nhóm là ít nhất để giao thông không bị tắc nghẽn vì phải chờ đợi quá lâu.
Trước hết ta nhận thấy rằng tại ngã năm này có 13 lối đi: AB, AC, AD, BA,
BC, BD, DA, DB, DC, EA, EB, EC, ED. Tất nhiên, để có thể giải được bài toán ta
phải tìm một cách nào đó để thể hiện mối liên quan giữa các lối đi này. Lối nào với
lối nào không thể đi đồng thời, lối nào và lối nào có thể đi đồng thời. Ví dụ cặp AB và
EC có thể đi đồng thời, nhưng AD và EB thì không, vì các hướng giao thông cắt
nhau. Ở đây ta sẽ dùng một sơ đồ trực quan như sau: tên của 13 lối đi được viết lên
mặt phẳng, hai lối đi nào nếu đi đồng thời sẽ xảy ra đụng nhau (tức là hai hướng đi
cắt qua nhau) ta nối lại bằng một đoạn thẳng, hoặc cong, hoặc ngoằn ngoèo tuỳ thích.
Ta sẽ có một sơ đồ như hình 1.2. Như vậy, trên sơ đồ này, hai lối đi có cạnh nối lại với
nhau là hai lối đi không thể cho đi đồng thời.
A
B
C
D
E
Hình 1.1

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
9















Với cách biểu diễn như vậy ta đã có một mô hình toán học đồ thị (Graph), trong
đó mỗi lối đi trở thành một đỉnh của đồ thị, hai lối đi không thể cùng đi đồng thời
được nối nhau bằng một đoạn ta gọi là cạnh của đồ thị. Bây giờ ta phải xác định các
nhóm, với số nhóm ít nhất, mỗi nhóm gồm các lối đi có thể đi đồng thời, nó ứng
với một pha của đèn hiệu điều khiển giao thông. Giả sử rằng, ta dùng màu để tô
lên các đỉnh của đồ thị này sao cho:
- Các lối đi cho phép cùng đi đồng thời sẽ có cùng một màu: Dễ dàng nhận
thấy rằng hai đỉnh có cạnh nối nhau sẽ không được tô cùng màu.
- Số nhóm là ít nhất: ta phải tính toán sao cho số màu được dùng là
ít nhất. Tóm lại, ta phải giải quyết bài toán sau:
"Tô màu cho đồ thị ở hình 1.2 sao cho:
- Hai đỉnh có cạnh nối với nhau (hai còn gọi là hai đỉnh kề nhau) không cùng màu.
- Số màu được dùng là ít nhất."
Nhƣ vậy:
Cả hai bài toán trên, ban đầu có vẻ rất khác nhau, nhưng sau khi phân tích để hình thức
hóa thì chúng đều được đưa về mô hình toán học đồ thị, và áp dụng thuật toán tô mầu
trên đồ thị để giải quyết các bài toán này.
Chú ý: Có rất nhiều các cấu trúc toán học có thể làm mô hình dữ liệu trong tin học, ví
dụ dãy, tập hợp, ánh xạ, cây, đồ thị, ..
- Một bài toán thực tế bất kỳ thường bao gồm các đối tượng dữ liệu và các yêu cầu xử
lý trên những đối tượng đó, cho nên trong giai đoạn phân tích và thiết kế, khi xây dựng
mô hình toán học cho bài toán cần chú trọng đến hai vấn đề :
+ Tổ chức biểu diễn các đối tượng dữ liệu của bài toán trong mô hình toán học
như thế nào? mô hình này ta còn gọi là mô hình dữ liệu trong tin học
AB AC AD
BA BC
BD
DA DB DC
EA EB EC ED
Hình 1.2

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
10
+ Xây dựng các thao tác xử lý trên các đối tượng của mô hình ra sao
1.1.2 Cài đặt chƣơng trình cho bài toán cần giải quyết
Khi cài đặt chương trình giải quyết bài toán tương ứng ta quan tâm đến hai vấn đề:
(1) Biểu diễn mô hình dữ liệu của bài toán trên máy tính như thế nào để máy
tính có thể hiểu và thực hiên các thao tác trên chúng. Giai đoạn này còn được gọi là
xây dựng cấu trúc dữ liệu cho bài toán. Ta có thể cài đặt một mô hình dữ liệu bởi
nhiều cấu trúc dữ liệu khác nhau. Trong mỗi cách cài đặt, một số phép toán trên mô
hình có thể được thực hiện thuận lơi, nhưng các phép toán khác có thể lại không thuận
lợi
(2) Mã hóa các giải thuật xử lý trên các cấu trúc dữ liệu tương ứng để giải quyết
các yêu cầu đặt ra của bài toán
Ta có thể sử dụng một ngôn ngữ lập trình cụ thể nào đó (Pascal,C,...) để cài đặt kết
quả ở giai đoạn phân tích và thiết kế chương trình, ở bước này ta dùng các cấu trúc dữ
liệu được cung cấp trong ngôn ngữ, ví dụ Array, Record,... để biểu diễn mô hì nh
dữ l i ệu của bài t oán, và mã hóa giải thuật bởi các câu lệnh trong ngôn ngữ lập
trình lựa chọn
Như vậy, ta có thể tóm tắt các bước từ bài toán đến chương trình như sau:
1) Về mặt dữ liệu: Mô hình dữ liệu -> Kiểu dữ liệu trừu tượng -> Cấu trúc dữ liệu.
Thật vậy: Trong quá trình phát triển chương trình, nhất là khi phát triển các hệ thống
phần mềm lớn, ta cần đến hai dạng biểu diễn dữ liệu: Biếu diễn trừu tượng và biểu
diễn cụ thể
a) Trong giai đoạn xác định va mô hình hóa bài toán: ta cần sử dụng dạng
biểu diễn trừu tượng: được xác định bởi mô hình dữ liệu – đó là mô hình
toán học của các đối tượng dữ liệu cùng với các phép toán thực hiện trên
các đối tượng đó, ví dụ như: Mô hình cây, danh sách, tập hợp, đồ thị, mô
hình ERA, …… > Khi ta dùng mô hình dữ liệu với một số xác định các
phép toán nào đó, ta sẽ có một kiểu dữ liệu trừu tượng , ví dụ: Ngăn xếp,
hàng đợi, bảng băm, ….
=> Dạng biểu diễn dữ liệu này không phụ thuộc vào ngôn ngữ lập trình cụ thể
b) Trong giai đoạn cài đặt chƣơng trình, ta cần sử dụng dạng biểu diễn cụ
thể của dữ liệu: Là biểu diễn xác định cách lưu trữ vật lý của dữ liệu trong
bộ nhớ máy tính. Biểu diễn cụ thể của dữ liệu được xác định bởi các cấu
trúc dữ liệu. Các cấu trúc dữ liệu được mô tả trong ngôn ngữ lập trình cụ
thể mà ta sử dụng
=> Dạng biểu diễn này phụ thuộc vào ngôn ngữ lập trình cụ thể
Từ biểu diễn trừu tượng, ta có thể chuyển dịch thành các biều diễn cụ thể khác
nhau, hay nói cách khác, từ các mô hình dữ liệu hoặc từ các kiểu dữ liệu trừu tượng, ta
có thể chuyển dịch thành các cấu trúc dữ liệu khác nhau. Ví dụ, ta có thể cài đặt danh
sách bởi cấu trúc dữ liệu mảng hoặc cấu trúc dữ liệu danh sách liên kết. Khi cài đặt mô
hình dữ liệu bởi cấu trúc dữ liệu nào đó, thì các phép tóan trên mô hình được thực hiện
bởi các thao tác cần thiết trên cấu trúc dữ liệu đó.

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
11
2) Về mặt xử lý dữ liệu: Giải thuật không hình thức ->giải thuật bằng ngôn ngữ giả -
>Giải thuật được mã hóa hoàn toàn bởi ngôn ngữ lập trình cụ thể, ví dụ: Pascal, C,...
Thật vây: Từ những yêu cầu xử lý thực tế, ta tìm các giải thuật trên mô hình dữ
l i ệu đã xây dựng. Giải thuật có thể mô tả một cách không hình thức ( tức là nó
chỉ nêu phương hướng giải hoặc các bước giải một cách tổng quát).
Tiếp theo ta hình thức hoá giải thuật bằng ngôn ngữ giả, rồi chi tiết hoá dần
("mịn hoá") các bước giải tổng quát ở trên ( làm mịn dấn) . Ở bước này ta cần dùng các
kiểu dữ liệu trừu tượng (không phải là các khai báo cài đặt trong ngôn ngữ lập trình
cụ thể) và các cấu trúc lệnh điều khiển trong ngôn ngữ lập trình (không chú trọng đến
cú pháp ngôn ngữ) , kêt hợp ngôn ngữ tự nhiên để mô tả giải thuật.
Cuối cùng trong pha cài đặt, ta tiến hành mã hóa hoàn toàn giải thuật được mô
tả bởi ngôn ngữ giả, sử dụng ngôn ngữ lập trình cụ thể, thao tác trên cấu trúc dữ liệu
cụ thể.
1.2 Các khái niệm cơ bản
1.2.1 Mô hình dữ liệu ( Data model )
Mô hình dữ liệu là gì?
Mô hình dữ liệu được sử dụng để mô tả cấu trúc logic của dữ liệu được xử lý
bởi hệ thống. Là mô hình toán học cùng với các phép toán có thể thực hiện trên các
đối tượng của mô hình.
Các thành phần dữ liệu thực tế đa dạng, phong phú và thường chứa đựng những quan
hệ nào đó với nhau, do đó cần phải tổ chức, lựa chọn và xây dựng các mô hình dữ liệu
thích hợp nhất sao cho vừa có thể phản ánh chính xác các dữ liệu thực tế này, vừa có
thể dễ dàng dùng máy tính để xử lý.
Để tìm ra cấu trúc toán học thích hợp với một bài toán đã cho, chúng ta cần
phải phân tích kỹ bài toán để tìm ra câu trả lời cho các câu hỏi sau:
+ Các thông tin quan trọng của bài toán có thể biểu diễn bởi các đối tượng toán
học nào ?
+ Có các mối quan hệ nào giữa các đối tượng ?
+ Các kết quả phải tìm của bài toán có thể biểu diễn bởi các khái niệm toán học
nào ?
Sau khi đã có mô hình toán học mô tả bài toán, một câu hỏi đặt ra là, ta phải
làm việc với mô hình như thế nào để tìm ra lời giải của bài toán?
Chúng ta sẽ thiết kế các thuật toán thông qua các hành động, các phép toán thực
hiện trên các đối tượng của mô hình
Ví dụ:
+ Trong mô hình dữ liệu đồ thị, trong số rất nhiều các phép toán, ta có thể kể ra
một số phép toán sau: Tìm các đỉnh kề của mỗi đỉnh, xác định đường đi ngắn nhất nối
2 đỉnh bất kỳ, tìm các thành phần liên thông, tìm các đỉnh treo, tô mầu đồ thị, …
+ Trong mô hình dữ liệu danh sách ta có những phép toán sau:
- Xác định độ dài danh sách

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
12
- Xen một phần tử mới vào danh sách
- Loại bỏ, sắp xếp …
Ta có thể phân loại các mô hình dữ liệu dựa trên mối quan hệ giữa các phần tử:
1- Mô hình dữ liệu tuyến tính (danh sách): Dùng để biểu diễn các phần tử có
quan hệ 1:1. Các phần tử trong mô hình có quan hệ tuyến tính theo thứ tự
xuất hiện của chúng, tức là, nếu mô hình dữ liệu tuyến tính chứa các phần tử
thì nó phải có phần tử đầu tiên và phần tử cuối cùng, mỗi phần tử có đúng
một phần đứng ngay trước và một phần tử đứng ngay sau. Hình 1.3 biểu
diễn một ví dụ về mô hình dữ liệu tuyến tính.










Hình 1.3 Mô hình dữ liệu danh sách
2- Mô hình dữ liệu phân cấp (mô hình cây): Dùng để biểu diễn các phần tử
có quan hệ 1: n, tức là, mỗi phần tử trong mô hình có nhiều hậu bối, nhưng
chỉ có một tiền bối. Hình 1.4 biểu diễn một ví dụ cụ thể về mô hình này. Nếu
ta di chuyển từ trên xuống dưới trong Hình 1.4 thì mỗi nút có thể trỏ đến
nhiều nút khác, nhưng nếu ta di chuyển từ dưới lên thì mỗi nút (trừ nút ở
gốc) chỉ có quan hệ với 1 nút. Mô hình dữ liệu phân cấp như vậy thường
được gọi là cây, và đây là một loại mô hình dữ liệu quan trọng trong khoa
học máy tính:










Hình 1.4 – Mô hình dữ liệu cây
Phần tử
cuối cùng
Phần
tử đầu
tiên
Phần tử
đứng sau A
A
Phần tử đứng
trước A
A
A chỉ có 1 tiền bối,
có nhiều hậu bối
Các hậu
bối của A
Tiền bối
của A

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
13
3 - Mô hình dữ liệu thứ ba là đồ thị: đây là mô hình dữ liệu phong phú và phức
tạp nhất. Trong đồ thị, các phần tử có mối quan hệ n:m. Tức là, mỗi phần tử có thể có
quan hệ với một hoặc nhiều phần tử khác. Hình 1.5 biểu diễn một ví dụ cụ thể về mô
hình này.






Hình 1.5 – Mô hình dữ liệu đồ thị
4 - Loại mô hình dữ liệu cuối cùng là tập hợp. Trong một tập hợp, các phần tử
không có mối quan hệ trực tiếp với nhau, giữa chúng chỉ có một mối quan hệ là thành
viên của tập hợp, ta không cần quan tâm tới vị trí chính xác của một phần tử nào đó
trong tập hợp. Hình 1.6 biểu diễn một ví dụ cụ thể về mô hình này.











Hình 1.6 – Mô hình tập hợp

Trên đây là bốn loại mô hình dữ liệu mà ta sẽ nghiên cứu. Ta cũng sẽ nghiên cứu
các dạng biểu diễn của các mô hình này bởi các cấu trúc dữ liệu khác nhau trong pha
cài đặt chương trình. Nói chung, hầu hết các cấu trúc dữ liệu đều rơi vào một trong
bốn dạng cơ bản này.
1.2.2. Khái niệm trừu tƣợng hóa
Trong tin học, trừu tượng hóa nghĩa là đơn giản hóa, làm cho nó sáng sủa
hơn và dễ hiểu hơn. Cụ thể trừu tượng hóa là che đi những chi tiết, làm nổi bật cái
tổng thể.



A

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
14
1.2.3 Kiểu dữ liệu trừu tƣợng
Trong Mô hình dữ liệu, chúng ta có thể thực hiện một tập hợp các phép toán rất
đa dạng, phong phú. Song trong nhiều áp dụng, chúng ta chỉ sử dụng mô hình với một
số xác định các phép toán nào đó. Khi đó chúng ta sẽ có một kiểu dữ liệu trừu tượng
Kiểu dữ liệu trừu tượng (abstract data type): là một mô hình dữ liệu được xét
cùng với một số xác định các phép toán nào đó. Ví dụ: Mô hình dữ liệu danh sách,
chỉ xét đến các phép toán thêm vào và lấy ra, ta gọi là kiểu dữ liệu hàng đợi hoặc ngăn
xếp, Mô hình tập hợp, chỉ xét đến các phép toán: Thêm vào, loại bỏ, tìm kiếm ta gọi là
kiểu dữ liệu trừu tượng từ điển, .....
Khi nói đến một kiểu dữ liệu cần phải đề cập đến hai đặc trưng cơ bản sau :
1) Tập các giá trị thuộc kiểu.
2) Tập hợp các phép toán có thể thực hiện được trên các dữ liệu của kiểu.
1.2.4 Dữ liệu
Thực tế dữ liệu tồn tại ở rất nhiều dạng: hình ảnh, âm thanh, …….có rất nhiều dạng
khác nhau. Trong một bài toán, dữ liệu được phân làm ba loại:





1.2.5 Biểu diễn dữ liệu trên máy tính
+ Trong MTĐT, các dữ liệu dù tồn tại ở những hình thức khác nhau (số, văn bản, hình
ảnh, đúng / sai,. …) đều được biểu diễn dưới dạng nhị phân khi đưa vào MT xử lý.
Tức là mỗi dữ liệu được biểu diễn dưới dạng một dãy các số nhị phân 0 hoặc 1.Ví dụ:
Số 10 = 0000 1010
=> Cách biểu diễn này rất không thuận tiện (dài, khó, không gợi nhớ, …) đối với con
người. Việc xuất hiện các ngôn ngữ lập trình bậc cao ( Pascal, C, … gần với ngôn ngữ
tự nhiên) đã giải phóng con người khỏi những khó khăn khi làm việc với cách biểu
diễn dữ liệu nhị phân trong MT
+ Trong các ngôn ngữ lập trình bậc cao: Các kiểu dữ liệu là sự trừu tượng hoá các tính
chất của các đối tượng dữ liệu có cùng bản chất trong thế giới thực (chỉ ra những tính
chất đặc trưng cho các đối tượng thuộc phạm vi bài toán đang xét)
Ví dụ: Ứng với các dữ liệu dạng số, tương ứng ta có các kiểu dữ liệu số nguyên,
số thực, số phức, … trong ngôn ngữ lập trình, ....
+ Như vậy tất cả các dữ liệu mô tả trong ngôn ngữ lập trình bậc cao được máy tính xử
lý đu phải thuộc một kiểu dữ liệu xác định.
1.2.6 Kiểu dữ liệu
Kiểu dữ liệu T được xác định bởi một bộ <V,O> , với :
V : tập các giá trị hợp lệ mà một đối tƣợng kiểu T có thể lƣu trữ
O : tập các thao tác xử lý có thể thi hành trên đối tƣợng kiểu T.

Dữ liệu
Dữ liệu vào: Các đối tượng cần xử lý của bài toán
Kết quả trung gian
Dữ liệu đầu ra: Kết quả xử lý

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
15
Ví du: Giả sử có kiểu dữ liệu mẫu tự = <Vc ,Oc> với
Vc = { a-z,A-Z}
Oc

= { lấy mã ASCII của ký tự, biến đổi ký tự
thường thành ký tự hoa…}
Giả sử có kiểu dữ liệu số nguyên = <Vi ,Oi> với
Vi = { -32768..32767}
Oi

= { +, -, *, /, %}
Nhƣ vậy: muốn sử dụng một kiểu dữ liệu trong cài đặt cần nắm vững các thuộc tính
của kiểu dữ liệu đó. Các thuộc tính của 1 kiểu dữ liệu bao gồm:
- Tên kiểu dữ liệu: Từ khóa thể hiện cho kiểu đó
- Miền giá trị: Một biến có kiểu dữ liệu đó có thể nhận các giá trị trong phạm vi
nào
- Kích thước lưu trữ: Để tối ưu hóa việc sử dụng kiểu dữ liệu phù hợp, tránh hiện
tượng dư thừa bộ nhớ.
- Tập các toán tử tác động lên kiểu dữ liệu: Các phép toán cơ bản mà kiểu dữ liệu
đó cung cấp
Ta thấy rằng, các loại dữ liệu cơ bản thường là các loại dữ liệu đơn giản, không có cấu
trúc. Chúng thường là các giá trị vô hướng như các số nguyên, số thực, các ký tự, các
giá trị logic ... Các loại dữ liệu này, do tính thông dụng và đơn giản của mình, thường
được các ngôn ngữ lập trình (NNLT) cấp cao xây dựng sẵn như một thành phần của
ngôn ngữ để giảm nhẹ công việc cho người lập trình. Chính vì vậy đôi khi người ta
còn gọi chúng là các kiểu dữ liệu định sẵn. Thông thường, các kiểu dữ liệu cơ bản bao
gồm :
Kiểu có thứ tự rời rạc: số nguyên, ký tự, logic , liệt kê, miền con …
Kiểu không rời rạc: số thực
Tùy ngôn ngữ lập trình, các kiểu dữ liệu định nghĩa sẵn có thể khác nhau đôi chút về
các thuộc tính. Ví dụ: Với ngôn ngữ C, các kiểu dữ liệu này chỉ gồm số nguyên, số
thực, ký tự. Và theo quan điểm của C, kiểu ký tự thực chất cũng là kiểu số nguyên về
mặt lưu trữ, chỉ khác về cách sử dụng. Trong khi đó PASCAL định nghĩa tất cả các
kiểu dữ liệu cơ sở đã liệt kê ở trên và phân biệt chúng một cách chặt chẽ.
Các kiểu cơ sở rất đơn giản và không thể hiện rõ sự tổ chức dữ liệu trong một
cấu trúc, thường chỉ được sử dụng làm nền để xây dựng các kiểu dữ liệu phức tạp khác
Tuy nhiên trong nhiều trường hợp, chỉ với các kiểu dữ liệu cơ sở không đủ để
phản ánh tự nhiên và đầy đủ bản chất của sự vật thực tế, dẫn đến nhu cầu phải xây
dựng các kiểu dữ liệu mới dựa trên việc tổ chức, liên kết các thành phần dữ liệu có
kiểu dữ liệu đã được định nghĩa. Những kiểu dữ liệu được xây dựng như thế gọi là
kiểu dữ liệu có cấu trúc. Đa số các ngôn ngữ lập trình đều cài đặt sẵn một số kiểu có
cấu trúc cơ bản như mảng, chuỗi, tập tin, bản ghi...và cung cấp cơ chế cho lập trình
viên tự định nghĩa kiểu dữ liệu mới.
Ví dụ : Để mô tả một đối tượng sinh viên, cần quan tâm đến các thông tin sau:

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
16
- Mã sinh viên: chuỗi ký tự
- Tên sinh viên: chuỗi ký tự
- Ngày sinh: kiểu ngày tháng
- Nơi sinh: chuỗi ký tự
- Điểm thi: số nguyên
Các kiểu dữ liệu cơ sở cho phép mô tả một số thông tin như :
Diemthi: integer;
Các thông tin khác đòi hỏi phải sử dụng các kiểu có cấu trúc như :
Masv: string[15];
Tensv: string[15];
Noisinh: String[15];
Để thể hiện thông tin về ngày tháng năm sinh cần phải xây dựng một kiểu dữ liệu có
cấu trúc bản ghi,
Type Date = record
Ngay: byte;
Thang: byte;
Năm: integer;
End;
Cuối cùng, ta có thể xây dựng kiểu dữ liệu có cấu trúc thể hiện thông tin về một sinh
viên :
Type SinhVien = Record
Masv: String[15];
Tensv: String[15];
Noisinh: String[15];
Diem thi: Integer;
Ngaysinh: Date
End;
Giả sử đã có cấu trúc phù hợp để lưu trữ một sinh viên, nhưng thực tế lại cần
quản lý nhiều sinh viên, lúc đó nảy sinh nhu cầu xây dựng kiểu dữ liệu có cấu trúc
mới, ví dụ danh sách hoặc mảng...
Mục tiêu của việc nghiên cứu cấu trúc dữ liệu chính là tìm những phương cách
thích hợp để tổ chức, liên kết dữ liệu, hình thành các kiểu dữ liệu có cấu trúc từ những
kiểu dữ liệu đã được định nghĩa.
1.2.7 Cấu trúc dữ liệu (Data Structures)
CTDL = { Các dữ liệu thành phần}
Trong đó:
Các dữ liệu thành phần có thể là dữ liệu đơn (sẵn có) hoặc là CTDL đã được
xây dựng, chúng được liên kết với nhau theo một phương pháp liên kết nào đó, từ

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
17
những CTDL này người ta xây dựng các giải thuật tương ứng tác động trên CTDL đó
một cách hiệu quả nhất
Ví dụ: Trong ngôn ngữ lập trình Pascal :
Mảng: Bao gồm một dãy có thứ tự các phần tử có cùng kiểu
Bản ghi: Bao gồm một tập các phần tử dữ liệu khác kiểu, có mối quan hệ với
nhau, ví dụ thông tin về 1 con người gồm họ tên, ngày sinh, chiều cao, cân nặng, ...
….
Một cấu trúc dữ liệu tốt phải thỏa mãn các tiêu chuẩn sau :
1- Phản ánh đúng thực tế :
Đây là tiêu chuẩn quan trọng nhất, quyết định tính đúng đắn của toàn bộ bài
toán. Cần xem xét kỹ lưỡng cũng như dự trù các trạng thái biến đổi của dữ liệu
trong chu trình sống để có thể chọn cấu trúc dữ liệu lưu trữ thể hiện chính xác đối
tượng thực tế.
Ví dụ : Một số tình huống chọn cấu trúc lưu trữ sai :
- Chọn một biến số nguyên integer để lưu trữ tiền thưởng bán hàng (được tính
theo công thức tiền thưởng bán hàng = trị giá hàng * 5%), do vậy sẽ làm tròn
mọi giá trị tiền thưởng gây thiệt hại cho nhân viên bán hàng. Trường hợp này
phải sử dụng biến số thực để phản ánh đúng kết quả của công thức tính thực tế.
- Trong trường trung học, mỗi lớp có thể nhận tối đa 28 học sinh. Lớp hiện có
20 học sinh, mỗi tháng mỗi học sinh đóng học phí 100.000 đ. Chọn một biến số
nguyên byte ( khả năng lưu trữ 0 - 255) để lưu trữ tổng học phí của lớp học
trong tháng là không phù hợp vì giá trị tổng học phí thu được > 255, vượt khỏi
khả năng lưu trữ của biến đã chọn, gây ra tình trạng tràn, dẫn đến sai lệch.
2 - Phù hợp với các thao tác trên đó:
Tiêu chuẩn này giúp tăng tính hiệu quả của giải thuật, cụ thể là việc phát
triển các thuật toán đơn giản, tự nhiên hơn; chương trình đạt hiệu quả cao hơn
về tốc độ xử lý.
Ví dụ : Một tình huống chọn cấu trúc lưu trữ không phù hợp:
Cần xây dựng một chương trình soạn thảo văn bản, các thao tác xử lý
thường xảy ra là chèn, xoá sửa các ký tự trên văn bản. Trong thời gian xử lý văn
bản, nếu chọn cấu trúc lưu trữ văn bản trực tiếp lên tập tin thì sẽ gây khó khăn
khi xây dựng các giải thuật cập nhật văn bản và làm chậm tốc độ xử lý của
chương trình vì phải làm việc trên bộ nhớ ngoài. Trường hợp này nên tìm một
cấu trúc dữ liệu có thể tổ chức ở bộ nhớ trong để lưu trữ văn bản suốt thời gian
soạn thảo.
LƢU Ý :
Đối với mỗi ứng dụng , cần chú ý đến thao tác nào được sử dụng nhiều nhất để
lựa chọn cấu trúc dữ liệu cho thích hợp.



Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
18
3 - Tiết kiệm tài nguyên hệ thống:
Cấu trúc dữ liệu chỉ nên sử dụng tài nguyên hệ thống vừa đủ để đảm nhiệm
được chức năng của nó. Thông thường có 2 loại tài nguyên cần lưu tâm nhất : CPU và
bộ nhớ. Tiêu chuẩn này nên cân nhắc tùy vào tình huống cụ thể khi viết chương trình.
Nếu cần một chương trình có tốc độ xử lý nhanh thì khi chọn cấu trúc dữ liệu yếu tố
tiết kiệm thời gian xử lý phải đặt nặng hơn tiêu chuẩn sử dụng tối ưu bộ nhớ, và ngược
lại.
Ví dụ : Một số tình huống chọn cấu trúc lưu trữ lãng phí:
- Sử dụng biến integer (2 bytes) để lưu trữ một giá trị cho biết tháng hiện hành . Biết
rằng tháng chỉ có thể nhận các giá trị từ 1-12, nên chỉ cần sử dụng kiểu byte là đủ.
- Để lưu trữ danh sách học viên trong một lớp, sử dụng mảng 50 phần tử (giới hạn số
học viên trong lớp tối đa là 50). Nếu số lượng học viên thật sự ít hơn 30, thì gây lãng
phí. Trường hợp này cần có một cấu trúc dữ liệu linh động hơn mảng, ví dụ danh sách
liên kết – ta sẽ đề cập đến trong các tiếp theo.
Nhƣ vậy:
Kiểu dữ liệu phức, hay còn gọi là cấu trúc dữ liệu, là kiểu dữ liệu trong đó các phần
tử của nó có thể phân tách thành các kiểu dữ liệu đơn hoặc kiểu dữ liệu phức khác. Ví
dụ, kiểu dữ liệu phức trong Pascal bao gồm: array và record.
1.2.8 Giải thuật
Khi đã có mô hình thích hợp cho một bài toán ta cần cố gắng tìm cách giải quyết
bài toán trong mô hình đó. Khởi đầu là tìm một giải thuật, đó là một chuỗi hữu
hạn các chỉ thị (instruction) mà mỗi chỉ thị có một ý nghĩa rõ ràng tương ứng với
một thao tác và thực hiện được trong một lượng thời gian hữu hạn.
Knuth (1973) định nghĩa giải thuật là một chuỗi hữu hạn các thao tác để giải một
bài toán nào đó. Các tính chất quan trọng của giải thuật là:
- Hữu hạn (finiteness): giải thuật phải luôn luôn kết thúc sau một số hữu hạn
bước.
- Xác định (definiteness): mỗi bước của giải thuật phải được xác định rõ ràng
và phải được thực hiện chính xác, nhất quán.
- Hiệu quả (effectiveness): các thao tác trong giải thuật phải được thực hiện
trong một lượng thời gian hữu hạn.
Ngoài ra một giải thuật còn phải có đầu vào (input) và đầu ra (output).
Nói tóm lại:
Một giải thuật phải giải quyết xong công việc khi ta cho dữ liệu vào. Có
nhiều cách để thể hiện giải thuật: dùng ngôn ngữ tự nhiên, dùng lưu đồ, dùng ngôn
ngữ giả, dùng các câu lệnh của ngôn ngữ lập trình, ... Và một cách dùng phổ
biến là dùng ngôn ngữ giả, đó là sự kết hợp của ngôn ngữ tự nhiên và các câu lệnh
của ngôn ngữ lập trình.
1.2.6 Mối quan hệ giữa cấu trúc dữ liệu và giải thuật
Trong một chương trình:. 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ại là dữ liệu, chính dữ liệu chứa đựng các thông tin cần thiết để thực

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
19
hiện giải thuật. Để xác định được giải thuật phù hợp cần phải biết nó tác động đến loại
dữ liệu nào (ví dụ để làm nhuyễn các hạt đậu , người ta dùng cách xay chứ không băm
bằng dao, vì đậu sẽ văng ra ngoài) và khi chọn lựa cấu trúc dữ liệu cũng cần phải hiểu
rõ những thao tác nào sẽ tác động đến nó (ví dụ để biểu diễn các điểm số của sinh viên
người ta dùng số thực thay vì chuỗi ký tự vì còn phải thực hiện thao tác tính trung bình
từ những điểm số đó).
Như vậy trong một chương trình máy tính, giải thuật và cấu trúc dữ liệu có mối
quan hệ chặt chẽ với nhau, được thể hiện qua công thức :
Cấu trúc dữ liệu + Giải thuật = Chƣơng trình
Với một cấu trúc dữ liệu đã chọn, sẽ có những giải thuật tương ứng, phù hợp.
Khi cấu trúc dữ liệu thay đổi thường giải thuật cũng phải thay đổi theo để tránh việc xử
lý gượng ép, thiếu tự nhiên trên một cấu trúc không phù hợp. Hơn nữa, một cấu trúc dữ
liệu tốt sẽ giúp giải thuật xử lý trên đó có thể phát huy tác dụng tốt hơn, vừa đáp ứng
nhanh vừa tiết kiệm bộ nhớ, giải thuật cũng dễ hiễu và đơn giản hơn.
* Cấu trúc lƣu trữ ( Storange structures)
+ CTDL được biểu diễn trong bộ nhớ máy tính còn được gọi là Cấu trúc lưu trữ







1.3. Phân tích giải thuật
1.3.1 Sự cần thiết phải phân tích giải thuật
Trong khi giải một bài toán chúng ta có thể có một số giải thuật khác nhau, vấn đề là
cần phải đánh giá các giải thuật đó để lựa chọn một giải thuật tốt (nhất). Thông
thường thì ta sẽ căn cứ vào các tiêu chuẩn sau:
(1) - Giải thuật đúng đắn.
(2) - Giải thuật đơn giản.
(3) - Giải thuật hiệu quả.
Với yêu cầu (1), để kiểm tra tính đúng đắn của giải thuật chúng ta có thể cài đặt giải
thuật đó và cho thực hiện trên máy với một số bộ dữ liệu mẫu rồi lấy kết quả thu
được so sánh với kết quả đã biết. Thực ra thì cách làm này không chắc chắn bởi vì
có thể giải thuật đúng với tất cả các bộ dữ liệu chúng ta đã thử nhưng lại sai với một
bộ dữ liệu nào đó. Vả lại cách làm này chỉ phát hiện ra giải thuật sai chứ chưa
chứng minh được là nó đúng. Tính đúng đắn của giải thuật cần phải được chứng
minh bằng toán học. Tất nhiên điều này không đơn giản và do vậy chúng ta sẽ
không đề cập đến ở đây.
+ CTDL được
lưu trữ ở:
Bộ nhớ trong còn gọi là cấu trúc lưu trữ trong, ví dụ:
Mảng, danh sách kế tiếp, ….,
Bộ nhớ ngoài còn gọi là cấu trúc lưu trữ ngoài, ví
dụ: Tệp tin, bảng,….

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
20
Khi chúng ta viết một chương trình để sử dụng một vài lần thì yêu cầu (2) là
quan trọng nhất. Chúng ta cần một giải thuật dễ viết chương trình để nhanh
chóng có được kết quả, thời gian thực hiện chương trình không được đề cao vì dù
sao thì chương trình đó cũng chỉ sử dụng một vài lần mà thôi.
Khi một chương trình được sử dụng nhiều lần thì thì yêu cầu tiết kiệm thời
gian thực hiện chương trình, tiết kiệm không gian lưu trữ lại rất quan trọng, đặc biệt
đối với những chương trình mà khi thực hiện cần dữ liệu nhập lớn do đó yêu cầu (3)
sẽ được xem xét một cách kĩ càng. Ta gọi nó là hiệu quả của giải thuật. Tính hiệu
quả thể hiện qua hai mặt:
- Thời gian (Chương trình chạy nhanh)
- Không gian (Chương trình bé chiếm ít bộ nhớ)
1.3.2 Thời gian thực hiện của giải thuật
Thời gian thực hiện của giải thuật phụ thuộc vào nhiều yếu tố
- Trước hết phụ thuộc vào độ lớn của dữ liệu đầu vào
- Ngoài ra T còn phụ thuộc vào
o Máy
o Ngôn ngữ
o Kỹ sảo của người lập trình,
o …..
Tuy nhiên các yếu tố này là không đồng đều do vậy không thể dựa vào chúng khi xác
lập thời gian thực hiện giải thuật T(n), với n là kích thước của bài toán
a) Thời gian thực hiện giải thuật là gì? đơn vị của T(n) tính bằng gì ? cách tính?
- Thời gian thực hiện một chương trình là một hàm của kích thước dữ liệu
vào, ký hiệu T(n) trong đó n là kích thước (độ lớn) của dữ liệu vào.
Ví dụ 1: Chương trình tính tổng của n số có thời gian thực hiện là T(n) = c*n trong đó
c là một hằng số.
- T(n) không thể tính bằng đơn vị cụ thể mà tính bằng số lần thực hiện các
lệnh của giải thuật.
 Một câu hỏi đặt ra là: Cách tính này có thể hiện được tính nhanh hay chậm
của giải thuật hay không ?
Câu trả lời: rõ ràng là có, thí dụ :
o GT1 có thời gian thực hiện là tỉ lệ với n
GT2 có thời gian thực hiện là tỉ lệ với n
2

Với n khá lớn thì giải thuật 1 nhanh hơn giải thuật 2
Thời gian mà ta đánh giá như trên gọi là độ phức tạp tính toán của giải thuật:
Ở giải thuật 1 ta nói GT có độ phức tạp tính toán cấp n và kí hiệu T(n) = O(n)
Ở giải thuật 2 thì thời gian T() = O(n
2
) hay O(g(n)), g(n) còn được gọi là cấp độ
phức tạp tính toán

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
21
Hàm g(n) thường chọn là: logn; n; n
2
; n
3
: ta gọi chung là hàm đa thức; g(n) = 2
n
ta gọi
là hàm mũ; các giải thuật có độ phức tạp tính toán là cấp các hàm đa thức thì chấp
nhận được. Ví dụ : T(n) = 60n2 + 9n + 9 = O(n)
* Một số ví dụ đánh giá độ phức tạp tính toán của GT
Ví dụ 1 : Tính trung bình cộng của một dãy gồm n số đọc vào từ bàn phím
1. Nhập vào số n
2. Cho T = 0; d = 1
3. Lặp While d <= n do
Begin
4- Đọc vào số n
5- Cộng thêm vào T
6- Tăng đếm lên 1
End;
7. Tính TBC = T/n;
8. Đưa ra TBC
* Đánh giá giải thuật:
Các lệnh Số lần thực hiện
1 1
2 1
3 n
4 n
5 n
6 n
7 1
8 1
T(n) = 4n + 4
T(n) = 4n + 4 =< 5n với mọi n >= 4
=> T(n) = O(n)
* Một số qui tắc cơ bản xác định cấp độ phức tạp tính toán
a. Qui tắc loại bỏ hằng số
Nếu ta có T(n) = O(C1.g(n)) thì ta cũng có T(n) = O(g(n))
b. Qui tắc lấy max
Nếu ta có T(n) = O(f(n) + g(n)) thì ta cũng có
T(n) = O(max(f(n),g(n)))
c. Qui tắc cộng
T1(n) và T2(n) là thời gian thực hiện của 2 đoạn chương trình P1 và P2 và T1(n) =
O(f(n)); T2(n)= O(g(n)) thì thời gian thực hiện 2 đoạn chương trình nối tiếp nhau là

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
22
T(n) = T1(n) + T2(n)
Hay T(n) = O(max(f(n),g(n)))
d. Qui tắc nhân
Thời gian thực hiện hai đoạn chương trình P1, P2 lồng nhau là : T(n) =
O(f(n).g(n))
Nhận xét:
Từ các qui tắc trên, ta thấy, việc đánh giá độ phức tạp thuật toán ta chỉ cần chú
ý đến các lệnh tích cực còn các lệnh khác có thể bỏ qua. Lệnh tích cực là lệnh
có số lần thực hiện >= số lần thực hiện các lệnh khác
Tóm lại: Qui tắc tổng quát để phân tích một chƣơng trình:
- Thời gian thực hiện của mỗi lệnh gán, READ, WRITE là O(1)
- Thời gian thực hiện của một chuỗi tuần tự các lệnh được xác định bằng qui 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.
- Thời gian thực hiện cấu trúc IF là thời gian lớn nhất thực hiện lệnh sau THEN
hoặc sau ELSE và 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 thời gian thực hiện thân vòng lặp (trên tất
cả các lần lặp). Nếu thời gian thực hiện thân vòng lặp không t hay đổi thì thời
gian thực hiện vòng lặp là tích của số lần lặp với thời gian thực hiện 1 lần thân
vòng lặp.
Ví dụ 1: Tính thời gian thực hiện của thủ tục sắp xếp “nổi bọt”
PROCEDURE Noi_bot(VAR a: ARRAY[1..n] OF integer);
VAR i, j, temp: Integer;
BEGIN
{1} FOR i:=1 TO n-1 DO
{2} FOR j:=n DOWNTO i+1 DO
{3} IF a[j-1]>a[j]THEN BEGIN{hoán vị a[i], a[j]}
{4} temp := a[j-1];
{5} a[j-1] := a[j];
{6} a[j] := temp; END;
END;
* Xác định độ phức tạp của giải thuật trên
Ta thấy toàn bộ chương trình chỉ gồm một lệnh lặp {1}, lồng trong lệnh {1} là lệnh
{2}, lồng trong lệnh {2} là lệnh {3} và lồng trong lệnh {3} là 3 lệnh nối tiếp nhau
{4}, {5} và {6}. Chúng ta sẽ tiến hành tính độ phức tạp theo thứ tự từ trong ra:
- Trước hết, cả ba lệnh gán {4}, {5} và {6} đều tốn O(1) thời gian, việc so sánh a[j-
1] > a[j] cũng tốn O(1) thời gian, do đó lệnh {3} tốn O(1) thời gian.
- Vòng lặp {2} thực hiện (n-i) lần, mỗi lần O(1) do đó vòng lặp {2} tốn O((n-i).1) =

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
23
O(n-i).
- Vòng lặp {1} lặp có i chạy từ 1 đến n-1nên thời gian thực hiện của vòng lặp {1} và
cũng là độ phức tạp của giải thuật là:
T(n) =

(n − i) =
n(n − 1)
= O(n
2
).
i =1
2

Chú ý:
Trong trường hợp vòng lặp không xác định được số lần lặp thì chúng ta phải lấy số
lần lặp trong trường hợp xấu nhất.
Ví dụ 2: Tìm kiếm tuần tự. Hàm tìm kiếm Search nhận vào một mảng a có n số
nguyên và một số nguyên x, hàm sẽ trả về giá trị logic TRUE nếu tồn tại một phần
tử a[i] = x, ngược lại hàm trả về FALSE.
Giải thuật tìm kiếm tuần tự là lần lượt so sánh x với các phần tử của mảng a, bắt đầu
từ a[1], nếu tồn tại a[i] = x thì dừng và trả về TRUE, ngược lại nếu tất cả các phần
tử của a đều khác X thì trả về FALSE.
FUNCTION Search(a:ARRAY[1..n] OF Integer;x:Integer):Boolean; VAR i:Integer;
Found:Boolean;
BEGIN
{1} i:=1;
{2} Found:=FALSE;
{3} WHILE(i<=n)AND (not Found) DO
{4} IF A[i]=X THEN Found:=TRUE
ELSE i:=i+1;
{5} Search:=Found;
END;
* Phân tích xác định độ phức tạp của giải thuật trên
Ta thấy các lệnh {1}, {2}, {3} và {5} nối tiếp nhau, do đó độ phức tạp của hàm
Search chính là độ phức tạp lớn nhất trong 4 lệnh này. Dễ dàng thấy rằng ba lệnh
{1}, {2} và {5} đều có độ phức tạp O(1) do đó độ phức tạp của hàm Search chính là
độ phức tạp của lệnh {3}. Lồng trong lệnh {3} là lệnh {4}. Lệnh {4} có độ phức tạp
O(1). Trong trường hợp xấu nhất (tất cả các phần tử của mảng a đều khác x) thì
vòng lặp {3} thực hiện n lần, vậy ta có T(n) = O(n).
1.3.3 Không gian của giải thuật
Được tính bằng số ô nhớ nguyên thuỷ được dùng trong giải thuật đó (= số biến
đơn)

1.4 Ngôn ngữ diễn đạt giải thuật
- Là công cụ trung gian giúp giao tiếp giữa người và MTĐT.
- Mỗi ngôn ngữ lập trình có một hệ kiểu, trong đó có một số là kiểu dữ liệu đơn hay
nguyên tử, một số là các cấu trúc dữ liệu bao gồm các kiểu đơn.

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
24
- Ngôn ngữ diễn đạt giải thuật bao gồm một tập hợp các câu lệnh tuân theo một cú
pháp nhất định. Thông qua các câu lệnh mà MT có thể hiểu và thực hiện những công
việc mà người dùng muốn MT làm
=> Ta sử dụng công cụ này để diễn đạt giải thuật.
- Trong môn học CTDL ta sử dụng ngôn ngữ lập trình Pascal để minh hoạ.
1.5. Đệ quy và giải thuật đệ
1.5.1 Khái niệm đệ quy
Một đối tượng được gọi là đệ quy nếu nó bao gồm chính nó như một bộ phận
hoặc đối tượng được định nghĩa dưới dạng của chính nó .
Ví dụ : Cho n là số nguyên dương, giai thừa của n được định nghĩa là :
n! = 1 nếu n = 0 hoặc n = 1;
n*(n-1) ! nếu n >1
1.5.2 Giải thuật đệ quy và thủ tục đệ quy
a) Giải thuật đệ quy
Nếu lời giải của bài toán T được thực hiện bởi lời giải của một bài toán T‟, có
dạng như T thì đó là một lời giải đệ quy. Giải thuật chứa lời giải đệ quy được gọi là
giải thuật đệ quy (T‟<T)
Ví dụ: Xét bài toán tìm một từ trong từ điển
Phác thảo giải thuật
Procedure SEARCH( dict, word)
{ dict được gọi là đầu mối để truy nhập được vào từ điển đang xét, word chỉ từ cần tìm
}
1) If (từ điển chỉ còn là một trang )
Then (tìm word trong trang này)
Else
Begin
Mở từ điển vào trang giữa;
Xác định xem nửa nào chứa word ;
If (word nằm ở nửa trước của từ điển )
Then SEARCH(dict1, word)
Else SEARCH(dict2, word)
End;
{dict1, dict2 là 2 đầu mối có thể truy cập được vào đầu trước và đầu sau của từ điển }
2) Return
b) Đặc điểm của thủ tục đệ quy
+ Trong thủ tục đệ quy có lời gọi đến chính nó.
+ Mỗi lần có lời gọi thì kích thước của bài toán đã thu nhỏ hơn trước
{

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
25
+ Có một trường hợp đặc biệt, trường hợp suy biến: Bài toán sẽ được giải
quyết theo một cách khác hẳn và gọi đệ quy cũng kết thúc.
Đệ quy gồm : Đệ quy trực tiếp (thủ tục chứa lời gọi đến chính nó) và đệ quy gián tiếp
(thủ tục chứa lời gọi đến thủ tục khác mà thủ tục này lại chứa lời gọi đến chính nó )
Tóm lại
Mặc dù các thuật ngữ kiểu dữ liệu (hay kiểu - data type), cấu trúc dữ liệu (data
structure), kiểu dữ liệu trừu tượng (abstract data type), mô hình dữ liệu (data model)
nghe có vẻ như nhau, nhưng chúng có ý nghĩa rất khác nhau.
Kiểu dữ liệu: là một tập hợp các giá trị và một tập hợp các phép toán trên các giá
trị đó. Ví dụ kiểu Boolean là một tập hợp có 2 giá trị TRUE, FALSE và các phép
toán trên nó như OR, AND, NOT …. Kiểu Integer là tập hợp các số nguyên có giá
trị từ -32768 đến 32767 cùng các phép toán cộng, trừ, nhân, chia, Div, Mod…
Kiểu dữ liệu có hai loại là kiểu dữ liệu sơ cấp và kiểu dữ liệu có cấu trúc hay
còn gọi là cấu trúc dữ liệu.
Kiểu dữ liệu cơ sở : là kiểu dữ liệu mà giá trị dữ liệu của nó là đơn nhất. Ví
dụ: kiểu Boolean, Integer….
Kiểu dữ liệu có cấu trúc hay còn gọi là cấu trúc dữ liệu: là kiểu dữ liệu mà
giá trị dữ liệu của nó là sự kết hợp của các giá trị khác. Ví dụ: ARRAY là một cấu
trúc dữ liệu.
Một kiểu dữ liệu trừu tƣợng: là một mô hình dữ liệu cùng với một tập hợp các
phép toán điển hình trên nó. Có thể nói kiểu dữ liệu trừu tượng là một kiểu dữ liệu do
chúng ta định nghĩa ở mức khái niệm (conceptual), nó chưa được cài đặt cụ thể bằng
một ngôn ngữ lập trình.
Khi cài đặt một kiểu dữ liệu trừu tượng trên một ngôn gnữ lập trình cụ thể, chúng
ta phải thực hiện hai nhiệm vụ:
1. Biểu diễn kiểu dữ liệu trừu tượng bằng một cấu trúc dữ liệu hoặc một kiểu dữ
liệu trừu tượng khác đã được cài đặt.
2. Viết các chương trình con thực hiện các phép toán trên kiểu dữ liệu trừu
tượng mà ta thường gọi là cài đặt các phép toán.


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
26
Chƣơng 2 MÔ HÌNH DỮ LIỆU DANH SÁCH
Tổng quan:
1. Mục tiêu
Sau khi học xong chương này, sinh viên
- Nắm vững các khái niệm: danh sách, ngăn xếp, hàng đợi.
- Cài đặt các kiểu dữ liệu và các phép toán bằng ngôn ngữ lập trình cụ thể.
- Biết ứng dụng lý thuyết được tìm hiểu để giải các bài toán thực tế.
2. Nội dung chính
Trong chương này chúng ta sẽ nghiên cứu
- Mô hình danh sách (LIST)
- Kiểu dữ liệu trừu tượng ngăn xếp (STACK)
- Kiểu dữ liệu trừu tượng hàng đợi (QUEUE)
2.1 Danh sách (List)
21.1 Khái niệm danh sách
Mô hình toán học của danh sách là một tập hợp hữu hạn biến động các phần
tử thuộc cùng một lớp đối tượng nào đó (có cùng một kiểu dữ liệu).
Ta cũng lưu ý rằng một đối tượng cũng có thể xuất hiện nhiều lần trong một
danh sách. Ta biểu diễn danh sách L như là một chuỗi các phần tử của nó: a1, a2, . . .,
an với n ≥ 0. thì:
+ Nếu n = 0 ta nói danh sách rỗng (empty list).
+ Nếu n > 0 ta gọi a1 là phần tử đầu tiên và an là phần tử cuối cùng của danh
sách.
+ Số phần tử của danh sách ta gọi là độ dài của danh sách.
+ Một tính chất quan trọng của danh sách đó là tính tuyến tính: Các phần tử của
danh sách có thứ tự tuyến tính theo vị trí (position) xuất hiện của các phần tử.
Ta nói ai đứng trước ai+1, với i từ 1 đến n-1; Tương tự ta nói ai là phần tử
đứng sau ai-1, với i từ 2 đến n. Ta cũng nói ai là phần tử tại vị trí thứ i, hay
phần tử thứ i của danh sách.
Ví dụ: Tập hợp họ tên các sinh viên của lớp TINHOC được liệt kê trên giấy như sau:
1. Nguyễn Trung Cang
2. Nguyễn Ngọc Chương
3. Lê Thị Lệ Sương
4. Trịnh Vũ Thành
5. Nguyễn Phú Vĩnh
6. Phạm Quách Què
7. Vũ Xuân Trường
8. Trần Rạo Rực
Là một danh sách. Danh sách này gồm có 8 phần tử, mỗi phần tử có một vị trí trong

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
27
danh sách theo thứ tự xuất hiện của nó.
- Danh sách con : Nếu L = (a
1
, a
2
, . . . , a
n
) là một danh sách thì ta gọi là một danh
sách con của L, một đoạn nào đó các phần tử kế tiếp của L. Danh sách rỗng được xem
là danh sách con của một danh sách bất kỳ.
- Một danh sách con bắt đầu từ phần tử đầu tiên gọi là phần đầu (prefix), một danh
sách kết thúc bởi phần tử cuối cùng gọi là phần cuối (postfix) của danh sách.
2.1. 2. Các phép toán cơ bản trên danh sách
Gọi L là một danh sách đã cho, p là một vị trí (position) trong danh sách, x là một
giá trị nào đó cùng kiểu với kiểu dữ liệu của các phần tử trong danh sách. Các phép
toán cơ bản sau được định nghĩa trên danh sách:
1) Chèn một phần tử vào danh sách
INSERT_LIST(x,p,L): xen phần tử x vào vị trí p trong danh sách L. Tức là nếu
danh sách là a1, a2, . , ap-1, ap ,. . , an thì sau khi xen ta có kết quả a1, a2, . . ., ap-1,
x, ap, . . . , an. Nếu vị trí p không tồn tại trong danh sách thì phép toán không được
xác định.
2) Tìm vị trí của một phần tử trong danh sách
LOCATE(x,L) thực hiện việc xác định vị trí phần tử có nội dung x đầu tiên trong
danh sách L. Locate trả kết quả là vị trí của phần tử x trong danh sách. Nếu x không
có trong danh sách thì vị trí sau phần tử cuối cùng của danh sách được trả
về, tức là ENDLIST(L).
3) Lấy giá trị của phần tử ở vị trí nào đó
RETRIEVE(p,L) lấy giá trị của phần tử ở vị trí p của danh sách L; nếu vị trí p
không có trong danh sách thì kết quả không xác định (có thể thông báo lỗi).
4) Xoá một phần tử ở vị trí nào đó trong danh sách
DELETE_LIST(p,L): xoá phần tử ở vị trí p t r ong danh sách L. Nếu vị trí p
không có trong danh sách thì phép toán không được định nghĩa và danh sách L sẽ
không thay đổi
5) Tìm vị trí của phần tử đứng sau phần tử có vị trí xác đinh
LINK(p,L) cho kết quả là vị trí của phần tử đi sau phần tử p; nếu p là phần tử cuối
cùng trong danh sách L thì LINK(p,L) cho kết quả là ENDLIST(L). Link không
xác định nếu p không phải là vị trí của một phần tử trong danh sách.
6) Tìm vị trí của phần tử đứng trước phần tử có vị trí xác đinh
PREVIOUS(p,L) cho kết quả là vị trí của phần tử đứng trước phần tử có vị trí p
trong danh sách. Nếu p là phần tử đầu tiên trong danh sách thì Previous(p,L) không
xác định. Previous cũng không xác định trong trường hợp p không phải là vị trí của
phần tử nào trong danh sách.
7) Tìm vị trí của phần tử đứng đầu danh sách
FIRST(L) cho kết quả là vị trí của phần tử đầu tiên trong danh sách. Nếu danh
sách rỗng thì ENDLIST(L) được trả về.
8)Kiểm tra tính rỗng của danh sách

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
28
EMPTY_LIST(L) cho kết quả TRUE nếu danh sách rỗng, ngược lại nó cho
giá trị FALSE.
9) Tạo một dách sách rỗng
MAKENULL_LIST(L) khởi tạo một danh sách L rỗng, chưa có dữ liệu.
Ghi chú:
Trong thiết kế các giải thuật sau này chúng ta dùng các phép toán trừu tượng
đã được định nghĩa ở trên đây như là các phép toán nguyên thủy (cơ bản). Thật vậy, từ
các phép toán nguyên thuỷ này ta có thể tự hình thành lên các phép toán phức tạp khác
như: Tạo danh sách chứa dữ liêu, sắp xếp danh sách, duyệt danh sách, tách, gộp, tính
toán, tổng hợp, ….phụ thuộc vào yêu cầu cụ thể của bài toán.

Lƣu ý:
Trên đây là các phép toán trừu tượng do chúng ta định nghĩa, nó chưa được
cài đặt trong các ngôn ngữ lập trình. Do đó để thực hiện được các phép toán đó ta
phải cài đặt chúng thành các chương trình con trong ngôn ngữ lập trình cụ thể. Trong
bài giảng này, với mỗi cấu trúc dữ liệu cài đặt mô hình danh sách ta vẫn giữ đúng
những tham số trong cách cài đặt trên để thống nhất trong cài đặt.
2.1.3 Biểu diễn (cài đặt) danh sách trên máy tính
2.1.3.1 Danh sách cài đặt bằng mảng
Cài đặt danh sách bởi mảng hay còn gọi là cấu trúc dữ liệu danh sách đặc, hoặc
cấu trúc dữ liệu danh sách kế tiếp, gọi tắt là: Danh sách đặc, hoặc danh sách kế tiếp, nó
thuộc loại cấu trúc dữ liệu tĩnh
a) Mô tả cài đặt :
- Giá sử N là số phân tử tối đa trong danh sách: Với cách cài đặt này, dĩ nhiên,
ta phải ước lượng số phần tử tối đa của danh sách để khai báo số phần tử của
mảng cho thích hợp. Dễ thấy rằng số phần tử của mảng phải được khai báo
lớn hơn số phần tử của danh sách. Nói chung là mảng còn thừa một số chỗ
trống, giả sử Item: Là kiểu dữ liệu của các phần tử trong danh sách
- Dùng một mảng để lưu giữ các phần tử của danh (giả sử mảng Elements).
- Count là một biến đếm đếm số lượng phần tử hiện có trong danh sách
Như vậy ta có thể định nghĩa danh sách như một cấu trúc bản ghi gồm 2 trường:
Elements: Chứa các phần tử trong danh sách
Count: Đếm số phần tử hiện có trong danh sách (chiều dài danh sách)
=> Khi đó mảng chứa các phần tư trong danh sách có dạng như sau
1)Muốn thêm 1 phần tử vào đầu hay cuối danh sách ta cần gọi các phép toán nào và gọi các
phép toán đó như thế nào?
2) Viết giải thuật nhập dữ liệu cho danh sách chứa n phần tử
3) Duyệt danh sách để hiển thị/tìm tất cả các phần tử ở vị trí chẵn

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
29
chỉ số mảng 1 2 …. count …. n
Nội dung
phần tử
Phần
tử 1
Phần
tử 2
…… Phần tử cuối
cùng trong
danh sách
(rỗng) (rỗng)
b) Dạng cài đặt
Const N = <maxlist>;
Type List = Record
Elements : Array[1..N] of Item;
Count : 0..N;
End;
Var L : List;
c) Cài đặt các phép toán cơ bản
1- Khởi tạo danh sách rỗng
Danh sách rỗng là một danh sách không chứa bất kỳ một phần tử nào (hay độ dài
danh sách bằng 0). Theo cách khai báo trên, trường count chỉ vị trí của phần tử
cuối cùng trong danh sách và đó cũng độ dài hiện tại của danh sách, vì vậy để khởi
tạo danh sách rỗng ta chỉ việc gán giá trị trường count này bằng 0.
Procedure MakeNullList(Var L: List)
Begin
L. count :=0;
End;
2- Kiểm tra danh sách rỗng
Danh sách rỗng là một danh sách mà độ dài của nó bằng 0.
Function Empty_list(L : List) : boolean;
Begin
Empty_list := L.count = 0;
End;
3- Xen một phần tử vào danh sách
Bài toán:
Cho một danh sách L, một giá trị x (cùng kiểu với kiểu dữ liệu của các phần tử
trong danh sách) và một vị trí p bất kỳ trong danh sách. Hãy chèn x vào vị trí p
trong danh sách L.
Phân tích cách chèn
Khi xen phần tử có nội dung x vào tại vị trí p của danh sách L thì sẽ xuất hiện các
khả năng sau:
- Mảng đầy: mọi phần tử của mảng đều chứa phần tử của danh sách, tức là phần
? Tại sao đối số L của thủ tục lại là tham biến

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
30
tử cuối cùng của danh sách nằm ở vị trí cuối cùng trong mảng. Nói cách khác, độ
dài của danh sách bằng chỉ số tối đa của mảng; Khi đó không còn chỗ cho phần tử
mới, vì vậy việc xen là không thể thực hiện được, chương trình báo lỗi.
- Ngược lại ta tiếp tục xét:
+ Nếu p không hợp lệ (p>count+1 hoặc p<1 ) thì chương trình con báo lỗi; (
vì: Vị trí xen p<1 thì khi đó p không phải là một vị trí phần tử trong trong
danh sách đặc. Nếu vị trí p>L.count+1 thì khi xen sẽ làm cho danh sách L
không còn là một danh sách đặc nữa.
+ Nếu vị trí p hợp lệ thì ta tiến hành xen theo các bước sau:
* Dời các phần tử từ vị trí p đến cuối danh sách ra sau 1 vị trí.
* Độ dài danh sách tăng 1.
* Đưa phần tử mới vào vị trí p
Giải thuật chèn:
Procedure InsertList(X: Item, P: byte , Var L: List)
Var i: integer;
Begin
if (L.count =MaxLength) then
Writeln(„Danh sach day‟)
else
if ((P<1) or (P>L.count+1)) then
Writeln („Vi tri khong hop le‟)
Else
begin
{Dời các phần tử từ vị trí p đến cuối danh sách
(Count) sang phải 1 vị trí}
For i:=L. count+1 to P+1 do
L.Elements[i]: =L.Elements[i-1];
{Đưa x vào vị trí p}
L.Elements[P]=X;
{Tăng độ dài danh sách lên 1}
L.count:= L.Count +1;
end
End;
4 - Xóa phần tử ra khỏi danh sách
Bài toán:
Cho danh sách L, hãy xoá phần tử ở vị trí p ra khỏi danh sách
Phân tích bài toán
Xoá một phần tử ở vị trí p ra khỏi danh sách L ta làm công việc ngược lại với chèn một

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
31
phần tử: Trước tiên ta kiểm tra vị trí phần tử cần xóa xem có hợp lệ hay chưa?
+ Nếu p>L.count hoặc p<1 thì đây không phải là vị trí của phần tử trong danh sách.
+ Ngược lại, vị trí đã hợp lệ thì ta phải dời các phần tử từ vị trí p+1 đến cuối
danh sách (count) lên trước một vị trí để đè lên phần tử cần xóa và độ dài danh sách
giảm đi 1 phần tử ( do đã xóa bớt 1 phần tử).
Giải thuật xoá
Procedure DeleteList(P: byte, var L: list)
Var i: integer;
Begin
if ((P<1) or (P>L.count)) then
Writeln(„khong hop le‟);
else if (EmptyList(L))then
Writeln(„Danh sach rong!‟);
Else
begin
{Dời các phần tử từ vị trí p+1 đến cuối danh sách
(vị trí count) sang trái 1 vị trí}
For i:= P-1 to L.count -1do
L.Elements[i]: = L.Elements[i+1];
{giảm count đi 1 đơn vị}
L. count := L.count -1;
End;
End;
5 - Định vị một phần tử trong danh sách
Để định vị vị trí phần tử đầu tiên có nội dung x trong danh sách L, ta có thể áp dung
phương pháp tìm kiếm tuần tự như sau:
Ta dò tìm từ đầu danh sách:
o Nếu tìm thấy x thì vị trí của phần tử tìm thấy được trả về,
o Nếu không tìm thấy thì hàm trả về vị trí sau vị trí của phần tử cuối
cùng trong danh sách, tức là ENDLIST(L). Giả sử ta cho ENDLIST(L)
:= L.count+1
Trong trường hợp có nhiều phần tử cùng giá trị x trong danh sách thì vị trí của
phần tử được tìm thấy đầu tiên được trả về.
Giải thuật
Procedure Locate(X:Item, L:list) :integer;
Var p: integer; found: boolean;
Bgein
Found = false;

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
32
P = First(L); //vị trí phần tử đầu tiên
/*trong khi chưa tìm thấy và chưa kết thúc danh sách thì xét
phần tử kế tiếp*/
while ((P < L.count+1) and (not Found) do
if (Retrieve(P,L) == X) then Found := true else P =
Link(P, L);
locate := P;
End;
Lưu ý : Các phép toán sau phải thiết kế trước Locate
- First(L):=1
- Retrieve(P,L):= L.Elements[p]
- Link(P,L):=P+1
6- Các phép toán khác cũng dễ dàng cài đặt nên xem nhƣ bài tập
Bài tập về nhà:
Vận dụng các phép toán trên danh sách đặc để viết chương trình nhập vào một
danh sách các số nguyên và hiển thị danh sách vừa nhập ra màn hình. Thêm phần tử
có nội dung x vào danh sách tại ví trí p (trong đó x và p được nhập từ bàn phím).
Xóa phần tử đầu tiên có nội dung x (nhập từ bàn phím) ra khỏi danh sách.
Gợi ý:
Giả sử ta đã cài đặt đầy đủ các phép toán cơ bản trên danh sách. Để thực hiện
yêu cầu như trên, ta cần thiết kế thêm một số chương trình con sau :
- Nhập danh sách từ bàn phím (READ_LIST(L)) (Phép toán này chưa có
trong kiểu danh sách)
- Hiển thị danh sách ra màn hình (in danh sách) (PRINT_LIST(L)) (Phép
toán này chưa có trong kiểu danh sách).
Thực ra thì chúng ta chỉ cần sử dụng các phép toán MakeNull_List,
Insert_List, Delete_List, Locate và các chương trình con Read_List, Print_List vừa
nói trên là có thể giải quyết được bài toán. Để đáp ứng yêu cầu đặt ra, ta viết chương
trình chính để nối kết những chương trình con lại với nhau gồm các câu lệnh như
sau:
BEGIN
MakeNullList(L); //Khởi tạo danh sách rỗng
ReadList(L);
Writeln(„Danh sach vua nhap: „);
Print_List(L); // In danh sach len man hinh
Insert_List(X,P,&L);
Writeln(„Danh sach sau khi them phan tu la: „);
PrintList(L);
Writeln(„Noi dung phan tu can xoa: „);readln(X);

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
33
P=Locate(X,L);
DeleteList(P,L);
Writeln(„Danh sach sau khi xoa‟,X, „là:‟);
PrintList(L);
END.
2.1.3.2 Danh sách cài đặt bởi con trỏ
Danh sách được cài đặt bởi con trỏ ta còn gọi là cấu trúc dữ liệu danh sách liên
kết - gọi tắt là danh sách liên kết, đây thuộc loại cấu trúc dữ liệu động
















Trong cách cài đặt này, ta dùng con trỏ để liên kết các ô nhớ chứa các phần tử của
danh sách. Các hình thức tổ chức liên kết các phần tử trong danh sách có thể là:
+ Liên kết đơn, tương ứng ta có cấu trúc dữ liệu danh sách liên kết đơn – gọi tắt là
danh sách liên kết đơn
+ Liên kết vòng: Tương ứng ta có cấu trúc dữ liệu danh sách liên kết vòng – gọi
tắt là danh sách liên kết vòng
+ Liên kết đôi: Tương ứng ta có cấu trúc dữ liệu danh sách liên kết đôi (liên kết
kép) – gọi tắt là danh sách liên kết kép
+ Đa liên kết: Tương ứng ta có danh sách đa móc nối
Trong bài giảng này ta trung nguyên cứu cấu trúc danh sách liên kết đơn. Các cấu
trúc danh sách liên kết khác dành cho bạn đọc.
a) Danh sách liên kết đơn (single link list)
Mỗi phần tử trong danh sách là một ô nhớ, mỗi ô nhớ là một cấu trúc ít nhất là
hai ngăn, một ngăn chứa dữ liệu của phần tử đó, một ngăn là con trỏ chứa địa













Ta có 1 kiểu lưu trữ khác: Lưu trữ móc nối, các ô nhớ chứa các phần tử trong danh sách
không nhất thiết phải nằm ở những vị trí kế tiếp nhau, và chúng gắn kết với nhau thông
qua cơ chế móc nối - lưu địa chỉ của nhau => danh sách được lưu trữ theo kiểu này gọi
là danh sách móc nối hay danh sách liên kết. Vậy lưu trữ móc nối là như thế nào, ta đi
tìm hiểu nguyên tắc lưu trữ của nó?








Các ô nhớ chứa các phần tử của danh sách
là một vùng liên tục, các vùng nhớ này
được cấp phát ngay khi dịch chương trình
vì nó là các ô nhớ tĩnh.
Nhược điểm chính: Số lượng các phần tử trong danh
sách bị hạn chế vì phụ thuộc vào vùng nhớ trống liên
tục trong bộ nhớ
Ta đã biết, với cấu trúc
danh sách kế tiêp thì:

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
34
chỉ của ô nhớ đứng kế sau trong danh sách, ta có thể hình dung cơ chế này qua ví dụ
sau:
Giả sử 1 nhóm có 4 bạn: Đông, Tây, Nam, Bắc có địa chỉ nhà ở lần lượt là
d,t,n,b. Giả sử: Đông có địa chỉ của Nam, Tây không có địa chỉ của bạn nào, Bắc
giữ địa chỉ của Đông, Nam có địa chỉ của Tây, điều này được mô tả qua Hình 2.1
như sau









Hình 2.1 – Danh sách liên kết đơn chứa 4 phần tử
Như vậy, nếu ta xét thứ tự các phần tử bằng cơ chế lưu địa chỉ này thì ta có một
danh sách: Bắc, Đông, Nam, Tây. Hơn nữa để có thể truy cập đến các phần tử
trong danh sách này thì chỉ cần giữ địa chỉ của Bắc (điah chỉ của ô nhớ chứa
phần tử đầu tiên trong danh sách).
* Mô tả cài đặt
Trong cài đặt, mỗi phần tử trong danh sách được cài đặt như một nút có hai
trường:
+ Trường info chứa giá trị của các phần tử trong danh sách;
+ Trường link là một con trỏ giữ địa chỉ của ô kế tiếp trong danh sách:
- Mỗi nút có dạng như sau:






- Hình ảnh danh sách có dạng như sau:



Nút cuối cùng trong danh sách không có nút đứng sau, lên Trường link của
phần tử cuối trong danh sách, trỏ đến một giá trị đặc biệt là Nil (trỏ tới đất – không
trỏ tới đâu). Cấu trúc danh sách như vậy gọi là danh sách cài đặt bằng con trỏ hay
INFO LINK

Là con trỏ, trỏ đến nút đứng kế sau trong danh sách
(nghĩa là nó chứa địa chỉ của ô nhớ nút đứng sau nó)
chứa giá trị phần tử của nút, giả sử có kiểu dữ liệu là Item
a1 a2 a
n+1
nil
L
a3 a
n
Bắc d
Đông n
Nam

t
Tây nil

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
35
danh sách liên kết đơn hoặc danh sách móc nối đơn hay ngắn gọn gọi là danh sách liên
kết.
+ Để truy nhập vào d/s ta phải truy nhập tuần tự đến vị trí mong muốn, xuất phát từ
phần tử đầu tiên, do đó để quản lý danh sách ta chỉ cần quản lý địa chỉ ô nhớ chứa
phần tử đầu tiên của danh sách, tức là cần một con trỏ trỏ đến phần tử đầu tiên trong
danh sách - giả sử con trỏ L.
+ Danh sách L rỗng khi: L=nil
* Dạng cài đặt
(Nói đến danh sách móc nối (Liên quan đến địa chỉ) ta phải nghĩ ngay đến biến trỏ,
vậy biến trỏ là gì ?
Nói thêm về con trỏ trong Pascal:
- Chiếm 4 byte, là đối tượng dùng để lưu trữ địa chỉ của đối tượng khác
- Khai báo:
Var <Tên biến con trỏ>: ^ <Kiểu con trỏ>;
Ví dụ: Var P: ^ integer ;
( Con trỏ P có kiểu nguyên, Biến động P^)
- Định nghĩa
Type <Tên kiểu con trỏ> = ^ <Kiểu T>;
Ví dụ: Type p = ^ Hs;
- Truy nhập đến đối tương qua con trỏ, sử dụng lệnh
<Tên biến con trỏ>^
- Một số hàm/thủ tục đối với con trỏ:
+ Thủ tục New(p): Xin cấp phát một vùng nhớ có kích thước bằng kích thước
của kiểu tương ứng với kiểu của con trỏ p:
*Nếu cấp phát được thì địa chỉ vùng nhớ đó sẽ được gán cho con trỏ p
*Nếu không cấp phát được thì P = nil
+ Thủ tục Dispose(p): Giải phóng (thu hồi) vùng nhớ do con trỏ p trỏ tới,
thường được sử dụng khi muốn loại bỏ 1 phần tử ra khỏi danh sách
-Với một nút được trỏ bới con trỏ p thì :
+ p^ . infor : Cho giá trị là trường infor của nút
+ p^ . Link: Cho địa chỉ của nút tiếp sau P
Dạng cài đặt của danh sách:
Type PList = ^ Nut;
Nut = Record
Infor:Iem;
Link : Plist;
End;
Var L: Plis

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
36
* Cài đặt các phép toán cơ bản với cấu trúc danh sách liên kết đơn
1- Tạo danh sách rỗng
procedure MakeNullList(L: Plist)
Begin
L:=nil;
End;
2- Kiểm tra một danh sách rỗng
Danh sách rỗng nếu con trỏ trỏ tới phần tử đầu danh sách = nil
Function EmptyList( L: Plist): boolean
Begin
EmptyList:= L=Nil;
End;
3 - Xen một phần tử vào danh sách :
Xen một phần tử có giá trị x vào danh sách L tại vị trí p ta phải cấp phát một ô mới để
lưu trữ phần tử mới này và nối kết lại các con trỏ để đưa ô mới này vào vị trí p. Sơ đồ
nối kết và thứ tự các thao tác (từ 1-> 4) được cho trong Hình 2.2.








Hình 2.2: Thêm một phần tử vào danh sách tại vị trí p
Phác thảo giải thuật thêm
Procedure InsertList( X: Item, P: byte, var L: PList )
Var Temp, M: pList;
dem: byte;
y: Item;
Begin
- Yêu cầu máy tính cấp phát ô nhớ chứa dữ liệu cần thêm: New(Temp) {1};
- Đổ dữ liệu cần thêm vào ô nhớ vừa cấp phát: Temp^.infor:=x; {2}
- Xác định vị trí thêm: ví trí thứ P:
Nếu p=1: thêm vào đầu danh sách: Temp^.link:=L;L:=Temp;
Nếu (p>1) and (p<length(L)) thì:
Di chuyển con trỏ M đến vị trí trước P:
L
A D C

B E
p = 3
x
3
4
Bỏ
Temp
M

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
37
M:=L; Dem:=1;
While Dem<> p-1 do
Begin
m:=m^.link
Dem:= Dem +1;
End;
- Sửa đổi các mối liên kết để gắn kết Temp vào vị trí p trong danh sách L:
Gắn Temp vào sau M:
Temp^.link:=M^.link; {3}
M^.link:= Temp; {4}
End;
Trong đó Length(l) là hàm xác định chiều dài danh sách
4- Xóa phần tử ra khỏi danh sách L









Hình 2.3: Xoá phần tử tại vị trí p
Tương tự như khi xen một phần tử vào danh sách liên kết, muốn xóa một phần
tử khỏi danh sách ta cần:
+ Xác định vị trí của phần tử muốn xóa trong danh sách L, giả sử ví trí thứ p, ta di
chuyển con trỏ M tới vị trí trước p, temp trỏ tới vị trí p
+ Nối kết lại các con trỏ theo thao tác 1 như trong Hình 2.3.
+ Giải phóng vùng nhớ chứa phần tử thứ P


Giải thuật:
Procedure DeleteList(P: integer, Var L: PList )
Var M, Temp: Plist;
Dem: integer;
Begin
If L=nil then write(„Danh sách rỗng!‟)
L
A D C

B E
p = 3
M
1

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
38
Else
If p<=length(L) then
Begin
If p=1 then
Begin
Temp:=L;
L:= L^.link;
End
Else
Begin
{di chuyển con trỏ M tới vị trí trước p, temp trỏ
tới vị trí p}
M:=L; Dem:=1;
While Dem<> p-1 do
Begin
M:=M^.link;
Dem:= Dem +1;
End;
Temp := M^.link;
{tạo liên kết 2}
M^.link:= Temp^.link;
End;
{giải phóng vùng nhớ chứa dữ liệu cần xoá}
Dispose(Temp);
End;
End;
5 – Các phép toán còn lại xem nhƣ bài tập dành cho bạn đọc
b) Một số dạng danh sách liên kết khác
1- Danh sách nối vòng ( Circularly linked list)
- Là một cải tiến của d/s nối đơn
- Trường Link của node cuối cùng trong d/s nối đơn chứa địa chỉ của node đầu
tiên của d/s
Hình ảnh của nó như sau:




A B C D
L
Hình 2.4 – hình ảnh danh sách liên kết đơn vòng

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
39
Ưu điểm:
- Giúp cho việc truy nhập vào các node được linh hoạt hơn: vì node nào cũng có thể
coi là node đầu tiên và con trỏ L trỏ tới node nào cũng được, từ một nút trong danh
sách ta có thể truy cập được đến các nút khác
Nhược điểm:
Trong xử lý, nếu không cẩn thận dẫn đến 1 chu trình không kết thúc (Vì không
biết được chỗ kết thúc d/s )
2- Danh sách nối kép (double link list)
* Với danh sách móc nối đơn và nối vòng, chỉ có phép duyệt 1 chiều, từ phần tử trước
có thể truy nhập đến phần tử đứng sau, nhưng từ phần tử đứng sau không truy cập trực
tiếp đến phần tử đứng ngay trước nó được. Khắc phục hạn chế này ta có danh sách liên
kết kép
* Mỗi phần tử trong danh sách nối kép là một nút (bản ghi) gồm 3 trường:
- Info : chứa thông tin về đối tượng.
- LPTR : con trỏ trỏ tới phần tử bên trái
- RPTR : con trỏ trỏ tới phần tử bên phải
+ Quy cách một node:


+ Hình ảnh danh sách móc nối đối có dạng:




Để truy nhập các phần tử của d/s ta có thể truy cập xuất phát từ một trong hai đầu
của danh sách. Do đó, ta quản lý danh sách bằng cách dùng 2 con trỏ L, R lần lượt trỏ
tới node cực trái, phải của danh sách. Khi đó d/s rỗng nếu: L=R= nill
(Tương tự như danh sách móc nối đơn, với danh sách nối kép, ta cũng có các phép
toán tác động tương ứng)
* Một số phép toán:
1) Phép bổ sung một phần tử (vào trước phần tử trỏ bởi con trỏ M)







LPTR INFO RPTR
A B C D
L R
A B C D
L R M
E
×
×
(1) (2)
(3)
(4)

q
Hình 2.5 – hình ảnh danh sách liên kết đôi
Hình 2.6 – Thêm một phần tử ở vị trí trƣớc M

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
40








(Giải thuật: Bạn đọc tự viết)
2) Phép loại bỏ 1 phần tử ra khỏi danh sách nối kép
+ Giả sử ta có một d/s nối kép, có 2 nút cực trái, cực phải là L, R. loại bỏ nút trỏ
bởi con trỏ M ra khỏi danh sách
(Chú ý: trong nhiều bài toán cụ thể: Yêu cầu, nút cần loại bỏ là nút thoả mãn điều
kiện nào đó, khi đó ta phải di chuyển con trỏ M trỏ đến nút đó )


















+ Giải thuật: Dành cho bạn đọc

2.1.3.3. So sánh hai phương pháp cài đặt danh sách bởi mảng và bởi con trỏ
Không thể kết luận phương pháp cài đặt nào hiệu quả hơn, mà nó hoàn toàn tuỳ
thuộc vào từng ứng dụng hay tuỳ thuộc vào các phép toán trên danh sách. Tuy nhiên
ta có thể tổng kết một số ưu nhược điểm của từng phương pháp làm cơ sở để lựa
Nếu danh sách rồng (L = R = nil):
Gắn pt vào
d/s
{
L := R := q;
q^ . LPtr := nil;
q^ . RPtr := nil;
Nếu danh sách khác rỗng :
{
q^ . LPtr := M^. LPtr;
q^ .RPtr := M;
M^. LPtr := q;
q^ .LPtr^. RPtr := q;
(1)
(2)
(3)
(4)
A B C D
L R
M
(1)
(1)
Hình 2.6 – Loại bỏ một phần tử ở vị trí M
Nếu danh sách rỗng: (L = R = nil): Return
Loại bỏ:
Nếu :
{
L := L^. RPtr ;
L^ . LPtr := nil ;
Nếu : M =L :
{
M^. PPtr^. LPtr := M^. LPtr;
M^. LPtr^. RPtr := M^. Rptr ;
(1)
(2)
{
R:= R^. LPtr ;
R^ . RPtr := nil ;
Nếu : M =R :
Dispose (M);

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
41
chọn phương pháp cài đặt thích hợp cho từng ứng dụng:
- Cài đặt bằng mảng đòi hỏi phải xác định số phần tử của mảng, do đó nếu
không thể ước lượng được số phần tử trong danh sách thì khó áp dụng cách cài đặt
này một cách hiệu quả vì nếu khai báo thiếu chỗ thì mảng thường xuyên bị đầy,
không thể làm việc được còn nếu khai báo quá thừa thì lãng phí bộ nhớ.
- Cài đặt bằng con trỏ thích hợp cho sự biến động của danh sách, danh sách
có thể rỗng hoặc lớn tuỳ ý chỉ phụ thuộc vào bộ nhớ tối đa của máy. Tuy nhiên ta
phải tốn thêm vùng nhớ cho các con trỏ (trường link).
- Cài đặt bằng mảng thì thời gian xen hoặc xoá một phần tử tỉ lệ với số
phần tử đi sau vị trí xen/ xóa. Trong khi cài đặt bằng con trỏ các phép toán này mất
chỉ một hằng thời gian.
- Phép truy nhập vào một phần tử trong danh sách, chẳng hạn như
PREVIOUS, chỉ tốn một hằng thời gian đối với cài đặt bằng mảng, trong khi đối với
danh sách cài đặt bằng con trỏ ta phải tìm từ đầu danh sách cho đến vị trí trước vị
trí của phần tử hiện hành.Nói chung danh sách liên kết thích hợp với danh sách
có nhiều biến động, tức là ta thường xuyên thêm, xoá các phần tử.





2.2 Ngăn xếp (Stack) và ứng dụng
2.2.1. Định nghĩa ngăn xếp
- Ngăn xếp (Stack) là một danh sách đặc biệt, trong đó việc thêm vào hoặc
loại bỏ một phần tử chỉ thực hiện tại một đầu của danh sách, đầu này gọi là
đỉnh (TOP) của ngăn xếp.
Ví dụ: Ta có thể xem hình ảnh trực quan của ngăn xếp bằng một chồng đĩa
đặt trên bàn. Muốn thêm vào chồng đó 1 đĩa ta để đĩa mới trên đỉnh chồng,
muốn lấy các đĩa ra khỏi chồng ta cũng phải lấy đĩa trên trước.
Như vậy ngăn xếp là một cấu trúc có tính chất “vào sau - ra trước” hay “vào
trước – ra sau“ (LIFO (last in - first out ) hay FILO (first in – last out)).








? Cho biết ưu khuyết điểm của danh sách đặc và danh sách liên kết
? Ưu khuyết điểm của các loại danh sách liên liên kết

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
42
- Hình ảnh của ngăn xếp có dạng như sau:

















2.2.2 Các phép toán cơ bản trên ngăn xếp:
1- MAKENULL_STACK(S): tạo một ngăn xếp rỗng.
2- POP(S,x) chương trình con lấy một phần tử tại đỉnh ngăn xếp S lưu vào
biến x .
3 - PUSH(x,S) chương trình con thêm một phần tử x vào đầu ngăn xếp.
4 - EMPTY_STACK(S) kiểm tra ngăn xếp rỗng. Hàm cho kết quả true
nếu ngăn xếp rỗng và false trong trường hợp ngược lại.

? Ta có thể truy xuất trực tiếp phần tử tại vị trí bất kỳ trong ngăn xếp được không?

2.2.3. Cài đặt ngăn xếp
Do ngăn xếp là một danh sách đặc biệt nên ta có thể cấu trúc dữ liệu danh sách để
biểu diễn nó. Như vậy, ta có thể khai báo ngăn xếp như sau:
type Stack = List ( hoặc = PList);
Tuy nhiên, để dễ hình dung:
+ Trong cách cài đặt sử dụng mảng ta nên thay trường Count = trường Top
+ Trong cách cài đặt sử dụng con trỏ, con trỏ quản lý ngăn xếp đặt tên là Top
Khi chúng ta đã dùng danh sách để biểu diễn cho ngăn xếp thì ta nên sử dụng các
phép toán trên danh sách để cài đặt các phép toán trên ngăn xếp. Sau đây là phần cài
đặt ngăn xếp bằng danh sách.

Hình 2.7 – cấu trúc ngăn xếp

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
43
1- Tạo ngăn xếp rỗng:
Procedure MakeNull_Stack( var S: Stack)
Begin
MakeNull_List(S);
End;
2 - Kiểm tra ngăn xếp rỗng:
Function Empty_Stack(var S: Stack )
Begin
Empty_Stack:= Empty_List(S);
End;
3 – Hàm trả về vị trí đỉnh của ngăn xếp
Function Top(S: Stack): integer;
Begin
Top:=S.count;
End;
4 - Thêm phần tử vào ngăn xếp
Procedure Push(X: Item, var S: Stack);
Begin
Insert_List (x, First (S), S);
End;
5 – Lấy 1 phần tử ra khỏi ngăn xếp S lƣu và biến x để xử lý: dành cho bạn đọc
Nhận xét:
Như trên ta thấy, ta hoàn toàn có thể dùng danh sách để biểu diễn cho một ngăn xếp
và dùng các phép toán đã được cài đặt của danh sách để cài đặt các phép toán trên
ngăn xếp. Ngoài ra, ta có thể cài đặt ngăn xếp trực tiếp bởi mảng hoặc con trỏ, và cài
đặt trực tiếp các phép toán trên ngăn xếp mà không phải gọi tới các phép toán của danh
sách. Xem như bài tập dành cho bạn đọc
2.2.4. Ứng dụng ngăn xếp
Cấu trúc ngăn xếp thích hợp với việc lưu trữ các loại dữ liệu mà có trình tự lưu trữ
ngược với trình tự truy xuất dữ liệu, ngăn xếp đóng vai trò là vùng nhớ tam thời do đó
một số ứng dụng sau đây thường dùng đến ngăn xếp:
1) Trong trình biên dịch (hoặc thông dịch), khi cần thực hiện các thủ tục (hoặc hàm),
ngăn xếp được dùng để lưu trữ môi trường của thủ tục.
2) Trong một số bài toán lý thuyết đồ thị (ví dụ: Tìm đường đi, ….), ngăn xếp được
dùng để lưu trữ các dữ liệu trung gian khi giải các bài toán này (ví dụ lưu vết
đường đi, …)
3) Ngoài ra, ngăn xếp còn được sử dụng trong các trường hợp khử đệ quy để loại bỏ
tính đệ qui của chương trình
Ví dụ:

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
44
Nếu một chương trình con đệ qui P(x) được gọi từ chương trình chính ta nói chương
trình con được thực hiện ở mức 1. Chương trình con này gọi chính nó, ta nói nó đi
sâu vào mức 2... cho đến một mức k. Rõ ràng mức k phải thực hiện xong thì mức k-1
mới được thực hiện tiếp tục, hay ta còn nói là chương trình con quay về mức k-1.
Trong khi một chương trình con từ mức i đi vào mức i+1 thì các biến cục bộ của
mức i và địa chỉ của mã lệnh còn dang dở phải được lưu trữ, địa chỉ này gọi là địa
chỉ trở về. Khi từ mức i+1 quay về mức i các giá trị đó được sử dụng. Như vậy
những biến cục bộ và địa chỉ lưu sau được dùng trước. Tính chất này gợi ý cho ta
dùng một ngăn xếp để lưu giữ các giá trị cần thiết của mỗi lần gọi tới chương trình
con. Mỗi khi lùi về một mức thì các giá trị này được lấy ra để tiếp tục thực hiện mức
này. Ta có thể tóm tắt quá trình như sau:
Bƣớc 1: Lưu các biến cục bộ và địa chỉ trở về.
Bƣớc 2: Nếu thoả điều kiện ngừng đệ qui thì chuyển sang bước 3. Nếu không
thì tính toán từng phần và quay lại bước 1 (đệ qui tiếp).
Bƣớc 3: Khôi phục lại các biến cục bộ và địa chỉ trở về.
Bài tập - thảo luận
Nêu phương pháp tính giá trị biểu thức theo ký pháp nghịch đảo Balan sử dụng ngăn
xếp
2.3. Hàng đợi (QUEUE)
2.3.1. Định nghĩa hàng đợi
Hàng đợi, hay ngắn gọn là hàng (queue) là một danh sách đặc biệt mà phép
thêm vào chỉ thực hiện tại một đầu của hàng, gọi là cuối hàng (REAR), còn phép
loại bỏ thì thực hiện ở đầu kia của hàng, gọi là đầu hàng (FRONT).
Ví dụ: Khi ta xếp hàng mua vé xem phim là một hình ảnh trực quan của khái niệm
trên, người mới đến thêm vào cuối hàng còn người ở đầu hàng mua vé và ra khỏi
hàng, vì vậy hàng còn được gọi là cấu trúc FIFO (first in - first out) hay "vào trước -
ra trước".
Hình ảnh của hàng có dạng như sau:
A1 A2 A3 A4



Bây giờ chúng ta sẽ thảo luận một vài phép toán cơ bản nhất trên hàng
2.3.2 Các phép toán cơ bản trên hàng
- MAKENULL_QUEUE(Q) khởi tạo một hàng rỗng.
- FRONT(Q) hàm trả về phần tử đầu tiên của hàng Q.
- INSERT_QUEUE(x,Q) thêm phần tử x vào cuối hàng Q.
- DEQUEUE(Q): xoá phần tử tại đầu của hàng Q.
- EMPTY_QUEUE(Q) hàm kiểm tra hàng rỗng.
Front Rear
Hình 2.8 – Hình ảnh hàng

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
45
- FULL_QUEUE(Q) kiểm tra hàng đầy.
2.3.3 Cài đặt hàng đợi
Như đã trình bày trong phần ngăn xếp, ta hoàn toàn có thể dùng danh sách để
biểu diễn cho một hàng và dùng các phép toán đã được cài đặt của danh sách để cài
đặt các phép toán trên hàng. Tuy nhiên làm như vậy có khi sẽ không hiệu quả,
chẳng hạn dùng danh sách cài đặt bằng mảng ta thấy lời gọi
INSERT_LIST(x,ENDLIST(Q),Q) tốn một hằng thời gian trong khi lời gọi
DELETE_LIST(FIRST(Q),Q) để xoá phần tử đầu hàng (phần tử ở vị trí 0 của
mảng) ta phải tốn thời gian tỉ lệ với số các phần tử trong hàng để thực hiện việc dời
toàn bộ hàng lên một vị trí. Để cài đặt hiệu quả hơn ta phải có một suy nghĩ khác
dựa trên tính chất đặc biệt của phép thêm và loại bỏ một phần tử trong hàng, Đó là sử
dụng trực tiếp mảng và con trỏ để cài đặt hàng.
2.3.3.1 Cài đặt hàng bằng mảng
Ta dùng một mảng để chứa các phần tử của hàng (giả sử hàng có tối đa n phần
tử), khởi đầu phần tử đầu tiên của hàng được đưa vào vị trí thứ 1 của mảng, phần
tử thứ 2 vào vị trí thứ 2 của mảng... Giả sử hàng có n phần tử, ta có front=1 và
rear=n.
+ Khi xoá một phần tử front tăng lên 1,
+ Khi thêm một phần tử rear tăng lên 1.
Như vậy hàng có khuynh hướng đi xuống, đến một lúc nào đó ta không thể
thêm vào hàng được nữa (rear = n) dù mảng còn nhiều chỗ trống (các vị trí trước
front) trường hợp này ta gọi là hàng bị tràn. Trong trường hợp toàn bộ mảng đã
chứa các phần tử của hàng ta gọi là hàng bị đầy.
Các cách khắc phục hàng bị tràn
1 - Dời toàn bộ hàng lên front -1 vị trí: cách này gọi là di chuyển tịnh
tiến. Trong trường hợp này ta luôn có front<=rear. (Hình 2.9, minh họa cho cách
này)













Rear →
Front →
Hàng tràn
1
2
3
4
5
6
7
8 Rear →
Front →
1
2
3
4
5
6
7
8
Hàng sau khi tịnh tiến
Hình 2.9 – Tịnh tiến hàng

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
46
2 - Xem mảng như là một vòng tròn: Nghĩa là khi hàng bị tràn nhưng chưa đầy
ta thêm phần tử mới vào vị trí 1 của mảng, thêm một phần tử mới nữa thì thêm
vào vị trí 2 (nếu có thể)...Rõ ràng cách làm này front có thể lớn hơn rear. Cách
khắc phục này gọi là dùng mảng xoay vòng (xem Hình 2.10).









Hình 2.10: Cài đặt hàng bằng mảng xoay vòng
Với phương pháp này, khi hàng bị tràn, tức là rear = n, nhưng chưa đầy, tức là
front>1, thì ta thêm phần tử mới vào vị trí 1 của mảng và cứ tiếp tục thêm vào vị
trí tiếp theo (nếu còn trống), vì từ 1 đến front-1 là các vị trí trống.
a) Dạng cài đặt hàng
Const n =... ; {chiều dài tối đa của mảng}
Type Item = ….; {Kiểu dữ liệu của các phần tử trong hàng}
Queue = Record
Elements: Array[1..n] of Item; //Lưu trữ nội dung các phần tử
Front, Rear: 0 .. n; {chỉ số đầu và đuôi hàng}
Count: 0..n; {đếm số phần tử hiện có trong hàng}
End;
Var Q: Queue;
b) Các phép toán trên hàng tròn
1 - Tạo hàng rỗng
Ta thấy: Lúc này front và rear không trỏ đến vị trí hợp lệ nào trong mảng vậy ta có
thể cho front và rear đều bằng 0, count=0.
Procedure MakeNull_Queue( var Q: Queue )
Begin
Q.Front=1;
Q.Rear=0;
Q.count:=0
End;
2- Kiểm tra hàng rỗng
Function Empty_Queue(Queue Q):Boolean;

2


3

...................
1 n
n-1
Rear
Front

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
47
Begin
Empty_Queue := Q.Count=0;
End;
3 - Kiểm tra hàng đầy
Hàng đầy nếu toàn bộ các ô trong mảng đang chứa các phần tử của hàng, tức
Count = Maxlength
Function Full_Queue(Q: Queue): Boolean;
Begin
Full_Queue:= Q.count=n;
End;
Lƣu ý:
Nếu biện luận tính đầy theo Front và Real, ta thấy với phương pháp này thì front có
thể lớn hơn rear. Ta có hai trường hợp hàng đầy như sau:
- Trường hợp Q.Rear=n và Q.Front =1
- Trường hợp Q.Front =Q.Rear+1.
Để đơn giản ta có thể gom cả hai trường hợp trên lại theo một công thức như sau:
(Q.rear-Q.front +1) mod n=0
3 - Xóa một phần tử ra khỏi hàng
Các bƣớc làm:
Khi xóa một phần tử ra khỏi hàng ta làm như sau:
- Nếu hàng rỗng thì báo lỗi không xóa;
- Nếu hàng khác rống:
Cập nhật lại giá trị của Front và count như sau:
Nếu hàng chỉ còn 1 phần tử thì khởi tạo lại hàng rỗng;
Ngược lại, thay đổi giá trị của Q.Front như sau:
Nếu Q.front <>n thì đặt lại Q.front = Q.Front +1;
Ngược lại Q.front=1
Giải Thuật
Procedure DeQueue(Var Q: Queue)
Begin
if ( not Empty_Queue(Q)) then
begin
{ Nếu hàng chỉ chứa một phần tử thì khởi tạo lại hàng}
if (Q.count= 0 ) then MakeNull_Queue(Q);
else
if Q. Front <>n then Q.Front : = Q.Front+1
Else Q.Front := 1;

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
48
Q.count:= Q.count – 1;
end
else Writeln(„Loi: Hang rong!‟);
End;
4 - Thêm một phần tử vào hàng
Khi thêm một phần tử vào hàng thì có thể xảy ra các trường hợp sau:
- Trường hợp hàng đầy thì báo lỗi và không thêm;
- Ngược lại, thay đổi giá trị của Q.Rear (Nếu Q.Rear = n thì đặt lại Q.rear=1;
Ngược lại Q.rear =Q.rear+1) và đặt nội dung cần thêm vào vị trí Q.rear mới.
Procedure Insert_Queue(X: Item,Var Q: Queue)
Begin
if (not Full_Queue(Q)) Then
Begin
If (Q.Rear <>MaxQueue) then
Q.Rear := Q.Rear +1
Else Q.Rear := 1;
Q.Elements[Q.Rear] := x;
End
else Writeln(„Loi: Hang day!‟);
End;

? Cài đặt hàng bằng mảng vòng có ưu điểm gì so với bằng mảng theo phương
pháp tịnh tiến? Trong ngôn ngữ lập trình có kiểu dữ liệu mảng vòng không?


2.3.3.2. Cài đặt hàng bằng danh sách liên kết (cài đặt bằng con trỏ)
Cách tự nhiên nhất là dùng hai con trỏ front và rear để trỏ tới phần tử đầu và
cuối hàng. Hàng được cài đặt như một danh sách liên kết
a) Dạng cài đặt:
Type Node = record
Infor: ITem; {lưu giá trị của phần tử }
Next: ^Node; //Con trỏ chỉ ô kế tiếp
End;
Queue = Record
Front, Rear: ^ Node; {là hai trường chỉ đến đầu và cuối
của hàng}
End;

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
49
b) Cài đặt các phép toán
1 - Khởi tạo hàng rỗng
Khi hàng rỗng Front va Rear cùng trỏ về xuống đất (có giá rị nil)
Procedure MakeNullQueue(var Q: Queue )
Begin
Q.Rear :=nil;
Q.Front :=nil;
End;
2 - Kiểm tra hàng rỗng
Hàng rỗng nếu Front và Rear cùng có giá trị nil
Function EmptyQueue(Q: Queue Q): Boolean;
Begin
If (Q.Front = nil)and(Q.Rear=nil) then
EmptyQueue := true
Else EmptyQueue:=false;
End;
3 - Các phép toán còn lại xem nhƣ bài tập dành cho bạn đọc
2.3.4. Một số ứng dụng của cấu trúc hàng
Hàng đợi là một cấu trúc dữ liệu được dùng khá phổ biến trong thiết kế giải thuật.
Bất kỳ nơi nào ta cần quản lí dữ liệu, quá trình... theo kiểu vào trước - ra trước đều
có thể ứng dụng hàng đợi.
Ví dụ rất dễ thấy là quản lí in trên mạng, nhiều máy tính yêu cầu in đồng thời và
ngay cả một máy tính cũng yêu cầu in nhiều lần. Nói chung có nhiều yêu cầu in dữ
liệu, nhưng máy in không thể đáp ứng tức thời tất cả các yêu cầu đó nên chương
trình quản lí in sẽ thiết lập một hàng đợi để quản lí các yêu cầu. Yêu cầu nào mà
chương trình quản lí in nhận trước nó sẽ giải quyết trước.
Các giải thuật duyệt theo chiều rộng một đồ thị có hướng hoặc vô hướng cũng
dùng hàng đợi để quản lí các nút đồ thị. Các giải thuật đổi biểu thức trung tố thành
hậu tố, tiền tố cũng cần dùng đến cấu trúc hàng.

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
50


Chƣơng 3 Mô hình dữ liệu Cây
Tổng quan:
1. Mục tiêu
Sau khi học xong chương này, sinh viên phải:
- Nắm vững khái niệm về cây, phân biệt các mô hình cây: Cây tổng quát, cây nhị
phân, cây nhị phân tìm kiếm, ...
- Biểu diễn và cài đặt được các phép toán cơ bản trên cây
2. Nội dung chính
Trong chương này chúng ta sẽ nghiên cứu các vấn đề sau:
- Các thuật ngữ cơ bản.
- Các mô hình dữ liệu Cây
- Các cách biểu diễn cây trên máy tính và cài đặt chúng
3.1 Cây tổng quát
Cây tổng quát hay còn gọi là cây đa phân – gọi tắt là cây
3.1.1 Định nghĩa cây và các khái niệm cơ bản trên cây
Có nhiều cách định nghĩa cây, sau đây ta định nghĩa bằng đệ quy như sau :
1) Tập có một đỉnh là một cây, cây này có gốc là đỉnh duy nhất của nó.
2) Giả sử T
1
, T
2
, ... , T
k
(k > 1) là các cây có gốc tương ứng là r
1
, r
2
, ... , r
k
; các cây T
1

, T
2
, ... , T
k
đôi một không cắt nhau. Nếu r là một đỉnh không thuộc các cây T
1
, T
2
,
... , T
k
thế thì tập T gồm đỉnh r và tất cả các đỉnh của các cây T
i
làm thành một cây mới
gốc r.






Ví dụ về cây:
Xét mục lục của một quyển sách. Mục lục này có thể xem là một cây:







Hình III.1 - Cây mục lục một quyển sách
r
r
1
r
2
r
k

T
1
T
2
T
k

Hình 3.1 – Cây mục lục của một quyển sách

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
51
Như vậy cây bao gồm một tập hữu hạn các đỉnh, trong đó có một đỉnh đặc biệt gọi là
gốc (root). Giữa các đỉnh có một quan hệ phân cấp gọi là quan hệ cha - con. Nếu a là
đỉnh gốc của một cây còn b là gốc cây con của đỉnh a thì ta nói a là đỉnh cha còn b là
đỉnh con.
+ Nếu tập các đỉnh = rỗng => Cây là cây rỗng
+ Số các cây con ở mỗi đỉnh được gọi là bậc của đỉnh đó. Đỉnh có bậc = 0 gọi là lá
(đỉnh tận cùng), đỉnh không là lá gọi là đỉnh trong.
+ Các đỉnh có cùng cha gọi là anh em, xét từ trái sang phải
+ Chiều cao của cây (height) của cây là số mức lớn nhất của đỉnh có trên cây
+ Tập các cây con phân biệt người ta gọi là Rừng
+ Gốc của cây có mức 1 (level = 1). Nếu đỉnh cha có mức i thì các đỉnh con của nó
sẽ có mức là i+1
+ Một dãy các đỉnh a
1
, a
2
, ... , a
n
(n>0) sao cho a
i
là cha của a
i+1
gọi là một đường đi
từ a
1
đến a
n
với độ dài n-1. Luôn tồn tại đường đi từ gốc đến một đỉnh bất kỳ trong
cây.
+ Trong một cây T, mỗi đỉnh a của nó là gốc của một cây nào đó gọi là cây con của
T.
+ Cây được sắp: Các đỉnh trong cây được sắp xếp theo thứ tự nào đó. Nếu đỉnh a có
các đỉnh con b
1
, b
2
, ..., b
m
theo thứ tự này thì ta nói b
1
là con trưởng và b
2
là em liền
kề của nó.
3.1.2 Các phép toán cơ bản trên cây
Xét cây gốc T:
- Hàm PARENT(n) trả về nút cha của nút n trên cây T, nếu n là nút gốc thì
hàm cho giá trị $. Trong cài đặt cụ thể thì $ là một giá trị nào đó do ta chọn, nó
phụ thuộc vào cấu trúc dữ liệu mà ta dùng để cài đặt cây.
- Hàm ELDEST_CHILD(n) cho nút con trái nhất của nút n trên cây T, nếu n
là lá thì hàm cho giá trị $.
- Hàm NEXT_SIBLING(n) cho nút anh em ruột phải nút n trên cây T, nếu n
không có anh em ruột phải thì hàm cho giá trị $.
- Hàm value(n) Trả về giá trị lưu tại nút n của cây T.
3.1.3 Các cách thăm ( duyệt) cây
- Duyệt cây: Là phép thăm các đỉnh trên cây, sao cho mỗi đỉnh chỉ được thăm duy nhất
một lần
- Xét cây tổng quát sau:






Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
52












* Xét ba phương pháp duyệt cây cơ bản:
- Duyệt cây theo thứ tự trước – Preorder
- Duyệt cây theo thứ tự giữa – Inorder
- Duyệt cây theo thứ tự sau - Postorder .
* Nguyên tắc duyệt, giải thuật tương ứng với từng phương pháp:
Giả sử xét cây gốc T

1) Duyệt theo tứ tự trƣớc (PreOrder Tree)
* Nguyên tắc duyệt cây theo thứ tự trước
















Nếu cây rỗng(T = nil): Không làm gì

PreOrder (T)
Nếu cây không rỗng :

{
1.Thăm gốc T
2.Thăm các cây con T
1
, T
2
, T
3
, .., T
n
theo
thứ tự trước
=> Gốc T
1
T
2
T
3
.. T
n

A
B C D
F E G
H I J
T
Hình 3.2 – Hình ảnh một cây

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
53

Ví dụ: Xét cây







* Giải thuật:
Procedure PreOrder (T: nodeType);
Var C: nodeType;
Begin
Visit (T); {Thăm gốc}
C := Eldestchild (T); { con cả}
While (C<>$) do
Begin
PreOrder (C);
C := NextSibling (C); {em liền kề}
End;
End;
2) Duyệt theo thứ tự giữa (InOrder Tree)
* Nguyên tắc duyệt cây theo thứ tự giữa:














Nếu cây rỗng(T = nil): Không làm gì

+InOrder T
Nếu cây không rỗng
:

{
1. Duyệt cây con thứ nhất T
1
của
gốc T theo thứ tự giữa
2. Thăm gốc của T
3 Duyệt các cây con còn lại T
2
, T
3
,
...., T
n
theo thứ tự giữa

A B CFHIEJ DG ==>
kq
(

(

(

gèc T
1

T
2

T
3

(

B C D
F E G
H I J
A
T
T
1
T
2
T
3
1
2
3
4
5 6
7
8
9
10

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
54
* Ví dụ: Xét cây








* Giải thuật:
Procedure InOrder (T: nodeType);
Var C: nodeType;
Begin
C := EldestChild (T); { con cả}
If (C <> $) then
begin
InOrder(C);
C := NextSibling (C); {em liền kề}
end;
Visit(T); {thăm gốc}
While (C <> $) do
Begin
InOrder (C);
C := NextSibling (C); {em liền kề}
End;
End;
3) Duyệt theo thứ tự sau
* Nguyên tắc duyệt cây theo thứ tự sau:









B A HFICJE GD ==>
kq
(

(

(

gèc T
1

T
2

(

T
3

B C D
F E G
H I J
A
T
T
1
T
2
T
3
2
1
6
4
3 5
8
7
10
9
Nếu cây rỗng(T = nil): Không làm gì

PostOrder T
Nếu cây không rỗng :

{
1.Thăm gốc T
2.Thăm các cây con T
1
, T
2
, T
3
, .., T
n

theo thứ tự sau
=> Gốc -> T
1
->

T
2
-> T
3
->

.. T
n



Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
55
* Ví dụ: Xét cây










* Giải thuật:
Procedure PostOrder (T: nodeType);
Var C: nodeType;
Begin
C := EldestChild (R); { con cả}
While (C <> $) do
Begin
PostOrder (C);
C := NextSibling (C); {em liền kề}
End;
Visit (R); {Thăm cha}
End;
3.1.4. Cài đặt cây
3.1.4.1 Biểu diễn cây bằng danh sách các con của mỗi đỉnh
a) Sử dụng mảng để biểu diễn cây
Biểu diễn cây dưới dạng mỗi nút có một danh sách các nút con. Danh sách các con
có thể cài đặt bằng bất kỳ cách nào chúng ta đã biết, tuy nhiên vì số nút con của một
nút là không biết trước nên dùng danh sách liên kết sẽ thích hợp hơn. Để tiện cho
việc truy cập đến các đỉnh trên cây ta nên tổ chức lưu trữ các đỉnh này sử dụng cấu
trúc mảng, giả sử ta gán chỉ danh cho các nút lần lượt là 1,2,….n
Ví dụ: Xét cây bên trái dưới đây được mô tả bởi mảng như sau :




=>

B HIFJEC GD A ==>
kq
(

(

(

gèc
T
1
T
2

T
3
T
3

(

B C D
F E G
H I J
A
T
T
1
T
2
T
3
10
1
7
4
2 3
6
5
9
8

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
56

















* Dạng cài đặt tƣơng ứng sẽ là:
Const n = MaxNode;
Type Item = <kiểu thông tin của đỉnh>;
Pointer = ^ Member;
Member = Record
Id: 1 .. n; {ID: chỉ danh của đỉnh}
Next: Pointer; {Next: trỏ tới em liền kề}
End;
Node = Record
Info: Item; {chứa giá của phần tử lưu tại đỉnh cây}
Child : Pointer; {con trỏ, trỏ tới con đầu tiên trong danh
sách các con của nó}
End;
Tree = Array [ 1 .. n ] of Node; {cây là một mảng}
Var T: Tree;
*) Các phép toán tác động trên cây
1 - Cho một đỉnh thứ k trên cây T, tìm con cả của nó
Function EldestChild (k: in): 0 .. n;
Begin
if (T[k].Child <> nil) then
4
A
B C
D
E
F G H
M I K
1
2 3
5
8 7 6
9 10
9
11
9
Info Child















…………
.
2
C
B
A
D
E
F
G
H
I
M
K
Id Next
3
4 5
6 7 8
9 10
11
TT
1
2
3
4
5
6
7
8
9
10
11
.
n
.
.
.
n
Hình ảnh cây
sau khi cài đặt
Hình 3.3 – Cây và hình ảnh cây biểu diễn bởi danh sách các con của mỗi đỉnh

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
57
EldestChild := T[k].Child^.Id;
Else EldestChild := 0;
End;
2 - Cho một đỉnh thứ k trên cây T, Tìm cha của nó
Function Parent (k: 1 .. n): 0 .. n;
Var i: 1 .. n; p: pointer; found : boolean;
Begin
i := 1; found := false;
While ( i<= maxNode)and(not found) do
begin
p := T[i].child;
While ( p<>nil)and(not found) do
if (p^. Id = k) then
begin
parent := i; found := true;
end
else p := p^. Next;
i := i + 1;
end;
if (not found) then parent := 0;
End;
3 – Các phép toán còn lại xem nhƣ bài tập dành cho bạn đọc
b) Sử dụng con trỏ để biểu diễn cây
Nếu ta biết trước số con tối đa của mỗi đỉnh trên cây, ta có thể dùng mảng để lưu danh
sách các con này
* Dạng cài đặt
const K = ... ; {K là số tối đa các con của mỗi đỉnh}
type pointer = ^node;
node = record
info : Item; {lưu thông tin của đỉnh}
childs : array[1..K] of pointer; {mảng chứa các con của đỉnh}
end;
var Root : pointer; {Root là con trỏ, trỏ tới gốc cây}
* Cài đặt các phép toán cơ bản với dạng cài đặt này: xem như bài tập dành cho bạn
đọc



Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
58
3.1.4.2 Biểu diễn cây bằng con trưởng và em liền kề của mỗi đỉnh
1. Cài đặt bởi mảng :
a) Dạng cài đặt
Mỗi đỉnh trên cây được cài đặt như một node như sau:
Type Node = record
info : Item; {Lưu giá trị của phần tử lưu tại đỉnh}
EldestChild : 0..N; {Chứa chỉ danh của đỉnh là con trưởng}
NextSibling : 0..N; {Chứa chỉ danh của đỉnh là em liền kề}
end;
Tree = array[1..N] of node; {cây là một mảng các nút}
Ví dụ: Xét cây sau:
















b) Các phép toán
Với cách cài đặt này ta thấy dễ dàng tìm con trưởng và em liền kề của đỉnh k – xem
như bài tập dành cho bạn đọc, ở đây ta xét phép toán tìm cha của đỉnh k trên cây T. Để
tìm cha của đỉnh thứ k trên cây T, ta làm như sau:
Duyệt lần lượt các đỉnh trên cây, xuất phát từ gốc (i =1), kiểm tra xem con trưởng J
của i có = k hay không? Nếu có => i chính là cha cần tìm, ngược lại, kiểm tra em liền
kề của con trưởng j xem có = k hay không, nếu có thì i chính là là cần tìm, nếu không
kiểm tra j là em liền kề của j cũ, cứ tiếp tục như vậy cho đến khi kiểm tra hết các con
của i mà không có con nào = k thì duyệt đỉnh i tiếp theo trên cây, ngược lại thì dừng
việc tìm kiếm và kết luận i chính là đỉnh cha cần tìm.

a1
a2 a3
a4
a5
E
a6 a7 a8
M a9 A
1
1
2 3
5
8 7 6
9 10
9
11
9
Cây sau khi cài đặt có
dạng như sau:
=============
ID Infor EldestChild NextSibling
1 a1 2 0
2 a2 4 3
3 a3 6 0
4 a4 0 5
5 a5 9 0
6 a6 0 7
7 a7 11 8
8 a8 0 0
9 a9 0 10
10 A 0 0
11 M 0 0

n

Hình 3.4 – Cây và hình ảnh cây đƣợc biểu diễn bởi mảng các con
trƣởng và em liền kề của mỗi đỉnh

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
59
Giải thuật:
function Parent(k : 1..N): 0..N;
var i,j : 0..N ; found : boolean;
begin
i := 1; found := false;
while (i<=N) and (not found) do
begin
j := T[i].EldestChild;
if j=k then
begin
parent := i; found := true;
end
else
begin
j := T[j].Nextsibling;
while (j<>0) and (not found) do
if j=k then
begin
parent := i; found := true;
end
else j := T[j].NextSibling;
end;
i:=i+1;
end;
if not found then parent := 0;
end;
2. Cài đặt cây bởi con trỏ :
* Dạng cài đặt
type
pointer = ^node;
node = record
info : Item; {lưu thông tin phần tử của đỉnh}
eldestchild : pointer; {Eldestchild: Trỏ tới gốc cây con cả của đinh}
Nextsibling : pointer; {NextSibling: Trỏ tới gốc em liền kề của đỉnh}
end;
var Root : pointer; {là con trỏ quản lý cây, luôn trỏ tới gốc cây}
 Cấu trúc cuả 1 nút có dạng như sau:
eldestchild
Info
Nextsibling


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
60
Ví dụ: Xét cây sau:






.










?
Hãy so sách các ưu khuyết điểm của các cách cài đặt cây.

Nhận xét:
Với cách cài đặt này ta thấy: Cây rỗng: Root = nil ; là cách biểu diễn để ta chuyển cây
đa phân trở về cây nhị phân tương đương
* Cài đặt các phép toán cơ bản trên cây: xem như bài tập dành cho bạn đọc
3.1.4.3 Biểu diễn cây bởi cha của mỗi đỉnh
Ví dụ: Xét cây



=>







a1
a2 a3
a4
a5
E
a6 a7 a8
M a9 A
1
1
2 3
5
8 7 6
9 10
9
11
9
Cây sau khi cài đặt có
dạng như sau:
=============
a1 nil

a2

a3 nil

nil a4

nil a9

nil a6

a5 nil

nil A nil

a7

nil a8 nil
nil M nil

Root
Hình 3.5 – Cây và hình ảnh cây đƣợc biểu diễn bởi con trƣởng và em
liền kề của mỗi đinh sử dụng con trỏ
A
B C
D
E
F G H
M I K
1
2 3
4 5
8 7 6
9 10
9
11
9
Node Info Parent
1 A 
2 B 1
3 C 1
4 D 2
5 E 2
6 F 3
7 G 3
8 H 3
9 I 5
10 K 5
11 M 7
Hình ảnh cây
sau khi cài đặt
Hình 3.6 – Cây và hình ảnh cây đƣợc biểu diễn bởi cha của mỗi đỉnh
sử dụng con trỏ

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
61



Cài đặt:
const N = ... ;
type Node = record
info : Item;
parent : 0..N;
end;
Tree = array[1..N] of Node;
var T : Tree;
Các phép toán quan trọng trên cây
1 - Cho một đỉnh k , tìm đỉnh cha của nó
Function Parent ( k: Integer; T: Tree): Integer;
Begin
If k = 0 then Parent := 0
Else Parent :=T[k].Parent;
End;
2 - Cho một đỉnh k , tìm đỉnh con cả của nó
Function EldestChild (k: Integer; T: Tree): Integer;
Var p, i : 0 … MaxNode ; found: Boolean;
Begin {1}
If k = 0 then EldestChild := 0;
i:=1; found:=false;
While ( i<=MaxNode) and (not found) do
Begin {2}
p :=T[i].Parent;
If k=p then
Begin {3}
EldestChild := i;
found := true;
End; {3}
i:=i+1;
End; {2}
If ( not found) then EldestChild := 0;
End; {1}


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
62
3 - Cho một đỉnh k , tìm em liền kề của nó
Function NextSibling ( k: Integer; T: Tree): Boolean;
Var p, i : 0 .. MaxNode ; found: Boolean;
Begin {1}
p := T[k].Parent;
If p = 0 then NextSibling := 0
Else
Begin {2}
i := k+1; { vì đỉnh em liền kề > đỉnh anh là 1}
found := false;
while ( i <= MaxNode) and (not found) do
begin {3}
if (T[i].Parent = p) then
begin {4}
NextSibling := i;
Found:= true;
end; {4}
i:= i+1;
end; {3}
if (not found) then NextSibling := 0;
End; {2}
End; {1}


4 – Lấy giá trị lƣu tại nút thứ k trên cây T
Function Value(k:integer): Item;
Begin
Value := T[k].info;
End;
3.2 Cây nhị phân (binary tree)
3.2.1 Định nghĩa
Cây nhị phân là cây rỗng hoặc là cây mà mỗi nút có tối đa hai nút con. Trong
đó các nút con của cây được phân biệt thứ tự rõ ràng, một nút con gọi là nút con trái
và một nút con gọi là nút con phải.
Ta qui ước vẽ nút con trái bên trái nút cha và nút con phải bên phải nút cha,
mỗi nút con được nối với nút cha của nó bởi một đoạn thẳng.
Ví dụ: Hai cây nhị phân s au l à khác nhau: một câ y có c on t r ái và một
câ y c ó c on phải

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
63




* Các dạng cây nhị phân đặc biệt









( Đây là các dạng của cây nhị phân suy biến, có dạng là một danh sách )








Nhận xét:

+ Trong cây nhị phân có cùng số đỉnh
- Cây nhị phân suy biến có chiều cao lớn nhất
- Cây nhị phân đầy đủ có chiều cao nhỏ nhất
+ Với cây nhị phân đầy đủ cần chú ý tới một số tính chất
- Số lượng tôí đa các đỉnh ở mức i là 2
i – 1

- Số lượng tối đa các đỉnh trên cây có chiều cao h là: 2
h
- 1

3.2.2. Duyệt cây nhị phân
Ta có thể áp dụng các phép duyệt cây tổng quát để duyệt cây nhị phân. Tuy nhiên
vì cây nhị phân là cấu trúc cây đặc biệt nên các phép duyệt cây nhị phân cũng đơn
giản hơn.
Có ba cách duyệt cây nhị phân thường dùng (xem kết hợp với Hình 3.7):
A
B B
A








Cây nhị phân hoàn chỉnh: Các nút
ứng với các mức trừ mức gần
mức cuối cùng đều có 2 con







Cây nhị phân đầy đủ: Các nút có
bậc tối đa ở mọi mức kể cả mức
gần mức cuối cùng










Cây Zic-Zắc









cây lệch trái cây lệch phải

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
64







- Duyệt tiền tự - thứ tự trước (root-Left-Right): duyệt nút gốc, duyệt tiền tự con
trái rồi duyệt tiền tự con phải.
- Duyệt trung tự - duyệt theo thứ tự giữa (Left-Root-Right): duyệt trung tự con
trái rồi đến nút gốc sau đó là duyệt trung tự con phải.
- Duyệt hậu tự - duyệt theo thứ tự sau(Left-Right-Root): duyệt hậu tự con trái
rồi duyệt hậu tự con phải sau đó là nút gốc.
Chú ý:
Danh sách duyệt tiền tự, hậu tự của cây nhị phân trùng với danh sách duyệt tiền
tự, hậu tự của cây đó khi ta áp dụng phép duyệt cây tổng quát. Nhưng danh sách
duyệt trung tự thì khác nhau.
Ví dụ: Xét cây:








Các danh sách duyệt cây nhị phân Các danh sách duyệt cây tổng quát

Tiền tự: ABDHIEJCFKLGM ABDHIEJCFKLGM

Trung tự: HDIBJEAKFLCGM HDIBJEAKFLCMG

Hậu tự: HIDJEBKLFMGCA HIDJEBKLFMGCA

?
1. Danh sách duyệt tiền tự và hậu tự của cây nhị phân luôn luôn giống với
danh sách duyệt của cây tổng quát. (Đúng / Sai)
2. Danh sách duyệt trung tự của cây nhị phân sẽ khác với các
duyệt tổng quát chỉ khi cây nhị phân bị khuyết con trái? (Đúng/ Sai)
B C
D E F G
H I J K M L
A
root
left right
Hình 3.7 – Cây nhị phân

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
65

3.2.3 Cài đặt cây nhị phân
a) Biểu diễn cây nhị phân bằng mảng
const maxNode = ?;
Type Item = <Kiểu dữ liệu của các >;
Node = Record
Infor : Item;{lưu giá trị của đỉnh}
Left, Right : 0 .. maxNode;{lưu chỉ danh của
con trái, con phải}
End;
Tree = Array[1 .. maxNode] of Node; {cây}
b) Biểu diễn cây nhị phân bằng con trỏ
Type Item = <Kiểu dữ liệu thông tin của đỉnh> ;
PTree = ^ Node;
Node = Record
Infor : Item;
Left, Right : PTree;{trỏ tới gốc cây con trái, con
phải}
End;
Var T : PTree; {T luôn trỏ tới gốc cây}
3.2.4 Các phép toán cơ bản trên cây nhị phân
- Ta cài đặt các phép toán với dạng biểu diễn cây bởi con trỏ, với dạng biểu diễn còn
việc cài đặt các phép toán xem như bài tập dành cho bạn đọc
- Giả sử xét cây có gốc được trỏ bởi con trỏ T, với cách khai báo như trên ta có thể
thiết kế các phép toán cơ bản trên cây nhị phân như sau:
1 - Tạo cây rỗng
Cây rỗng là một cây là không chứa một nút nào cả. Như vậy khi tạo cây rỗng ta chỉ
cần cho cây trỏ T trỏ tới giá trị Nil.
Procedure MakeNullTree(var T: PTree)
Begin
T : =Nil;
End;
2 - Kiểm tra cây rỗng
Fuction EmptyTree(T: PTree)
Begin
EmptyTree := T=nil;
End;


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
66
3 - Xác định con trái của một nút đƣợc trỏ bởi p
Function LeftChild(p: PTree): Ptree;
Begin
if (p<>NiL) then LeftChild:= p^.left
lse LeftChild := NiL;
End;
4 - Xác định con phải của một nút đƣợc trỏ bởi P
Function RightChild(p: PTree): Ptree;
Begin
if (p<>NiL) then RightChild:= p^.right
else RightChild := NiL;
End;
5 - Kiểm tra nút lá:
Nếu 1 nút là nút lá thì nó không có con, khi đó con trái và con phải của nó cùng
bằng nil
Function IsLeaf(p: PTree): boolean;
{ hàm kiểm tra nút được trỏ bởi con trỏ P có là nút lá hay không}
Begin
isLeaf := (LeftChild(p)=Nil)and(RightChild(p)=Nil);
End;
6 - Xác định số nút của cây
Function NumberNodes(T: PTree): integer;
Begin
if(EmptyTree(T)) NumberNodes:= 0
else NumberNodes:= 1+NumberNodes(LeftChild(T)+
NumberNodes(RightChild(T));
End;
7 - Tạo cây mới từ hai cây có sẵn
Cho hai cây con l, r và x là giá trị bất kỳ. Hãy tạo một cây nhị phân có
gốc lưu x và l,r là hai cây con trái, phải của gốc này

Cách giải:
- yêu cầu MT cấp phát ô nhớ để làm gốc của cây
- Đổ x vào ngăn Info của ô nhớ vừa cấp phát
- Gắn l, r tương ứng vào nhánh trái, phải của gốc



Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
67
Giải thuật:
Function Create2(v:Item,l: Ptree, r: Ptree): Ptree;
Var M: Ptree;
Begin
New(M); M^. Info :=v;
M^.left=l; M^.right=r;
Create2 := M;
End;
8 - Các thủ tục duyệt cây: tiền tự, trung tự, hậu tự
Thủ tục duyệt tiền tự
Procedure PreOrder(T: PTree)
Begin
if T<>nil then
Begin
Write(T^.Info: 5);
PreOrder(LeftChild(T));
PreOrder(RightChild(T));
End;
Thủ tục duyệt trung tự
Procedure InOrder(T: PTree)
Begin
if T<>nil then
Begin
InOrder(LeftChild(T));
Write(T^.Info: 5);
InOrder(RightChild(T));
End;
Thủ tục duyệt hậu tự
Procedure PostOrder(T: PTree)
Begin
if T<>nil then
Begin
PostOrder(LeftChild(T));
PostOrder(RightChild(T));
Write(T^.Info: 5);
End;
?Hãy sử dụng thủ tục Create2 để tạo một cây nhị phân

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
68

3.3 Cây tìm kiếm nhị phân (binary search tree)
Ta thấy rằng, nhu cầu tìm kiếm là quan trọng, trong hầu hết các hệ thống lưu
trữ, quản lý dữ liệu, thao tác tìm kiếm là thao tác thường được dùng nhất để khai thác
thông tin, đối với cây tổng quát và cây đa phân, việc tìm kiếm bị hạn chế, để tìm kiếm
ta chỉ có thể áp dụng phương pháp tìm kiếm tuần tự, vì vậy người ta đưa ra một cấu
trúc cây thỏa mãn nhu cầu tìm kiếm trên bằng cách tạo thêm một số ràng buộc trên cây
nhị phân – được gọi là: Cây tìm kiếm nhị phân (TKNP), ta có thể áp dụng phương
pháp tìm kiếm nhị phân để tìm thông tin lưu trên cây này.

3.3.1 Định nghĩa cây TKNP
Cây tìm kiếm nhị phân là một cây nhị phân thoả mãn các điều kiện sau:
Điều kiện 1:
Tất cả các khoá tại các đỉnh của cây con bên trái đều có giá trị đi trước (< ) các
khoá tại đỉnh gốc
Điều kiện 1:
Khoá tại gốc đi trước (<) tất cả các khoá ở các đỉnh của cây con bên phải
Điều kiện 3:
Cây con bên trái và cây con bên phải cũng là cây tìm kiếm nhị phân

* Ví dụ








Lưu ý:
Dữ liệu lưu trữ tại mỗi nút có thể rất phức tạp,ví dụ là một record chẳng hạn,
Khi đó, khoá của nút được tính dựa trên một trường nào đó, ta gọi là trường khoá.
Trường khoá phải chứa các giá trị có thể so sánh được, tức là nó phải lấy giá trị từ
một tập hợp có thứ tự.
Nhận xét:
- Trên cây TKNP không có hai nút cùng khoá.
- Cây con của một cây TKNP là cây TKNP.
- Khi duyệt trung tự (InOrder) cây TKNP ta được một dãy có thứ tự tăng.

10
6 15
4 8 12 23
1 5 7 9 11 14 20
Hình 3.8 – Hình ảnh 1 cây TKNP

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
69
3.3.2 Cài đặt cây tìm kiếm nhị phân
Cây TKNP, trước hết, là một cây nhị phân. Do đó ta có thể áp dụng các cách
cài đặt như đã trình bày trong phần cây nhị phân để cài đặt cây nhị phân tìm kiếm,
điều lưu ý ở đây là mỗi đỉnh trên cây phải có một thành phần khóa, xác định duy nhất
cho đỉnh đó.
Một cách cài đặt cây TKNP thường gặp là cài đặt bằng con trỏ. Mỗi nút của
cây như là một mẩu tin (record) có tối thiểu ba trường: một trường chứa khoá, hai
trường kia là hai con trỏ trỏ đến hai nút con (nếu nút con vắng mặt ta gán con trỏ bằng
NIL)
*Dạng cài đặt sử dụng con trỏ:
Type BSTree = ^ Node;
Node = Record
key : KeyType; {lưu khóa của đỉnh }
[các trường lưu thông tin khác nếu có]
Left, Right: BSTree; {trỏ tới gốc cây con trái, gốc cây con
phải}
End;
Var Root: BSTree; {trỏ tới gốc cây}
* Cài đặt sử dụng mảng – xem như bài tập dành cho bạn đọc
Nhận xét
Cây NPTK có sự khác biệt so với cây nhị phân về các phép toán. Với
cây nhị phân tìm kiếm ta có các phép toán như: tìm kiếm, thêm hoặc xoá một
nút trên cây TKNP để cây sau khi xoa phải luôn đảm bảo tính chất cuả cây TKNP.
3.3.3 Các phép toán cơ bản trên cây tìm kiếm nhị phân
1- Khởi tạo cây TKNP rỗng
Ta cho con trỏ quản lý nút gốc (Root) của cây bằng NIL.
Procedure MakeNullTree(Var Root: BSTree)
Begin
Root :=NiL;
End;
2 - Tìm kiếm một nút có khóa cho trƣớc trên cây TKNP
Để tìm kiếm 1 nút có khoá x trên cây TKNP, ta tiến hành từ nút gốc bằng cách so
sánh khoá của nút gốc với khoá x.
- Nếu nút gốc bằng NULL thì không có khoá x trên cây.
- Ngược lại, Nếu x bằng khoá của nút gốc thì giải thuật dừng và ta đã tìm được nút
chứa khoá x.
- Nếu x lớn hơn khoá của nút gốc thì ta tiến hành việc tìm khoá x trên cây con
bên phải.
- Nếu x nhỏ hơn khoá của nút gốc thì ta tiến hành việc tìm khoá x trên cây con

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
70
bên trái.
Ví dụ: tìm nút có khoá 30 trong cây ở trong Hình 3.9 như sau:











- So sánh 30 với khoá nút gốc là 20, vì 30 > 20 vậy ta tìm tiếp trên cây con
bên phải, tức là cây có nút gốc có khoá là 35.
- So sánh 30 với khoá của nút gốc là 35, vì 30 < 35 vậy ta tìm tiếp trên cây
con bên trái, tức là cây có nút gốc có khoá là 22.
- So sánh 30 với khoá của nút gốc là 22, vì 30 > 22 vậy ta tìm tiếp trên cây
con bên phải, tức là cây có nút gốc có khoá là 30.
- So sánh 30 với khoá nút gốc là 30, 30 = 30 vậy đến đây giải thuật dừng và
ta tìm được nút chứa khoá cần tìm.
Giải thuật dưới đây trả về kết quả là con trỏ trỏ tới nút chứa khoá x hoặc Nil nếu
không tìm thấy khoá x trên cây TKNP.
Procedure SEARCH ( Var P: BSTree; x: KeyType, Root: BSTree );
Var found: Boolean;
Begin
p := root;
found := false;
While ( p <> nil)And( Not found) do
if p^.key = x then found := true
else
if ( x < p^.key) then p := p^.Left
else p := p^.Right;
End;
? Cây tìm kiếm nhị phân được tổ chức như thế nào để quá trình tìm kiếm
được hiệu quả nhất?

Nhận xét:
20
10 35
5 17 22 42
15 30
Hình 3.9 – hình ảnh một cây TKNP

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
71
Giải thuật này sẽ rất hiệu quả về mặt thời gian nếu cây TKNP được tổ chức tốt,
nghĩa là cây tương đối "cân bằng". Về cây cân bằng các bạn có thể tham khảo
thêm trong các tài liệu tham khảo của môn học này.
3 - Thêm một nút có khóa cho trƣớc vào cây TKNP
* Phân tích bài toán
Theo định nghĩa cây tìm kiếm nhị phân ta thấy trên cây tìm kiếm nhị phân không
có hai nút có cùng một khoá. Do đó nếu ta muốn thêm một nút có khoá x vào cây
TKNP thì:
- Trước hết ta phải tìm kiếm để xác định có nút nào chứa khoá x chưa?
+ Nếu có thì giải thuật kết thúc (không làm gì cả!).
+ Ngược lại, sẽ thêm một nút mới chứa khoá x này.
- Thêm khoá x vào cây TKNP đảm bảo cấu trúc cây TKNP không bị phá vỡ.
Có nhiều cách để thêm, tuy nhiên để tránh phức tạp, người ta thường thực hiện
thêm ở mức lá
* Cách giải cụ thể như sau:
Ta tiến hành từ nút gốc bằng cách so sánh khóa cuả nút gốc với khoá x.
- Nếu nút gốc bằng Nil thì khoá x chưa có trên cây, do đó ta thêm một nút mới
chứa khoá x.
- Nếu x bằng khoá của nút gốc thì giải thuật dừng, trường hợp này ta không thêm nút.
- Nếu x lớn hơn khoá của nút gốc thì ta tiến hành (một cách đệ qui) giải thuật này
trên cây con bên phải.
- Nếu x nhỏ hơn khoá của nút gốc thì ta tiến hành (một cách đệ qui) giải thuật này
trên cây con bên trái.
Ví dụ: thêm khoá 19 vào cây ở trong Hình 3.9, ta làm như sau:
- So sánh 19 với khoá của nút gốc là 20, vì 19 < 20 vậy ta xét tiếp đến cây bên
trái, tức là cây có nút gốc có khoá là 10.
- So sánh 19 với khoá của nút gốc là 10, vì 19 > 10 vậy ta xét tiếp đến cây
bên phải, tức là cây có nút gốc có khoá là 17.
- So sánh 19 với khoá của nút gốc là 17, vì 19 > 17 vậy ta xét tiếp đến cây
bên phải. Nút con bên phải bằng NULL, chứng tỏ rằng khoá 19 chưa có trên
cây, ta thêm nút mới chứa khoá 19 và nút mới này là con bên phải của nút
có khoá là 17, như Hình 3.10







20
10 35
5 17 22 42
15 30
Hình 3.10 – Thêm khóa 19 vào cây TKNP
19

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
72

* Thủ tục sau đây tiến hành việc thêm một khoá vào cây TKNP.
Procedure INSERT ( Var R: pointer; x : KeyType );
Var Q: Pointer;
Begin
R := Root;
If R = nill then
begin
New (Q);
Q^.Key := x;
Q^.Left := nill;
Q^.Right := nill;
R := Q;
end
Else
With R^ Do
If (x < key) then INSERT (Lefl , x)
Else
if (x > key ) then INSERT ( Right, X )
else Writeln ( “ Cay da co Infor X ”);
End;
4 - Xóa một nút có khóa cho trƣớc ra khỏi cây TKNP
Bài toán
Input: Cây nhị phân tìm kiếm gốc T, khoá x
Output: Nếu đỉnh có khoá x có trên T thì loại bỏ đỉnh này sao cho T sau khi loại
bỏ x vẫn là cây TKNP
Cách giải
- Nếu không tìm thấy nút chứa khoá x thì giải thuật kết thúc.
- Nếu tìm gặp nút được trỏ bởi P có chứa khoá x, ta có ba trường hợp sau:
TH1 : Nếu p là lá: ta thay nó bởi Nil.
TH2 : Nếu p có một trong 2 con là C (rỗng):
- Treo cây con khác C vào vị trí của p (như hình dưới)
- Giải phóng vùng nhớ được trỏ bởi p





Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
73










TH3: Đỉnh loại bỏ được trỏ bởi P có 2 con đều khác rỗng:
Thay nút được trỏ bởi p bởi nút lớn nhất trên cây con trái của nó (nút cực phải của
cây con trái) hoặc là nút bé nhất trên cây con phải của nó (nút cực trái của cây con
phải). Rồi xóa nút cực phải (hoặc nút cực trái ), việc xoá nút này sẽ rơi vào một trong
2 trường hợp ở trên (TH1 hoặc TH2)
Trong hình dưới đây, ta thay x bởi khoá của nút cực trái của cây con bên phải rồi
ta xoá nút cực trái này.













Các giải thuật:
a) Giải thuật xoá một nút có khoá nhỏ nhất (nút cực trái của một cây )
Hàm dưới đây trả về khoá của nút cực trái, đồng thời xoá nút này.
Funciton DeleteMin (var Root:Ptree):keytype;
Var k: Keytype;
Begin
if (Root^.left = Nil)then
begin
T
¬
¬
¬
¬
¬
¬
X
M
T
1
P

T
¬
¬
¬
¬
¬
¬
M
T
1

T
¬
¬
¬
¬
¬
¬
X
M
T
1
P

T
2
y
T
¬
¬
¬
¬
¬
¬
y
M
T
1
P

T
2
x

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
74
k :=Root^.key;
Root := Root^.right;
DeleteMin := k;
End
else DeleteMin := DeleteMin(Root^.left);
End;
b) Giải thuật xóa một nút có khoá x cho trƣớc trên cây TKNP
Procedure DeleteNode(X: keytype,var Root: PTree);
Begin
if (Root <>Nil) then
if (x < Root^.Key) then DeleteNode(x,Root^.left)
else
if (x > Root^.Key)then DeleteNode(x,Root^.right)
else
if (Root^.left=Nil)and(Root^.right=Nil)then
Root :=Nil;
else
if (Root^.left = Nil)then
Root = Root^.right
else
if (Root^.right=Nil)then
Root := Root^.left
else {cả hai con đều khác rỗng}
Root^.Key = DeleteMin(Root->right);
End;

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
75
CHƢƠNG 4 Mô hình dữ liệu Đồ thị (Graph)
Tổng quan:
1. Mục đích và yêu cầu:
Sau khi học xong chương này, sinh viên nắm vững và cài đặt được các kiểu dữ
liệu trừu tượng đồ thị và vận dụng để giải những bài toán thực tế.
2.Nội dung chính
Trong chương này chúng ta sẽ nghiên cứu một số kiểu dữ liệu trừu tượng cơ
bản như sau:
-Các khái niệm cơ bản
- Kiểu dữ liệu trừu tượng đồ thị
- Biểu diễn đồ thị
- Các phép duyệt đồ thị
- Một số bài toán trên đồ thị
4.1 Định nghĩa đồ thị và các khái niệm
- Một đồ thị G bao gồm một tập hợp V các đỉnh và một tập hợp E các cung ,
ký hiệu G=(V,E).
- Các đỉnh còn được gọi là nút (node) hay điểm (point).
- Các cung nối giữa hai đỉnh, hai đỉnh này có thể trùng nhau.
- Hai đỉnh có cung nối nhau gọi là hai đỉnh kề (adjacency).
- Một cung nối giữa hai đỉnh v, w có thể coi như là một cặp điểm (v,w). Nếu cặp
này có thứ tự thì ta có cung có thứ tự (cung), ngược lại thì cung không có thứ tự
(cạnh). Nếu các cung trong đồ thị G có thứ tự thì G gọi là đồ thị có hướng
(directed graph). Nếu các cung trong đồ thị G không có thứ tự thì đồ thị G là đồ thị
vô hướng (undirected graph).
Trong các phần sau này ta dùng từ đồ thị (graph) để nói đến đồ thị nói chung,
khi nào cần phân biệt rõ ta sẽ dùng đồ thị có hướng, đồ thị vô hướng. Hình V.1a
cho ta một ví dụ về đồ thị có hướng, hình V.1b cho ví dụ về đồ thị vô hướng.
Trong các đồ thị này thì các vòng tròn được đánh số biểu diễn các đỉnh, còn các
cung được biểu diễn bằng các đoạn thẳng có hướng (trong Hình 4.1) hoặc không
có hướng (trong Hình 4.2).






Thông thường trong một đồ thị, các đỉnh biểu diễn cho các đối tượng còn các
cung biểu diễn mối quan hệ (relationship) giữa các đối tượng đó. Chẳng hạn các
đỉnh có thể biểu diễn cho các thành phố còn các cung biểu diễn cho đường giao
1 2
4 3
1 2
4 3
Hình 4.1 Hình 4.2

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
76
thông nối giữa hai thành phố.
- Một đường đi (path) trên đồ thị là một dãy tuần tự các đỉnh v
1
, v
2
,..., v
n

sao cho (v
i
,v
i+1
) là một cung trên đồ thị (i=1,...,n-1). Đường đi này là đường đi từ
v
1
đến v
n
và đi qua các đỉnh v
2
,...,v
n-1
. Đỉnh v
1
còn gọi là đỉnh đầu, v
n
gọi là đỉnh
cuối. Độ dài của đường đi này bằng (n-1). Trường hợp đặc biệt dãy chỉ có một
đỉnh v thì ta coi đó là đường đi từ v đến chính nó có độ dài bằng không. Ví dụ
dãy 1,2,4 trong đồ thị Hình 4.1 là một đường đi từ đỉnh 1 đến đỉnh 4, đường đi
này có độ dài là hai.
- Đường đi gọi là đơn (simple) nếu mọi đỉnh trên đường đi đều khác nhau, ngoại
trừ đỉnh đầu và đỉnh cuối có thể trùng nhau. Một đường đi có đỉnh đầu và đỉnh
cuối trùng nhau gọi là một chu trình (cycle). Một chu trình đơn là một đường đi
đơn có đỉnh đầu và đỉnh cuối trùng nhau và có độ dài ít nhất là 1. Ví dụ trong
Hình 4.1 thì 3, 2, 4, 3 tạo thành một chu trình có độ dài 3. Trong Hình 4.2 thì
1,3,4,2,1 là một chu trình có độ dài 4.
Trong nhiều ứng dụng ta thường kết hợp các giá trị (value) hay nhãn (label) với
các đỉnh và/hoặc các cạnh, lúc này ta nói đồ thị có nhãn. Nhãn kết hợp với các
đỉnh và/hoặc cạnh có thể biểu diễn tên, giá, khoảng cách,... Nói chung nhãn có thể
có kiểu tuỳ ý. Ở đây nhãn là các giá trị số nguyên biểu diễn cho giá cước vận
chuyển một tấn hàng giữa các thành phố 1, 2, 3, 4 chẳng hạn.
Đồ thị con của một đồ thị G=(V,E) là một đồ thị G'=(V',E') trong đó:
V‟cV và E‟ gồm tất cả các cạnh (v,w) e E sao cho v,w e V‟.
4.2 Các phép toán cơ bản trên đồ thị
Các phép toán được định nghĩa trên đồ thị là rất đơn giản như là:
- Đọc nhãn của đỉnh.
- Đọc nhãn của cạnh.
- Thêm một đỉnh vào đồ thị.
- Thêm một cạnh vào đồ thị.
- Xoá một đỉnh.
- Xoá một cạnh.
- Lần theo (navigate) các cung trên đồ thị để đi từ đỉnh này sang đỉnh khác.
Thông thường trong các giải thuật trên đồ thị, ta thường phải thực hiện một thao tác
nào đó với tất cả các đỉnh kề của một đỉnh, tức là một đoạn giải thuật có dạng sau:
For (mỗi đỉnh w kề với v)
{ thao tác nào đó trên w }
Để cài đặt các giải thuật như vậy ta cần bổ sung thêm khái niệm về chỉ số của các đỉnh
kề với v. Hơn nữa ta cần định nghĩa thêm các phép toán sau đây:
- FIRST(v) trả về chỉ số của đỉnh đầu tiên kề với v. Nếu không có đỉnh nào
kề với v thì $ được trả về. Giá trị $ được chọn tuỳ theo cấu trúc dữ liệu cài đặt
đồ thị.
- NEXT(v,i) trả về chỉ số của đỉnh nằm sau đỉnh có chỉ số i và kề với v. Nếu

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
77
không có đỉnh nào kề với v theo sau đỉnh có chỉ số i thì $ được trả về.
- VERTEX(i) trả về đỉnh có chỉ số i.
4.3 Biểu diễn đồ thị
Một số cấu trúc dữ liệu có thể dùng để biểu diễn đồ thị. Việc chọn cấu trúc dữ
liệu nào là tuỳ thuộc vào các phép toán trên các cung và đỉnh của đồ thị. Hai cấu
trúc thường gặp là biểu diễn đồ thị bằng ma trận kề (adjacency matrix) và biểu
diễn đồ thị bằng danh sách các đỉnh kề (adjacency list).
4.3.1. Biểu diễn đồ thị bằng ma trận kề
Ta dùng một mảng hai chiều, chẳng hạn mảng A, kiểu boolean để biểu diễn các
đỉnh kề. Nếu đồ thị có n đỉnh thì ta dùng mảng A có kích thước nxn. Giả sử các
đỉnh được đánh số 1..n thì A[i,j] = true, nếu có đỉnh nối giữa đỉnh thứ i và đỉnh
thứ j, ngược lại thì A[i,j] = false. Rõ ràng, nếu G là đồ thị vô hướng thì ma trận
kề sẽ là ma trận đối xứng.
Chẳng hạn đồ thị trong Hình 4.2 có biểu diễn ma trận kề như sau:
j i 1 2 3 4
1
true true true false
2 true true true true
3 true true true true
4 false true true true
Ta cũng có thể biểu diễn true là 1 còn false là 0. Với cách biểu diễn đồ thị bằng ma
trận kề như trên chúng ta có thể định nghĩa chỉ số của đỉnh là số nguyên xác định
duy nhất đỉnh đó.
a) Dạng cài đặt: xem như bài tập dành cho bạn đọc
b) Cài đặt các phép toán: xem như bài tập dành cho bạn đọc
Trên đồ thị có nhãn (trọng số) thì ma trận kề có thể dùng để lưu trữ nhãn của
các cung chẳng hạn cung giữa i và j có nhãn a thì A[i,j]=a. Ma trận A còn được gọi
là ma trận trọng số.
Ở đây các cặp đỉnh không có cạnh nối thì ta gán cho nó giá trị mặc định
Nhận xét:
Cách biểu diễn đồ thị bằng ma trận kề cho phép kiểm tra một cách trực tiếp hai
đỉnh nào đó có kề nhau không. Nhưng nó phải mất thời gian duyệt qua toàn bộ
mảng để xác định tất cả các cạnh trên đồ thị. Thời gian này độc lập với số cạnh và
số đỉnh của đồ thị. Ngay cả số cạnh của đồ thị rất nhỏ chúng ta cũng phải cần một
mảng N x N phần tử để lưu trữ. Do vậy, nếu ta cần làm việc thường xuyên với
các cạnh của đồ thị thì ta có thể phải dùng cách biểu diễn khác cho thích hợp hơn.
4.3.2. Biểu diễn đồ thị bằng danh sách các đỉnh kề
Trong cách biểu diễn này, ta sẽ lưu trữ các đỉnh kề với một đỉnh i trong một danh
sách liên kết theo một thứ tự nào đó. Như vậy ta cần một mảng một chiều G có n
phần tử để biểu diễn cho đồ thị có n đỉnh. G[i] là con trỏ trỏ tới danh sách các đỉnh

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
78
kề với đỉnh i.
a) Dạng cài đặt: Bạn đọc có thể tham khảo trong tài liệu tham khảo đính kèm với
bài giảng
b) Cài đặt các phép toán: Xem như bài tập dành cho bạn đọc
4.4 Các phép duyệt đồ thị (TRAVERSALS OF GRAPH)
Trong khi giải quyết các vấn đề thực tế, nhiều bài toán được mô hình hoá
bằng mô hình đồ thị, khi xây dựng các thao tác xử lý trên đồ thị ta
cần đi qua các đỉnh và các cung của đồ thị một cách có hệ thống. Việc đi qua
các đỉnh của đồ thị một cách có hệ thống như vậy gọi là duyệt đồ thị. Có hai
phép duyệt đồ thị phổ biến đó là duyệt theo chiều sâu và duyệt theo chiều rộng.
4.4.1. Duyệt theo chiều sâu (depth-first search)
Giả sử ta có đồ thị G=(V,E) với các đỉnh ban đầu được đánh dấu là chưa duyệt
(unvisited). Từ một đỉnh v nào đó ta bắt đầu duyệt như sau: đánh dấu v đã duyệt,
với mỗi đỉnh w chưa duyệt kề với v, ta thực hiện đệ qui quá trình trên cho w. Sở dĩ
cách duyệt này có tên là duyệt theo chiều sâu vì nó sẽ duyệt theo một hướng nào đó
sâu nhất có thể được.
4.4.2. Duyệt theo chiều rộng (breadth-first search)
Giả sử ta có đồ thị G với các đỉnh ban đầu được đánh dấu là chưa duyệt
(unvisited). Từ một đỉnh v nào đó ta bắt đầu duyệt như sau: đánh dấu v đã
được duyệt, kế đến là duyệt tất cả các đỉnh kề với v. Khi ta duyệt một đỉnh
v rồi đến đỉnh w thì các đỉnh kề của v được duyệt trước các đỉnh kề của w,
vì vậy ta dùng một hàng để lưu trữ các nút theo thứ tự được duyệt để có thể
duyệt các đỉnh kề với chúng. Ta cũng dùng mảng một chiều mark để đánh
dấu một nút là đã duyệt hay chưa, tương tự như duyệt theo chiều sâu.
4.4. Một số bài toán ứng dụng trên đồ thị:
Xem như phần bài tập dành cho bạn đọc



Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
79
Chƣơng 5 Mô hình dữ liệu Tập hợp
Tổng quan:
1. Mục tiêu
Sau khi học xong chương này, sinh viên phải:
- Nắm vững khái niệm về kiểu dữ liệu trừu tượng tập hợp và một số loại tập hợp
đặc biệt như từ điển, bảng băm, hàng ưu tiên.
- Cài đặt tập hợp và các loại tập hợp đặc biệt bằng ngôn ngữ lập trình cụ thể.
2. Nội dung chính
Trong chương này chúng ta sẽ nghiên cứu các vấn đề sau:
- Khái niệm tập hợp
- Kiểu dữ liệu trừu tượng tập hợp.
- Cài đặt tập hợp
- Từ điển
- Cấu trúc bảng băm
- Hàng ưu tiên.
5.1. Khái niệm tập hợp
Tập hợp trong toán học là một cấu trúc rời rạc cơ bản để từ đó dựng lên các cấu
trúc rời rạc khác như: Các tổ hợp – là những tập hợp không sắp thứ tự của các phần tử;
đồ thi – là tập hợp các đỉnh và các cạnh nối các đỉnh đó, cây – là một tập hợp các đỉnh
và các cạnh nối hai đỉnh có quan hệ phân cấp, ..... Tập hợp được dùng để mô hình hoá
hay biểu diễn một nhóm bất kỳ các đối tượng trong thế giới thực cho nên nó đóng
vai trò rất quan trọng trong mô hình hoá cũng như trong thiết kế các giải thuật.
Các tập hợp dùng để nhóm các đối tượng lại với nhau, thường thường các đối
tượng trong tập hợp có tính chất tương tự nhau. Ví dụ: Tất cả các sinh viên vừa mới
nhập trường lập nên một tập hợp, hoặc các dinh viên đang học môn CTDL& thuật
toán lập nên một tập hợp, hoặc những sinh viên vừa mới nhập học và những sinh
viên đang học môn CTDL & TT cũng lập nên một tập hợp
Định nghĩa tập hợp: Các đối tượng trong tập hợp cũng được gọi là các phần tử
trong tập hợp đó. Tập hợp được nói là chứa các phần tử của nó, tập hợp thường dùng
để nhóm các phần tử có các tính chất chung lại với nhau, nhưng nó cũng có thể chứa
các phần tử chẳng có mối quan hệ gì với nhau. Ví dụ: A={a,2,Fred, Lan}. Một tập hợp
có thể là vô hạn hoặc hữu hạn
Để mô tả một tập hợp, trong toán học thường có các cách sau để xác định một tập hợp
A :
- Dùng biểu đồ Ven là một đường cong khép kín, các điểm trong đường cong
đó chỉ các phần tử của tập hợp.
- Liệt kê tất cả các phần tử của tập hợp A. Ví dụ: A = {1, 2, 3}
- Nêu lên các đặc trưng cho biết chính xác một đối tượng bất kỳ có là một phần
tử của tập A hay không. Ví dụ: A = {x | x là số nguyên chẵn}
Tập hợp có thể có thứ tự hoặc không có thứ tự, tức là, có thể có quan hệ thứ tự xác

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
80
định trên các phần tử của tập hợp hoặc không. Tuy nhiên, trong chương này, chúng
ta giả sử rằng các phần tử của tập hợp có thứ tự tuyến tính, tức là, trên tập hợp S có
quan hệ < và = thoả mản hai tính chất:
Với mọi a,b e S thì a<b hoặc b<a hoặc a=b
Với mọi a,b,c e S, a<b và b<c thì a<c
5. 2 Mô hình dữ liệu tập hợp
Trong thiết kế thuật toán, có thể sử dụng tập hợp như một mô hình dữ liệu. Khi
đó, ngoài các phép toán hợp, giao, hiệu, chúng ta phải cần đến nhiều các phép toán
khác. Sau đây chúng ta sẽ đưa ra một số phép toán cơ bản quan trọng nhất, các phép
toán này sẽ được mô tả bởi các thủ tục hoặc hàm:
1- Thủ tục UNION(A,B,C) nhận vào 3 tham số là A,B,C; Thực hiện phép toán lấy
hợp của hai tập A và B và trả ra kết quả là tập hợp C = A B.
2 - Thủ tục INTERSECTION(A,B,C) nhận vào 3 tham số là A,B,C; Thực hiện
phép toán lấy giao của hai tập A và B và trả ra kết quả là tập hợp C = A ∩ B.
3 - Thủ tục DIFFERENCE(A,B,C) nhận vào 3 tham số là A,B,C; Thực hiện phép
toán lấy hợp của hai tập A và B và trả ra kết quả là tập hợp C = A\B
5 - Hàm MEMBER(x,A) cho kết quả kiểu logic (đúng/sai) tùy theo x có thuộc
A hay không. Nếu x e A thì hàm cho kết quả là đúng, ngược lại cho kết quả sai.
5 - Thủ tục MAKENULLSET(A) tạo tập hợp A tập rỗng
6 - Thủ tục INSERTSET(x,A) thêm x vào tập hợp A
7 - Thủ tục DELETESET(x,A) xoá x khỏi tập hợp A
8 - Thủ tục ASSIGN(A,B) gán A cho B ( tức là B:=A )
9 - Hàm MIN(A) cho phần tử bé nhất trong tập A
10 - Hàm EQUAL(A,B) cho kết quả TRUE nếu A=B ngược lại cho kết quả FALSE
=> Vấn đề tiếp theo đặt ra là: Ta cần biểu diễn tập hợp như thế nào trong máy tính để
các phép toán được thực hiện với hiệu quả cao
5.3 Cài đặt tập hợp và cài đặt các phép toán trên tập hợp
Các phần tử của tập hợp có thể là các đối tượng có kiểu dữ liệu cơ bản hoặc là các đối
tương có kiểu dữ liệu phức tạp, các đối tượng này có thể được biểu diễn bởi bản ghi,
các trường là các thuộc tính của đối tượng. Khi đó ta có thể mô tả kiểu dữ liệu của các
phần tử của tập hợp như sau:
Type elementtype = record
Key : Keytype;
[ Các dữ liệu trường khác nếu có]
end;
Trong đó: Key (khoá): Để xác định duy nhất một đối tượng.
Keytype : Kiểu dữ liệu của khoá
Có nhiều phương pháp để cài đặt mô hình dữ liệu tập hợp trong MT (cho ta các CTDL
tập hợp khác nhau). Trong từng áp dụng, tuỳ thuộc vào các phép toán cần thực hiện,

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
81
giá trị các phần tử trong tập hợp và kích cỡ (số các phần tử của tập hợp) mà ta lựa
chọn cách cài đặt sao cho các phép toán thực hiện có hiệu quả nhất.
Chẳng hạn nếu chúng ta thường xuyên sử dụng phép thêm vào và loại bỏ các
phần tử trong tập hợp thì chúng ta sẽ tìm cách cài đặt hiệu quả cho các phép toán
này. Còn nếu phép tìm kiếm một phần tử xảy ra thường xuyên thì ta có thể phải tìm
cách cài đặt phù hợp để có hiệu quả tốt nhất.
Ta xét các phương pháp cài đặt tập hợp sau:






5.3.1.Cài đặt tập hợp bởi vectơ bit
+ Giả sử, xét tập hợp A gồm các số nguyên thuộc phạm vi từ 1 đến n (hoặc được
mã hóa thành các số nguyên thuộc phạm vi từ 1 -> n). Khi đó ta có thể dùng véc tơ
bit (mảng boolean) để biểu diễn tập A : (A[1], A[2], ... ,A[n]), trong đó thành phần
thứ i : A[i] = true nếu i e A, A[i] = false nếu ieA
+ Cách cài đặt:
const n = ...;
type set = array[1..n] of boolean;
var A, B, C : set;
x : 1..n;
Ví dụ: Giả sử các phần tử của tập hợp được lấy trong các số nguyên từ 1 đến 10
, khi đó tập hợp được biểu diễn bởi một mảng một chiều có 10 phần tử với các giá
trị phần tử thuộc kiểu logic. Chẳng hạn tập hợp A={1,3,5,8} được biểu diễn trong
mảng có 10 phần tử như sau:
1 2 3 4 5 6 7 8 9 10
1 0 1 0 1 0 0 1 0 0
Nhận xét:
- Với cách khai báo này các phép toán trên tập được thực hiện dễ dàng bằng cách sử
dụng các phép toán logic trong ngôn ngữ lập trình. Ví dụ thêm x vào A chỉ việc cho
A[x] := true , để xác định xem x có là phần tử của tập A không ta chỉ cần biết A[x] là
true hay false, ……. ;
Ta xét phép toán hợp 2 tập hợp A, B thành tập C:
Procedure Union(A,B : set ; var C : set);
var i : integer;
begin for i:=1 to n do
C[i] := A[i] or B[i];
Danh sách kế tiếp (mảng)(2)
Cài đặt tập hợp bởi
danh sách
Cài đặt tập hợp bởi véc tơ bit (1)
Danh sách liên kết (3)
Danh sách được sắp (5)

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
82
end;
Các phép toán giao, hiệu, việc kiểm tra một phần tử có thuộc tập hợp hay không, thủ
tục thêm một phần tử vào tập hợp, xóa một phần tử ra khỏi tập hợp, .... cũng rất đơn
giản và xem như bài tập dành cho bạn đọc.
5.3.2 Cài đặt tập hợp bởi mảng
- Giả sử số phần tử của tập hợp không vượt quá một hằng nào đó Maxsize, khi đó ta có
thể cài đặt tập hợp bởi mảng như sau:
const Maxsize = <kích cỡ tối đa của tập hợp> ;
type
set = record
last : integer; {last: ghi chỉ số của phần tử cuối cùng của tập hợp}
elements : array[1..n] of elementype;{mảng các phần tử của tập hợp}
end;
Nhận xét:
Do kích thước mảng bị hạn chế, việc thực hiện các phép tính hợp, chèn phần tử
vào tập hợp có thể dẫn đến một tập hợp có số phần tử vượt quá cỡ của mảng. Do đó
khi sử dụng cách cài đặt này ta phải đặt Maxsize cho phù hợp để tiết kiệm bộ nhớ và
tránh bị tràn
- Viết các thủ tục ứng với các phép toán: Xem như bài tập dành cho bạn đọc
5.3.3 Cài đặt bởi danh sách liên kết hoặc danh sách đƣợc sắp
Tập hợp cũng có thể cài đặt bằng danh sách liên kết, trong đó mỗi phần tử của
danh sách là một thành viên của tập hợp. Không như biểu diễn bằng vectơ bít, sự
biểu diễn này dùng kích thước bộ nhớ tỉ lệ với số phần tử của tập hợp chứ không
phải là kích thước đủ lớn cho toàn thể các tập hợp đang xét. Hơn nữa, ta có thể biểu
diễn một tập hợp bất kỳ. Mặc dù thứ tự của các phần tử trong tập hợp là không quan
trọng nhưng nếu một danh sách liên kết có thứ tự nó có thể trợ giúp tốt cho các
phép duyệt danh sách. Chẳng hạn nếu tập hợp A được biểu diễn bằng một danh
sách có thứ tự tăng thì hàm MEMBER(x,A) có thể thực hiện việc so sánh x một
cách tuần tự từ đầu danh sách cho đến khi gặp một phần tử y ≥ x chứ không cần so
sánh với tất cả các phần tử trong tập hợp.
Một ví dụ khác, chẳng hạn ta muốn tìm giao của hai tập hợp A và B có n phần tử.
Nếu A,B biểu diễn bằng các danh sách liên kết chưa có thứ tự thì để tìm giao của A
và B ta phải tiến hành như sau:
for (mỗi x thuộc A )
{ Duyệt danh sách B xem x có thuộc B không. Nếu có thì x thuộc giao của hai
tập hợp A và B; }
Rõ ràng quá trình này có thể phải cần đến n x m phép kiểm tra (với n,m là độ dài
của A và B).
Nếu A,B được biểu diễn bằng danh sách có thứ tự tăng thì đối với một phần tử e∈ A
ta chỉ tìm kiếm trong B cho đến khi gặp phần tử x ≥ e. Quan trọng hơn nếu f

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
83
đứng ngay sau e trong A thì để tìm kiếm f trong B ta chỉ cần tìm từ phần tử x trở
đi chứ không phải từ đầu danh sách lưu trữ tập hợp B.
=> Việc cài đặt tập hợp bởi danh sách liên kết sẽ khắc phục hạn chế về không gian khi
sử dụng mảng. Tuy nhiên trong cách cài đặt này, việc thực hiện các phép toán trên tập
hợp sẽ phức tạp hơn
a) Dạng cài đặt:
type Set = ^ Cell;
Cell = record
element : elementtype;
next : Set;
end;
var A, B, C : Set;
Các tập hợp A, B, C sẽ được biểu diễn bởi các danh sách liên kết, trong đó các con trỏ
A,B,C sẽ trỏ tới đầu của các danh sách đó
b) Các phép toán cơ bản trên tập hợp
1 - Thủ tục UNION
Giải sử xét phép tìm hợp của hai tập A và B, kết quả là tập C
Cách làm:
Muốn tìm hợp của A và B, ta chép B vào C, sau đó duyệt A, với mỗi e của A
mà e không thuộc C thì đưa nó vào C.
Giải thuật:
Procedure UNION(A, B: pointer; Var C: pointer);
var Ap,Bp,Cp,Ck : pointer;
found : boolean;
Begin
C := nil; Bp := B;
while Bp <> nil do
begin
new(Cp);
Cp^.element := Bp^.element;
Cp^.next := C;
C := Cp;
end;
Ap := A;
while Ap <> nil do
begin
Ck := C;
found := false;

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
84
while (Ck <> nil) and (not found) do
if Ap^.element = Ck^.element then found := true
else Ck := Ck^.next;
if not found then
begin
new(Cp);
Cp^.element := Ap^.element;
Cp^.next := C;
C := Cp;
end;
Ap := Ap^.next;
end;
end;
2 - Thủ tục INTERSECTION
Giải sử xét phép tìm giao của hai tập A và B, kết quả là tập C
Để tìm giao của A và B ta duyệt tập A, với mỗi phần tử e của A ta tìm nó trong B,
nếu thấy thì đưa vào C, nếu không thấy thì duyệt phần tử tiếp theo trong A.
3- Thủ tục gán ASSIGN(A,B): Chép các các phần tử của tập A sang tập B,
Duyệt các lần lượt các phần tử trong A để chép sang B
5 – Hàm MIN(A):Tìm phần tử nhỏ nhất trong tập A
Hàm trả ra phần tử đầu danh sách (Nếu danh sách được sắp tăng dần theo giá trị của
các phần tử).
5 - Toán tử DELETESET là hoàn toàn giống như DELETE_LIST.
6 - Phép INSERTSET(x,A) cũng tương tự INSERT_LIST tuy nhiên ta phải chú
ý rằng khi xen x vào A phải đảm bảo thứ tự của danh sách được sắp.
7 - Thủ tục DIFFERENCE(A,B,C) nhận vào 3 tham số là A,B,C; Thực hiện phép
toán lấy hợp của hai tập A và B và trả ra kết quả là tập hợp C = A\B
Cách làm:
Duyệt các phần tử trong A, với mỗi phần tử e e A, ta kiểm tra xem e có thuộc B
không? Nếu không thuộc B, ta thêm e vào C, ngược lại ta duyệt phần tử e tiếp
theo trong A
8 - Hàm MEMBER(x,A) cho kết quả kiểu logic (đúng/sai) tùy theo x có thuộc
A hay không. Nếu x e A thì hàm cho kết quả là đúng, ngược lại cho kết quả sai.
Cách làm:
Duyệt danh tử đầu đến cuối tập A hoặc duyệt đến khi tìm thấy x thì dừng.
9 - Thủ tục MAKENULLSET(A) tạo tập hợp A tập rỗng: A:= nil
10 - Hàm EQUAL(A,B) cho kết quả TRUE nếu A=B ngược lại cho kết quả FALSE:
Kiểm tra các phần tử trong A có thuộc B không và ngược lại để kết luận A có bằng B

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
85
không.
=> Việc cài đặt cụ thể các phép toán từ 2->10 xem như bài tập dành cho bạn đọc
5.5 Từ điển (Dictionary)
Từ điển là một kiểu dữ liệu trừu tượng, là một tập hợp đặc biệt, trong đó chúng
ta chỉ quan tâm đến các phép toán InsertSet, DeleteSet, Member và MakeNullSet. Sở
dĩ chúng ta nghiên cứu từ điển là do trong nhiều ứng dụng không sử dụng đến các
phép toán hợp, giao, hiệu của hai tập hợp. Ngược lại ta cần một cấu trúc làm sao
cho việc tìm kiếm, thêm và bớt phần tử có phần hiệu quả nhất gọi là từ điển. Chúng
ta cũng chấp nhận MakeNullSet như là phép khởi tạo cấu trúc từ điển.
5.5.1 Từ điển là gì?
Mô hình dữ liệu tập hợp, nhưng chỉ xét đến các phép toán Insert (thêm một
phần từ vào tập hợp), Delete (loại bỏ một phần tử nào đó khỏi tập hợp), Member (tìm
xem trong tập hợp có chứa một phần tử nào đó không) được gọi là kiểu dữ liệu trừu
tượng từ điển (Dictionary)
5.5.2 Các phƣơng pháp cài đặt từ điển
Từ điển là một tập hợp, đương nhiên chúng ta phải sử dụng các phương pháp cài đặt
tập hợp để cài đặt từ điển:
- Bằng véc tơ bit: Sử dụng phương pháp này khi từ điển là tập hợp gồm các
phần tử có thể dùng làm tập chỉ số cho mảng (thuộc kiểu dữ liệu vô hướng đếm
được hoặc được mã hóa thành kiểu dữ liệu vô hướng đếm được)
- Bằng danh sách ( kế tiếp hoặc móc nối), không thuận lợi cho phép tìm
kiếm(với danh sách móc nối vì phải truy cập tuần tự -> chí thích hợp khi áp
dụng phương pháp tìm kiếm tuần tự), không thuận lơij cho phép toán thêm vào
và lấy ra (với danh sách kế tiếp – vì có hiện tượng co, dãn, dịch chuyển các
phàn tử khác)
- Giả sử từ điển là danh sách được sắp thứ tự tuyến tính -> sử dụng cây tìm
kiếm nhị phân: Độ phức về mặt thời gian của các phép toán trên từ điển là lớn
(tương tự như danh sách ) trong trường hợp cây suy biến thành danh sách.
- Ta cũng có thể sử dụng cây cần bằng để biểu diễn tập hợp: Ưu điểm là không
xảy ra trường hợp suy biến như cây TKNP, tuy nhiên các phép toán loại bỏ và
xen vào trên cây cân bằng khá phức tạp, vì phải cân bằng lại cây
Do đó, để biểu diễn từ điển thuận lợi cho các phép toán, và có thể áp dụng cho
tập hợp có kích cỡ lớn (từ điểm thường có kích thước lớn), ta xét cách cài đặt
khác đó là: sử dụng CTDL bảng băm để cài đặt từ điển
5.5.3 Cấu trúc dữ liệu Bảng băm, cài đặt từ điển bởi bảng băm
Băm là phương pháp rất thích hợp để cài đặt tập hợp có số phần tử lớn(từ điển). Có 2
phương pháp băm khác nhau:
+ Phương pháp Băm mở: Cho phép sử dụng một không gian không hạn chế để
lưu giữ các phần tử của tập hợp
+ Phương pháp băm đóng: Sử dụng một không gian cố định và do đó tập hợp
được cài đặt phải có cỡ không vượt quá không gian cho phép

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
86
5.5.3.1. Cài đặt từ điển bằng bảng băm mở
a) Định nghĩa bảng băm mở
- Tư tưởng cơ bản của bảng Băm là: Phân chia một tập hợp đã cho thành một số cố
định các lớp (N lớp) được đánh số 0, 1, …, N-1. Sử dụng mảng T với chỉ số chạy từ 0
đến N-1. Mỗi thành phần T[i] chứa một con trỏ, trỏ tới phần tử đầu tiên của danh sách
chứa các phần tử của tập hợp thuộc lớp thứ i. Các phần tử của tập hợp thuộc mỗi lớp
được tổ chức dưới dạng một danh sách liên kết, mỗi danh sách được gọi là con tr“rổ” ,
T được gọi là bảng Băm (hash table).










- Việc chia các phần tử của tập hợp vào các lớp được thực hiện bời hàm băm
(hash function) h.
b) Hàm băm:
Hàm băm là một ánh xạ từ tập dữ liệu A đến các số nguyên 0..N-1:
h : A → 0..N-1;
Theo đó giả sử x e A thì h(x) là một số nguyên sao cho: 0≤h(x) ≤N-1.
- Có 2 tiêu chuẩn chính để lựa chọn một hàm băm:
+Hàm băm phải cho phép tính được dễ dàng và nhanh chóng giá trị Băm của
mỗi khoá
+ Nó phải phân bố đều các khoá vào các rổ
Có nhiều cách để xây dựng hàm băm, cách đơn giản nhất là „nguyên hóa x „ và sau
đó lấy h(x) = x % N.
Ví dụ : Cho tập hợp A = {1, 5, 7, 2, 5, 15}








Nil
nil
0
1
n-1
Hình 1: Cấu trúc bảng băm mở

Nil

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
87
Bảng băm là mảng gồm 5 phần tử và hàm băm h(x) = x % 5; Ta có bảng băm lưu trữ
A như sau :















Hàm băm có thể đƣợc thiết kế nhƣ sau
{Ham bam h(X)=X Mod B}
Function h(X: KeyType): 0..N-1;
Begin
H := X%B;
End;
Ví dụ: Viết một hàm Băm trong Pascal (sử dụng phương pháp lấy phần dư) để băm
các khoá là các xâu ký tự có độ dài 10 thành các giá trị từ 1 đến N-1
Type KeyType = String[10];
Function h (x: KeyType): 0 .. N-1;
Var I, Sum: integer;
Begin
Sum:= 0;
For I = 1 to 10 do
Sum := Sum + ord(x[i]);
h := Sum mod N;
End;
c) Cài đặt từ điển bởi bảng băm mở:
Const N = …;
Type pointer = ^ Element;


Bảng băm mở
Bảng băm chứa các con
trỏ, trỏ tới các phần tử
đầu mỗi danh sách
Danh sách của mỗi “rổ”


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
88
Element = record
key : KeyType;
[data: ElementType]; {các trường thông tin khác
nếu có}
Next : Pointer;
End;
Dictionary = array [0 .. N-1] of pointer;
Var T: Dictionary;
d) Cài đặt các phép toán trên từ điển
1 - Khởi tạo bảng băm mở rỗng
Lúc này tất cả các “rổ” là rỗng nên ta gán tất cả các con trỏ trỏ đến đầu các danh
sách trong mỗi rổ là Nil.
Procedure MakeNullSet( var D: Dictionary)
Var i: integer;
Begin
For i:=0 to B-1do
D[i] := Nil;
End;
2 - Kiểm tra một thành viên có trong từ điển không
Để kiểm tra xem phần tử x nào đó có trong từ điển hay không, ta giá trị băm
(h(x)) của nó. Theo cấu trúc của bảng băm thì khoá x sẽ nằm trong “rổ” được trỏ
bởi T[h(x)]. Như vậy để tìm khoá x trước hết ta phải tính h(x) sau đó duyệt danh
sách của “rổ” được trỏ bởi T[h(x)] để tìm x.
Giải thuật nhƣ sau:
Function Member(x: KeyType; Var T: Dictionary): boolean;
Var P: ponter; found: boolean;
Begin
P := T[h(x)];
Found := false;
While (p<>nil)and(not found)do
If P^. key = x then found := true
Else P := P^. Next;
Member := found;
End;
3 - Thêm một phần tử vào từ điển
Để thêm một phần tử có khóa x vào từ điển ta phải tính h(x) để xác định xem nó
sẽ được thêm vào “rổ”/ “lớp” nào. Vì ta không quan tâm đến thứ tự các phần tử
trong mỗi “rổ” nên ta để đơn gi ả n t a thêm phần tử mới ngay đầu “rổ” này.

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
89
Giải thuật nhƣ sau:
Procedure Insert (x: KeyType; var T: Dictionary );
Var i: 1 .. N-1;
P: Pointer;
Begin
If not Member(x, T) then
Begin
i := h(x);
New(P);
P^. key := x;
P^. Next := T[i];
T[i]:= P;
End;
End;
5 - Xoá một phần tử trong từ điển
Xoá một phần tử x trong từ điển, ta làm như sau:
- Tính giá trị băm h(x)
- Kiểm tra xem phần tử x có thuộc “lớp” được trỏ bởi T[h(x)] không? Nếu có thì
loại bỏ phần tử này. Khi loại x cần phân biệt x nằm ở đầu “lớp” và x không nằm
ở đầu “ lớp”.
Giải thuật như sau
Procedure Delete(x: KeyType; Var T : Dictionary);
Var i: 0 .. N-1;
P, Q : pointer;
Found : Boolean;
Begin
i := h(x);
if T[i]<>nil then
if T[i]^.key = x then {loại x khói danh sách}
T[i] := T[i]^.Next;
Else
Begin
{xem xét các thành phần tiếp theo trong danh sách}
Q := T[i];
P := Q^.Next;
Found := false;
While(P <>nil) and(not found) do

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
90
If P^. Key= x then
Begin {loại x khỏi d.s}
Q^.Next := P^.Next;
Found := true;
End
Else
Begin
Q := P;
P := Q^.Next;
End;
End;
End;
5.5.3.2. Cài đặt từ điển bằng bảng băm đóng
a) Thế nào là bảng băm đóng?
Bảng băm đóng lưu giữ các phần tử của từ điển ngay trong mảng (các phần
tử của “rổ” i lưu trong chính phần tử thứ i của mảng) chứ không dùng mảng
để lưu trữ các con trỏ trỏ tới đầu của các danh sách liên kết – “rổ”.
Tương tự như băm mở, trong bảng băm đóng “rổ” thứ i chứa phần tử có giá trị
băm là i, nhưng vì có thể có nhiều phần tử có cùng giá trị băm nên ta sẽ gặp
trường hợp sau: ta muốn đưa vào “rổ” i một phần tử x nhưng “rổ” này đã bị chiếm
bởi một phần tử y nào đó => gây r a đụng độ. Như vậy khi thiết kế một bảng
băm đóng ta phải có cách để giải quyết sự đụng độ này.
* Giải quyết đụng độ
Cách giải quyết đụng độ đó gọi là băm lại (rehash) như sau:
- Chọn tuần tự các vị trí h1,..., hk theo một cách nào đó cho tới khi gặp một vị trí trống
để đặt x vào. Dãy h1,..., hk gọi là dãy các phép thử, k gọi là tổng số lần băm lại, h
i

giá trị hàm băm của lần băm lại thứ i
- Có nhiều cách băm lại. Một chiến lược đơn giản là băm lại tuyến tính, trong đó
dãy các phép thử có dạng :


Ví dụ N=8 và các phần tử của từ điển là a,b,c,d có giá trị băm lần lượt là: h(a)=3,
h(b)=0, h(c)=5, h(d)=3. Ta muốn đưa các phần tử này lần lượt vào bảng băm.
Khởi đầu bảng băm là rỗng, có thể coi mỗi “rổ‟ chứa một giá trị đặc biệt
Empty, Empty không bằng với bất kỳ một phần tử nào mà ta có thể xét trong tập
hợp các phần tử muốn đưa vào bảng băm.
Ta đặt a vào “rổ” 3, b vào “rổ” 0, c vào “rổ” 5. Xét phần tử d, d có h(d)=3
nhưng “rổ” 3 đã bị a chiếm ta tìm vị trí h1(d)= (h(d)+1) mod N = 4, vị trí này là
một “rổ” rỗng ta đặt d vào.
h
i
(x) = (h(x)+1) mod N

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
91
0 1 2 3 4 5 6 7
b a d c
Hình 5.1 - Giải quyết đụng độ trong bảng băm đóng bằng chiến lƣợc băm lại
tuyến tính
Trong bảng băm đóng, phép kiểm tra một thành viên (thủ tục MEMBER
(x,A)) phải xét dãy các “rổ” h(x),h1(x),h2(x),... cho đến khi tìm thấy x hoặc tìm thấy
một vị trí trống. Bởi vì nếu hk(x) là vị trí trống được gặp đầu tiên thì x không thể
được tìm gặp ở một vị trí nào xa hơn nữa. Tuy nhiên, nói chung điều đó chỉ đúng
với trường hợp ta không hề xoá đi một phần tử nào trong bảng băm. Nếu chúng ta
chấp nhận phép xoá thì chúng ta qui ước rằng phần tử bị xóa sẽ được thay bởi một
giá trị đặc biệt, gọi là Deleted, giá trị Deleted không bằng với bất kỳ một phần tử
nào trong tập hợp đang xét vào nó cũng phải khác giá trị Empty. Empty cũng là
một giá trị đặc biệt cho ta biết ô trống.
Ví dụ
- Tìm phần tử e trong bảng băm trên, giả sử h(e)=5. Chúng ta tìm kiếm e tại các vị trí
5,5,6. “rổ” 6 là chứa Empty, vậy không có e trong bảng băm.
- Tìm d, vì h(d)=3 ta khởi đầu tại vị trí này và duyệt qua các “rổ” 5,5. Phần tử d
được tìm thấy tại “rổ” 5.
* Sử dụng bảng băm đóng để cài đặt từ điển
Dưới đây là khai báo và thủ tục cần thiết để cài đặt từ điển bằng bảng băm đóng.
Để dễ dàng minh hoạ các giá trị Deleted và Empty, giả sử rằng ta cần cài đặt từ
điển gồm các chuỗi 10 kí tự. Ta có thể qui ước: Empty là chuỗi 10 dấu + và Deleted
là chuỗi 10 dấu *.
b) Cài đặt từ điển bởi bảng băm đóng
Const N = … ;
Deleted = -1000; {Gia dinh gia tri cho o da bi xoa}
Empty=1000; {Gia dinh gia tri cho o chua su dung}
type ElementType = integer;
Dictionary = array[ 0 .. N-1] of elementType;
Var T : Dictionary;
c) Cài đặt các phép toán từ điển trên bảng băm đóng:
Với mỗi giá khoá x, để thực hiện các phép toán Insert, Delete, Member ta đều phải xác
định vị trí trong bảng có chứa x, hoăc vị trí trong bảng cần đặt x vào. Tư tưởng để tìm
ra các vị trí đó là thăm dò lần lượt các vị trí h(x), h
1
(x), h
2
(x), ….. Điều đó được thực
hiện bởi thủ tục Location
+ Cách làm:
- Với mỗi giá trị khoá x, thủ tục này cho phép thăm dò các vị trí trong bảng,
xuất phát từ vị trí được xác định bởi giá trị băm h(x), rồi lần lượt qua các vị trí h
1
(x),
h
2
(x), … cho tới khi hoặc tìm được vị trí có chứa x , hoặc tìm ra vị trí trống đầu tiên

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
92
- Quá trình thăm dò sẽ dừng lại nếu đi qua toàn bộ bảng mà không thành công
(Không tìm ra vị trí chứa x, hoặc tìm ra vị trí trống đầu tiên). Tham biến k: Lưu vị trí
mà tại đó quá trình thăm dò dừng lại.
Tham biến j: Lưu vị trí loại bỏ (Deleted) đầu tiên hoặc vị trí trống đầu tiên mà quá
trình thăm dò phát hiện ra nếu trong bảng còn có các vị trí như thế
1- Thủ tục xác định vị trí đặt x
Procedure Location(x: keytype; var k,j : integer);
Var I : integer; { I ghi lại giá trị băm đầu tiên h(x)}
Begin
I := h(x);
J : = I;
If (T[i] = x )or(T[i] = empty )then k := I
Else
Begin
K := (i+1)mod n;
While (k <>i)and(T[k]<>x)and(T[k]<>enpty)do
Begin
If (T[k] = deleted )and(T[j]<>deleted)then j := k;
K := (k+1)mod N;
End;
If (T[k]= empty)and(T[j]<>deleted)then j := k;
End;
End;
2- Thủ tục tìm xem x có thuộc từ điển T hay không?
Function Member(x : keytype; var T : Dictionary): boolean;
var k, j : integer;
Begin
Location(x,k,j);
If T[k]= x then Member := true
Else Member := false;
End;
3- Thủ tục thêm một phần tử x vào từ điển T
Procedure Insert (x : KeyType; var T : Dictionary);
var k,j : integer;
Begin
Location(x,k,j);
If (T[k]<>x) then

Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
93
If (T[j] = deleted)or(T[j] = empty )then T[j] := x
Else Writeln(„bảng đầy‟)
Else Writeln(„ bảng đã có x‟);
End;
4 – Thủ tục xoá phần tử x thuộc từ điển T
Procedure Delete (x : keyType; var T : Dictionary);
var k, j : integer;
Begin
Location(x,k,j);
If T[k] = x then T[k] := deleted;
End;
5.5.3.3 Các phương pháp xác định hàm băm
a) Phƣơng pháp chia
"Lấy phần dư của giá trị khoá khi chia cho số “rổ”" . Tức là hàm băm có dạng:
H(x)= x mod B
Phương pháp này rõ ràng là rất đơn giản nhưng nó có thể không cho kết quả
ngẫu nhiên lắm. Chẳng hạn B=1000 thì H(x) chỉ phụ thuộc vào ba số cuối cùng của
khoá mà không phụ thuộc vào các số đứng trước. Kết quả có thể ngẫu nhiên hơn nếu
B là một số nguyên tố.
b) Phƣơng pháp nhân
"Lấy khoá nhân với chính nó rồi chọn một số chữ số ở giữa làm kết quả của hàm
băm".
Ví dụ:
x
x
2 h(x) g ồm 3 số ở giữa

5502 29181605 181 hoặc 816
367 00135689 135 356
1256 01552516 552 525
2983 08898289 898 982

Vì các chữ số ở giữa phụ thuộc vào tất cả các chữ số có mặt trong khoá do vậy
các khoá có khác nhau đôi chút thì hàm băm cho kết quả khác nhau.
c) Phƣơng pháp tách gấp
Đối với các khoá dài và kích thước thay đổi người ta thường dùng phương pháp
phân đoạn, tức là phân khoá ra thành nhiều đoạn có kích thước bằng nhau từ một đầu
( trừ đoạn tại đầu cuối ), nói chung mỗi đoạn có độ dài bằng độ dài của kết quả hàm
băm. Phân đoạn có thể là tách sau đó thì gấp:


Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN
94
Cách làm: tách khóa ra từng đoạn rồi xếp các đoạn thành hàng được canh thẳng
một đầu rồi có thể gấp (thực hiện một phép toán nào đó, ví dụ như:cộng, trừ, ….
chúng lại) rồi áp dụng phương pháp chia để có kết quả băm.
ví dụ: khoá 17056329 tách thành
329
056
017
cộng lại ta được 392. 392 mod 1000 = 392 là kết quả băm khoá đã cho.

MỤC LỤC
PHẦN TỔNG QUAN .......................................................................................... 5 Chƣơng 1 MỞ ĐẦU ........................................................................................... 7
1. 1 Từ bài toán đến chƣơng trình ...................................................................................... 7 1.2 Các khái niệm cơ bản .................................................................................................. 11 1.2.1 Mô hình dữ liệu ( Data model ) ........................................................................... 11 1.2.2. Khái niệm trừu tƣợng hóa ................................................................................... 13 1.2.3 Kiểu dữ liệu trừu tƣợng ....................................................................................... 14 1.2.4 Dữ liệu .................................................................................................................... 14 1.2.5 Biểu diễn dữ liệu trên máy tính ........................................................................... 14 1.2.6 Kiểu dữ liệu ........................................................................................................... 14 1.2.7 Cấu trúc dữ liệu (Data Structures) ..................................................................... 16 1.2.8 Giải thuật ............................................................................................................... 18 1.2.6 Mối quan hệ giữa cấu trúc dữ liệu và giải thuật ................................................ 18 1.3. Phân tích giải thuật ................................................................................................... 19 1.3.1 Sự cần thiết phải phân tích giải thuật...................................................................... 19 1.3.2 Thời gian thực hiện của giải thuật .......................................................................... 20 1.3.3 Không gian của giải thuật .................................................................................... 23 1.4 Ngôn ngữ diễn đạt giải thuật ...................................................................................... 23 1.5. Đệ quy và giải thuật đệ ............................................................................................... 24 1.5.1 Khái niệm đệ quy .................................................................................................. 24 1.5.2 Giải thuật đệ quy và thủ tục đệ quy .................................................................... 24

Chƣơng 2 MÔ HÌNH DỮ LIỆU DANH SÁCH ........................................... 26
2.1 Danh sách (List) ........................................................................................................... 26 21.1 Khái niệm danh sách ............................................................................................. 26 2.1. 2. Các phép toán cơ bản trên danh sách ............................................................... 27 2.1.3 Biểu diễn (cài đặt) danh sách trên máy tính ...................................................... 28 2.1.3.1 Danh sách cài đặt bằng mảng ......................................................................... 28 2.1.3.2 Danh sách cài đặt bởi con trỏ ......................................................................... 33 2.2 Ngăn xếp (Stack) và ứng dụng..................................................................................... 41 2.2.1. Định nghĩa ngăn xếp ............................................................................................ 41 2.2.2 Các phép toán cơ bản trên ngăn xếp: .................................................................. 42 2.2.3. Cài đặt ngăn xếp ................................................................................................... 42 2 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

2.2.4. Ứng dụng ngăn xếp .............................................................................................. 43 2.3. Hàng đợi (QUEUE)................................................................................................ 44 2.3.1. Định nghĩa hàng đợi ............................................................................................ 44 2.3.2 Các phép toán cơ bản trên hàng .......................................................................... 44 2.3.3 Cài đặt hàng đợi .................................................................................................... 45 2.3.3.1 Cài đặt hàng bằng mảng.................................................................................. 45 2.3.3.2. Cài đặt hàng bằng danh sách liên kết (cài đặt bằng con trỏ) ........................ 48 2.3.4. Một số ứng dụng của cấu trúc hàng.................................................................... 49

Chƣơng 3 Mô hình dữ liệu Cây ...................................................................... 50
3.1 Cây tổng quát ............................................................................................................... 50 3.1.1 Định nghĩa cây và các khái niệm cơ bản trên cây.............................................. 50 3.1.2 Các phép toán cơ bản trên cây ............................................................................ 51 3.1.3 Các cách thăm ( duyệt) cây .................................................................................. 51 3.1.4. Cài đặt cây ............................................................................................................ 55 3.1.4.1 Biểu diễn cây bằng danh sách các con của mỗi đỉnh .................................... 55 3.1.4.2 Biểu diễn cây bằng con trưởng và em liền kề của mỗi đỉnh .......................... 58 3.1.4.3 Biểu diễn cây bởi cha của mỗi đỉnh ................................................................ 60 3.2 Cây nhị phân (binary tree) ......................................................................................... 62 3.2.1 Định nghĩa ............................................................................................................. 62 3.2.2. Duyệt cây nhị phân .............................................................................................. 63 3.2.3 Cài đặt cây nhị phân ............................................................................................. 65 3.2.4 Các phép toán cơ bản trên cây nhị phân ............................................................ 65 3.3 Cây tìm kiếm nhị phân (binary search tree) ............................................................. 68 3.3.1 Định nghĩa cây TKNP .......................................................................................... 68 3.3.2 Cài đặt cây tìm kiếm nhị phân ............................................................................ 69 3.3.3 Các phép toán cơ bản trên cây tìm kiếm nhị phân ............................................ 69

CHƢƠNG 4

Mô hình dữ liệu Đồ thị (Graph) .......................................... 75

4.1 Định nghĩa đồ thị và các khái niệm............................................................................ 75 4.2 Các phép toán cơ bản trên đồ thị ................................................................................ 76 4.3 Biểu diễn đồ thị ............................................................................................................ 77 4.3.1. Biểu diễn đồ thị bằng ma trận kề ........................................................................ 77 4.3.2. Biểu diễn đồ thị bằng danh sách các đỉnh kề ..................................................... 77 4.4 Các phép duyệt đồ thị (TRAVERSALS OF GRAPH) .......................................... 78 4.4.1. Duyệt theo chiều sâu (depth-first search) ........................................................... 78 4.4.2. Duyệt theo chiều rộng (breadth-first search) ..................................................... 78 3 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

........3 Cấu trúc dữ liệu Bảng băm......... 2 Mô hình dữ liệu tập hợp .......................... Cài đặt từ điển bằng bảng băm mở . Một số bài toán ứng dụng trên đồ thị: .................................................................. 93 4 Cấu trúc dữ liệu ......................................................................1...............3............5.. 85 5...................3 Các phương pháp xác định hàm băm ..3..............................3....................................5 Từ điển (Dictionary) .2..... 81 5.........................1 Từ điển là gì?....... 79 5..........................3........................................................................2 Các phƣơng pháp cài đặt từ điển ................................. Khái niệm tập hợp........... 85 5.....................1................5.................4................... Cài đặt từ điển bằng bảng băm đóng ....... 85 5..... 90 5............................. 86 5...................................... 82 5......... cài đặt từ điển bởi bảng băm .................. 82 5.................................4.....................3 Cài đặt bởi danh sách liên kết hoặc danh sách đƣợc sắp ................................ 80 5..................................................................... 79 5................................5.........................Cài đặt tập hợp bởi vectơ bit .................................2 Cài đặt tập hợp bởi mảng .............Bộ môn CNPM – Khoa CNTT – ĐHTN ...... 80 5.....................1.............5...............................5.....................3.......3.............................3 Cài đặt tập hợp và cài đặt các phép toán trên tập hợp ................. 85 5.......... 78 Chƣơng 5 Mô hình dữ liệu Tập hợp ......5............

trong đó có khoảng 30 tiết lý thuyết và 15 tiết bài tập mà giáo viên sẽ hướng dẫn cho sinh viên trên lớp. Sau khi học xong môn này. do vậy nội dung giáo trình chú trọng về các cấu trúc dữ liệu và các giải thuật trên các cấu trúc dữ liệu đó. Đối tƣợng sử dụng Môn học cấu trúc dữ liệu được dùng để giảng dạy cho các sinh viên sau: Sinh viên chuyên ngành công nghệ thông tin (môn bắt buộc ) Sinh viên chuyên ngành tin học kinh tế (môn bắt buộc) Sinh viên chuyên ngành Điện tử . cây. Chƣơng 1: Trình bày cách tiếp cận từ một bài toán đến chương trình.1. Bên cạnh tài liệu này còn có tài liệu thực hành cấu trúc dữ liệu. chúng tôi tập trung trình bày cấu trúc danh sách liên kết đơn. đồ thị bằng một ngôn ngữ lập trình căn bản. 5 Cấu trúc dữ liệu . Vận dụng được các kiểu dữ liệu trừu tượng. hàng đợi. PHẦN TỔNG QUAN - 2. kiểu dữ liệu trừu tượng. viết giải thuật giải quyết bài toán và các bước tinh chế giải thuật đưa đến cài đặt cụ thể trong một ngôn ngữ lập trình Chƣơng 2: Trình bày mô hình dữ liệu danh sách. sinh viên cần phải: .Viễn thông và tự động hóa (môn bắt buộc) 3. Chương này có nhiều cài đặt tương đối chi tiết để các bạn sinh viên mới tiếp cận với lập trình có cơ hội nâng cao khả năng lập trình trong ngôn ngữ C đồng thời cũng nhằm minh hoạ việc cài đặt một kiểu dữ liệu trừu tượng trong một ngôn ngữ lập trình cụ thể. ngăn xếp. Mục đích yêu cầu Môn học cấu trúc dữ liệu cung cấp cho sinh viên một khối lượng lớn các kiến thức cơ bản về các cấu trúc dữ liệu và các phép toán trên từng cấu trúc đó. mô hình dữ liệu.Bộ môn CNPM – Khoa CNTT – ĐHTN . bảng băm. giải thuật và cấu trúc dữ liệu Nắm vững và cài đặt được các mô hình dữ liệu. và cấu trúc danh sách liên kết kép cho những bài toán cần duyệt danh sách theo 2 chiều xuôi. kiểu dữ liệu trừu tượng cơ bản như danh sách. tập hợp. các cấu trúc dữ liệu để cài đặt danh sách.Nắm vững khái niệm kiểu dữ liệu. nó bao gồm mô hình hoá bài toán. Nội dung cốt lõi Nội dung giáo trình gồm 5 chương và đuợc trình bày trong 45 tiết cho sinh viên. các mô hình dữ liệu để giải quyết bài toán đơn giản trong thực tế. Ngăn xếp và hàng đợi cũng được trình bày trong chương này như là hai cấu trúc danh sách đăc biệt. ngược một cách thuận lợi. thiết lập cấu trúc dữ liệu theo mô hình bài toán.

1990. Kiến thức tiên quyết Để học tốt môn học cấu trúc dữ liệu này. Addison– Wesley. bài toán tìm cây khung tối thiểu.. Chƣơng 4: Trình bày mô hình dữ liệu đồ thị. Roberto Tamassia. "Cấu trúc dữ liệu". . [5] N. cùng với các cấu trúc lưu trữ thích hợp cho nó là bảng băm. "Data Structure and Algorihtms". khái niệm cây tổng quát. V. các phép duyệt cây tổng quát và cài đặt cây tổng quát. Do hạn chế về thời lượng lên lớp nên chúng tôi Chương này Chương này dành để nói về mô hình dữ liệu t ập hợp. Weley International Edition. E. Chƣơng 5: . Goodrich. đó là tập hợp với ba phép toán thêm. Hopcroft. 2004. [4] Michel T. Nhà xuất bản Khoa học và Kỹ thuật. David Mount. 4.… .Chƣơng 3: Chương này giới thiệu về kiểu dữ liệu trừu tượng cây. Cấu trúc dữ liệu  thuật toán. 1983 [2] Đỗ Xuân Lôi . J. 5.. các cách biểu diễn đồ thị hay là cài đặt đồ thị. Cuối cùng. Ở đây chúng tôi cũng trình bày các phép duyệt đồ thị bao gồm duyệt theo chiều rộng và duyệt theo chiều sâu một đồ thị và đề cập một số bài toán thường gặp trên đồ thị như là bài toán tìm đường đi ngắn nhất. các cách đơn giản để cài đặt tập hợp như cài đặt bằng vectơ bít hay bằng danh sách. BK tp HCM. [6] Nguyễn Trung Trực. . [3] Đinh Mạnh Tƣờng. [7] Lê Minh Trung. D. Danh mục tài liệu tham khảo [1] Aho. 1997 6 Cấu trúc dữ liệu . Ullman. Wirth " Cấu trúc dữ liệu + giải thuật= Chương trình". “Lập trình nâng cao bằng Pascal với các cấu trúc dữ liệu”. chúng tôi trình bày cây tìm kiếm nhị phân như là một cấu trúc dùng để lưu trữ và tìm kiếm dữ liệu hiệu quả. 1995. "Cấu trúc dữ liệu và giải thuật". 1983. J. 2003.Kiến thức toán rời rạc. sinh viên cần phải có các kiến thức cơ bản sau: . Phần chính của chương này trình bày kiểu dữ liệu trừu tượng tự điển. các cách cài đặt cây nhị phân. xoá và tìm kiếm phần tử. Hà nội.Bộ môn CNPM – Khoa CNTT – ĐHTN . Kế đến chúng tôi trình bày về cây nhị phân. Do hạn chế về thời lượng lên lớp nên chương này chúng tôi chỉ giới thiệu để sinh viên tham khảo thêm về cách cài đặt đồ thị và các bài toán trên đồ thị. “Data Structures and Algorithms in C++”. Nhà xuất bản khoa học và kỹ thuật.Kiến thức và kỹ năng lập trình căn bản. A.

Cách tiếp cận từ bài toán đến chương trình . . Ta cần phải tô màu cho các nước trên bản đồ thế giới. Để giảm bớt sự phức tạp của bài toán thực tế. ta phải bắt đầu từ việc xác định bài toán. Thông thường.Kỹ thuật đệ quy trong cách giải bài toán . Mục tiêu Sau khi học xong chương này. sinh viên sẽ: Nắm được các bước trong lập trình để giải quyết cho một bài toán. kiểu dữ liệu. kiểu dữ liệu trừu tượng.2 Xác định & mô hình hóa bài toán cần giải quyết Khi giải quyết 1 bài toán thực tế. Nắm vững khái niệm cơ bản liên quan đến cấu trúc dữ liệu và giải thuật như: Mô hình dữ liệu..Chƣơng 1 MỞ ĐẦU Tổng quan: 1. Kiến thức cơ bản cần thiết Các kiến thức cơ bản cần thiết để học chương này bao gồm: Khả năng nhận biết và giải quyết bài toán theo hướng tin học hóa.Các khái niệm cơ bản liên quan đến cấu trúc dữ liệu và thuật toán . ta phải hình thức hóa nó. Nội dung chính Chương này chúng ta sẽ nghiên cứu các vấn đề sau: . khi khởi đầu. tức là phải trả lời rõ ràng câu hỏi "phải làm gì?" sau đó là "làm như thế nào?". hầu hết các bài toán là không đơn giản. Nắm vững các tiêu chuẩn để lựa chọn cấu trúc dữ liệu tốt cho bài toán 2. Trong đó mỗi nước đều 7 Cấu trúc dữ liệu . không rõ ràng. Mục đích trả lời hai câu hỏi: Bài toán cho cái gì và yêu cầu làm những gì? sau đó trả lời câu hỏi “để thực hiện các yêu cầu của bài toán thì làm như thế nào” Giai đoạn 2: Cài đặt chương trình: Sử dụng ngôn ngữ lập trình để xây dựng chương trình tương ứng với cách làm của giai đoạn trước nó 1.Các tiêu chuẩn để lựa chọn cấu trúc dữ liệu tốt cho bài toán .Bộ môn CNPM – Khoa CNTT – ĐHTN .. 3.Ngôn ngữ diễn đạt giải thuật 1.1. Có thể có rất nhiều bài toán thực tế có cùng một mô hình toán. nghĩa là phát biểu lại bài toán thực tế thành một bài toán hình thức (hay còn gọi là mô hình toán). Nhiều thời gian và công sức bỏ ra để xác định bài toán cần giải quyết. giải thuật.. Ví dụ 1: Tô màu bản đồ thế giới. 1 Từ bài toán đến chƣơng trình Để giải một bài toán trong thực tế bằng máy tính ta thường trải qua 2 giai đoạn: Giai đoạn 1: Xác định bài toán cần giải quyết và xây dựng mô hình toán học cho bài toán cần giải quyết.

BC. Hãy tìm một phương án tô màu sao cho số màu sử dụng là ít nhất. trên sơ đồ này.Bộ môn CNPM – Khoa CNTT – ĐHTN . và số lượng nhóm chia là ít nhất có thể được. Ta sẽ có một sơ đồ như hình 1. Mô hình toán học được sử dụng trong bài toán này là mô hình đồ thị. AD. AC. EB. Ở đây ta sẽ dùng một sơ đồ trực quan như sau: tên của 13 lối đi được viết lên mặt phẳng. DB. ED. EA. nhưng AD và EB thì không. vì các hướng giao thông cắt nhau. hoặc cong. hai lối đi nào nếu đi đồng thời sẽ xảy ra đụng nhau (tức là hai hướng đi cắt qua nhau) ta nối lại bằng một đoạn thẳng. Ví dụ 2: Đèn giao thông Cho một ngã năm như hình 1. mỗi nhóm sẽ tương ứng với một pha điều khiển của đèn hiệu.2. EC. 8 Cấu trúc dữ liệu . DA.1 Ta có thể xem đầu vào của bài toán là tất cả các lối đi tại ngã năm này. hai nước láng giềng của nhau thì hai đỉnh ứng với nó được nối với nhau bằng một cạnh. Lối nào với lối nào không thể đi đồng thời. trong đó C và E là các đường một chiều theo chiều mũi tên. Ví dụ cặp AB và EC có thể đi đồng thời. các đường khác là hai chiều. BA. nghĩa là: phân chia các lối đi tại ngã năm này thành các nhóm. Trước hết ta nhận thấy rằng tại ngã năm này có 13 lối đi: AB. Ta có thể xem mỗi nước trên bản đồ thế giới là một đỉnh của đồ thị.được tô một màu và hai nước láng giềng (cùng biên giới) thì phải được tô bằng hai màu khác nhau.1. Tất nhiên. Bài toán lúc này trở thành bài toán tô màu cho đồ thị như sau: Mỗi đỉnh đều phải được tô màu. mỗi nhóm gồm các lối đi có thể cùng đi đồng thời nhưng không xảy ra tai nạn giao thông (các lối đi này có các hướng đi không cắt nhau). vì vậy ta phải tìm kiếm lời giải với số nhóm là ít nhất để giao thông không bị tắc nghẽn vì phải chờ đợi quá lâu. hai lối đi có cạnh nối lại với nhau là hai lối đi không thể cho đi đồng thời. Hãy thiết kế một bảng đèn hiệu điều khiển giao thông tại ngã năm này một cách hợp lý. đầu ra của bài toán là các nhóm lối đi có thể đi đồng thời mà không xảy ra tai nạn giao thông. B A C D E Hình 1. Như vậy. để có thể giải được bài toán ta phải tìm một cách nào đó để thể hiện mối liên quan giữa các lối đi này. BD. hai đỉnh có cạnh nối thì phải tô bằng hai màu khác nhau và ta cần tìm một phương án tô màu sao cho số màu được sử dụng là ít nhất. DC. hoặc ngoằn ngoèo tuỳ thích. lối nào và lối nào có thể đi đồng thời.

. đồ thị." Nhƣ vậy: Cả hai bài toán trên. ta phải giải quyết bài toán sau: "Tô màu cho đồ thị ở hình 1. ban đầu có vẻ rất khác nhau. . ví dụ dãy.Số nhóm là ít nhất: ta phải tính toán sao cho số màu được dùng là ít nhất. hai lối đi không thể cùng đi đồng thời được nối nhau bằng một đoạn ta gọi là cạnh của đồ thị. Tóm lại. với số nhóm ít nhất. ánh xạ. .Hai đỉnh có cạnh nối với nhau (hai còn gọi là hai đỉnh kề nhau) không cùng màu. nó ứng với một pha của đèn hiệu điều khiển giao thông.AB AC AD BA BC BD DA DB DC EA EB EC ED Hình 1. . Giả sử rằng. khi xây dựng mô hình toán học cho bài toán cần chú trọng đến hai vấn đề : + Tổ chức biểu diễn các đối tượng dữ liệu của bài toán trong mô hình toán học như thế nào? mô hình này ta còn gọi là mô hình dữ liệu trong tin học 9 Cấu trúc dữ liệu . trong đó mỗi lối đi trở thành một đỉnh của đồ thị. cho nên trong giai đoạn phân tích và thiết kế. mỗi nhóm gồm các lối đi có thể đi đồng thời. Bây giờ ta phải xác định các nhóm. tập hợp. nhưng sau khi phân tích để hình thức hóa thì chúng đều được đưa về mô hình toán học đồ thị.2 sao cho: . ta dùng màu để tô lên các đỉnh của đồ thị này sao cho: . cây. . và áp dụng thuật toán tô mầu trên đồ thị để giải quyết các bài toán này.Số màu được dùng là ít nhất.2 Với cách biểu diễn như vậy ta đã có một mô hình toán học đồ thị (Graph).Một bài toán thực tế bất kỳ thường bao gồm các đối tượng dữ liệu và các yêu cầu xử lý trên những đối tượng đó. Chú ý: Có rất nhiều các cấu trúc toán học có thể làm mô hình dữ liệu trong tin học.Bộ môn CNPM – Khoa CNTT – ĐHTN .Các lối đi cho phép cùng đi đồng thời sẽ có cùng một màu: Dễ dàng nhận thấy rằng hai đỉnh có cạnh nối nhau sẽ không được tô cùng màu.

) để cài đặt kết quả ở giai đoạn phân tích và thiết kế chương trình. 10 Cấu trúc dữ liệu .Bộ môn CNPM – Khoa CNTT – ĐHTN . ta có thể tóm tắt các bước từ bài toán đến chương trình như sau: 1) Về mặt dữ liệu: Mô hình dữ liệu -> Kiểu dữ liệu trừu tượng -> Cấu trúc dữ liệu. Các cấu trúc dữ liệu được mô tả trong ngôn ngữ lập trình cụ thể mà ta sử dụng => Dạng biểu diễn này phụ thuộc vào ngôn ngữ lập trình cụ thể Từ biểu diễn trừu tượng. Record. thì các phép tóan trên mô hình được thực hiện bởi các thao tác cần thiết trên cấu trúc dữ liệu đó. nhất là khi phát triển các hệ thống phần mềm lớn.. hay nói cách khác.. một số phép toán trên mô hình có thể được thực hiện thuận lơi.. ví dụ như: Mô hình cây. Giai đoạn này còn được gọi là xây dựng cấu trúc dữ liệu cho bài toán. Ta có thể cài đặt một mô hình dữ liệu bởi nhiều cấu trúc dữ liệu khác nhau. Thật vậy: Trong quá trình phát triển chương trình.. ví dụ: Ngăn xếp.+ Xây dựng các thao tác xử lý trên các đối tượng của mô hình ra sao 1. từ các mô hình dữ liệu hoặc từ các kiểu dữ liệu trừu tượng. Ví dụ. danh sách. ta có thể chuyển dịch thành các biều diễn cụ thể khác nhau. ví dụ Array.1. …. tập hợp. ở bước này ta dùng các cấu trúc dữ liệu được cung cấp trong ngôn ngữ.2 Cài đặt chƣơng trình cho bài toán cần giải quyết Khi cài đặt chương trình giải quyết bài toán tương ứng ta quan tâm đến hai vấn đề: (1) Biểu diễn mô hình dữ liệu của bài toán trên máy tính như thế nào để máy tính có thể hiểu và thực hiên các thao tác trên chúng. ta có thể cài đặt danh sách bởi cấu trúc dữ liệu mảng hoặc cấu trúc dữ liệu danh sách liên kết. …… > Khi ta dùng mô hình dữ liệu với một số xác định các phép toán nào đó. để biểu diễn m ô h ì n h d ữ l i ệ u c ủ a b à i t o á n .C. ta sẽ có một kiểu dữ liệu trừu tượng . nhưng các phép toán khác có thể lại không thuận lợi (2) Mã hóa các giải thuật xử lý trên các cấu trúc dữ liệu tương ứng để giải quyết các yêu cầu đặt ra của bài toán Ta có thể sử dụng một ngôn ngữ lập trình cụ thể nào đó (Pascal. ta cần sử dụng dạng biểu diễn cụ thể của dữ liệu: Là biểu diễn xác định cách lưu trữ vật lý của dữ liệu trong bộ nhớ máy tính. Trong mỗi cách cài đặt.. bảng băm. => Dạng biểu diễn dữ liệu này không phụ thuộc vào ngôn ngữ lập trình cụ thể b) Trong giai đoạn cài đặt chƣơng trình. hàng đợi. ta cần đến hai dạng biểu diễn dữ liệu: Biếu diễn trừu tượng và biểu diễn cụ thể a) Trong giai đoạn xác định va mô hình hóa bài toán: ta cần sử dụng dạng biểu diễn trừu tượng: được xác định bởi mô hình dữ liệu – đó là mô hình toán học của các đối tượng dữ liệu cùng với các phép toán thực hiện trên các đối tượng đó. và mã hóa giải thuật bởi các câu lệnh trong ngôn ngữ lập trình lựa chọn Như vậy. Biểu diễn cụ thể của dữ liệu được xác định bởi các cấu trúc dữ liệu. đồ thị.. Khi cài đặt mô hình dữ liệu bởi cấu trúc dữ liệu nào đó. mô hình ERA. ta có thể chuyển dịch thành các cấu trúc dữ liệu khác nhau.

Ở bước này ta cần dùng các kiểu dữ liệu trừu tượng (không phải là các khai báo cài đặt trong ngôn ngữ lập trình cụ thể) và các cấu trúc lệnh điều khiển trong ngôn ngữ lập trình (không chú trọng đến cú pháp ngôn ngữ) .2) Về mặt xử lý dữ liệu: Giải thuật không hình thức ->giải thuật bằng ngôn ngữ giả >Giải thuật được mã hóa hoàn toàn bởi ngôn ngữ lập trình cụ thể. tô mầu đồ thị. … + Trong mô hình dữ liệu danh sách ta có những phép toán sau: . Để tìm ra cấu trúc toán học thích hợp với một bài toán đã cho. do đó cần phải tổ chức. sử dụng ngôn ngữ lập trình cụ thể. Các thành phần dữ liệu thực tế đa dạng.1 Mô hình dữ liệu ( Data model ) Mô hình dữ liệu là gì? Mô hình dữ liệu được sử dụng để mô tả cấu trúc logic của dữ liệu được xử lý bởi hệ thống. 1. Là mô hình toán học cùng với các phép toán có thể thực hiện trên các đối tượng của mô hình. các phép toán thực hiện trên các đối tượng của mô hình Ví dụ: + Trong mô hình dữ liệu đồ thị. tìm các đỉnh treo. trong số rất nhiều các phép toán.. lựa chọn và xây dựng các mô hình dữ liệu thích hợp nhất sao cho vừa có thể phản ánh chính xác các dữ liệu thực tế này. thao tác trên cấu trúc dữ liệu cụ thể. phong phú và thường chứa đựng những quan hệ nào đó với nhau. vừa có thể dễ dàng dùng máy tính để xử lý. C. ví dụ: Pascal. tìm các thành phần liên thông. kêt hợp ngôn ngữ tự nhiên để mô tả giải thuật.. ta tìm c á c giải thuật trên mô hình d ữ l i ệ u đ ã x â y d ự n g .Bộ môn CNPM – Khoa CNTT – ĐHTN .2. Thật vây: Từ những yêu cầu xử lý thực tế. ta phải làm việc với mô hình như thế nào để tìm ra lời giải của bài toán? Chúng ta sẽ thiết kế các thuật toán thông qua các hành động. Cuối cùng trong pha cài đặt. ta tiến hành mã hóa hoàn toàn giải thuật được mô tả bởi ngôn ngữ giả.. một câu hỏi đặt ra là. rồi chi tiết hoá dần ("mịn hoá") các bước giải tổng quát ở trên ( làm mịn dấn) . xác định đường đi ngắn nhất nối 2 đỉnh bất kỳ. ta có thể kể ra một số phép toán sau: Tìm các đỉnh kề của mỗi đỉnh. chúng ta cần phải phân tích kỹ bài toán để tìm ra câu trả lời cho các câu hỏi sau: + Các thông tin quan trọng của bài toán có thể biểu diễn bởi các đối tượng toán học nào ? + Có các mối quan hệ nào giữa các đối tượng ? + Các kết quả phải tìm của bài toán có thể biểu diễn bởi các khái niệm toán học nào ? Sau khi đã có mô hình toán học mô tả bài toán.Xác định độ dài danh sách 11 Cấu trúc dữ liệu . Giải thuật có thể mô tả một cách không hình thức ( tức là nó chỉ nêu phương hướng giải hoặc các bước giải một cách tổng quát). Tiếp theo ta hình thức hoá giải thuật bằng ngôn ngữ giả.2 Các khái niệm cơ bản 1.

Nếu ta di chuyển từ trên xuống dưới trong Hình 1. nhưng chỉ có một tiền bối.Xen một phần tử mới vào danh sách . Hình 1. Hình 1.4 biểu diễn một ví dụ cụ thể về mô hình này. Mô hình dữ liệu phân cấp như vậy thường được gọi là cây. và đây là một loại mô hình dữ liệu quan trọng trong khoa học máy tính: Tiền bối của A A chỉ có 1 tiền bối. nếu mô hình dữ liệu tuyến tính chứa các phần tử thì nó phải có phần tử đầu tiên và phần tử cuối cùng. tức là. nhưng nếu ta di chuyển từ dưới lên thì mỗi nút (trừ nút ở gốc) chỉ có quan hệ với 1 nút. mỗi phần tử trong mô hình có nhiều hậu bối.3 biểu diễn một ví dụ về mô hình dữ liệu tuyến tính.4 – Mô hình dữ liệu cây 12 Cấu trúc dữ liệu . có nhiều hậu bối A Các hậu bối của A Hình 1. sắp xếp … Ta có thể phân loại các mô hình dữ liệu dựa trên mối quan hệ giữa các phần tử: 1Mô hình dữ liệu tuyến tính (danh sách): Dùng để biểu diễn các phần tử có quan hệ 1:1.3 Mô hình dữ liệu danh sách Mô hình dữ liệu phân cấp (mô hình cây): Dùng để biểu diễn các phần tử có quan hệ 1: n. Các phần tử trong mô hình có quan hệ tuyến tính theo thứ tự xuất hiện của chúng. mỗi phần tử có đúng một phần đứng ngay trước và một phần tử đứng ngay sau. tức là.Loại bỏ.4 thì mỗi nút có thể trỏ đến nhiều nút khác.Bộ môn CNPM – Khoa CNTT – ĐHTN . Phần tử đứng trước A Phần tử đứng sau A A Phần tử đầu tiên Phần tử cuối cùng 2- Hình 1..

Ta cũng sẽ nghiên cứu các dạng biểu diễn của các mô hình này bởi các cấu trúc dữ liệu khác nhau trong pha cài đặt chương trình.5 – Mô hình dữ liệu đồ thị 4 . trừu tượng hóa nghĩa là đơn giản hóa. Hình 1. Cụ thể trừu tượng hóa là che đi những chi tiết. Hình 1.3 . Tức là. các phần tử có mối quan hệ n:m.Mô hình dữ liệu thứ ba là đồ thị: đây là mô hình dữ liệu phong phú và phức tạp nhất. Hình 1.2. Nói chung.6 biểu diễn một ví dụ cụ thể về mô hình này. mỗi phần tử có thể có quan hệ với một hoặc nhiều phần tử khác. Trong một tập hợp.2.6 – Mô hình tập hợp Trên đây là bốn loại mô hình dữ liệu mà ta sẽ nghiên cứu. các phần tử không có mối quan hệ trực tiếp với nhau. 13 Cấu trúc dữ liệu . giữa chúng chỉ có một mối quan hệ là thành viên của tập hợp.Bộ môn CNPM – Khoa CNTT – ĐHTN . Khái niệm trừu tƣợng hóa Trong tin học. làm cho nó sáng sủa hơn và dễ hiểu hơn. ta không cần quan tâm tới vị trí chính xác của một phần tử nào đó trong tập hợp.5 biểu diễn một ví dụ cụ thể về mô hình này. làm nổi bật cái tổng thể. A Hình 1.Loại mô hình dữ liệu cuối cùng là tập hợp. Trong đồ thị. 1. hầu hết các cấu trúc dữ liệu đều rơi vào một trong bốn dạng cơ bản này.

. dữ liệu được phân làm ba loại: Dữ liệu vào: Các đối tượng cần xử lý của bài toán Dữ liệu Kết quả trung gian Dữ liệu đầu ra: Kết quả xử lý 1.. 1. hình ảnh. văn bản. Trong một bài toán.3 Kiểu dữ liệu trừu tƣợng Trong Mô hình dữ liệu. … gần với ngôn ngữ tự nhiên) đã giải phóng con người khỏi những khó khăn khi làm việc với cách biểu diễn dữ liệu nhị phân trong MT + Trong các ngôn ngữ lập trình bậc cao: Các kiểu dữ liệu là sự trừu tượng hoá các tính chất của các đối tượng dữ liệu có cùng bản chất trong thế giới thực (chỉ ra những tính chất đặc trưng cho các đối tượng thuộc phạm vi bài toán đang xét) Ví dụ: Ứng với các dữ liệu dạng số. + Như vậy tất cả các dữ liệu mô tả trong ngôn ngữ lập trình bậc cao được máy tính xử lý đu phải thuộc một kiểu dữ liệu xác định. C. . đúng / sai.Bộ môn CNPM – Khoa CNTT – ĐHTN . … trong ngôn ngữ lập trình. Mô hình tập hợp. .Ví dụ: Số 10 = 0000 1010 => Cách biểu diễn này rất không thuận tiện (dài. không gợi nhớ. 14 Cấu trúc dữ liệu . loại bỏ. ……. số thực. các dữ liệu dù tồn tại ở những hình thức khác nhau (số. Ví dụ: Mô hình dữ liệu danh sách.. Song trong nhiều áp dụng.. phong phú. ta gọi là kiểu dữ liệu hàng đợi hoặc ngăn xếp.6 Kiểu dữ liệu Kiểu dữ liệu T được xác định bởi một bộ <V... Khi nói đến một kiểu dữ liệu cần phải đề cập đến hai đặc trưng cơ bản sau : 1) Tập các giá trị thuộc kiểu. Khi đó chúng ta sẽ có một kiểu dữ liệu trừu tượng Kiểu dữ liệu trừu tượng (abstract data type): là một mô hình dữ liệu được xét cùng với một số xác định các phép toán nào đó. …) đối với con người. Tức là mỗi dữ liệu được biểu diễn dưới dạng một dãy các số nhị phân 0 hoặc 1.có rất nhiều dạng khác nhau. khó. âm thanh.2. 2) Tập hợp các phép toán có thể thực hiện được trên các dữ liệu của kiểu. …) đều được biểu diễn dưới dạng nhị phân khi đưa vào MT xử lý.2. tìm kiếm ta gọi là kiểu dữ liệu trừu tượng từ điển. chỉ xét đến các phép toán thêm vào và lấy ra. 1. tương ứng ta có các kiểu dữ liệu số nguyên.4 Dữ liệu Thực tế dữ liệu tồn tại ở rất nhiều dạng: hình ảnh.2. với : V : tập các giá trị hợp lệ mà một đối tƣợng kiểu T có thể lƣu trữ O : tập các thao tác xử lý có thể thi hành trên đối tƣợng kiểu T..1.2.. chúng ta chỉ sử dụng mô hình với một số xác định các phép toán nào đó. số phức.O> . chỉ xét đến các phép toán: Thêm vào. Việc xuất hiện các ngôn ngữ lập trình bậc cao ( Pascal. chúng ta có thể thực hiện một tập hợp các phép toán rất đa dạng.5 Biểu diễn dữ liệu trên máy tính + Trong MTĐT.

các loại dữ liệu cơ bản thường là các loại dữ liệu đơn giản. Đa số các ngôn ngữ lập trình đều cài đặt sẵn một số kiểu có cấu trúc cơ bản như mảng.. tránh hiện tượng dư thừa bộ nhớ. Ví dụ : Để mô tả một đối tượng sinh viên. *.và cung cấp cơ chế cho lập trình viên tự định nghĩa kiểu dữ liệu mới. chuỗi. Các loại dữ liệu này. số thực. do tính thông dụng và đơn giản của mình. bản ghi. chỉ với các kiểu dữ liệu cơ sở không đủ để phản ánh tự nhiên và đầy đủ bản chất của sự vật thực tế. cần quan tâm đến các thông tin sau: 15 Cấu trúc dữ liệu . Các thuộc tính của 1 kiểu dữ liệu bao gồm:  Tên kiểu dữ liệu: Từ khóa thể hiện cho kiểu đó  Miền giá trị: Một biến có kiểu dữ liệu đó có thể nhận các giá trị trong phạm vi nào  Kích thước lưu trữ: Để tối ưu hóa việc sử dụng kiểu dữ liệu phù hợp.. Chúng thường là các giá trị vô hướng như các số nguyên.. các ký tự. ký tự. logic . kiểu ký tự thực chất cũng là kiểu số nguyên về mặt lưu trữ. Những kiểu dữ liệu được xây dựng như thế gọi là kiểu dữ liệu có cấu trúc. liệt kê. thường được các ngôn ngữ lập trình (NNLT) cấp cao xây dựng sẵn như một thành phần của ngôn ngữ để giảm nhẹ công việc cho người lập trình. /. số thực. biến đổi ký tự thường thành ký tự hoa…} Giả sử có kiểu dữ liệu số nguyên = <Vi . tập tin. miền con … Kiểu không rời rạc: số thực Tùy ngôn ngữ lập trình.Bộ môn CNPM – Khoa CNTT – ĐHTN ..Ví du: Giả sử có kiểu dữ liệu mẫu tự = <Vc .Oi> với Vi = { -32768. Chính vì vậy đôi khi người ta còn gọi chúng là các kiểu dữ liệu định sẵn.. -. các kiểu dữ liệu cơ bản bao gồm : Kiểu có thứ tự rời rạc: số nguyên.Oc> với Vc = { a-z. các giá trị logic . Các kiểu cơ sở rất đơn giản và không thể hiện rõ sự tổ chức dữ liệu trong một cấu trúc. Thông thường. thường chỉ được sử dụng làm nền để xây dựng các kiểu dữ liệu phức tạp khác Tuy nhiên trong nhiều trường hợp.A-Z} Oc = { lấy mã ASCII của ký tự. Ví dụ: Với ngôn ngữ C. Trong khi đó PASCAL định nghĩa tất cả các kiểu dữ liệu cơ sở đã liệt kê ở trên và phân biệt chúng một cách chặt chẽ. các kiểu dữ liệu định nghĩa sẵn có thể khác nhau đôi chút về các thuộc tính. Và theo quan điểm của C. liên kết các thành phần dữ liệu có kiểu dữ liệu đã được định nghĩa. không có cấu trúc. chỉ khác về cách sử dụng.32767} Oi = { +. ký tự. %} Nhƣ vậy: muốn sử dụng một kiểu dữ liệu trong cài đặt cần nắm vững các thuộc tính của kiểu dữ liệu đó. dẫn đến nhu cầu phải xây dựng các kiểu dữ liệu mới dựa trên việc tổ chức. các kiểu dữ liệu này chỉ gồm số nguyên.  Tập các toán tử tác động lên kiểu dữ liệu: Các phép toán cơ bản mà kiểu dữ liệu đó cung cấp Ta thấy rằng.

1.Bộ môn CNPM – Khoa CNTT – ĐHTN .. Type Date = record Ngay: byte. Giả sử đã có cấu trúc phù hợp để lưu trữ một sinh viên.2.Nơi sinh: chuỗi ký tự .Mã sinh viên: chuỗi ký tự .Tên sinh viên: chuỗi ký tự . Mục tiêu của việc nghiên cứu cấu trúc dữ liệu chính là tìm những phương cách thích hợp để tổ chức. Ngaysinh: Date End.. ví dụ danh sách hoặc mảng.Điểm thi: số nguyên Các kiểu dữ liệu cơ sở cho phép mô tả một số thông tin như : Diemthi: integer. Tensv: String[15]. Thang: byte. lúc đó nảy sinh nhu cầu xây dựng kiểu dữ liệu có cấu trúc mới.. liên kết dữ liệu. Noisinh: String[15]. Cuối cùng. Để thể hiện thông tin về ngày tháng năm sinh cần phải xây dựng một kiểu dữ liệu có cấu trúc bản ghi.7 Cấu trúc dữ liệu (Data Structures) CTDL = { Các dữ liệu thành phần} Trong đó: Các dữ liệu thành phần có thể là dữ liệu đơn (sẵn có) hoặc là CTDL đã được xây dựng. Năm: integer. ta có thể xây dựng kiểu dữ liệu có cấu trúc thể hiện thông tin về một sinh viên : Type SinhVien = Record Masv: String[15]. từ 16 Cấu trúc dữ liệu . Các thông tin khác đòi hỏi phải sử dụng các kiểu có cấu trúc như : Masv: string[15]. Noisinh: String[15]. Tensv: string[15]. nhưng thực tế lại cần quản lý nhiều sinh viên. Diem thi: Integer.Ngày sinh: kiểu ngày tháng . chúng được liên kết với nhau theo một phương pháp liên kết nào đó. End. hình thành các kiểu dữ liệu có cấu trúc từ những kiểu dữ liệu đã được định nghĩa.

Chọn một biến số nguyên integer để lưu trữ tiền thưởng bán hàng (được tính theo công thức tiền thưởng bán hàng = trị giá hàng * 5%). dẫn đến sai lệch. .000 đ. quyết định tính đúng đắn của toàn bộ bài toán. Ví dụ : Một tình huống chọn cấu trúc lưu trữ không phù hợp: Cần xây dựng một chương trình soạn thảo văn bản.Phản ánh đúng thực tế : Đây là tiêu chuẩn quan trọng nhất. tự nhiên hơn. cụ thể là việc phát triển các thuật toán đơn giản. xoá sửa các ký tự trên văn bản. mỗi lớp có thể nhận tối đa 28 học sinh. do vậy sẽ làm tròn mọi giá trị tiền thưởng gây thiệt hại cho nhân viên bán hàng. ngày sinh. 17 Cấu trúc dữ liệu .. Trong thời gian xử lý văn bản. có mối quan hệ với nhau.Bộ môn CNPM – Khoa CNTT – ĐHTN . Trường hợp này phải sử dụng biến số thực để phản ánh đúng kết quả của công thức tính thực tế. LƢU Ý : Đối với mỗi ứng dụng .Phù hợp với các thao tác trên đó: Tiêu chuẩn này giúp tăng tính hiệu quả của giải thuật. các thao tác xử lý thường xảy ra là chèn. Chọn một biến số nguyên byte ( khả năng lưu trữ 0 .. 2 . chiều cao. chương trình đạt hiệu quả cao hơn về tốc độ xử lý. nếu chọn cấu trúc lưu trữ văn bản trực tiếp lên tập tin thì sẽ gây khó khăn khi xây dựng các giải thuật cập nhật văn bản và làm chậm tốc độ xử lý của chương trình vì phải làm việc trên bộ nhớ ngoài. Một cấu trúc dữ liệu tốt phải thỏa mãn các tiêu chuẩn sau : 1. Lớp hiện có 20 học sinh.những CTDL này người ta xây dựng các giải thuật tương ứng tác động trên CTDL đó một cách hiệu quả nhất Ví dụ: Trong ngôn ngữ lập trình Pascal : Mảng: Bao gồm một dãy có thứ tự các phần tử có cùng kiểu Bản ghi: Bao gồm một tập các phần tử dữ liệu khác kiểu. Cần xem xét kỹ lưỡng cũng như dự trù các trạng thái biến đổi của dữ liệu trong chu trình sống để có thể chọn cấu trúc dữ liệu lưu trữ thể hiện chính xác đối tượng thực tế. Ví dụ : Một số tình huống chọn cấu trúc lưu trữ sai : . cân nặng. …. mỗi tháng mỗi học sinh đóng học phí 100. . gây ra tình trạng tràn. ví dụ thông tin về 1 con người gồm họ tên.Trong trường trung học.255) để lưu trữ tổng học phí của lớp học trong tháng là không phù hợp vì giá trị tổng học phí thu được > 255. Trường hợp này nên tìm một cấu trúc dữ liệu có thể tổ chức ở bộ nhớ trong để lưu trữ văn bản suốt thời gian soạn thảo. vượt khỏi khả năng lưu trữ của biến đã chọn. cần chú ý đến thao tác nào được sử dụng nhiều nhất để lựa chọn cấu trúc dữ liệu cho thích hợp.

. . Ví dụ : Một số tình huống chọn cấu trúc lưu trữ lãng phí: . nhất quán. 1.6 Mối quan hệ giữa cấu trúc dữ liệu và giải thuật Trong một chương trình:. hay còn gọi là cấu trúc dữ liệu. 1. đó là sự kết hợp của ngôn ngữ tự nhiên và các câu lệnh của ngôn ngữ lập trình. Có nhiều cách để thể hiện giải thuật: dùng ngôn ngữ tự nhiên. Trường hợp này cần có một cấu trúc dữ liệu linh động hơn mảng. Khởi đầu là tìm một giải thuật. là kiểu dữ liệu trong đó các phần tử của nó có thể phân tách thành các kiểu dữ liệu đơn hoặc kiểu dữ liệu phức khác. Biết rằng tháng chỉ có thể nhận các giá trị từ 1-12. sử dụng mảng 50 phần tử (giới hạn số học viên trong lớp tối đa là 50).3 .Sử dụng biến integer (2 bytes) để lưu trữ một giá trị cho biết tháng hiện hành .2. ví dụ danh sách liên kết – ta sẽ đề cập đến trong các tiếp theo. dùng ngôn ngữ giả.Bộ môn CNPM – Khoa CNTT – ĐHTN . . Giải thuật phản ánh các phép xử lý. Nếu cần một chương trình có tốc độ xử lý nhanh thì khi chọn cấu trúc dữ liệu yếu tố tiết kiệm thời gian xử lý phải đặt nặng hơn tiêu chuẩn sử dụng tối ưu bộ nhớ. Nhƣ vậy: Kiểu dữ liệu phức. Tiêu chuẩn này nên cân nhắc tùy vào tình huống cụ thể khi viết chương trình.2. Nếu số lượng học viên thật sự ít hơn 30. nên chỉ cần sử dụng kiểu byte là đủ. đó là một chuỗi hữu hạn các chỉ thị (instruction) mà mỗi chỉ thị có một ý nghĩa rõ ràng tương ứng với một thao tác và thực hiện được trong một lượng thời gian hữu hạn.Hữu hạn (finiteness): giải thuật phải luôn luôn kết thúc sau một số hữu hạn bước. Ví dụ. Và một cách dùng phổ biến là dùng ngôn ngữ giả. còn đối tượng xử lý của giải thuật lại là dữ liệu. .. . Ngoài ra một giải thuật còn phải có đầu vào (input) và đầu ra (output).Hiệu quả (effectiveness): các thao tác trong giải thuật phải được thực hiện trong một lượng thời gian hữu hạn. Knuth (1973) định nghĩa giải thuật là một chuỗi hữu hạn các thao tác để giải một bài toán nào đó. chính dữ liệu chứa đựng các thông tin cần thiết để thực 18 Cấu trúc dữ liệu . kiểu dữ liệu phức trong Pascal bao gồm: array và record. và ngược lại.8 Giải thuật Khi đã có mô hình thích hợp cho một bài toán ta cần cố gắng tìm cách giải quyết bài toán trong mô hình đó.Xác định (definiteness): mỗi bước của giải thuật phải được xác định rõ ràng và phải được thực hiện chính xác.Để lưu trữ danh sách học viên trong một lớp. dùng các câu lệnh của ngôn ngữ lập trình. Thông thường có 2 loại tài nguyên cần lưu tâm nhất : CPU và bộ nhớ. Các tính chất quan trọng của giải thuật là: . dùng lưu đồ.Tiết kiệm tài nguyên hệ thống: Cấu trúc dữ liệu chỉ nên sử dụng tài nguyên hệ thống vừa đủ để đảm nhiệm được chức năng của nó. Nói tóm lại: M ột giải thuật phải giải quyết xong công việc khi ta cho dữ liệu vào. thì gây lãng phí.

được thể hiện qua công thức : Cấu trúc dữ liệu + Giải thuật = Chƣơng trình Với một cấu trúc dữ liệu đã chọn. để kiểm tra tính đúng đắn của giải thuật chúng ta có thể cài đặt giải thuật đó và cho thực hiện trên máy với một số bộ dữ liệu mẫu rồi lấy kết quả thu được so sánh với kết quả đã biết. Thực ra thì cách làm này không chắc chắn bởi vì có thể giải thuật đúng với tất cả các bộ dữ liệu chúng ta đã thử nhưng lại sai với một bộ dữ liệu nào đó. vấn đề là cần phải đánh giá các giải thuật đó để lựa chọn một giải thuật tốt (nhất).Giải thuật hiệu quả. Thông thường thì ta sẽ căn cứ vào các tiêu chuẩn sau: (1) . sẽ có những giải thuật tương ứng. 1. Với yêu cầu (1). (3) . giải thuật và cấu trúc dữ liệu có mối quan hệ chặt chẽ với nhau. Tính đúng đắn của giải thuật cần phải được chứng minh bằng toán học. bảng. Phân tích giải thuật 1. Để xác định được giải thuật phù hợp cần phải biết nó tác động đến loại dữ liệu nào (ví dụ để làm nhuyễn các hạt đậu .…. vừa đáp ứng nhanh vừa tiết kiệm bộ nhớ. người ta dùng cách xay chứ không băm bằng dao. danh sách kế tiếp. giải thuật cũng dễ hiễu và đơn giản hơn. Tất nhiên điều này không đơn giản và do vậy chúng ta sẽ không đề cập đến ở đây.hiện giải thuật. …. một cấu trúc dữ liệu tốt sẽ giúp giải thuật xử lý trên đó có thể phát huy tác dụng tốt hơn. 19 Cấu trúc dữ liệu .3. thiếu tự nhiên trên một cấu trúc không phù hợp. Bộ nhớ ngoài còn gọi là cấu trúc lưu trữ ngoài. * Cấu trúc lƣu trữ ( Storange structures) + CTDL được biểu diễn trong bộ nhớ máy tính còn được gọi là Cấu trúc lưu trữ + CTDL được lưu trữ ở: Bộ nhớ trong còn gọi là cấu trúc lưu trữ trong. Như vậy trong một chương trình máy tính.1 Sự cần thiết phải phân tích giải thuật Trong khi giải một bài toán chúng ta có thể có một số giải thuật khác nhau. (2) . phù hợp. Vả lại cách làm này chỉ phát hiện ra giải thuật sai chứ chưa chứng minh được là nó đúng. vì đậu sẽ văng ra ngoài) và khi chọn lựa cấu trúc dữ liệu cũng cần phải hiểu rõ những thao tác nào sẽ tác động đến nó (ví dụ để biểu diễn các điểm số của sinh viên người ta dùng số thực thay vì chuỗi ký tự vì còn phải thực hiện thao tác tính trung bình từ những điểm số đó).Giải thuật đơn giản. Khi cấu trúc dữ liệu thay đổi thường giải thuật cũng phải thay đổi theo để tránh việc xử lý gượng ép.Giải thuật đúng đắn..Bộ môn CNPM – Khoa CNTT – ĐHTN . ví dụ: Tệp tin.3. Hơn nữa. ví dụ: Mảng.

Chúng ta cần một giải thuật dễ viết chương trình để nhanh chóng có được kết quả. Ví dụ 1: Chương trình tính tổng của n số có thời gian thực hiện là T(n) = c*n trong đó c là một hằng số. Tuy nhiên các yếu tố này là không đồng đều do vậy không thể dựa vào chúng khi xác lập thời gian thực hiện giải thuật T(n). đặc biệt đối với những chương trình mà khi thực hiện cần dữ liệu nhập lớn do đó yêu cầu (3) sẽ được xem xét một cách kĩ càng.Thời gian thực hiện một chương trình là một hàm của kích thước dữ liệu vào.T(n) không thể tính bằng đơn vị cụ thể mà tính bằng số lần thực hiện các lệnh của giải thuật. với n là kích thước của bài toán a) Thời gian thực hiện giải thuật là gì? đơn vị của T(n) tính bằng gì ? cách tính? .Bộ môn CNPM – Khoa CNTT – ĐHTN .  Một câu hỏi đặt ra là: Cách tính này có thể hiện được tính nhanh hay chậm của giải thuật hay không ? Câu trả lời: rõ ràng là có. thí dụ : o GT1 có thời gian thực hiện là tỉ lệ với n GT2 có thời gian thực hiện là tỉ lệ với n2 Với n khá lớn thì giải thuật 1 nhanh hơn giải thuật 2 Thời gian mà ta đánh giá như trên gọi là độ phức tạp tính toán của giải thuật: Ở giải thuật 1 ta nói GT có độ phức tạp tính toán cấp n và kí hiệu T(n) = O(n) Ở giải thuật 2 thì thời gian T() = O(n2) hay O(g(n)). tiết kiệm không gian lưu trữ lại rất quan trọng.3. Tính hiệu quả thể hiện qua hai mặt:  Thời gian (Chương trình chạy nhanh)  Không gian (Chương trình bé chiếm ít bộ nhớ) 1.. Ta gọi nó là hiệu quả của giải thuật. Khi một chương trình được sử dụng nhiều lần thì thì yêu cầu tiết kiệm thời gian thực hiện chương trình. g(n) còn được gọi là cấp độ phức tạp tính toán 20 Cấu trúc dữ liệu . o ….Khi chúng ta viết một chương trình để sử dụng một vài lần thì yêu cầu (2) là quan trọng nhất. ký hiệu T(n) trong đó n là kích thước (độ lớn) của dữ liệu vào.2 Thời gian thực hiện của giải thuật Thời gian thực hiện của giải thuật phụ thuộc vào nhiều yếu tố  Trước hết phụ thuộc vào độ lớn của dữ liệu đầu vào  Ngoài ra T còn phụ thuộc vào o Máy o Ngôn ngữ o Kỹ sảo của người lập trình. thời gian thực hiện chương trình không được đề cao vì dù sao thì chương trình đó cũng chỉ sử dụng một vài lần mà thôi. .

Cho T = 0.Đọc vào số n 5. n3: ta gọi chung là hàm đa thức. Đưa ra TBC * Đánh giá giải thuật: Các lệnh 1 2 3 4 5 6 7 8 T(n) = Số lần thực hiện 1 1 n n n n 1 1 4n + 4 T(n) = 4n + 4 =< 5n với mọi n >= 4 => T(n) = O(n) * Một số qui tắc cơ bản xác định cấp độ phức tạp tính toán a.g(n)) thì ta cũng có T(n) = O(g(n)) b. các giải thuật có độ phức tạp tính toán là cấp các hàm đa thức thì chấp nhận được. Qui tắc cộng T1(n) và T2(n) là thời gian thực hiện của 2 đoạn chương trình P1 và P2 và T1(n) = O(f(n)). d = 1 3.Cộng thêm vào T 6.g(n))) c. n2. Ví dụ : T(n) = 60n2 + 9n + 9 = O(n) * Một số ví dụ đánh giá độ phức tạp tính toán của GT Ví dụ 1 : Tính trung bình cộng của một dãy gồm n số đọc vào từ bàn phím 1. Nhập vào số n 2.Bộ môn CNPM – Khoa CNTT – ĐHTN . Qui tắc lấy max Nếu ta có T(n) = O(f(n) + g(n)) thì ta cũng có T(n) = O(max(f(n). n. g(n) = 2n ta gọi là hàm mũ. Qui tắc loại bỏ hằng số Nếu ta có T(n) = O(C1. 8.Hàm g(n) thường chọn là: logn. Lặp While d <= n do Begin 4. Tính TBC = T/n. 7. T2(n)= O(g(n)) thì thời gian thực hiện 2 đoạn chương trình nối tiếp nhau là 21 Cấu trúc dữ liệu .Tăng đếm lên 1 End.

lồng trong lệnh {1} là lệnh {2}. Lệnh tích cực là lệnh có số lần thực hiện >= số lần thực hiện các lệnh khác Tóm lại: Qui tắc tổng quát để phân tích một chƣơng trình: .n] OF integer). {5} và {6} đều tốn O(1) thời gian. READ.T(n) = T1(n) + T2(n) Hay T(n) = O(max(f(n). cả ba lệnh gán {4}.Vòng lặp {2} thực hiện (n-i) lần.Thời gian thực hiện vòng lặp là tổng thời gian thực hiện thân vòng lặp (trên tất cả các lần lặp). a[j]} {4} temp := a[j-1].Bộ môn CNPM – Khoa CNTT – ĐHTN .Thời gian thực hiện của mỗi lệnh gán.Thời gian thực hiện của một chuỗi tuần tự các lệnh được xác định bằng qui tắc cộng. END.Trước hết.g(n))) d. việc đánh giá độ phức tạp thuật toán ta chỉ cần chú ý đến các lệnh tích cực còn các lệnh khác có thể bỏ qua. Thường thời gian kiểm tra điều kiện là O(1). VAR i. j. {5} và {6}.g(n)) Nhận xét: Từ các qui tắc trên. Ví dụ 1: Tính thời gian thực hiện của thủ tục sắp xếp “nổi bọt” PROCEDURE Noi_bot(VAR a: ARRAY[1.. 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. việc so sánh a[j1] > a[j] cũng tốn O(1) thời gian. Qui tắc nhân Thời gian thực hiện hai đoạn chương trình P1. END. Nếu thời gian thực hiện thân vòng lặp không t h a y đổi thì thời gian thực hiện vòng lặp là tích của số lần lặp với thời gian thực hiện 1 lần thân vòng lặp. . WRITE là O(1) . ta thấy. . lồng trong lệnh {2} là lệnh {3} và lồng trong lệnh {3} là 3 lệnh nối tiếp nhau {4}.Thời gian thực hiện cấu trúc IF là thời gian lớn nhất thực hiện lệnh sau THEN hoặc sau ELSE và thời gian kiểm tra điều kiện. {5} a[j-1] := a[j]. mỗi lần O(1) do đó vòng lặp {2} tốn O((n-i). * Xác định độ phức tạp của giải thuật trên Ta thấy toàn bộ chương trình chỉ gồm một lệnh lặp {1}. temp: Integer. P2 lồng nhau là : T(n) = O(f(n). do đó lệnh {3} tốn O(1) thời gian. Chúng ta sẽ tiến hành tính độ phức tạp theo thứ tự từ trong ra: .1) = 22 Cấu trúc dữ liệu . BEGIN {1} FOR i:=1 TO n-1 DO {2} FOR j:=n DOWNTO i+1 DO {3} IF a[j-1]>a[j]THEN BEGIN{hoán vị a[i]. . {6} a[j] := temp.

4 Ngôn ngữ diễn đạt giải thuật . trong đó có một số là kiểu dữ liệu đơn hay nguyên tử.n] OF Integer. Lồng trong lệnh {3} là lệnh {4}.Vòng lặp {1} lặp có i chạy từ 1 đến n-1nên thời gian thực hiện của vòng lặp {1} và cũng là độ phức tạp của giải thuật là: n(n − 1) T(n) = ∑(n − i) = = O(n2). . {3} và {5} nối tiếp nhau. {2} và {5} đều có độ phức tạp O(1) do đó độ phức tạp của hàm Search chính là độ phức tạp của lệnh {3}. . hàm sẽ trả về giá trị logic TRUE nếu tồn tại một phần tử a[i] = x.. Trong trường hợp xấu nhất (tất cả các phần tử của mảng a đều khác x) thì vòng lặp {3} thực hiện n lần. Found:Boolean. bắt đầu từ a[1].O(n-i). Dễ dàng thấy rằng ba lệnh {1}. một số là các cấu trúc dữ liệu bao gồm các kiểu đơn. VAR i:Integer. do đó độ phức tạp của hàm Search chính là độ phức tạp lớn nhất trong 4 lệnh này.3 Không gian của giải thuật Được tính bằng số ô nhớ nguyên thuỷ được dùng trong giải thuật đó (= số biến đơn) 1. 2 i =1 Chú ý: Trong trường hợp vòng lặp không xác định được số lần lặp thì chúng ta phải lấy số lần lặp trong trường hợp xấu nhất. Giải thuật tìm kiếm tuần tự là lần lượt so sánh x với các phần tử của mảng a. {5} Search:=Found.Bộ môn CNPM – Khoa CNTT – ĐHTN . {3} WHILE(i<=n)AND (not Found) DO {4} IF A[i]=X THEN Found:=TRUE ELSE i:=i+1. Hàm tìm kiếm Search nhận vào một mảng a có n số nguyên và một số nguyên x. {2}. {2} Found:=FALSE.3. ngược lại hàm trả về FALSE. vậy ta có T(n) = O(n). * Phân tích xác định độ phức tạp của giải thuật trên Ta thấy các lệnh {1}.x:Integer):Boolean. END. ngược lại nếu tất cả các phần tử của a đều khác X thì trả về FALSE. 23 Cấu trúc dữ liệu .Mỗi ngôn ngữ lập trình có một hệ kiểu. Lệnh {4} có độ phức tạp O(1). nếu tồn tại a[i] = x thì dừng và trả về TRUE. 1. BEGIN {1} i:=1. FUNCTION Search(a:ARRAY[1.Là công cụ trung gian giúp giao tiếp giữa người và MTĐT. Ví dụ 2: Tìm kiếm tuần tự.

. Đệ quy và giải thuật đệ 1. có dạng như T thì đó là một lời giải đệ quy.5.Ngôn ngữ diễn đạt giải thuật bao gồm một tập hợp các câu lệnh tuân theo một cú pháp nhất định.5.. dict2 là 2 đầu mối có thể truy cập được vào đầu trước và đầu sau của từ điển } 2) Return b) Đặc điểm của thủ tục đệ quy + Trong thủ tục đệ quy có lời gọi đến chính nó. giai thừa của n được định nghĩa là : n! = 1 nếu n = 0 hoặc n = 1.5. word) { dict được gọi là đầu mối để truy nhập được vào từ điển đang xét. If (word nằm ở nửa trước của từ điển ) Then SEARCH(dict1. Ví dụ : Cho n là số nguyên dương.2 Giải thuật đệ quy và thủ tục đệ quy a) Giải thuật đệ quy Nếu lời giải của bài toán T được thực hiện bởi lời giải của một bài toán T‟. 1.Trong môn học CTDL ta sử dụng ngôn ngữ lập trình Pascal để minh hoạ. n*(n-1) ! nếu n >1 1. word) End. {dict1. word chỉ từ cần tìm } 1) If (từ điển chỉ còn là một trang ) Then (tìm word trong trang này) Else Begin Mở từ điển vào trang giữa. Giải thuật chứa lời giải đệ quy được gọi là giải thuật đệ quy (T‟<T) Ví dụ: Xét bài toán tìm một từ trong từ điển Phác thảo giải thuật Procedure SEARCH( dict.Bộ môn CNPM – Khoa CNTT – ĐHTN . word) Else SEARCH(dict2. + Mỗi lần có lời gọi thì kích thước của bài toán đã thu nhỏ hơn trước  24 Cấu trúc dữ liệu .1 Khái niệm đệ quy Một đối tượng được gọi là đệ quy nếu nó bao gồm chính nó như một bộ phận hoặc đối tượng được định nghĩa dưới dạng của chính nó . Thông qua các câu lệnh mà MT có thể hiểu và thực hiện những công việc mà người dùng muốn MT làm => Ta sử dụng công cụ này để diễn đạt giải thuật. Xác định xem nửa nào chứa word .

Kiểu Integer là tập hợp các số nguyên có giá trị từ -32768 đến 32767 cùng các phép toán cộng. FALSE và các phép toán trên nó như OR. nó chưa được cài đặt cụ thể bằng một ngôn ngữ lập trình. chia.Bộ môn CNPM – Khoa CNTT – ĐHTN . Integer…. nhưng chúng có ý nghĩa rất khác nhau. Div. Đệ quy gồm : Đệ quy trực tiếp (thủ tục chứa lời gọi đến chính nó) và đệ quy gián tiếp (thủ tục chứa lời gọi đến thủ tục khác mà thủ tục này lại chứa lời gọi đến chính nó ) Tóm lại Mặc dù các thuật ngữ kiểu dữ liệu (hay kiểu . nhân. mô hình dữ liệu (data model) nghe có vẻ như nhau. Kiểu dữ liệu có cấu trúc hay còn gọi là cấu trúc dữ liệu: là kiểu dữ liệu mà giá trị dữ liệu của nó là sự kết hợp của các giá trị khác. Ví dụ: ARRAY là một cấu trúc dữ liệu. Kiểu dữ liệu c ơ sở : là kiểu dữ liệu mà giá trị dữ liệu của nó là đơn nhất. NOT …. Có thể nói kiểu dữ liệu trừu tượng là một kiểu dữ liệu do chúng ta định nghĩa ở mức khái niệm (conceptual). AND. 25 Cấu trúc dữ liệu . Kiểu dữ liệu: là một tập hợp các giá trị và một tập hợp các phép toán trên các giá trị đó. trường hợp suy biến: Bài toán sẽ được giải quyết theo một cách khác hẳn và gọi đệ quy cũng kết thúc. 2.+ Có một trường hợp đặc biệt. trừ. Mod… Kiểu dữ liệu có hai loại là kiểu dữ liệu sơ cấp và kiểu dữ liệu có cấu trúc hay còn gọi là cấu trúc dữ liệu. Khi cài đặt một kiểu dữ liệu trừu tượng trên một ngôn gnữ lập trình cụ thể. Ví dụ kiểu Boolean là một tập hợp có 2 giá trị TRUE.data type). Biểu diễn kiểu dữ liệu trừu tượng bằng một cấu trúc dữ liệu hoặc một kiểu dữ liệu trừu tượng khác đã được cài đặt. chúng ta phải thực hiện hai nhiệm vụ: 1. kiểu dữ liệu trừu tượng (abstract data type). Viết các chương trình con thực hiện các phép toán trên kiểu dữ liệu trừu tượng mà ta thường gọi là cài đặt các phép toán. Một kiểu dữ liệu trừu tƣợng: là một mô hình dữ liệu cùng với một tập hợp các phép toán điển hình trên nó. cấu trúc dữ liệu (data structure). Ví dụ: kiểu Boolean.

+ Số phần tử của danh sách ta gọi là độ dài của danh sách. + Nếu n > 0 ta gọi a1 là phần tử đầu tiên và an là phần tử cuối cùng của danh sách. .Cài đặt các kiểu dữ liệu và các phép toán bằng ngôn ngữ lập trình cụ thể.Kiểu dữ liệu trừu tượng ngăn xếp (STACK) . mỗi phần tử có một vị trí trong 26 Cấu trúc dữ liệu . Ta biểu diễn danh sách L như là một chuỗi các phần tử của nó: a1. với i từ 2 đến n. Ví dụ: Tập hợp họ tên các sinh viên của lớp TINHOC được liệt kê trên giấy như sau: 1. Phạm Quách Què 7. . hàng đợi. an với n ≥ 0. thì: + Nếu n = 0 ta nói danh sách rỗng (empty list). Ta nói ai đứng trước ai+1.Biết ứng dụng lý thuyết được tìm hiểu để giải các bài toán thực tế.Nắm vững các khái niệm: danh sách.. . Nguyễn Phú Vĩnh 6. hay phần tử thứ i của danh sách. Vũ Xuân Trường 8. sinh viên .Mô hình danh sách (LIST) . a2.1 Danh sách (List) 21.Kiểu dữ liệu trừu tượng hàng đợi (QUEUE) 2. . với i từ 1 đến n-1. Trần Rạo Rực Là một danh sách. Nguyễn Ngọc Chương 3. 2. + Một tính chất quan trọng của danh sách đó là tính tuyến tính: Các phần tử của danh sách có thứ tự tuyến tính theo vị trí (position) xuất hiện của các phần tử. Nội dung chính Trong chương này chúng ta sẽ nghiên cứu . Tương tự ta nói ai là phần tử đứng sau ai-1. Trịnh Vũ Thành 5. Ta cũng nói ai là phần tử tại vị trí thứ i. Nguyễn Trung Cang 2. ngăn xếp. Danh sách này gồm có 8 phần tử.Bộ môn CNPM – Khoa CNTT – ĐHTN . .Chƣơng 2 MÔ HÌNH DỮ LIỆU DANH SÁCH Tổng quan: 1. Lê Thị Lệ Sương 4.1 Khái niệm danh sách Mô hình toán học của danh sách là một tập hợp hữu hạn biến động các phần tử thuộc cùng một lớp đối tượng nào đó (có cùng một kiểu dữ liệu). Ta cũng lưu ý rằng một đối tượng cũng có thể xuất hiện nhiều lần trong một danh sách. Mục tiêu Sau khi học xong chương này.

an thì sau khi xen ta có kết quả a1.L) thực hiện việc xác định vị trí phần tử có nội dung x đầu tiên trong danh sách L. Tức là nếu danh sách là a1. .L) lấy giá trị của phần tử ở vị trí p của danh sách L. Link không xác định nếu p không phải là vị trí của một phần tử trong danh sách. x là một giá trị nào đó cùng kiểu với kiểu dữ liệu của các phần tử trong danh sách. ap-1. 2) Tìm vị trí của một phần tử trong danh sách LOCATE(x. . 2. Nếu p là phần tử đầu tiên trong danh sách thì Previous(p.danh sách theo thứ tự xuất hiện của nó.L) cho kết quả là vị trí của phần tử đứng trước phần tử có vị trí p trong danh sách. . . 3) Lấy giá trị của phần tử ở vị trí nào đó RETRIEVE(p. nếu vị trí p không có trong danh sách thì kết quả không xác định (có thể thông báo lỗi). . . . .1. a2. 6) Tìm vị trí của phần tử đứng trước phần tử có vị trí xác đinh PREVIOUS(p. 4) Xoá một phần tử ở vị trí nào đó trong danh sách DELETE_LIST(p. . nếu p là phần tử cuối cùng trong danh sách L thì LINK(p. Locate trả kết quả là vị trí của phần tử x trong danh sách. . một danh sách kết thúc bởi phần tử cuối cùng gọi là phần cuối (postfix) của danh sách. an. Nếu vị trí p không có trong danh sách thì phép toán không được định nghĩa và danh sách L sẽ không thay đổi 5) Tìm vị trí của phần tử đứng sau phần tử có vị trí xác đinh LINK(p. Danh sách rỗng được xem là danh sách con của một danh sách bất kỳ. .L) không xác định.L) cho kết quả là vị trí của phần tử đi sau phần tử p.Bộ môn CNPM – Khoa CNTT – ĐHTN . 7) Tìm vị trí của phần tử đứng đầu danh sách FIRST(L) cho kết quả là vị trí của phần tử đầu tiên trong danh sách. Nếu danh sách rỗng thì ENDLIST(L) được trả về. Các phép toán cơ bản sau được định nghĩa trên danh sách: 1) Chèn một phần tử vào danh sách INSERT_LIST(x.L) cho kết quả là ENDLIST(L). Nếu vị trí p không tồn tại trong danh sách thì phép toán không được xác định. a2. an ) là một danh sách thì ta gọi là một danh sách con của L.. . x. . . một đoạn nào đó các phần tử kế tiếp của L. . p là một vị trí (position) trong danh sách. 2.L): xoá phần tử ở vị trí p t r o n g danh sách L.Danh sách con : Nếu L = (a1 . . ap-1. ap.p. Previous cũng không xác định trong trường hợp p không phải là vị trí của phần tử nào trong danh sách. tức là ENDLIST(L).Một danh sách con bắt đầu từ phần tử đầu tiên gọi là phần đầu (prefix). ap . Các phép toán cơ bản trên danh sách Gọi L là một danh sách đã cho. Nếu x không có trong danh sách thì vị trí sau phần tử cuối cùng của danh sách được trả về.L): xen phần tử x vào vị trí p trong danh sách L.. a2. 8)Kiểm tra tính rỗng của danh sách 27 Cấu trúc dữ liệu . .

Do đó để thực hiện được các phép toán đó ta phải cài đặt chúng thành các chương trình con trong ngôn ngữ lập trình cụ thể. Dễ thấy rằng số phần tử của mảng phải được khai báo lớn hơn số phần tử của danh sách. nó thuộc loại cấu trúc dữ liệu tĩnh a) Mô tả cài đặt : . tính toán.3. giả sử Item: Là kiểu dữ liệu của các phần tử trong danh sách . sắp xếp danh sách. . 1)Muốn thêm 1 phần tử vào đầu hay cuối danh sách ta cần gọi các phép toán nào và gọi các phép toán đó như thế nào? 2) Viết giải thuật nhập dữ liệu cho danh sách chứa n phần tử 3) Duyệt danh sách để hiển thị/tìm tất cả các phần tử ở vị trí chẵn Lƣu ý: Trên đây là các phép toán trừu tượng do chúng ta định nghĩa.Dùng một mảng để lưu giữ các phần tử của danh (giả sử mảng Elements). Ghi chú: Trong thiết kế các giải thuật sau này chúng ta dùng các phép toán trừu tượng đã được định nghĩa ở trên đây như là các phép toán nguyên thủy (cơ bản).EMPTY_LIST(L) cho kết quả TRUE nếu danh sách rỗng. duyệt danh sách. gọi tắt là: Danh sách đặc.Bộ môn CNPM – Khoa CNTT – ĐHTN . gộp. tách.phụ thuộc vào yêu cầu cụ thể của bài toán. Thật vậy. …. tổng hợp.3 Biểu diễn (cài đặt) danh sách trên máy tính 2. 9) Tạo một dách sách rỗng MAKENULL_LIST(L) khởi tạo một danh sách L rỗng. với mỗi cấu trúc dữ liệu cài đặt mô hình danh sách ta vẫn giữ đúng những tham số trong cách cài đặt trên để thống nhất trong cài đặt. dĩ nhiên. từ các phép toán nguyên thuỷ này ta có thể tự hình thành lên các phép toán phức tạp khác như: Tạo danh sách chứa dữ liêu. nó chưa được cài đặt trong các ngôn ngữ lập trình.1.1. hoặc cấu trúc dữ liệu danh sách kế tiếp.1 Danh sách cài đặt bằng mảng Cài đặt danh sách bởi mảng hay còn gọi là cấu trúc dữ liệu danh sách đặc. Trong bài giảng này. ngược lại nó cho giá trị FALSE. hoặc danh sách kế tiếp.Giá sử N là số phân tử tối đa trong danh sách: Với cách cài đặt này. Nói chung là mảng còn thừa một số chỗ trống. chưa có dữ liệu. 2. ta phải ước lượng số phần tử tối đa của danh sách để khai báo số phần tử của mảng cho thích hợp.Count là một biến đếm đếm số lượng phần tử hiện có trong danh sách Như vậy ta có thể định nghĩa danh sách như một cấu trúc bản ghi gồm 2 trường: Elements: Chứa các phần tử trong danh sách Count: Đếm số phần tử hiện có trong danh sách (chiều dài danh sách) => Khi đó mảng chứa các phần tư trong danh sách có dạng như sau 28 Cấu trúc dữ liệu .

Hãy chèn x vào vị trí p trong danh sách L. ? Tại sao đối số L của thủ tục lại là tham biến 2. (rỗng) n (rỗng) b) Dạng cài đặt Const N = <maxlist>. Phân tích cách chèn Khi xen phần tử có nội dung x vào tại vị trí p của danh sách L thì sẽ xuất hiện các khả năng sau: . vì vậy để khởi tạo danh sách rỗng ta chỉ việc gán giá trị trường count này bằng 0.chỉ số mảng Nội dung phần tử 1 Phần tử 1 2 Phần tử 2 …. Begin Empty_list := L. trường count chỉ vị trí của phần tử cuối cùng trong danh sách và đó cũng độ dài hiện tại của danh sách. Procedure MakeNullList(Var L: List) Begin L. Type List = Record Elements : Array[1. End.Khởi tạo danh sách rỗng Danh sách rỗng là một danh sách không chứa bất kỳ một phần tử nào (hay độ dài danh sách bằng 0).N..count = 0. Var L : List. c) Cài đặt các phép toán cơ bản 1.Mảng đầy: mọi phần tử của mảng đều chứa phần tử của danh sách. tức là phần 29 Cấu trúc dữ liệu .. Function Empty_list(L : List) : boolean. …… count Phần tử cuối cùng trong danh sách ….Bộ môn CNPM – Khoa CNTT – ĐHTN .Xen một phần tử vào danh sách Bài toán: Cho một danh sách L. 3. Count : 0. count :=0. End. End. Theo cách khai báo trên.Kiểm tra danh sách rỗng Danh sách rỗng là một danh sách mà độ dài của nó bằng 0.N] of Item. một giá trị x (cùng kiểu với kiểu dữ liệu của các phần tử trong danh sách) và một vị trí p bất kỳ trong danh sách.

{Đưa x vào vị trí p} L. * Độ dài danh sách tăng 1. chương trình báo lỗi.Elements[i]: =L.count:= L.count+1 thì khi xen sẽ làm cho danh sách L không còn là một danh sách đặc nữa.count+1)) then Writeln („Vi tri khong hop le‟) Else begin {Dời các phần tử từ vị trí p đến cuối danh sách (Count) sang phải 1 vị trí} For i:=L. Khi đó không còn chỗ cho phần tử mới.Elements[P]=X. hãy xoá phần tử ở vị trí p ra khỏi danh sách Phân tích bài toán Xoá một phần tử ở vị trí p ra khỏi danh sách L ta làm công việc ngược lại với chèn một 30 Cấu trúc dữ liệu . * Đưa phần tử mới vào vị trí p Giải thuật chèn: Procedure InsertList(X: Item. Var L: List) Var i: integer.tử cuối cùng của danh sách nằm ở vị trí cuối cùng trong mảng. 4 . Nói cách khác.count =MaxLength) then Writeln(„Danh sach day‟) else if ((P<1) or (P>L. count+1 to P+1 do L.Bộ môn CNPM – Khoa CNTT – ĐHTN . Begin if (L.Xóa phần tử ra khỏi danh sách Bài toán: Cho danh sách L. ( vì: Vị trí xen p<1 thì khi đó p không phải là một vị trí phần tử trong trong danh sách đặc.Ngược lại ta tiếp tục xét: + Nếu p không hợp lệ (p>count+1 hoặc p<1 ) thì chương trình con báo lỗi. {Tăng độ dài danh sách lên 1} L. vì vậy việc xen là không thể thực hiện được.Count +1. . P: byte . end End. độ dài của danh sách bằng chỉ số tối đa của mảng. Nếu vị trí p>L.Elements[i-1]. + Nếu vị trí p hợp lệ thì ta tiến hành xen theo các bước sau: * Dời các phần tử từ vị trí p đến cuối danh sách ra sau 1 vị trí.

tức là ENDLIST(L).Bộ môn CNPM – Khoa CNTT – ĐHTN . Begin if ((P<1) or (P>L.count)) then Writeln(„khong hop le‟). {giảm count đi 1 đơn vị} L. found: boolean. End. vị trí đã hợp lệ thì ta phải dời các phần tử từ vị trí p+1 đến cuối danh sách (count) lên trước một vị trí để đè lên phần tử cần xóa và độ dài danh sách giảm đi 1 phần tử ( do đã xóa bớt 1 phần tử). Var p: integer.count -1. + Ngược lại. o Nếu không tìm thấy thì hàm trả về vị trí sau vị trí của phần tử cuối cùng trong danh sách. Else begin {Dời các phần tử từ vị trí p+1 đến cuối danh sách (vị trí count) sang trái 1 vị trí} For i:= P-1 to L.Elements[i]: = L. count := L. else if (EmptyList(L))then Writeln(„Danh sach rong!‟). Giải thuật Procedure Locate(X:Item.phần tử: Trước tiên ta kiểm tra vị trí phần tử cần xóa xem có hợp lệ hay chưa? + Nếu p>L. Giải thuật xoá Procedure DeleteList(P: byte. L:list) :integer. End.count -1do L.count+1 Trong trường hợp có nhiều phần tử cùng giá trị x trong danh sách thì vị trí của phần tử được tìm thấy đầu tiên được trả về. ta có thể áp dung phương pháp tìm kiếm tuần tự như sau: Ta dò tìm từ đầu danh sách: o Nếu tìm thấy x thì vị trí của phần tử tìm thấy được trả về. 31 Cấu trúc dữ liệu .count hoặc p<1 thì đây không phải là vị trí của phần tử trong danh sách. 5 .Định vị một phần tử trong danh sách Để định vị vị trí phần tử đầu tiên có nội dung x trong danh sách L.Elements[i+1]. Bgein Found = false. Giả sử ta cho ENDLIST(L) := L. var L: list) Var i: integer.

Nhập danh sách từ bàn phím (READ_LIST(L)) (Phép toán này chưa có trong kiểu danh sách) . Print_List vừa nói trên là có thể giải quyết được bài toán. Insert_List.Các phép toán khác cũng dễ dàng cài đặt nên xem nhƣ bài tập Bài tập về nhà: Vận dụng các phép toán trên danh sách đặc để viết chương trình nhập vào một danh sách các số nguyên và hiển thị danh sách vừa nhập ra màn hình.readln(X).First(L):=1 . //Khởi tạo danh sách rỗng ReadList(L). Để đáp ứng yêu cầu đặt ra.Link(P.P = First(L). locate := P.L):= L. Writeln(„Danh sach vua nhap: „).&L).Retrieve(P. Delete_List.Elements[p] . 32 Cấu trúc dữ liệu . Locate và các chương trình con Read_List. End.P. ta viết chương trình chính để nối kết những chương trình con lại với nhau gồm các câu lệnh như sau: BEGIN MakeNullList(L). Gợi ý: Giả sử ta đã cài đặt đầy đủ các phép toán cơ bản trên danh sách. Xóa phần tử đầu tiên có nội dung x (nhập từ bàn phím) ra khỏi danh sách.L) == X) then Found := true else P = Link(P.L):=P+1 6. Để thực hiện yêu cầu như trên. Lưu ý : Các phép toán sau phải thiết kế trước Locate . Thực ra thì chúng ta chỉ cần sử dụng các phép toán MakeNull_List. Print_List(L). // In danh sach len man hinh Insert_List(X.Bộ môn CNPM – Khoa CNTT – ĐHTN .count+1) and (not Found) do if (Retrieve(P.Hiển thị danh sách ra màn hình (in danh sách) (PRINT_LIST(L)) (Phép toán này chưa có trong kiểu danh sách). //vị trí phần tử đầu tiên /*trong khi chưa tìm thấy và chưa kết thúc danh sách thì xét phần tử kế tiếp*/ while ((P < L. Writeln(„Noi dung phan tu can xoa: „). ta cần thiết kế thêm một số chương trình con sau : . PrintList(L). Thêm phần tử có nội dung x vào danh sách tại ví trí p (trong đó x và p được nhập từ bàn phím). Writeln(„Danh sach sau khi them phan tu la: „). L).

tương ứng ta có cấu trúc dữ liệu danh sách liên kết đơn – gọi tắt là danh sách liên kết đơn + Liên kết vòng: Tương ứng ta có cấu trúc dữ liệu danh sách liên kết vòng – gọi tắt là danh sách liên kết vòng + Liên kết đôi: Tương ứng ta có cấu trúc dữ liệu danh sách liên kết đôi (liên kết kép) – gọi tắt là danh sách liên kết kép + Đa liên kết: Tương ứng ta có danh sách đa móc nối Trong bài giảng này ta trung nguyên cứu cấu trúc danh sách liên kết đơn.P=Locate(X. a) Danh sách liên kết đơn (single link list) Mỗi phần tử trong danh sách là một ô nhớ.L). ta dùng con trỏ để liên kết các ô nhớ chứa các phần tử của danh sách.2 Danh sách cài đặt bởi con trỏ Danh sách được cài đặt bởi con trỏ ta còn gọi là cấu trúc dữ liệu danh sách liên kết .X. đây thuộc loại cấu trúc dữ liệu động Các ô nhớ chứa các phần tử của danh sách là một vùng liên tục. Các hình thức tổ chức liên kết các phần tử trong danh sách có thể là: + Liên kết đơn. Nhược điểm chính: Số lượng các phần tử trong danh sách bị hạn chế vì phụ thuộc vào vùng nhớ trống liên tục trong bộ nhớ Ta đã biết. 2. ta đi tìm hiểu nguyên tắc lưu trữ của nó? Trong cách cài đặt này. một ngăn chứa dữ liệu của phần tử đó. „là:‟). các ô nhớ chứa các phần tử trong danh sách không nhất thiết phải nằm ở những vị trí kế tiếp nhau.gọi tắt là danh sách liên kết. một ngăn là con trỏ chứa địa 33 Cấu trúc dữ liệu . END.lưu địa chỉ của nhau => danh sách được lưu trữ theo kiểu này gọi là danh sách móc nối hay danh sách liên kết. mỗi ô nhớ là một cấu trúc ít nhất là hai ngăn.L).Bộ môn CNPM – Khoa CNTT – ĐHTN . với cấu trúc danh sách kế tiêp thì: Ta có 1 kiểu lưu trữ khác: Lưu trữ móc nối. Vậy lưu trữ móc nối là như thế nào. PrintList(L). và chúng gắn kết với nhau thông qua cơ chế móc nối . Writeln(„Danh sach sau khi xoa‟. các vùng nhớ này được cấp phát ngay khi dịch chương trình vì nó là các ô nhớ tĩnh.3. Các cấu trúc danh sách liên kết khác dành cho bạn đọc. DeleteList(P.1.

trỏ đến nút đứng kế sau trong danh sách (nghĩa là nó chứa địa chỉ của ô nhớ nút đứng sau nó) Hình ảnh danh sách có dạng như sau: a2 a3 an an+1 nil L a1 Nút cuối cùng trong danh sách không có nút đứng sau. mỗi phần tử trong danh sách được cài đặt như một nút có hai trường: + Trường info chứa giá trị của các phần tử trong danh sách. Tây.n. Hơn nữa để có thể tru y cập đến các phần tử trong danh sách này thì chỉ cần giữ địa chỉ của Bắc (điah chỉ của ô nhớ chứa phần tử đầu tiên trong danh sách). + Trường link là một con trỏ giữ địa chỉ của ô kế tiếp trong danh sách: . nếu ta xét thứ tự các phần tử bằng cơ chế lưu địa chỉ này thì ta có một danh sách: Bắc. Tây. Nam có địa chỉ của Tây.Mỗi nút có dạng như sau: INFO LINK chứa giá trị phần tử của nút. Bắc có địa chỉ nhà ở lần lượt là d. Đông.1 như sau Bắc d Đông Tây nil Nam t n Hình 2.Bộ môn CNPM – Khoa CNTT – ĐHTN . Giả sử: Đông có địa chỉ của Nam.t.1 – Danh sách liên kết đơn chứa 4 phần tử Như vậy. Tây không có địa chỉ của bạn nào.chỉ của ô nhớ đứng kế sau trong danh sách. * Mô tả cài đặt Trong cài đặt. Nam. Cấu trúc danh sách như vậy gọi là danh sách cài đặt bằng con trỏ hay 34 Cấu trúc dữ liệu .b. ta có thể hình dung cơ chế này qua ví dụ sau: Giả sử 1 nhóm có 4 bạn: Đông. Nam. lên Trường link của phần tử cuối trong danh sách. Bắc giữ địa chỉ của Đông. điều này được mô tả qua Hình 2. trỏ đến một giá trị đặc biệt là Nil (trỏ tới đất – không trỏ tới đâu). giả sử có kiểu dữ liệu là Item Là con trỏ.

Link: Cho địa chỉ của nút tiếp sau P Dạng cài đặt của danh sách: Type PList = ^ Nut. Nut = Record Infor:Iem.Chiếm 4 byte. Ví dụ: Type p = ^ Hs. Ví dụ: Var P: ^ integer . là đối tượng dùng để lưu trữ địa chỉ của đối tượng khác . ( Con trỏ P có kiểu nguyên.Một số hàm/thủ tục đối với con trỏ: + Thủ tục New(p): Xin cấp phát một vùng nhớ có kích thước bằng kích thước của kiểu tương ứng với kiểu của con trỏ p: *Nếu cấp phát được thì địa chỉ vùng nhớ đó sẽ được gán cho con trỏ p *Nếu không cấp phát được thì P = nil + Thủ tục Dispose(p): Giải phóng (thu hồi) vùng nhớ do con trỏ p trỏ tới.giả sử con trỏ L. sử dụng lệnh <Tên biến con trỏ>^ . End. Var L: Plis 35 Cấu trúc dữ liệu .Khai báo: Var <Tên biến con trỏ>: ^ <Kiểu con trỏ>. xuất phát từ phần tử đầu tiên. infor : Cho giá trị là trường infor của nút + p^ . . vậy biến trỏ là gì ? Nói thêm về con trỏ trong Pascal: . + Để truy nhập vào d/s ta phải truy nhập tuần tự đến vị trí mong muốn.Bộ môn CNPM – Khoa CNTT – ĐHTN . do đó để quản lý danh sách ta chỉ cần quản lý địa chỉ ô nhớ chứa phần tử đầu tiên của danh sách. Link : Plist. + Danh sách L rỗng khi: L=nil * Dạng cài đặt (Nói đến danh sách móc nối (Liên quan đến địa chỉ) ta phải nghĩ ngay đến biến trỏ.danh sách liên kết đơn hoặc danh sách móc nối đơn hay ngắn gọn gọi là danh sách liên kết.Truy nhập đến đối tương qua con trỏ. thường được sử dụng khi muốn loại bỏ 1 phần tử ra khỏi danh sách -Với một nút được trỏ bới con trỏ p thì : + p^ .Định nghĩa Type <Tên kiểu con trỏ> = ^ <Kiểu T>. tức là cần một con trỏ trỏ đến phần tử đầu tiên trong danh sách . Biến động P^) .

2. P: byte. Begin . M: pList.Kiểm tra một danh sách rỗng Danh sách rỗng nếu con trỏ trỏ tới phần tử đầu danh sách = nil Function EmptyList( L: Plist): boolean Begin EmptyList:= L=Nil.* Cài đặt các phép toán cơ bản với cấu trúc danh sách liên kết đơn 1.Đổ dữ liệu cần thêm vào ô nhớ vừa cấp phát: Temp^. . {2} . End.Tạo danh sách rỗng procedure MakeNullList(L: Plist) Begin L:=nil. M L A B p=3 Bỏ C D E 4 Temp x 3 Hình 2. dem: byte.Xác định vị trí thêm: ví trí thứ P: Nếu p=1: thêm vào đầu danh sách: Temp^.2: Thêm một phần tử vào danh sách tại vị trí p Phác thảo giải thuật thêm Procedure InsertList( X: Item. 2.Bộ môn CNPM – Khoa CNTT – ĐHTN . y: Item. var L: PList ) Var Temp. Sơ đồ nối kết và thứ tự các thao tác (từ 1-> 4) được cho trong Hình 2. Nếu (p>1) and (p<length(L)) thì: Di chuyển con trỏ M đến vị trí trước P: 36 Cấu trúc dữ liệu .link:=L.Yêu cầu máy tính cấp phát ô nhớ chứa dữ liệu cần thêm: New(Temp) {1}. End.Xen một phần tử vào danh sách : Xen một phần tử có giá trị x vào danh sách L tại vị trí p ta phải cấp phát một ô mới để lưu trữ phần tử mới này và nối kết lại các con trỏ để đưa ô mới này vào vị trí p.infor:=x.L:=Temp. 3 .

3: Xoá phần tử tại vị trí p Tương tự như khi xen một phần tử vào danh sách liên kết.Bộ môn CNPM – Khoa CNTT – ĐHTN . End. Var L: PList ) Var M. giả sử ví trí thứ p. Sửa đổi các mối liên kết để gắn kết Temp vào vị trí p trong danh sách L: Gắn Temp vào sau M: Temp^. {4} End.- M:=L. Dem: integer. Dem:=1.link:= Temp. ta di chuyển con trỏ M tới vị trí trước p. Trong đó Length(l) là hàm xác định chiều dài danh sách 4. While Dem<> p-1 do Begin m:=m^. Temp: Plist. muốn xóa một phần tử khỏi danh sách ta cần: + Xác định vị trí của phần tử muốn xóa trong danh sách L.3. + Giải phóng vùng nhớ chứa phần tử thứ P Giải thuật: Procedure DeleteList(P: integer. Begin If L=nil then write(„Danh sách rỗng!‟) 37 Cấu trúc dữ liệu .link:=M^.link. {3} M^.link Dem:= Dem +1. temp trỏ tới vị trí p + Nối kết lại các con trỏ theo thao tác 1 như trong Hình 2.Xóa phần tử ra khỏi danh sách L M L A B C D E p=3 1 Hình 2.

End Else Begin {di chuyển con trỏ M tới vị trí trước p. End.link:= Temp^. {tạo liên kết 2} M^. End.Trường Link của node cuối cùng trong d/s nối đơn chứa địa chỉ của node đầu tiên của d/s Hình ảnh của nó như sau: L A B C D Hình 2.link. End. Temp := M^.link.Bộ môn CNPM – Khoa CNTT – ĐHTN .4 – hình ảnh danh sách liên kết đơn vòng 38 Cấu trúc dữ liệu .Else If p<=length(L) then Begin If p=1 then Begin Temp:=L.link. temp trỏ tới vị trí p} M:=L.link. L:= L^. Dem:= Dem +1. While Dem<> p-1 do Begin M:=M^. 5 – Các phép toán còn lại xem nhƣ bài tập dành cho bạn đọc b) Một số dạng danh sách liên kết khác 1. {giải phóng vùng nhớ chứa dữ liệu cần xoá} Dispose(Temp).Là một cải tiến của d/s nối đơn .Danh sách nối vòng ( Circularly linked list) . End. Dem:=1.

từ phần tử trước có thể truy nhập đến phần tử đứng sau.6 – Thêm một phần tử ở vị trí trƣớc M 39 Cấu trúc dữ liệu .Danh sách nối kép (double link list) * Với danh sách móc nối đơn và nối vòng. từ một nút trong danh sách ta có thể truy cập được đến các nút khác Nhược điểm: Trong xử lý. Khắc phục hạn chế này ta có danh sách liên kết kép * Mỗi phần tử trong danh sách nối kép là một nút (bản ghi) gồm 3 trường: .Ưu điểm: . ta cũng có các phép toán tác động tương ứng) * Một số phép toán: 1) Phép bổ sung một phần tử (vào trước phần tử trỏ bởi con trỏ M) L A B (1) E × (4) × (3) C (2) q M D R Hình 2.5 – hình ảnh danh sách liên kết đôi Để truy nhập các phần tử của d/s ta có thể truy cập xuất phát từ một trong hai đầu của danh sách. nếu không cẩn thận dẫn đến 1 chu trình không kết thúc (Vì không biết được chỗ kết thúc d/s ) 2. .Bộ môn CNPM – Khoa CNTT – ĐHTN . phải của danh sách.Giúp cho việc truy nhập vào các node được linh hoạt hơn: vì node nào cũng có thể coi là node đầu tiên và con trỏ L trỏ tới node nào cũng được.Info : chứa thông tin về đối tượng.LPTR : con trỏ trỏ tới phần tử bên trái . với danh sách nối kép. Khi đó d/s rỗng nếu: L=R= nill (Tương tự như danh sách móc nối đơn.RPTR : con trỏ trỏ tới phần tử bên phải + Quy cách một node: LPTR INFO RPTR + Hình ảnh danh sách móc nối đối có dạng: L A B C D R Hình 2. Do đó. ta quản lý danh sách bằng cách dùng 2 con trỏ L. R lần lượt trỏ tới node cực trái. nhưng từ phần tử đứng sau không truy cập trực tiếp đến phần tử đứng ngay trước nó được. chỉ có phép duyệt 1 chiều.

RPtr := q. LPtr := q. L^ . LPtr . LPtr := M^.1. q^ . Nếu danh sách khác rỗng : { (1) (2) (4) (Giải thuật: Bạn đọc tự viết) 2) Phép loại bỏ 1 phần tử ra khỏi danh sách nối kép + Giả sử ta có một d/s nối kép. RPtr := nil. Tuy nhiên ta có thể tổng kết một số ưu nhược điểm của từng phương pháp làm cơ sở để lựa 40 Cấu trúc dữ liệu . q^ .Bộ môn CNPM – Khoa CNTT – ĐHTN . (3) q^ . Nếu : M =R : { R:= R^. PPtr^. q^ . + Giải thuật: Dành cho bạn đọc 2. Nếu : M^. LPtr. LPtr := nil . có 2 nút cực trái.LPtr^. Rptr . So sánh hai phương pháp cài đặt danh sách bởi mảng và bởi con trỏ Không thể kết luận phương pháp cài đặt nào hiệu quả hơn. LPtr := M^. R. RPtr := M^. (1) (2) Dispose (M). loại bỏ nút trỏ bởi con trỏ M ra khỏi danh sách (Chú ý: trong nhiều bài toán cụ thể: Yêu cầu. LPtr. M^. M^. LPtr^.3.Gắn pt vào d/s Nếu danh sách rồng (L = R = nil): { L := R := q. q^ . mà nó hoàn toàn tuỳ thuộc vào từng ứng dụng hay tuỳ thuộc vào các phép toán trên danh sách. khi đó ta phải di chuyển con trỏ M trỏ đến nút đó ) (1) L A B C (1) Hình 2. cực phải là L. RPtr .6 – Loại bỏ một phần tử ở vị trí M Nếu danh sách rỗng: (L = R = nil): Return Loại bỏ: Nếu : M =L : M D R { { L := L^. LPtr := nil. R^ . RPtr := nil .RPtr := M.3. nút cần loại bỏ là nút thoả mãn điều kiện nào đó.

không thể làm việc được còn nếu khai báo quá thừa thì lãng phí bộ nhớ. chỉ tốn một hằng thời gian đối với cài đặt bằng mảng.2 Ngăn xếp (Stack) và ứng dụng 2.Cài đặt bằng mảng thì thời gian xen hoặc xoá một phần tử tỉ lệ với số phần tử đi sau vị trí xen/ xóa. muốn lấy các đĩa ra khỏi chồng ta cũng phải lấy đĩa trên trước. do đó nếu không thể ước lượng được số phần tử trong danh sách thì khó áp dụng cách cài đặt này một cách hiệu quả vì nếu khai báo thiếu chỗ thì mảng thường xuyên bị đầy.Cài đặt bằng mảng đòi hỏi phải xác định số phần tử của mảng. ? Cho biết ưu khuyết điểm của danh sách đặc và danh sách liên kết ? Ưu khuyết điểm của các loại danh sách liên liên kết 2. đầu này gọi là đỉnh (TOP) của ngăn xếp. Tuy nhiên ta phải tốn thêm vùng nhớ cho các con trỏ (trường link).Nói chung danh sách liên kết thích hợp với danh sách có nhiều biến động. Ví dụ: Ta có thể xem hình ảnh trực quan của ngăn xếp bằng một chồng đĩa đặt trên bàn. chẳng hạn như PREVIOUS. .Bộ môn CNPM – Khoa CNTT – ĐHTN . danh sách có thể rỗng hoặc lớn tuỳ ý chỉ phụ thuộc vào bộ nhớ tối đa của máy. tức là ta thường xuyên thêm. Trong khi cài đặt bằng con trỏ các phép toán này mất chỉ một hằng thời gian. . 41 Cấu trúc dữ liệu .ra trước” hay “vào trước – ra sau“ (LIFO (last in .1. Như vậy ngăn xếp là một cấu trúc có tính chất “vào sau . trong đó việc thêm vào hoặc loại bỏ một phần tử chỉ thực hiện tại một đầu của danh sách.chọn phương pháp cài đặt thích hợp cho từng ứng dụng: .Ngăn xếp (Stack) là một danh sách đặc biệt.Phép truy nhập vào một phần tử trong danh sách. trong khi đối với danh sách cài đặt bằng con trỏ ta phải tìm từ đầu danh sách cho đến vị trí trước vị trí của phần tử hiện hành. xoá các phần tử. Muốn thêm vào chồng đó 1 đĩa ta để đĩa mới trên đỉnh chồng.first out ) hay FILO (first in – last out)).Cài đặt bằng con trỏ thích hợp cho sự biến động của danh sách. . Định nghĩa ngăn xếp .2.

POP(S. 42 Cấu trúc dữ liệu . Như vậy. để dễ hình dung: + Trong cách cài đặt sử dụng mảng ta nên thay trường Count = trường Top + Trong cách cài đặt sử dụng con trỏ.MAKENULL_STACK(S): tạo một ngăn xếp rỗng.7 – cấu trúc ngăn xếp 2.2.PUSH(x. Cài đặt ngăn xếp Do ngăn xếp là một danh sách đặc biệt nên ta có thể cấu trúc dữ liệu danh sách để biểu diễn nó. 3 .x) chương trình con lấy một phần tử tại đỉnh ngăn xếp S lưu vào biến x .Hình ảnh của ngăn xếp có dạng như sau: Hình 2.2. 2. 4 .2 Các phép toán cơ bản trên ngăn xếp: 1. ? Ta có thể truy xuất trực tiếp phần tử tại vị trí bất kỳ trong ngăn xếp được không? 2.S) chương trình con thêm một phần tử x vào đầu ngăn xếp. Tuy nhiên.EMPTY_STACK(S) kiểm tra ngăn xếp rỗng.3. Sau đây là phần cài đặt ngăn xếp bằng danh sách.. ta có thể khai báo ngăn xếp như sau: type Stack = List ( hoặc = PList). con trỏ quản lý ngăn xếp đặt tên là Top Khi chúng ta đã dùng danh sách để biểu diễn cho ngăn xếp thì ta nên sử dụng các phép toán trên danh sách để cài đặt các phép toán trên ngăn xếp.Bộ môn CNPM – Khoa CNTT – ĐHTN . Hàm cho kết quả true nếu ngăn xếp rỗng và false trong trường hợp ngược lại.

ngăn xếp đóng vai trò là vùng nhớ tam thời do đó một số ứng dụng sau đây thường dùng đến ngăn xếp: 1) Trong trình biên dịch (hoặc thông dịch). End. Begin Insert_List (x. Begin Top:=S. var S: Stack). ngăn xếp được dùng để lưu trữ môi trường của thủ tục.Bộ môn CNPM – Khoa CNTT – ĐHTN .Kiểm tra ngăn xếp rỗng: Function Empty_Stack(var S: Stack ) Begin Empty_Stack:= Empty_List(S).4. End. S). 4 . …. 5 – Lấy 1 phần tử ra khỏi ngăn xếp S lƣu và biến x để xử lý: dành cho bạn đọc Nhận xét: Như trên ta thấy.). ta hoàn toàn có thể dùng danh sách để biểu diễn cho một ngăn xếp và dùng các phép toán đã được cài đặt của danh sách để cài đặt các phép toán trên ngăn xếp. khi cần thực hiện các thủ tục (hoặc hàm). End. End. 3 – Hàm trả về vị trí đỉnh của ngăn xếp Function Top(S: Stack): integer. ngăn xếp được dùng để lưu trữ các dữ liệu trung gian khi giải các bài toán này (ví dụ lưu vết đường đi. ngăn xếp còn được sử dụng trong các trường hợp khử đệ quy để loại bỏ tính đệ qui của chương trình Ví dụ: 43 Cấu trúc dữ liệu .Tạo ngăn xếp rỗng: Procedure MakeNull_Stack( var S: Stack) Begin MakeNull_List(S).1. Ứng dụng ngăn xếp Cấu trúc ngăn xếp thích hợp với việc lưu trữ các loại dữ liệu mà có trình tự lưu trữ ngược với trình tự truy xuất dữ liệu.Thêm phần tử vào ngăn xếp Procedure Push(X: Item.count.2. Ngoài ra. và cài đặt trực tiếp các phép toán trên ngăn xếp mà không phải gọi tới các phép toán của danh sách. ta có thể cài đặt ngăn xếp trực tiếp bởi mảng hoặc con trỏ. Xem như bài tập dành cho bạn đọc 2. 2 . 2) Trong một số bài toán lý thuyết đồ thị (ví dụ: Tìm đường đi. First (S). …) 3) Ngoài ra.

. Bƣớc 3: Khôi phục lại các biến cục bộ và địa chỉ trở về.1.thảo luận Nêu phương pháp tính giá trị biểu thức theo ký pháp nghịch đảo Balan sử dụng ngăn xếp 2. . còn phép loại bỏ thì thực hiện ở đầu kia của hàng. Bài tập . địa chỉ này gọi là địa chỉ trở về. 44 Cấu trúc dữ liệu . Nếu không thì tính toán từng phần và quay lại bước 1 (đệ qui tiếp). Ví dụ: Khi ta xếp hàng mua vé xem phim là một hình ảnh trực quan của khái niệm trên.first out) hay "vào trước ra trước". Định nghĩa hàng đợi Hàng đợi. hay ngắn gọn là hàng (queue) là một danh sách đặc biệt mà phép thêm vào chỉ thực hiện tại một đầu của hàng. Tính chất này gợi ý cho ta dùng một ngăn xếp để lưu giữ các giá trị cần thiết của mỗi lần gọi tới chương trình con. Như vậy những biến cục bộ và địa chỉ lưu sau được dùng trước.EMPTY_QUEUE(Q) hàm kiểm tra hàng rỗng.3. . Hàng đợi (QUEUE) 2. vì vậy hàng còn được gọi là cấu trúc FIFO (first in . Hình ảnh của hàng có dạng như sau: A1 Front Hình 2. Bƣớc 2: Nếu thoả điều kiện ngừng đệ qui thì chuyển sang bước 3. Khi từ mức i+1 quay về mức i các giá trị đó được sử dụng. cho đến một mức k. gọi là đầu hàng (FRONT). hay ta còn nói là chương trình con quay về mức k-1.MAKENULL_QUEUE(Q) khởi tạo một hàng rỗng.FRONT(Q) hàm trả về phần tử đầu tiên của hàng Q.Nếu một chương trình con đệ qui P(x) được gọi từ chương trình chính ta nói chương trình con được thực hiện ở mức 1. Trong khi một chương trình con từ mức i đi vào mức i+1 thì các biến cục bộ của mức i và địa chỉ của mã lệnh còn dang dở phải được lưu trữ. . Ta có thể tóm tắt quá trình như sau: Bƣớc 1: Lưu các biến cục bộ và địa chỉ trở về.3. Rõ ràng mức k phải thực hiện xong thì mức k-1 mới được thực hiện tiếp tục. gọi là cuối hàng (REAR)..2 Các phép toán cơ bản trên hàng .Bộ môn CNPM – Khoa CNTT – ĐHTN . .DEQUEUE(Q): xoá phần tử tại đầu của hàng Q. ta nói nó đi sâu vào mức 2.INSERT_QUEUE(x. người mới đến thêm vào cuối hàng còn người ở đầu hàng mua vé và ra khỏi hàng.Q) thêm phần tử x vào cuối hàng Q.8 – Hình ảnh hàng A2 A3 A4 Rear Bây giờ chúng ta sẽ thảo luận một vài phép toán cơ bản nhất trên hàng 2. Chương trình con này gọi chính nó. Mỗi khi lùi về một mức thì các giá trị này được lấy ra để tiếp tục thực hiện mức này.3.

Như vậy hàng có khuynh hướng đi xuống. khởi đầu phần tử đầu tiên của hàng được đưa vào vị trí thứ 1 của mảng.. + Khi thêm một phần tử rear tăng lên 1.1 Cài đặt hàng bằng mảng Ta dùng một mảng để chứa các phần tử của hàng (giả sử hàng có tối đa n phần tử). (Hình 2. 2..3..3 Cài đặt hàng đợi Như đã trình bày trong phần ngăn xếp. ta có front=1 và rear=n. + Khi xoá một phần tử front tăng lên 1. Để cài đặt hiệu quả hơn ta phải có một suy nghĩ khác dựa trên tính chất đặc biệt của phép thêm và loại bỏ một phần tử trong hàng. Trong trường hợp này ta luôn có front<=rear.Q) tốn một hằng thời gian trong khi lời gọi DELETE_LIST(FIRST(Q). ta hoàn toàn có thể dùng danh sách để biểu diễn cho một hàng và dùng các phép toán đã được cài đặt của danh sách để cài đặt các phép toán trên hàng. Đó là sử dụng trực tiếp mảng và con trỏ để cài đặt hàng. đến một lúc nào đó ta không thể thêm vào hàng được nữa (rear = n) dù mảng còn nhiều chỗ trống (các vị trí trước front) trường hợp này ta gọi là hàng bị tràn. Giả sử hàng có n phần tử. Tuy nhiên làm như vậy có khi sẽ không hiệu quả.3. 2.Q) để xoá phần tử đầu hàng (phần tử ở vị trí 0 của mảng) ta phải tốn thời gian tỉ lệ với số các phần tử trong hàng để thực hiện việc dời toàn bộ hàng lên một vị trí.Dời toàn bộ hàng lên front -1 vị trí: cách này gọi là di chuyển tịnh tiến. chẳng hạn dùng danh sách cài đặt bằng mảng ta thấy lời gọi INSERT_LIST(x. phần tử thứ 2 vào vị trí thứ 2 của mảng.ENDLIST(Q). Trong trường hợp toàn bộ mảng đã chứa các phần tử của hàng ta gọi là hàng bị đầy.9 – Tịnh tiến hàng 45 Cấu trúc dữ liệu . Các cách khắc phục hàng bị tràn 1 .9.3.Bộ môn CNPM – Khoa CNTT – ĐHTN 1 2 3 Front → 4 5 6 7 Rear → 8 Hàng sau khi tịnh tiến . minh họa cho cách này) 1 2 3 Front → 4 5 6 7 Rear → 8 Hàng tràn Hình 2.FULL_QUEUE(Q) kiểm tra hàng đầy.

Q... {chỉ số đầu và đuôi hàng} Count: 0.count:=0 End..n] of Item. thêm một phần tử mới nữa thì thêm vào vị trí 2 (nếu có thể). n. Q.. Procedure MakeNull_Queue( var Q: Queue ) Begin Q.... {Kiểu dữ liệu của các phần tử trong hàng} Queue = Record Elements: Array[1.... 46 Cấu trúc dữ liệu . .. Var Q: Queue.Front=1.2 . Rear: 0 ...10: Cài đặt hàng bằng mảng xoay vòng Với phương pháp này.. khi hàng bị tràn.. {đếm số phần tử hiện có trong hàng} End. //Lưu trữ nội dung các phần tử Front. thì ta thêm phần tử mới vào vị trí 1 của mảng và cứ tiếp tục thêm vào vị trí tiếp theo (nếu còn trống).Bộ môn CNPM – Khoa CNTT – ĐHTN .Xem mảng như là một vòng tròn: Nghĩa là khi hàng bị tràn nhưng chưa đầy ta thêm phần tử mới vào vị trí 1 của mảng. b) Các phép toán trên hàng tròn 1 ... vì từ 1 đến front-1 là các vị trí trống. 1 2 Front 3 .Rõ ràng cách làm này front có thể lớn hơn rear... 2. Cách khắc phục này gọi là dùng mảng xoay vòng (xem Hình 2. tức là rear = n.Rear=0.....Tạo hàng rỗng Ta thấy: Lúc này front và rear không trỏ đến vị trí hợp lệ nào trong mảng vậy ta có thể cho front và rear đều bằng 0.n.. tức là front>1. {chiều dài tối đa của mảng} Type Item = ….Kiểm tra hàng rỗng Function Empty_Queue(Queue Q):Boolean. nhưng chưa đầy.. Rear n n-1 Hình 2. count=0. a) Dạng cài đặt hàng Const n =.10)..

Ngược lại. 47 Cấu trúc dữ liệu .Trường hợp Q.Count=0.Front +1. Front <>n then Q.Front =Q.Xóa một phần tử ra khỏi hàng Các bƣớc làm: Khi xóa một phần tử ra khỏi hàng ta làm như sau: . Begin Full_Queue:= Q. Ta có hai trường hợp hàng đầy như sau: .Bộ môn CNPM – Khoa CNTT – ĐHTN .count= 0 ) then MakeNull_Queue(Q). 3 . tức Count = Maxlength Function Full_Queue(Q: Queue): Boolean.Trường hợp Q.Front+1 Else Q.Rear=n và Q. Lƣu ý: Nếu biện luận tính đầy theo Front và Real. .front <>n thì đặt lại Q.rear-Q.Front như sau: Nếu Q. End. End.front=1 Giải Thuật Procedure DeQueue(Var Q: Queue) Begin if ( not Empty_Queue(Q)) then begin { Nếu hàng chỉ chứa một phần tử thì khởi tạo lại hàng} if (Q.front +1) mod n=0 3 . Ngược lại Q.Front =1 .Rear+1.Kiểm tra hàng đầy Hàng đầy nếu toàn bộ các ô trong mảng đang chứa các phần tử của hàng.Front := 1.Nếu hàng rỗng thì báo lỗi không xóa. else if Q. thay đổi giá trị của Q.Front : = Q.Nếu hàng khác rống: Cập nhật lại giá trị của Front và count như sau: Nếu hàng chỉ còn 1 phần tử thì khởi tạo lại hàng rỗng. Để đơn giản ta có thể gom cả hai trường hợp trên lại theo một công thức như sau: (Q.count=n.Begin Empty_Queue := Q.front = Q. ta thấy với phương pháp này thì front có thể lớn hơn rear.

rear=1.Q.Var Q: Queue) Begin if (not Full_Queue(Q)) Then Begin If (Q. end else Writeln(„Loi: Hang rong!‟). Cài đặt hàng bằng danh sách liên kết (cài đặt bằng con trỏ) Cách tự nhiên nhất là dùng hai con trỏ front và rear để trỏ tới phần tử đầu và cuối hàng.Rear := Q. . Hàng được cài đặt như một danh sách liên kết a) Dạng cài đặt: Type Node = record Infor: ITem.rear+1) và đặt nội dung cần thêm vào vị trí Q.Rear +1 Else Q. //Con trỏ chỉ ô kế tiếp End.Ngược lại. Rear: ^ Node.rear mới.rear =Q. End.count – 1. 4 .2.Rear <>MaxQueue) then Q. End.Rear = n thì đặt lại Q. ? Cài đặt hàng bằng mảng vòng có ưu điểm gì so với bằng mảng theo phương pháp tịnh tiến? Trong ngôn ngữ lập trình có kiểu dữ liệu mảng vòng không? 2.3.3. thay đổi giá trị của Q. Ngược lại Q. {lưu giá trị của phần tử } Next: ^Node. Queue = Record Front.Rear (Nếu Q. 48 Cấu trúc dữ liệu .Rear := 1. Q.count:= Q.Thêm một phần tử vào hàng Khi thêm một phần tử vào hàng thì có thể xảy ra các trường hợp sau: .Bộ môn CNPM – Khoa CNTT – ĐHTN . Procedure Insert_Queue(X: Item.Rear] := x. {là hai trường chỉ đến đầu và cuối của hàng} End. End else Writeln(„Loi: Hang day!‟).Trường hợp hàng đầy thì báo lỗi và không thêm.Elements[Q.

Bộ môn CNPM – Khoa CNTT – ĐHTN .ra trước đều có thể ứng dụng hàng đợi. Begin If (Q. Q.b) Cài đặt các phép toán 1 . Ví dụ rất dễ thấy là quản lí in trên mạng. quá trình. tiền tố cũng cần dùng đến cấu trúc hàng.Các phép toán còn lại xem nhƣ bài tập dành cho bạn đọc 2.Rear=nil) then EmptyQueue := true Else EmptyQueue:=false. theo kiểu vào trước . 3 ..Khởi tạo hàng rỗng Khi hàng rỗng Front va Rear cùng trỏ về xuống đất (có giá rị nil) Procedure MakeNullQueue(var Q: Queue ) Begin Q. Một số ứng dụng của cấu trúc hàng Hàng đợi là một cấu trúc dữ liệu được dùng khá phổ biến trong thiết kế giải thuật. 49 Cấu trúc dữ liệu . End. nhưng máy in không thể đáp ứng tức thời tất cả các yêu cầu đó nên chương trình quản lí in sẽ thiết lập một hàng đợi để quản lí các yêu cầu. Nói chung có nhiều yêu cầu in dữ liệu.Front = nil)and(Q.Front :=nil.4.. Các giải thuật duyệt theo chiều rộng một đồ thị có hướng hoặc vô hướng cũng dùng hàng đợi để quản lí các nút đồ thị.Kiểm tra hàng rỗng Hàng rỗng nếu Front và Rear cùng có giá trị nil Function EmptyQueue(Q: Queue Q): Boolean.Rear :=nil. Bất kỳ nơi nào ta cần quản lí dữ liệu. nhiều máy tính yêu cầu in đồng thời và ngay cả một máy tính cũng yêu cầu in nhiều lần. End.3. Yêu cầu nào mà chương trình quản lí in nhận trước nó sẽ giải quyết trước. Các giải thuật đổi biểu thức trung tố thành hậu tố. 2 .

sau đây ta định nghĩa bằng đệ quy như sau : 1) Tập có một đỉnh là một cây.. .Các cách biểu diễn cây trên máy tính và cài đặt chúng 3. T2 .1 Định nghĩa cây và các khái niệm cơ bản trên cây Có nhiều cách định nghĩa cây. r2.Bộ môn CNPM – Khoa CNTT – ĐHTN .. . phân biệt các mô hình cây: Cây tổng quát.mục lục lục một quyển sách Hình 3. Mục tiêu Sau khi học xong chương này... . các cây T1 .Nắm vững khái niệm về cây..Các mô hình dữ liệu Cây ..Biểu diễn và cài đặt được các phép toán cơ bản trên cây 2. r r1 r2 rk T1 T2 Tk Ví dụ về cây: Xét mục lục của một quyển sách. . Mục lục này có thể xem là một cây: Hình III.Chƣơng 3 Mô hình dữ liệu Cây Tổng quan: 1. sinh viên phải: . Nếu r là một đỉnh không thuộc các cây T 1 . cây nhị phân tìm kiếm.. Tk đôi một không cắt nhau.. . . .1. Tk thế thì tập T gồm đỉnh r và tất cả các đỉnh của các cây Ti làm thành một cây mới gốc r. cây nhị phân.Các thuật ngữ cơ bản. . cây này có gốc là đỉnh duy nhất của nó. rk .. . . .1 – CâyCây mụccủa một quyển sách 50 Cấu trúc dữ liệu . T2 .1 . Nội dung chính Trong chương này chúng ta sẽ nghiên cứu các vấn đề sau: .. Tk (k  1) là các cây có gốc tương ứng là r1. T2 .1 Cây tổng quát Cây tổng quát hay còn gọi là cây đa phân – gọi tắt là cây 3. 2) Giả sử T1 .

a2..  Các đỉnh có cùng cha gọi là anh em. Trong cài đặt cụ thể thì $ là một giá trị nào đó do ta chọn. nó phụ thuộc vào cấu trúc dữ liệu mà ta dùng để cài đặt cây.Hàm value(n) Trả về giá trị lưu tại nút n của cây T. nếu n không có anh em ruột phải thì hàm cho giá trị $. b2.. mỗi đỉnh a của nó là gốc của một cây nào đó gọi là cây con của T.Hàm NEXT_SIBLING(n) cho nút anh em ruột phải nút n trên cây T. Đỉnh có bậc = 0 gọi là lá (đỉnh tận cùng).  Nếu tập các đỉnh = rỗng => Cây là cây rỗng  Số các cây con ở mỗi đỉnh được gọi là bậc của đỉnh đó.Xét cây tổng quát sau: 51 Cấu trúc dữ liệu . đỉnh không là lá gọi là đỉnh trong.2 Các phép toán cơ bản trên cây Xét cây gốc T: . bm theo thứ tự này thì ta nói b1 là con trưởng và b2 là em liền kề của nó. trong đó có một đỉnh đặc biệt gọi là gốc (root). .1.3 Các cách thăm ( duyệt) cây ..  Trong một cây T.. . Luôn tồn tại đường đi từ gốc đến một đỉnh bất kỳ trong cây. . Nếu đỉnh a có các đỉnh con b1.1. 3. sao cho mỗi đỉnh chỉ được thăm duy nhất một lần . .Hàm PARENT(n) trả về nút cha của nút n trên cây T. nếu n là lá thì hàm cho giá trị $. .Bộ môn CNPM – Khoa CNTT – ĐHTN .. Nếu đỉnh cha có mức i thì các đỉnh con của nó sẽ có mức là i+1  Một dãy các đỉnh a1. an (n>0) sao cho ai là cha của ai+1 gọi là một đường đi từ a1 đến an với độ dài n-1.  Cây được sắp: Các đỉnh trong cây được sắp xếp theo thứ tự nào đó.Duyệt cây: Là phép thăm các đỉnh trên cây. .con. Nếu a là đỉnh gốc của một cây còn b là gốc cây con của đỉnh a thì ta nói a là đỉnh cha còn b là đỉnh con. Giữa các đỉnh có một quan hệ phân cấp gọi là quan hệ cha .Như vậy cây bao gồm một tập hữu hạn các đỉnh. 3.Hàm ELDEST_CHILD(n) cho nút con trái nhất của nút n trên cây T. nếu n là nút gốc thì hàm cho giá trị $. xét từ trái sang phải  Chiều cao của cây (height) của cây là số mức lớn nhất của đỉnh có trên cây  Tập các cây con phân biệt người ta gọi là Rừng  Gốc của cây có mức 1 (level = 1).

Duyệt cây theo thứ tự trước – Preorder . .Duyệt cây theo thứ tự sau .T A B C D F H I J E G Hình 3.Thăm gốc T 2. T2. Tn 52 Cấu trúc dữ liệu . Tn theo thứ tự trước => Gốc T1 T2 T3 . T3.Thăm các cây con T1. giải thuật tương ứng với từng phương pháp: Giả sử xét cây gốc T 1) Duyệt theo tứ tự trƣớc (PreOrder Tree) * Nguyên tắc duyệt cây theo thứ tự trước PreOrder (T) Nếu cây rỗng(T = nil): Không làm gì Nếu cây không rỗng : { 1..Postorder ... * Nguyên tắc duyệt.Bộ môn CNPM – Khoa CNTT – ĐHTN .2 – Hình ảnh một cây * Xét ba phương pháp duyệt cây cơ bản: .Duyệt cây theo thứ tự giữa – Inorder .

T3. Tn theo thứ tự giữa 53 Cấu trúc dữ liệu .Ví dụ: Xét cây T1 2 B 4 5 H 6 F I 1 A T2 C 7 J 3 E 8 T T3 D G 10 ( T2 9 kq ==> A B CFHIEJ DG gèc T1 * Giải thuật: Procedure PreOrder (T: nodeType). C := NextSibling (C). Var C: nodeType. .... End.. Begin Visit (T). End. {Thăm gốc} C := Eldestchild (T). Thăm gốc của T 3 Duyệt các cây con còn lại T2.Bộ môn CNPM – Khoa CNTT – ĐHTN ( T3 ( ( . Duyệt cây con thứ nhất T1 của gốc T theo thứ tự giữa 2. { con cả} While (C<>$) do Begin PreOrder (C). 2) Duyệt theo thứ tự giữa (InOrder Tree) * Nguyên tắc duyệt cây theo thứ tự giữa: {em liền kề} +InOrder T Nếu cây rỗng(T = nil): Không làm gì Nếu cây không rỗng : { 1.

{ con cả} If (C <> $) then begin InOrder(C). Begin C := EldestChild (T).Thăm các cây con T1. Tn 54 Cấu trúc dữ liệu .. . Tn theo thứ tự sau => Gốc -> T1 -> T2 -> T3 -> .* Ví dụ: Xét cây T T1 1 B 4 3 H 5 F I 2 A T2 C 8 J 6 E 7 10 D G T3 kq ==> B A HFICJE GD ( T2 ( ( T1 gèc 9 * Giải thuật: Procedure InOrder (T: nodeType). C := NextSibling (C).. {em liền kề} end. Visit(T). Var C: nodeType. {em liền kề} End..Bộ môn CNPM – Khoa CNTT – ĐHTN ( T3 . T2. {thăm gốc} While (C <> $) do Begin InOrder (C).Thăm gốc T 2. T3. C := NextSibling (C). 3) Duyệt theo thứ tự sau * Nguyên tắc duyệt cây theo thứ tự sau: PostOrder T Nếu cây rỗng(T = nil): Không làm gì Nếu cây không rỗng : { 1. End.

3. Begin C := EldestChild (R). { con cả} While (C <> $) do Begin PostOrder (C).…. {em liền kề} End.Bộ môn CNPM – Khoa CNTT – ĐHTN ( gèc .1. Danh sách các con có thể cài đặt bằng bất kỳ cách nào chúng ta đã biết. Visit (R).* Ví dụ: Xét cây T T1 1 B 4 2 H 3 F I 10 A T2 C 6 J 7 E 5 9 D G T3 T3 kq ==> B HIFJEC GD A ( T2 ( T3 ( T1 8 * Giải thuật: Procedure PostOrder (T: nodeType). giả sử ta gán chỉ danh cho các nút lần lượt là 1. tuy nhiên vì số nút con của một nút là không biết trước nên dùng danh sách liên kết sẽ thích hợp hơn.1 Biểu diễn cây bằng danh sách các con của mỗi đỉnh a) Sử dụng mảng để biểu diễn cây Biểu diễn cây dưới dạng mỗi nút có một danh sách các nút con. {Thăm cha} End.2. Cài đặt cây 3.4. Để tiện cho việc truy cập đến các đỉnh trên cây ta nên tổ chức lưu trữ các đỉnh này sử dụng cấu trúc mảng.1. Var C: nodeType.n Ví dụ: Xét cây bên trái dưới đây được mô tả bởi mảng như sau : => 55 Cấu trúc dữ liệu . C := NextSibling (C).4.

tìm con cả của nó Function EldestChild (k: in): 0 .Cho một đỉnh thứ k trên cây T. *) Các phép toán tác động trên cây 1 . n.3 – Cây và hình ảnh cây biểu diễn bởi danh sách các con của mỗi đỉnh . Begin if (T[k]. trỏ tới con đầu tiên trong danh sách các con của nó} End. {chứa giá của phần tử lưu tại đỉnh cây} Child : Pointer..Child <> nil) then 56 Cấu trúc dữ liệu . Node = Record Info: Item.Bộ môn CNPM – Khoa CNTT – ĐHTN . {cây là một mảng} Var T: Tree... {ID: chỉ danh của đỉnh} Next: Pointer. Pointer = ^ Member. Const n = MaxNode. Member = Record Id: 1 . n Type Item = <kiểu thông tin của đỉnh>. n. n . {con trỏ. Hình 3. . {Next: trỏ tới em liền kề} End. Tree = Array [ 1 .TT Info Child 1 A 2 B 4 D 5 E I 6 C 3 F 7 G H 8 1 2 3 4 5 6 11 9 7 8 Hình ảnh cây sau khi cài đặt 9 10 A B C D E F G H I K Id Next 2 4 6 9 3 5 7 10 8 9 10 9 K M 11 11 M ………… . n ] of Node. * Dạng cài đặt tƣơng ứng sẽ là: .

trỏ tới gốc cây} * Cài đặt các phép toán cơ bản với dạng cài đặt này: xem như bài tập dành cho bạn đọc 57 Cấu trúc dữ liệu . n. Begin i := 1. {K là số tối đa các con của mỗi đỉnh} type pointer = ^node. var Root : pointer. {lưu thông tin của đỉnh} childs : array[1.. {mảng chứa các con của đỉnh} end. n. Var i: 1 . . end else p := p^. n): 0 ... {Root là con trỏ. end.K] of pointer. ta có thể dùng mảng để lưu danh sách các con này * Dạng cài đặt const K = . 2 .Bộ môn CNPM – Khoa CNTT – ĐHTN . Else EldestChild := 0.Child^. End.Cho một đỉnh thứ k trên cây T.. p: pointer. found := false. End.EldestChild := T[k]. i := i + 1.. Next. found : boolean. While ( i<= maxNode)and(not found) do begin p := T[i]. found := true. While ( p<>nil)and(not found) do if (p^. 3 – Các phép toán còn lại xem nhƣ bài tập dành cho bạn đọc b) Sử dụng con trỏ để biểu diễn cây Nếu ta biết trước số con tối đa của mỗi đỉnh trên cây.Id. node = record info : Item.child. if (not found) then parent := 0. Id = k) then begin parent := i.. Tìm cha của nó Function Parent (k: 1 .

3. ngược lại thì dừng việc tìm kiếm và kết luận i chính là đỉnh cha cần tìm.. 58 Cấu trúc dữ liệu . kiểm tra em liền kề của con trưởng j xem có = k hay không. ta làm như sau: Duyệt lần lượt các đỉnh trên cây.4. Để tìm cha của đỉnh thứ k trên cây T. ngược lại.. ở đây ta xét phép toán tìm cha của đỉnh k trên cây T. kiểm tra xem con trưởng J của i có = k hay không? Nếu có => i chính là cha cần tìm.N. {Lưu giá trị của phần tử lưu tại đỉnh} EldestChild : 0.Bộ môn CNPM – Khoa CNTT – ĐHTN . nếu có thì i chính là là cần tìm.N.. xuất phát từ gốc (i =1). {Chứa chỉ danh của đỉnh là em liền kề} end. Cài đặt bởi mảng : a) Dạng cài đặt Mỗi đỉnh trên cây được cài đặt như một node như sau: Type Node = record info : Item. nếu không kiểm tra j là em liền kề của j cũ.1. cứ tiếp tục như vậy cho đến khi kiểm tra hết các con của i mà không có con nào = k thì duyệt đỉnh i tiếp theo trên cây. {Chứa chỉ danh của đỉnh là con trưởng} NextSibling : 0.4 – Cây và hình ảnh cây đƣợc biểu diễn bởi mảng các con trƣởng và em liền kề của mỗi đỉnh a2 a3 a4 a5 a6 a7 a8 a9 A M 4 6 0 9 0 11 0 0 0 0 3 0 5 0 7 8 0 10 0 0 5 a5 a6 a7 6 7 E a9 A M 10 1 11 9 9 9 b) Các phép toán Với cách cài đặt này ta thấy dễ dàng tìm con trưởng và em liền kề của đỉnh k – xem như bài tập dành cho bạn đọc. {cây là một mảng các nút} Ví dụ: Xét cây sau: 1 a1 2 a2 a4 a3 3 a8 8 Cây sau khi cài đặt có dạng như sau: ============= ID Infor EldestChild NextSibling 1 a1 2 0 2 3 4 5 6 7 8 9 10 11 … n Hình 3.2 Biểu diễn cây bằng con trưởng và em liền kề của mỗi đỉnh 1. Tree = array[1.N] of node.

2.j : 0. if j=k then begin parent := i. found := true.Nextsibling. while (i<=N) and (not found) do begin j := T[i]. found : boolean.NextSibling.N . found := false. {lưu thông tin phần tử của đỉnh} eldestchild : pointer. if not found then parent := 0. {Eldestchild: Trỏ tới gốc cây con cả của đinh} Nextsibling : pointer. Cài đặt cây bởi con trỏ : * Dạng cài đặt type pointer = ^node. luôn trỏ tới gốc cây}  Cấu trúc cuả 1 nút có dạng như sau: eldestchild Info Nextsibling 59 Cấu trúc dữ liệu . while (j<>0) and (not found) do if j=k then begin parent := i.. end.Bộ môn CNPM – Khoa CNTT – ĐHTN . var Root : pointer..EldestChild. i:=i+1.Giải thuật: function Parent(k : 1. node = record info : Item.. {NextSibling: Trỏ tới gốc em liền kề của đỉnh} end. end. end else j := T[j]. {là con trỏ quản lý cây.N): 0. found := true. end else begin j := T[j]. var i.N. begin i := 1. end.

5 – Cây và hình ảnh cây đƣợc biểu diễn bởi con trƣởng và em liền kề của mỗi đinh sử dụng con trỏ ? Hãy so sách các ưu khuyết điểm của các cách cài đặt cây. 9 a7 nil A nil nil a8 nil nil M nil Hình 3.6 – Cây và hình ảnh cây đƣợc biểu diễn bởi cha của mỗi đỉnh sử dụng con trỏ 60 Cấu trúc dữ liệu .Root Ví dụ: Xét cây sau: 1 a1 2 a2 a4 a3 3 nil a4 5 a5 a6 a7 6 7 E a9 A 10 1 9 M 11 9 nil a9 a8 8 a5 nil Cây sau khi cài đặt có dạng như sau: ============= a2 a1 nil a3 nil nil a6 .3 Biểu diễn cây bởi cha của mỗi đỉnh Node Info Parent Ví dụ: Xét cây 1 A 2 B 4 D 5 E I 6 C 3 F 7 G H 8 1 2 3 Hình ảnh cây 4 sau khi cài đặt => 5 6 7 8 9 10 11 A B C D E F G H I K M  1 1 2 2 3 3 3 5 5 7 9 10 9 K M 11 9 Hình 3. Nhận xét: Với cách cài đặt này ta thấy: Cây rỗng: Root = nil .Bộ môn CNPM – Khoa CNTT – ĐHTN . là cách biểu diễn để ta chuyển cây đa phân trở về cây nhị phân tương đương * Cài đặt các phép toán cơ bản trên cây: xem như bài tập dành cho bạn đọc 3.1.4.

End. End. End..Cài đặt: const N = .N.. 2 . While ( i<=MaxNode) and (not found) do Begin {2} p :=T[i]. T: Tree): Integer. Begin {1} If k = 0 then EldestChild := 0.Parent. If k=p then Begin {3} EldestChild := i. Begin If k = 0 then Parent := 0 Else Parent :=T[k].Cho một đỉnh k . type Node = record info : Item. found: Boolean. found:=false..Parent. {3} i:=i+1. i : 0 … MaxNode . tìm đỉnh cha của nó Function Parent ( k: Integer. .N] of Node. Var p. T: Tree): Integer. Tree = array[1. i:=1. {1} 61 Cấu trúc dữ liệu .Cho một đỉnh k . parent : 0. End. {2} If ( not found) then EldestChild := 0. end. tìm đỉnh con cả của nó Function EldestChild (k: Integer. Các phép toán quan trọng trên cây 1 . var T : Tree..Bộ môn CNPM – Khoa CNTT – ĐHTN . found := true.

. End. 3. while ( i <= MaxNode) and (not found) do begin {3} if (T[i].Cho một đỉnh k .Parent = p) then begin {4} NextSibling := i. T: Tree): Boolean. Begin Value := T[k]. MaxNode .Bộ môn CNPM – Khoa CNTT – ĐHTN . {3} if (not found) then NextSibling := 0. {1} 4 – Lấy giá trị lƣu tại nút thứ k trên cây T Function Value(k:integer): Item. end.Parent. found: Boolean.3 .2 Cây nhị phân (binary tree) 3. mỗi nút con được nối với nút cha của nó bởi một đoạn thẳng. i : 0 . Ta qui ước vẽ nút con trái bên trái nút cha và nút con phải bên phải nút cha. {4} i:= i+1. Ví dụ: Hai cây n h ị p h â n s a u l à k h á c n h a u : m ộ t c â y c ó c o n t r á i v à m ộ t cây có con phải 62 Cấu trúc dữ liệu . If p = 0 then NextSibling := 0 Else Begin {2} i := k+1. Begin {1} p := T[k]. { vì đỉnh em liền kề > đỉnh anh là 1} found := false. Found:= true.2.1 Định nghĩa Cây nhị phân là cây rỗng hoặc là cây mà mỗi nút có tối đa hai nút con. một nút con gọi là nút con trái và một nút con gọi là nút con phải. Var p. End. {2} End. end. tìm em liền kề của nó Function NextSibling ( k: Integer. Trong đó các nút con của cây được phân biệt thứ tự rõ ràng.info.

A ≠ B

A B

* Các dạng cây nhị phân đặc biệt

Cây Zic-Zắc cây lệch trái cây lệch phải

( Đây là các dạng của cây nhị phân suy biến, có dạng là một danh sách )

Cây nhị phân hoàn chỉnh: Các nút ứng với các mức trừ mức gần Nhậncuối cùng đều có 2 con mức xét:

Cây nhị phân đầy đủ: Các nút có bậc tối đa ở mọi mức kể cả mức gần mức cuối cùng

+ Trong cây nhị phân có cùng số đỉnh - Cây nhị phân suy biến có chiều cao lớn nhất - Cây nhị phân đầy đủ có chiều cao nhỏ nhất + Với cây nhị phân đầy đủ cần chú ý tới một số tính chất - Số lượng tôí đa các đỉnh ở mức i là 2i – 1 - Số lượng tối đa các đỉnh trên cây có chiều cao h là: 2h - 1 3.2.2. Duyệt cây nhị phân Ta có thể áp dụng các phép duyệt cây tổng quát để duyệt cây nhị phân. Tuy nhiên vì cây nhị phân là cấu trúc cây đặc biệt nên các phép duyệt cây nhị phân cũng đơn giản hơn. Có ba cách duyệt cây nhị phân thường dùng (xem kết hợp với Hình 3.7):
63 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

root

left Hình 3.7 – Cây nhị phân

right

- Duyệt tiền tự - thứ tự trước (root-Left-Right): duyệt nút gốc, duyệt tiền tự con trái rồi duyệt tiền tự con phải. - Duyệt trung tự - duyệt theo thứ tự giữa (Left-Root-Right): duyệt trung tự con trái rồi đến nút gốc sau đó là duyệt trung tự con phải. - Duyệt hậu tự - duyệt theo thứ tự sau(Left-Right-Root): duyệt hậu tự con trái rồi duyệt hậu tự con phải sau đó là nút gốc. Chú ý: Danh sách duyệt tiền tự, hậu tự của cây nhị phân trùng với danh sách duyệt tiền tự, hậu tự của cây đó khi ta áp dụng phép duyệt cây tổng quát. Nhưng danh sách duyệt trung tự thì khác nhau. Ví dụ: Xét cây:
A B C

D

E

F

G

H

I

J

K

L

M

Các danh sách duyệt cây nhị phân Tiền tự: Trung tự: Hậu tự: ? ABDHIEJCFKLGM HDIBJEAKFLCGM HIDJEBKLFMGCA

Các danh sách duyệt cây tổng quát ABDHIEJCFKLGM HDIBJEAKFLCMG HIDJEBKLFMGCA

1. Danh sách duyệt tiền tự và hậu tự của cây nhị phân luôn luôn giống với danh sách duyệt của cây tổng quát. (Đúng / Sai) 2. Danh sách duyệt trung tự của cây nhị phân sẽ khác với các duyệt tổng quát chỉ khi cây nhị phân bị khuyết con trái? (Đúng/ Sai)

64 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

3.2.3 Cài đặt cây nhị phân a) Biểu diễn cây nhị phân bằng mảng const maxNode = ?; Type Item = <Kiểu dữ liệu của các >; Node = Record Infor : Item;{lưu giá trị của đỉnh} Left, Right : 0 .. maxNode;{lưu chỉ danh của con trái, con phải} End; Tree = Array[1 .. maxNode] of Node; {cây} b) Biểu diễn cây nhị phân bằng con trỏ Type Item = <Kiểu dữ liệu thông tin của đỉnh> ; PTree = ^ Node; Node = Record Infor : Item; Left, Right : PTree;{trỏ tới gốc cây con trái, con phải} End; Var T : PTree; {T luôn trỏ tới gốc cây} 3.2.4 Các phép toán cơ bản trên cây nhị phân - Ta cài đặt các phép toán với dạng biểu diễn cây bởi con trỏ, với dạng biểu diễn còn việc cài đặt các phép toán xem như bài tập dành cho bạn đọc - Giả sử xét cây có gốc được trỏ bởi con trỏ T, với cách khai báo như trên ta có thể thiết kế các phép toán cơ bản trên cây nhị phân như sau: 1 - Tạo cây rỗng Cây rỗng là một cây là không chứa một nút nào cả. Như vậy khi tạo cây rỗng ta chỉ cần cho cây trỏ T trỏ tới giá trị Nil. Procedure MakeNullTree(var T: PTree) Begin T : =Nil; End; 2 - Kiểm tra cây rỗng Fuction EmptyTree(T: PTree) Begin EmptyTree := T=nil; End;
65 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

r là hai cây con trái. 5 . End. 7 . Begin if(EmptyTree(T)) NumberNodes:= 0 else NumberNodes:= 1+NumberNodes(LeftChild(T)+ NumberNodes(RightChild(T)). 4 .Đổ x vào ngăn Info của ô nhớ vừa cấp phát .yêu cầu MT cấp phát ô nhớ để làm gốc của cây .Gắn l. r tương ứng vào nhánh trái. Begin if (p<>NiL) then LeftChild:= p^. phải của gốc này Cách giải: .left lse LeftChild := NiL. 6 .Xác định số nút của cây Function NumberNodes(T: PTree): integer.Kiểm tra nút lá: Nếu 1 nút là nút lá thì nó không có con. Hãy tạo một cây nhị phân có gốc lưu x và l. phải của gốc 66 Cấu trúc dữ liệu . End.Tạo cây mới từ hai cây có sẵn Cho hai cây con l. Begin if (p<>NiL) then RightChild:= p^.3 . khi đó con trái và con phải của nó cùng bằng nil Function IsLeaf(p: PTree): boolean. End.Xác định con trái của một nút đƣợc trỏ bởi p Function LeftChild(p: PTree): Ptree. { hàm kiểm tra nút được trỏ bởi con trỏ P có là nút lá hay không} Begin isLeaf := (LeftChild(p)=Nil)and(RightChild(p)=Nil). End.Xác định con phải của một nút đƣợc trỏ bởi P Function RightChild(p: PTree): Ptree. r và x là giá trị bất kỳ.Bộ môn CNPM – Khoa CNTT – ĐHTN .right else RightChild := NiL.

Write(T^. trung tự. End. InOrder(RightChild(T)). M^. Write(T^. End. r: Ptree): Ptree. Thủ tục duyệt hậu tự Procedure PostOrder(T: PTree) Begin if T<>nil then Begin PostOrder(LeftChild(T)). Create2 := M.l: Ptree. Begin New(M). 8 .Info: 5). End. ?Hãy sử dụng thủ tục Create2 để tạo một cây nhị phân 67 Cấu trúc dữ liệu . hậu tự Thủ tục duyệt tiền tự Procedure PreOrder(T: PTree) Begin if T<>nil then Begin Write(T^. PostOrder(RightChild(T)). PreOrder(LeftChild(T)). Thủ tục duyệt trung tự Procedure InOrder(T: PTree) Begin if T<>nil then Begin InOrder(LeftChild(T)). Var M: Ptree. End.Info: 5).Info: 5).Các thủ tục duyệt cây: tiền tự. PreOrder(RightChild(T)).Bộ môn CNPM – Khoa CNTT – ĐHTN .left=l.right=r. Info :=v.Giải thuật: Function Create2(v:Item. M^. M^.

Bộ môn CNPM – Khoa CNTT – ĐHTN . tức là nó phải lấy giá trị từ một tập hợp có thứ tự. ta có thể áp dụng phương pháp tìm kiếm nhị phân để tìm thông tin lưu trên cây này.1 Định nghĩa cây TKNP Cây tìm kiếm nhị phân là một cây nhị phân thoả mãn các điều kiện sau: Điều kiện 1: Tất cả các khoá tại các đỉnh của cây con bên trái đều có giá trị đi trước (< ) các khoá tại đỉnh gốc Điều kiện 1: Khoá tại gốc đi trước (<) tất cả các khoá ở các đỉnh của cây con bên phải Điều kiện 3: Cây con bên trái và cây con bên phải cũng là cây tìm kiếm nhị phân * Ví dụ 10 6 4 8 12 15 23 1 5 7 9 11 14 20 Hình 3. ta gọi là trường khoá. Trường khoá phải chứa các giá trị có thể so sánh được. Nhận xét: Trên cây TKNP không có hai nút cùng khoá. Khi đó.3. nhu cầu tìm kiếm là quan trọng. vì vậy người ta đưa ra một cấu trúc cây thỏa mãn nhu cầu tìm kiếm trên bằng cách tạo thêm một số ràng buộc trên cây nhị phân – được gọi là: Cây tìm kiếm nhị phân (TKNP).3.3 Cây tìm kiếm nhị phân (binary search tree) Ta thấy rằng. thao tác tìm kiếm là thao tác thường được dùng nhất để khai thác thông tin. trong hầu hết các hệ thống lưu trữ.ví dụ là một record chẳng hạn. Cây con của một cây TKNP là cây TKNP. 3. để tìm kiếm ta chỉ có thể áp dụng phương pháp tìm kiếm tuần tự. quản lý dữ liệu. đối với cây tổng quát và cây đa phân. Khi duyệt trung tự (InOrder) cây TKNP ta được một dãy có thứ tự tăng.8 – Hình ảnh 1 cây TKNP Lưu ý: Dữ liệu lưu trữ tại mỗi nút có thể rất phức tạp. việc tìm kiếm bị hạn chế. khoá của nút được tính dựa trên một trường nào đó. 68 Cấu trúc dữ liệu .

hai trường kia là hai con trỏ trỏ đến hai nút con (nếu nút con vắng mặt ta gán con trỏ bằng NIL) *Dạng cài đặt sử dụng con trỏ: Type BSTree = ^ Node.2 Cài đặt cây tìm kiếm nhị phân Cây TKNP. Do đó ta có thể áp dụng các cách cài đặt như đã trình bày trong phần cây nhị phân để cài đặt cây nhị phân tìm kiếm.Tìm kiếm một nút có khóa cho trƣớc trên cây TKNP Để tìm kiếm 1 nút có khoá x trên cây TKNP.3. End.3. 3. ta tiến hành từ nút gốc bằng cách so sánh khoá của nút gốc với khoá x. . {trỏ tới gốc cây} * Cài đặt sử dụng mảng – xem như bài tập dành cho bạn đọc Nhận xét Cây NPTK có sự khác biệt so với cây nhị phân về các phép toán. {lưu khóa của đỉnh } [các trường lưu thông tin khác nếu có] Left.3 Các phép toán cơ bản trên cây tìm kiếm nhị phân 1. Với cây nhị phân tìm kiếm ta có các phép toán như: tìm kiếm. Mỗi nút của cây như là một mẩu tin (record) có tối thiểu ba trường: một trường chứa khoá. .3. Procedure MakeNullTree(Var Root: BSTree) Begin Root :=NiL. Var Root: BSTree. điều lưu ý ở đây là mỗi đỉnh trên cây phải có một thành phần khóa.Nếu x lớn hơn khoá của nút gốc thì ta tiến hành việc tìm khoá x trên cây con bên phải. Node = Record key : KeyType.Bộ môn CNPM – Khoa CNTT – ĐHTN .Khởi tạo cây TKNP rỗng Ta cho con trỏ quản lý nút gốc (Root) của cây bằng NIL. Nếu x bằng khoá của nút gốc thì giải thuật dừng và ta đã tìm được nút chứa khoá x. 2 . . là một cây nhị phân.Ngược lại. {trỏ tới gốc cây con trái. xác định duy nhất cho đỉnh đó. Right: BSTree. thêm hoặc xoá một nút trên cây TKNP để cây sau khi xoa phải luôn đảm bảo tính chất cuả cây TKNP. Một cách cài đặt cây TKNP thường gặp là cài đặt bằng con trỏ. . gốc cây con phải} End.Nếu x nhỏ hơn khoá của nút gốc thì ta tiến hành việc tìm khoá x trên cây con 69 Cấu trúc dữ liệu .Nếu nút gốc bằng NULL thì không có khoá x trên cây. trước hết.

vì 30 > 20 vậy ta tìm tiếp trên cây con bên phải. .So sánh 30 với khoá nút gốc là 20. . 30 = 30 vậy đến đây giải thuật dừng và ta tìm được nút chứa khoá cần tìm.Bộ môn CNPM – Khoa CNTT – ĐHTN . vì 30 > 22 vậy ta tìm tiếp trên cây con bên phải. Giải thuật dưới đây trả về kết quả là con trỏ trỏ tới nút chứa khoá x hoặc Nil nếu không tìm thấy khoá x trên cây TKNP. x: KeyType. Ví dụ: tìm nút có khoá 30 trong cây ở trong Hình 3. While ( p <> nil)And( Not found) do if p^. tức là cây có nút gốc có khoá là 30. Root: BSTree ).So sánh 30 với khoá của nút gốc là 35.Right. Procedure SEARCH ( Var P: BSTree. Var found: Boolean.bên trái. Begin p := root.So sánh 30 với khoá của nút gốc là 22.So sánh 30 với khoá nút gốc là 30. ? Cây tìm kiếm nhị phân được tổ chức như thế nào để quá trình tìm kiếm được hiệu quả nhất? Nhận xét: 70 Cấu trúc dữ liệu . found := false. tức là cây có nút gốc có khoá là 22.key) then p := p^. . vì 30 < 35 vậy ta tìm tiếp trên cây con bên trái.key = x then found := true else if ( x < p^.9 – hình ảnh một cây TKNP . End.Left else p := p^. tức là cây có nút gốc có khoá là 35.9 như sau: 20 10 35 5 17 22 42 15 30 Hình 3.

Có nhiều cách để thêm. người ta thường thực hiện thêm ở mức lá * Cách giải cụ thể như sau: Ta tiến hành từ nút gốc bằng cách so sánh khóa cuả nút gốc với khoá x.Nếu x nhỏ hơn khoá của nút gốc thì ta tiến hành (một cách đệ qui) giải thuật này trên cây con bên trái.Thêm một nút có khóa cho trƣớc vào cây TKNP * Phân tích bài toán Theo định nghĩa cây tìm kiếm nhị phân ta thấy trên cây tìm kiếm nhị phân không có hai nút có cùng một khoá. vì 19 > 10 vậy ta xét tiếp đến cây bên phải.So sánh 19 với khoá của nút gốc là 17. vì 19 > 17 vậy ta xét tiếp đến cây bên phải. + Ngược lại.10 – Thêm khóa 19 vào cây TKNP . .Trước hết ta phải tìm kiếm để xác định có nút nào chứa khoá x chưa? + Nếu có thì giải thuật kết thúc (không làm gì cả!). Về cây cân bằng các bạn có thể tham khảo thêm trong các tài liệu tham khảo của môn học này. tuy nhiên để tránh phức tạp. vì 19 < 20 vậy ta xét tiếp đến cây bên trái. Ví dụ: thêm khoá 19 vào cây ở trong Hình 3.Giải thuật này sẽ rất hiệu quả về mặt thời gian nếu cây TKNP được tổ chức tốt. sẽ thêm một nút mới chứa khoá x này. . như Hình 3. .10 20 10 35 5 17 22 42 15 19 30 71 Cấu trúc dữ liệu .Nếu x lớn hơn khoá của nút gốc thì ta tiến hành (một cách đệ qui) giải thuật này trên cây con bên phải. ta thêm nút mới chứa khoá 19 và nút mới này là con bên phải của nút có khoá là 17.So sánh 19 với khoá của nút gốc là 20. do đó ta thêm một nút mới chứa khoá x. trường hợp này ta không thêm nút.So sánh 19 với khoá của nút gốc là 10.Thêm khoá x vào cây TKNP đảm bảo cấu trúc cây TKNP không bị phá vỡ. .Nếu x bằng khoá của nút gốc thì giải thuật dừng.9.Nếu nút gốc bằng Nil thì khoá x chưa có trên cây. . Do đó nếu ta muốn thêm một nút có khoá x vào cây TKNP thì: . tức là cây có nút gốc có khoá là 10. 3 . . Nút con bên phải bằng NULL. tức là cây có nút gốc có khoá là 17. nghĩa là cây tương đối "cân bằng". chứng tỏ rằng khoá 19 chưa có trên cây.Bộ môn CNPM – Khoa CNTT – ĐHTN Hình 3. . ta làm như sau: .

Bộ môn CNPM – Khoa CNTT – ĐHTN . x) Else if (x > key ) then INSERT ( Right. End. TH2: Nếu p có một trong 2 con là  (rỗng): . khoá x Output: Nếu đỉnh có khoá x có trên T thì loại bỏ đỉnh này sao cho T sau khi loại bỏ x vẫn là cây TKNP Cách giải . Q^. Var Q: Pointer.Right := nill. If R = nill then begin New (Q). Procedure INSERT ( Var R: pointer.* Thủ tục sau đây tiến hành việc thêm một khoá vào cây TKNP. 4 .Nếu tìm gặp nút được trỏ bởi P có chứa khoá x.Xóa một nút có khóa cho trƣớc ra khỏi cây TKNP Bài toán Input: Cây nhị phân tìm kiếm gốc T. X ) else Writeln ( “ Cay da co Infor X ”).Key := x. ta có ba trường hợp sau: TH1: Nếu p là lá: ta thay nó bởi Nil. . R := Q.Nếu không tìm thấy nút chứa khoá x thì giải thuật kết thúc. Begin R := Root. x : KeyType ).Left := nill.Treo cây con khác  vào vị trí của p (như hình dưới) .Giải phóng vùng nhớ được trỏ bởi p 72 Cấu trúc dữ liệu . Q^. Q^. end Else With R^ Do If (x < key) then INSERT (Lefl .

M P X       T M       T T1 T1 TH3: Đỉnh loại bỏ được trỏ bởi P có 2 con đều khác rỗng: Thay nút được trỏ bởi p bởi nút lớn nhất trên cây con trái của nó (nút cực phải của cây con trái) hoặc là nút bé nhất trên cây con phải của nó (nút cực trái của cây con phải). Begin if (Root^. Rồi xóa nút cực phải (hoặc nút cực trái ). đồng thời xoá nút này. việc xoá nút này sẽ rơi vào một trong 2 trường hợp ở trên (TH1 hoặc TH2) Trong hình dưới đây.       T M M P X P y       T T1 T2 T1 T2 y x Các giải thuật: a) Giải thuật xoá một nút có khoá nhỏ nhất (nút cực trái của một cây ) Hàm dưới đây trả về khoá của nút cực trái. ta thay x bởi khoá của nút cực trái của cây con bên phải rồi ta xoá nút cực trái này. Var k: Keytype.Bộ môn CNPM – Khoa CNTT – ĐHTN . Funciton DeleteMin (var Root:Ptree):keytype.left = Nil)then begin 73 Cấu trúc dữ liệu .

End.k :=Root^.Root^. Root := Root^. DeleteMin := k.key.Key = DeleteMin(Root->right).Bộ môn CNPM – Khoa CNTT – ĐHTN .left = Nil)then Root = Root^. b) Giải thuật xóa một nút có khoá x cho trƣớc trên cây TKNP Procedure DeleteNode(X: keytype. else if (Root^.Root^.left=Nil)and(Root^.right) else if (Root^. 74 Cấu trúc dữ liệu .left) else if (x > Root^.Key) then DeleteNode(x. End else DeleteMin := DeleteMin(Root^.left else {cả hai con đều khác rỗng} Root^. Begin if (Root <>Nil) then if (x < Root^.left).right=Nil)then Root :=Nil.right else if (Root^.right.var Root: PTree).right=Nil)then Root := Root^.Key)then DeleteNode(x. End.

.Hai đỉnh có cung nối nhau gọi là hai đỉnh kề (adjacency).1b cho ví dụ về đồ thị vô hướng.1) hoặc không có hướng (trong Hình 4. . Hình V. Trong các phần sau này ta dùng từ đồ thị (graph) để nói đến đồ thị nói chung. .w). w có thể coi như là một cặp điểm (v.1 2 4 1 3 Hình 4. hai đỉnh này có thể trùng nhau. Nếu cặp này có thứ tự thì ta có cung có thứ tự (cung). khi nào cần phân biệt rõ ta sẽ dùng đồ thị có hướng.Bộ môn CNPM – Khoa CNTT – ĐHTN . đồ thị vô hướng.Nội dung chính Trong chương này chúng ta sẽ nghiên cứu một số kiểu dữ liệu trừu tượng cơ bản như sau: -Các khái niệm cơ bản .Một đồ thị G bao gồm một tập hợp V các đỉnh và một tập hợp E các cung .CHƢƠNG 4 Mô hình dữ liệu Đồ thị (Graph) Tổng quan: 1. còn các cung được biểu diễn bằng các đoạn thẳng có hướng (trong Hình 4. Mục đích và yêu cầu: Sau khi học xong chương này.Các cung nối giữa hai đỉnh. 2. Chẳng hạn các đỉnh có thể biểu diễn cho các thành phố còn các cung biểu diễn cho đường giao 75 Cấu trúc dữ liệu . sinh viên nắm vững và cài đặt được các kiểu dữ liệu trừu tượng đồ thị và vận dụng để giải những bài toán thực tế.2 2 4 Thông thường trong một đồ thị.Một số bài toán trên đồ thị 4.2). Nếu các cung trong đồ thị G có thứ tự thì G gọi là đồ thị có hướng (directed graph).Một cung nối giữa hai đỉnh v.1 Định nghĩa đồ thị và các khái niệm . .E).1a cho ta một ví dụ về đồ thị có hướng. hình V.Các phép duyệt đồ thị . các đỉnh biểu diễn cho các đối tượng còn các cung biểu diễn mối quan hệ (relationship) giữa các đối tượng đó.Kiểu dữ liệu trừu tượng đồ thị . 1 3 Hình 4. ngược lại thì cung không có thứ tự (cạnh). Trong các đồ thị này thì các vòng tròn được đánh số biểu diễn các đỉnh. ký hiệu G=(V. Nếu các cung trong đồ thị G không có thứ tự thì đồ thị G là đồ thị vô hướng (undirected graph).Các đỉnh còn được gọi là nút (node) hay điểm (point).Biểu diễn đồ thị .

ngoại trừ đỉnh đầu và đỉnh cuối có thể trùng nhau. . Giá trị $ được chọn tuỳ theo cấu trúc dữ liệu cài đặt đồ thị. v2. Đường đi này là đường đi từ v1 đến vn và đi qua các đỉnh v2.Xoá một đỉnh. . . 4 chẳng hạn. . 4. Trong nhiều ứng dụng ta thường kết hợp các giá trị (value) hay nhãn (label) với các đỉnh và/hoặc các cạnh. Trong Hình 4. lúc này ta nói đồ thị có nhãn. Một chu trình đơn là một đường đi đơn có đỉnh đầu và đỉnh cuối trùng nhau và có độ dài ít nhất là 1..2 thì 1.. đường đi này có độ dài là hai...2.vn-1.1 là một chu trình có độ dài 4..E) là một đồ thị G'=(V'.Một đường đi (path) trên đồ thị là một dãy tuần tự các đỉnh v1. .Thêm một đỉnh vào đồ thị. Nói chung nhãn có thể có kiểu tuỳ ý... Ở đây nhãn là các giá trị số nguyên biểu diễn cho giá cước vận chuyển một tấn hàng giữa các thành phố 1. ta thường phải thực hiện một thao tác nào đó với tất cả các đỉnh kề của một đỉnh. Đỉnh v1 còn gọi là đỉnh đầu...FIRST(v) trả về chỉ số của đỉnh đầu tiên kề với v.w)  E sao cho v.n-1). Ví dụ trong Hình 4. Một đường đi có đỉnh đầu và đỉnh cuối trùng nhau gọi là một chu trình (cycle). giá.4 trong đồ thị Hình 4.E') trong đó: V‟V và E‟ gồm tất cả các cạnh (v. Đồ thị con của một đồ thị G=(V. Nếu 76 Cấu trúc dữ liệu . 2..3. 4.Xoá một cạnh. . . tức là một đoạn giải thuật có dạng sau: For (mỗi đỉnh w kề với v) { thao tác nào đó trên w } Để cài đặt các giải thuật như vậy ta cần bổ sung thêm khái niệm về chỉ số của các đỉnh kề với v.2 Các phép toán cơ bản trên đồ thị Các phép toán được định nghĩa trên đồ thị là rất đơn giản như là: .4.w  V‟.i) trả về chỉ số của đỉnh nằm sau đỉnh có chỉ số i và kề với v.. .1 thì 3.Thêm một cạnh vào đồ thị. Ví dụ dãy 1. Hơn nữa ta cần định nghĩa thêm các phép toán sau đây: .. 2.. Thông thường trong các giải thuật trên đồ thị.Đọc nhãn của đỉnh. Nhãn kết hợp với các đỉnh và/hoặc cạnh có thể biểu diễn tên.1 là một đường đi từ đỉnh 1 đến đỉnh 4. Trường hợp đặc biệt dãy chỉ có một đỉnh v thì ta coi đó là đường đi từ v đến chính nó có độ dài bằng không.vi+1) là một cung trên đồ thị (i=1.. Nếu không có đỉnh nào kề với v thì $ được trả về. vn gọi là đỉnh cuối. 3. Độ dài của đường đi này bằng (n-1).2.NEXT(v.Đọc nhãn của cạnh.Bộ môn CNPM – Khoa CNTT – ĐHTN . . khoảng cách.Đường đi gọi là đơn (simple) nếu mọi đỉnh trên đường đi đều khác nhau..Lần theo (navigate) các cung trên đồ thị để đi từ đỉnh này sang đỉnh khác. vn sao cho (vi.thông nối giữa hai thành phố. 3 tạo thành một chu trình có độ dài 3.

j] = true.1.VERTEX(i) trả về đỉnh có chỉ số i.3. Như vậy ta cần một mảng một chiều G có n phần tử để biểu diễn cho đồ thị có n đỉnh. 4. nếu ta cần làm việc thường xuyên với các cạnh của đồ thị thì ta có thể phải dùng cách biểu diễn khác cho thích hợp hơn.không có đỉnh nào kề với v theo sau đỉnh có chỉ số i thì $ được trả về. Do vậy. nếu G là đồ thị vô hướng thì ma trận kề sẽ là ma trận đối xứng. ngược lại thì A[i.. Ở đây các cặp đỉnh không có cạnh nối thì ta gán cho nó giá trị mặc định Nhận xét: Cách biểu diễn đồ thị bằng ma trận kề cho phép kiểm tra một cách trực tiếp hai đỉnh nào đó có kề nhau không.3. 4. Giả sử các đỉnh được đánh số 1. Việc chọn cấu trúc dữ liệu nào là tuỳ thuộc vào các phép toán trên các cung và đỉnh của đồ thị.j]=a. Nếu đồ thị có n đỉnh thì ta dùng mảng A có kích thước nxn. Hai cấu trúc thường gặp là biểu diễn đồ thị bằng ma trận kề (adjacency matrix) và biểu diễn đồ thị bằng danh sách các đỉnh kề (adjacency list).2. kiểu boolean để biểu diễn các đỉnh kề.3 Biểu diễn đồ thị Một số cấu trúc dữ liệu có thể dùng để biểu diễn đồ thị. 4. Biểu diễn đồ thị bằng danh sách các đỉnh kề Trong cách biểu diễn này. nếu có đỉnh nối giữa đỉnh thứ i và đỉnh thứ j. Ngay cả số cạnh của đồ thị rất nhỏ chúng ta cũng phải cần một mảng N x N phần tử để lưu trữ.n thì A[i. Ma trận A còn được gọi là ma trận trọng số.Bộ môn CNPM – Khoa CNTT – ĐHTN . G[i] là con trỏ trỏ tới danh sách các đỉnh 77 Cấu trúc dữ liệu . chẳng hạn mảng A. . Với cách biểu diễn đồ thị bằng ma trận kề như trên chúng ta có thể định nghĩa chỉ số của đỉnh là số nguyên xác định duy nhất đỉnh đó. Chẳng hạn đồ thị trong Hình 4. a) Dạng cài đặt: xem như bài tập dành cho bạn đọc b) Cài đặt các phép toán: xem như bài tập dành cho bạn đọc Trên đồ thị có nhãn (trọng số) thì ma trận kề có thể dùng để lưu trữ nhãn của các cung chẳng hạn cung giữa i và j có nhãn a thì A[i. Biểu diễn đồ thị bằng ma trận kề Ta dùng một mảng hai chiều.j] = false. ta sẽ lưu trữ các đỉnh kề với một đỉnh i trong một danh sách liên kết theo một thứ tự nào đó. Rõ ràng.2 có biểu diễn ma trận kề như sau: j 1 2 3 4 i 1 true true true false 2 true true true true 3 true true true true 4 false true true true Ta cũng có thể biểu diễn true là 1 còn false là 0. Thời gian này độc lập với số cạnh và số đỉnh của đồ thị. Nhưng nó phải mất thời gian duyệt qua toàn bộ mảng để xác định tất cả các cạnh trên đồ thị.

Từ một đỉnh v nào đó ta bắt đầu duyệt như sau: đánh dấu v đã duyệt.4. vì vậy ta dùng một hàng để lưu trữ các nút theo thứ tự được duyệt để có thể duyệt các đỉnh kề với chúng. 4. với mỗi đỉnh w chưa duyệt kề với v.1.E) với các đỉnh ban đầu được đánh dấu là chưa duyệt (unvisited). Khi ta duyệt một đỉnh v rồi đến đỉnh w thì các đỉnh kề của v được duyệt trước các đỉnh kề của w. khi xây dựng các thao tác xử lý trên đồ thị ta cần đi qua các đỉnh và các cung của đồ thị một cách có hệ thống. Có hai phép duyệt đồ thị phổ biến đó là duyệt theo chiều sâu và duyệt theo chiều rộng.4. Ta cũng dùng mảng một chiều mark để đánh dấu một nút là đã duyệt hay chưa. Một số bài toán ứng dụng trên đồ thị: Xem như phần bài tập dành cho bạn đọc 78 Cấu trúc dữ liệu . ta thực hiện đệ qui quá trình trên cho w. Sở dĩ cách duyệt này có tên là duyệt theo chiều sâu vì nó sẽ duyệt theo một hướng nào đó sâu nhất có thể được. Duyệt theo chiều rộng (breadth-first search) Giả sử ta có đồ thị G với các đỉnh ban đầu được đánh dấu là chưa duyệt (unvisited). 4. 4. kế đến là duyệt tất cả các đỉnh kề với v. Việc đi qua các đỉnh của đồ thị một cách có hệ thống như vậy gọi là duyệt đồ thị. Từ một đỉnh v nào đó ta bắt đầu duyệt như sau: đánh dấu v đã được duyệt.kề với đỉnh i. Duyệt theo chiều sâu (depth-first search) Giả sử ta có đồ thị G=(V.2. a) Dạng cài đặt: Bạn đọc có thể tham khảo trong tài liệu tham khảo đính kèm với bài giảng b) Cài đặt các phép toán: Xem như bài tập dành cho bạn đọc 4.4 Các phép duyệt đồ thị (TRAVERSALS OF GRAPH) Trong khi giải qu yết các vấn đề thực tế.4. nhiều bài toán được mô hình hoá bằng mô hình đồ thị.Bộ môn CNPM – Khoa CNTT – ĐHTN . tương tự như duyệt theo chiều sâu.

.Bộ môn CNPM – Khoa CNTT – ĐHTN . . hoặc những sinh viên vừa mới nhập học và những sinh viên đang học môn CTDL & TT cũng lập nên một tập hợp Định nghĩa tập hợp: Các đối tượng trong tập hợp cũng được gọi là các phần tử trong tập hợp đó.Khái niệm tập hợp . đồ thi – là tập hợp các đỉnh và các cạnh nối các đỉnh đó. tập hợp thường dùng để nhóm các phần tử có các tính chất chung lại với nhau.Liệt kê tất cả các phần tử của tập hợp A.Kiểu dữ liệu trừu tượng tập hợp.Nắm vững khái niệm về kiểu dữ liệu trừu tượng tập hợp và một số loại tập hợp đặc biệt như từ điển.Cấu trúc bảng băm .Từ điển .Fred.Dùng biểu đồ Ven là một đường cong khép kín.Nêu lên các đặc trưng cho biết chính xác một đối tượng bất kỳ có là một phần tử của tập A hay không. nhưng nó cũng có thể chứa các phần tử chẳng có mối quan hệ gì với nhau. 2. sinh viên phải: . cây – là một tập hợp các đỉnh và các cạnh nối hai đỉnh có quan hệ phân cấp.Cài đặt tập hợp và các loại tập hợp đặc biệt bằng ngôn ngữ lập trình cụ thể.Chƣơng 5 Mô hình dữ liệu Tập hợp Tổng quan: 1. Mục tiêu Sau khi học xong chương này. bảng băm. Khái niệm tập hợp Tập hợp trong toán học là một cấu trúc rời rạc cơ bản để từ đó dựng lên các cấu trúc rời rạc khác như: Các tổ hợp – là những tập hợp không sắp thứ tự của các phần tử...2. Nội dung chính Trong chương này chúng ta sẽ nghiên cứu các vấn đề sau: .Hàng ưu tiên. . thường thường các đối tượng trong tập hợp có tính chất tương tự nhau. Các tập hợp dùng để nhóm các đối tượng lại với nhau. hoặc các dinh viên đang học môn CTDL& thuật toán lập nên một tập hợp. có thể có quan hệ thứ tự xác 79 Cấu trúc dữ liệu . các điểm trong đường cong đó chỉ các phần tử của tập hợp.1. Lan}. hàng ưu tiên. 5. Ví dụ: A = {x | x là số nguyên chẵn} Tập hợp có thể có thứ tự hoặc không có thứ tự. tức là.Cài đặt tập hợp . . Tập hợp được nói là chứa các phần tử của nó. 3} .. Tập hợp được dùng để mô hình hoá hay biểu diễn một nhóm bất kỳ các đối tượng trong thế giới thực cho nên nó đóng vai trò rất quan trọng trong mô hình hoá cũng như trong thiết kế các giải thuật.. Ví dụ: A={a. Một tập hợp có thể là vô hạn hoặc hữu hạn Để mô tả một tập hợp. trong toán học thường có các cách sau để xác định một tập hợp A: . Ví dụ: A = {1. 2. Ví dụ: Tất cả các sinh viên vừa mới nhập trường lập nên một tập hợp.

a<b và b<c thì a<c 5.b  S thì a<b hoặc b<a hoặc a=b Với mọi a. Sau đây chúng ta sẽ đưa ra một số phép toán cơ bản quan trọng nhất.Hàm MIN(A) cho phần tử bé nhất trong tập A 10 . chúng ta giả sử rằng các phần tử của tập hợp có thứ tự tuyến tính. Khi đó ta có thể mô tả kiểu dữ liệu của các phần tử của tập hợp như sau: Type elementtype = record Key : Keytype.định trên các phần tử của tập hợp hoặc không. tuỳ thuộc vào các phép toán cần thực hiện. 80 Cấu trúc dữ liệu .c  S. Khi đó.C) nhận vào 3 tham số là A.Thủ tục UNION(A.C.B. trên tập hợp S có quan hệ < và = thoả mản hai tính chất: Với mọi a.A) thêm x vào tập hợp A 7 .B. 3 . [ Các dữ liệu trường khác nếu có] end.Thủ tục INTERSECTION(A.B.Thủ tục INSERTSET(x.A) cho kết quả kiểu logic (đúng/sai) tùy theo x có thuộc A hay không. ngoài các phép toán hợp. Thực hiện phép toán lấy hợp của hai tập A và B và trả ra kết quả là tập hợp C = A B.3 Cài đặt tập hợp và cài đặt các phép toán trên tập hợp Các phần tử của tập hợp có thể là các đối tượng có kiểu dữ liệu cơ bản hoặc là các đối tương có kiểu dữ liệu phức tạp. trong chương này.B.b.C.Bộ môn CNPM – Khoa CNTT – ĐHTN .C. tức là.A) xoá x khỏi tập hợp A 8 . chúng ta phải cần đến nhiều các phép toán khác. ngược lại cho kết quả sai. Trong đó: Key (khoá): Để xác định duy nhất một đối tượng. 5 .B. Tuy nhiên. Thực hiện phép toán lấy hợp của hai tập A và B và trả ra kết quả là tập hợp C = A\B 5 . Keytype : Kiểu dữ liệu của khoá Có nhiều phương pháp để cài đặt mô hình dữ liệu tập hợp trong MT (cho ta các CTDL tập hợp khác nhau).C) nhận vào 3 tham số là A. giao. các đối tượng này có thể được biểu diễn bởi bản ghi.Thủ tục MAKENULLSET(A) tạo tập hợp A tập rỗng 6 . hiệu. các trường là các thuộc tính của đối tượng.Thủ tục DELETESET(x.Hàm EQUAL(A. Thực hiện phép toán lấy giao của hai tập A và B và trả ra kết quả là tập hợp C = A ∩ B.B) gán A cho B ( tức là B:=A ) 9 .B.Thủ tục ASSIGN(A. 2 .Hàm MEMBER(x.C) nhận vào 3 tham số là A. các phép toán này sẽ được mô tả bởi các thủ tục hoặc hàm: 1.B) cho kết quả TRUE nếu A=B ngược lại cho kết quả FALSE => Vấn đề tiếp theo đặt ra là: Ta cần biểu diễn tập hợp như thế nào trong máy tính để các phép toán được thực hiện với hiệu quả cao 5. Trong từng áp dụng. 2 Mô hình dữ liệu tập hợp Trong thiết kế thuật toán. Nếu x  A thì hàm cho kết quả là đúng.Thủ tục DIFFERENCE(A. có thể sử dụng tập hợp như một mô hình dữ liệu.

Bộ môn CNPM – Khoa CNTT – ĐHTN . B.n. B thành tập C: Procedure Union(A. A[i] = false nếu iA + Cách cài đặt: const n = . C : set.1. x : 1. Còn nếu phép tìm kiếm một phần tử xảy ra thường xuyên thì ta có thể phải tìm cách cài đặt phù hợp để có hiệu quả tốt nhất. Ví dụ thêm x vào A chỉ việc cho A[x] := true . để xác định xem x có là phần tử của tập A không ta chỉ cần biết A[x] là true hay false.Với cách khai báo này các phép toán trên tập được thực hiện dễ dàng bằng cách sử dụng các phép toán logic trong ngôn ngữ lập trình.. .. Khi đó ta có thể dùng véc tơ bit (mảng boolean) để biểu diễn tập A : (A[1]..B : set . var A. Ví dụ: Giả sử các phần tử của tập hợp được lấy trong các số nguyên từ 1 đến 10 .3.A[n]). var i : integer. khi đó tập hợp được biểu diễn bởi một mảng một chiều có 10 phần tử với các giá trị phần tử thuộc kiểu logic. Ta xét các phương pháp cài đặt tập hợp sau: Cài đặt tập hợp bởi véc tơ bit (1) Danh sách kế tiếp (mảng)(2) Cài đặt tập hợp bởi danh sách Danh sách liên kết (3) Danh sách được sắp (5) 5. A[2]. trong đó thành phần thứ i : A[i] = true nếu i  A.n] of boolean.. ……. . var C : set).giá trị các phần tử trong tập hợp và kích cỡ (số các phần tử của tập hợp) mà ta lựa chọn cách cài đặt sao cho các phép toán thực hiện có hiệu quả nhất. 81 Cấu trúc dữ liệu .8} được biểu diễn trong mảng có 10 phần tử như sau: 1 1 2 0 3 1 4 0 5 1 6 0 7 0 8 1 9 0 10 0 Nhận xét: . xét tập hợp A gồm các số nguyên thuộc phạm vi từ 1 đến n (hoặc được mã hóa thành các số nguyên thuộc phạm vi từ 1 -> n). Ta xét phép toán hợp 2 tập hợp A.. type set = array[1.5. begin for i:=1 to n do C[i] := A[i] or B[i].3. .. Chẳng hạn tập hợp A={1. Chẳng hạn nếu chúng ta thường xuyên sử dụng phép thêm vào và loại bỏ các phần tử trong tập hợp thì chúng ta sẽ tìm cách cài đặt hiệu quả cho các phép toán này.Cài đặt tập hợp bởi vectơ bit + Giả sử..

ta có thể biểu diễn một tập hợp bất kỳ.Bộ môn CNPM – Khoa CNTT – ĐHTN . Không như biểu diễn bằng vectơ bít. thủ tục thêm một phần tử vào tập hợp..2 Cài đặt tập hợp bởi mảng . Nếu A. sự biểu diễn này dùng kích thước bộ nhớ tỉ lệ với số phần tử của tập hợp chứ không phải là kích thước đủ lớn cho toàn thể các tập hợp đang xét. Hơn nữa.3.Giả sử số phần tử của tập hợp không vượt quá một hằng nào đó Maxsize. cũng rất đơn giản và xem như bài tập dành cho bạn đọc.. chẳng hạn ta muốn tìm giao của hai tập hợp A và B có n phần tử.B được biểu diễn bằng danh sách có thứ tự tăng thì đối với một phần tử e∈ A ta chỉ tìm kiếm trong B cho đến khi gặp phần tử x ≥ e.Viết các thủ tục ứng với các phép toán: Xem như bài tập dành cho bạn đọc 5. việc kiểm tra một phần tử có thuộc tập hợp hay không.3 Cài đặt bởi danh sách liên kết hoặc danh sách đƣợc sắp Tập hợp cũng có thể cài đặt bằng danh sách liên kết. Nếu có thì x thuộc giao của hai tập hợp A và B. Nhận xét: Do kích thước mảng bị hạn chế.m là độ dài của A và B).. } Rõ ràng quá trình này có thể phải cần đến n x m phép kiểm tra (với n.. việc thực hiện các phép tính hợp. Do đó khi sử dụng cách cài đặt này ta phải đặt Maxsize cho phù hợp để tiết kiệm bộ nhớ và tránh bị tràn . . Các phép toán giao. Chẳng hạn nếu tập hợp A được biểu diễn bằng một danh sách có thứ tự tăng thì hàm MEMBER(x. 5. xóa một phần tử ra khỏi tập hợp.end.B biểu diễn bằng các danh sách liên kết chưa có thứ tự thì để tìm giao của A và B ta phải tiến hành như sau: for (mỗi x thuộc A ) { Duyệt danh sách B xem x có thuộc B không. hiệu.n] of elementype. chèn phần tử vào tập hợp có thể dẫn đến một tập hợp có số phần tử vượt quá cỡ của mảng. Mặc dù thứ tự của các phần tử trong tập hợp là không quan trọng nhưng nếu một danh sách liên kết có thứ tự nó có thể trợ giúp tốt cho các phép duyệt danh sách.A) có thể thực hiện việc so sánh x một cách tuần tự từ đầu danh sách cho đến khi gặp một phần tử y ≥ x chứ không cần so sánh với tất cả các phần tử trong tập hợp. Một ví dụ khác. {last: ghi chỉ số của phần tử cuối cùng của tập hợp} elements : array[1. trong đó mỗi phần tử của danh sách là một thành viên của tập hợp. Nếu A.{mảng các phần tử của tập hợp} end. type set = record last : integer.3. khi đó ta có thể cài đặt tập hợp bởi mảng như sau: const Maxsize = <kích cỡ tối đa của tập hợp> . Quan trọng hơn nếu f 82 Cấu trúc dữ liệu .

Ck : pointer. ta chép B vào C. B. C sẽ được biểu diễn bởi các danh sách liên kết. end. Bp := B.Cp. C := Cp. trong đó các con trỏ A.next := C. end. B: pointer. kết quả là tập C Cách làm: Muốn tìm hợp của A và B.element := Bp^. Ap := A.Bộ môn CNPM – Khoa CNTT – ĐHTN .Thủ tục UNION Giải sử xét phép tìm hợp của hai tập A và B. found : boolean. với mỗi e của A mà e không thuộc C thì đưa nó vào C. việc thực hiện các phép toán trên tập hợp sẽ phức tạp hơn a) Dạng cài đặt: type Set = ^ Cell. found := false.B. next : Set. while Bp <> nil do begin new(Cp). while Ap <> nil do begin Ck := C. 83 Cấu trúc dữ liệu . Tuy nhiên trong cách cài đặt này.element.đứng ngay sau e trong A thì để tìm kiếm f trong B ta chỉ cần tìm từ phần tử x trở đi chứ không phải từ đầu danh sách lưu trữ tập hợp B. Cp^. Var C: pointer). Các tập hợp A. Cell = record element : elementtype. Giải thuật: Procedure UNION(A. var A. B. => Việc cài đặt tập hợp bởi danh sách liên kết sẽ khắc phục hạn chế về không gian khi sử dụng mảng. C : Set.Bp.C sẽ trỏ tới đầu của các danh sách đó b) Các phép toán cơ bản trên tập hợp 1 . var Ap. Begin C := nil. sau đó duyệt A. Cp^.

A) cũng tương tự INSERT_LIST tuy nhiên ta phải chú ý rằng khi xen x vào A phải đảm bảo thứ tự của danh sách được sắp.Hàm EQUAL(A. nếu không thấy thì duyệt phần tử tiếp theo trong A. end.C.Thủ tục gán ASSIGN(A.element then found := true else Ck := Ck^. Cách làm: Duyệt danh tử đầu đến cuối tập A hoặc duyệt đến khi tìm thấy x thì dừng. Ap := Ap^. ngược lại cho kết quả sai. 3. end. 9 .next. Cp^.Thủ tục MAKENULLSET(A) tạo tập hợp A tập rỗng: A:= nil 10 .Thủ tục DIFFERENCE(A.element. kết quả là tập C Để tìm giao của A và B ta duyệt tập A. 2 . nếu thấy thì đưa vào C. Cp^.B) cho kết quả TRUE nếu A=B ngược lại cho kết quả FALSE: Kiểm tra các phần tử trong A có thuộc B không và ngược lại để kết luận A có bằng B 84 Cấu trúc dữ liệu .B.B.Thủ tục INTERSECTION Giải sử xét phép tìm giao của hai tập A và B. 7 .next := C.while (Ck <> nil) and (not found) do if Ap^. Duyệt các lần lượt các phần tử trong A để chép sang B 5 – Hàm MIN(A):Tìm phần tử nhỏ nhất trong tập A Hàm trả ra phần tử đầu danh sách (Nếu danh sách được sắp tăng dần theo giá trị của các phần tử). end.A) cho kết quả kiểu logic (đúng/sai) tùy theo x có thuộc A hay không. C := Cp. Thực hiện phép toán lấy hợp của hai tập A và B và trả ra kết quả là tập hợp C = A\B Cách làm: Duyệt các phần tử trong A. 6 .Toán tử DELETESET là hoàn toàn giống như DELETE_LIST.Hàm MEMBER(x. Nếu x  A thì hàm cho kết quả là đúng.element = Ck^.C) nhận vào 3 tham số là A. 5 . ta kiểm tra xem e có thuộc B không? Nếu không thuộc B.B): Chép các các phần tử của tập A sang tập B. ngược lại ta duyệt phần tử e tiếp theo trong A 8 .Bộ môn CNPM – Khoa CNTT – ĐHTN . ta thêm e vào C.element := Ap^. với mỗi phần tử e của A ta tìm nó trong B. if not found then begin new(Cp).next.Phép INSERTSET(x. với mỗi phần tử e  A.

Member và MakeNullSet.Bằng danh sách ( kế tiếp hoặc móc nối).Bộ môn CNPM – Khoa CNTT – ĐHTN . nhưng chỉ xét đến các phép toán Insert (thêm một phần từ vào tập hợp).2 Các phƣơng pháp cài đặt từ điển Từ điển là một tập hợp.Giả sử từ điển là danh sách được sắp thứ tự tuyến tính -> sử dụng cây tìm kiếm nhị phân: Độ phức về mặt thời gian của các phép toán trên từ điển là lớn (tương tự như danh sách ) trong trường hợp cây suy biến thành danh sách.3 Cấu trúc dữ liệu Bảng băm. đương nhiên chúng ta phải sử dụng các phương pháp cài đặt tập hợp để cài đặt từ điển: . 5. không thuận lơij cho phép toán thêm vào và lấy ra (với danh sách kế tiếp – vì có hiện tượng co.không. tuy nhiên các phép toán loại bỏ và xen vào trên cây cân bằng khá phức tạp. ta xét cách cài đặt khác đó là: sử dụng CTDL bảng băm để cài đặt từ điển 5.5.1 Từ điển là gì? Mô hình dữ liệu tập hợp. vì phải cân bằng lại cây Do đó. Chúng ta cũng chấp nhận MakeNullSet như là phép khởi tạo cấu trúc từ điển.5.Ta cũng có thể sử dụng cây cần bằng để biểu diễn tập hợp: Ưu điểm là không xảy ra trường hợp suy biến như cây TKNP. hiệu của hai tập hợp. không thuận lợi cho phép tìm kiếm(với danh sách móc nối vì phải truy cập tuần tự -> chí thích hợp khi áp dụng phương pháp tìm kiếm tuần tự). Ngược lại ta cần một cấu trúc làm sao cho việc tìm kiếm.5. để biểu diễn từ điển thuận lợi cho các phép toán. . là một tập hợp đặc biệt.Bằng véc tơ bit: Sử dụng phương pháp này khi từ điển là tập hợp gồm các phần tử có thể dùng làm tập chỉ số cho mảng (thuộc kiểu dữ liệu vô hướng đếm được hoặc được mã hóa thành kiểu dữ liệu vô hướng đếm được) . giao. cài đặt từ điển bởi bảng băm Băm là phương pháp rất thích hợp để cài đặt tập hợp có số phần tử lớn(từ điển). và có thể áp dụng cho tập hợp có kích cỡ lớn (từ điểm thường có kích thước lớn). Delete (loại bỏ một phần tử nào đó khỏi tập hợp). dãn. Có 2 phương pháp băm khác nhau: + Phương pháp Băm mở: Cho phép sử dụng một không gian không hạn chế để lưu giữ các phần tử của tập hợp + Phương pháp băm đóng: Sử dụng một không gian cố định và do đó tập hợp được cài đặt phải có cỡ không vượt quá không gian cho phép 85 Cấu trúc dữ liệu . dịch chuyển các phàn tử khác) . => Việc cài đặt cụ thể các phép toán từ 2->10 xem như bài tập dành cho bạn đọc 5. trong đó chúng ta chỉ quan tâm đến các phép toán InsertSet. DeleteSet. Member (tìm xem trong tập hợp có chứa một phần tử nào đó không) được gọi là kiểu dữ liệu trừu tượng từ điển (Dictionary) 5. thêm và bớt phần tử có phần hiệu quả nhất gọi là từ điển. Sở dĩ chúng ta nghiên cứu từ điển là do trong nhiều ứng dụng không sử dụng đến các phép toán hợp.5 Từ điển (Dictionary) Từ điển là một kiểu dữ liệu trừu tượng.

5.5.3.1. Cài đặt từ điển bằng bảng băm mở a) Định nghĩa bảng băm mở - Tư tưởng cơ bản của bảng Băm là: Phân chia một tập hợp đã cho thành một số cố định các lớp (N lớp) được đánh số 0, 1, …, N-1. Sử dụng mảng T với chỉ số chạy từ 0 đến N-1. Mỗi thành phần T[i] chứa một con trỏ, trỏ tới phần tử đầu tiên của danh sách chứa các phần tử của tập hợp thuộc lớp thứ i. Các phần tử của tập hợp thuộc mỗi lớp được tổ chức dưới dạng một danh sách liên kết, mỗi danh sách được gọi là con tr“rổ” , T được gọi là bảng Băm (hash table).
0 1 Nil Nil

n-1

nil Hình 1: Cấu trúc bảng băm mở

Việc chia các phần tử của tập hợp vào các lớp được thực hiện bời hàm băm (hash function) h. b) Hàm băm: Hàm băm là một ánh xạ từ tập dữ liệu A đến các số nguyên 0..N-1: h : A → 0..N-1; Theo đó giả sử x  A thì h(x) là một số nguyên sao cho: 0≤h(x) ≤N-1. - Có 2 tiêu chuẩn chính để lựa chọn một hàm băm: +Hàm băm phải cho phép tính được dễ dàng và nhanh chóng giá trị Băm của mỗi khoá + Nó phải phân bố đều các khoá vào các rổ Có nhiều cách để xây dựng hàm băm, cách đơn giản nhất là „nguyên hóa x „ và sau đó lấy h(x) = x % N. Ví dụ : Cho tập hợp A = {1, 5, 7, 2, 5, 15} -

86 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

Bảng băm là mảng gồm 5 phần tử và hàm băm h(x) = x % 5; Ta có bảng băm lưu trữ A như sau :

Bảng băm chứa các con trỏ, trỏ tới các phần tử đầu mỗi danh sách

Danh sách của mỗi “rổ”
Bảng băm mở

Hàm băm có thể đƣợc thiết kế nhƣ sau {Ham bam h(X)=X Mod B} Function h(X: KeyType): 0..N-1; Begin H := X%B; End; Ví dụ: Viết một hàm Băm trong Pascal (sử dụng phương pháp lấy phần dư) để băm các khoá là các xâu ký tự có độ dài 10 thành các giá trị từ 1 đến N-1 Type KeyType = String[10]; Function h (x: KeyType): 0 .. N-1; Var I, Sum: integer; Begin Sum:= 0; For I = 1 to 10 do Sum := Sum + ord(x[i]); h := Sum mod N; End; c) Cài đặt từ điển bởi bảng băm mở: Const N = …; Type pointer = ^ Element;
87 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

Element = record key : KeyType; [data: ElementType]; {các trường thông tin khác nếu có} Next : Pointer; End; Dictionary = array [0 .. N-1] of pointer; Var T: Dictionary; d) Cài đặt các phép toán trên từ điển 1 - Khởi tạo bảng băm mở rỗng Lúc này tất cả các “rổ” là rỗng nên ta gán tất cả các con trỏ trỏ đến đầu các danh sách trong mỗi rổ là Nil. Procedure MakeNullSet( var D: Dictionary) Var i: integer; Begin For i:=0 to B-1do D[i] := Nil; End; 2 - Kiểm tra một thành viên có trong từ điển không Để kiểm tra xem phần tử x nào đó có trong từ điển hay không, ta giá trị băm (h(x)) của nó. Theo cấu trúc của bảng băm thì khoá x sẽ nằm trong “rổ” được trỏ bởi T[h(x)]. Như vậy để tìm khoá x trước hết ta phải tính h(x) sau đó duyệt danh sách của “rổ” được trỏ bởi T[h(x)] để tìm x. Giải thuật nhƣ sau: Function Member(x: KeyType; Var T: Dictionary): boolean; Var P: ponter; found: boolean; Begin P := T[h(x)]; Found := false; While (p<>nil)and(not found)do If P^. key = x then found := true Else P := P^. Next; Member := found; End; 3 - Thêm một phần tử vào từ điển Để thêm một phần tử có khóa x vào từ điển ta phải tính h(x) để xác định xem nó sẽ được thêm vào “rổ”/ “lớp” nào. Vì ta không quan tâm đến thứ tự các phần tử trong mỗi “rổ” nên ta đ ể đ ơ n g i ả n t a thêm phần tử mới ngay đầu “rổ” này.
88 Cấu trúc dữ liệu - Bộ môn CNPM – Khoa CNTT – ĐHTN

Tính giá trị băm h(x) .. key := x. 5 . Next := T[i]. Giải thuật như sau Procedure Delete(x: KeyType. Begin i := h(x). ta làm như sau: . Var i: 1 . var T: Dictionary ).Bộ môn CNPM – Khoa CNTT – ĐHTN . N-1. T[i]:= P.Next. Found : Boolean. P. P^. Found := false. N-1. End. Q : pointer. Khi loại x cần phân biệt x nằm ở đầu “lớp” và x không nằm ở đầu “ lớp”. Var i: 0 .. Var T : Dictionary).Giải thuật nhƣ sau: Procedure Insert (x: KeyType.Next.Xoá một phần tử trong từ điển Xoá một phần tử x trong từ điển. P := Q^.Kiểm tra xem phần tử x có thuộc “lớp” được trỏ bởi T[h(x)] không? Nếu có thì loại bỏ phần tử này. New(P). Else Begin {xem xét các thành phần tiếp theo trong danh sách} Q := T[i]. P^. T) then Begin i := h(x). While(P <>nil) and(not found) do 89 Cấu trúc dữ liệu .key = x then {loại x khói danh sách} T[i] := T[i]^. if T[i]<>nil then if T[i]^. Begin If not Member(x. End. P: Pointer.

Chọn tuần tự các vị trí h1.3. hk theo một cách nào đó cho tới khi gặp một vị trí trống để đặt x vào.. Key= x then Begin {loại x khỏi d... P := Q^. h(b)=0.d có giá trị băm lần lượt là: h(a)=3.. trong bảng băm đóng “rổ” thứ i chứa phần tử có giá trị băm là i. Ta đặt a vào “rổ” 3.c. Dãy h1. Tương tự như băm mở. b vào “rổ” 0.Next. k gọi là tổng số lần băm lại.Next. trong đó dãy các phép thử có dạng : hi(x) = (h(x)+1) mod N Ví dụ N=8 và các phần tử của từ điển là a. End. nhưng vì có thể có nhiều phần tử có cùng giá trị băm nên ta sẽ gặp trường hợp sau: ta muốn đưa vào “rổ” i một phần tử x nhưng “rổ” này đã bị chiếm bởi một phần tử y nào đó = > g â y r a đụng độ. vị trí này là một “rổ” rỗng ta đặt d vào. h(d)=3. Cài đặt từ điển bằng bảng băm đóng a) Thế nào là bảng băm đóng? Bảng băm đóng lưu giữ các phần tử của từ điển ngay trong mảng (các phần tử của “rổ” i lưu trong chính phần tử thứ i của mảng) chứ không dùng mảng để lưu trữ các con trỏ trỏ tới đầu của các danh sách liên kết – “rổ”. Ta muốn đưa các phần tử này lần lượt vào bảng băm. Xét phần tử d.2. 5. Found := true. * Giải quyết đụng độ Cách giải quyết đụng độ đó gọi là băm lại (rehash) như sau: . có thể coi mỗi “rổ‟ chứa một giá trị đặc biệt Empty.. c vào “rổ” 5. hi là giá trị hàm băm của lần băm lại thứ i .b.Next := P^. End. End.Có nhiều cách băm lại. Một chiến lược đơn giản là băm lại tuyến tính.5. 90 Cấu trúc dữ liệu .. End Else Begin Q := P.. h(c)=5.s} Q^.If P^.Bộ môn CNPM – Khoa CNTT – ĐHTN . hk gọi là dãy các phép thử. d có h(d)=3 nhưng “rổ” 3 đã bị a chiếm ta tìm vị trí h1(d)= (h(d)+1) mod N = 4 . Khởi đầu bảng băm là rỗng. Empty không bằng với bất kỳ một phần tử nào mà ta có thể xét trong tập hợp các phần tử muốn đưa vào bảng băm.. Như vậy khi thiết kế một bảng băm đóng ta phải có cách để giải quyết sự đụng độ này.

để thực hiện các phép toán Insert. cho đến khi tìm thấy x hoặc tìm thấy một vị trí trống. b) Cài đặt từ điển bởi bảng băm đóng Const N=…. h2(x). giả sử h(e)=5. * Sử dụng bảng băm đóng để cài đặt từ điển Dưới đây là khai báo và thủ tục cần thiết để cài đặt từ điển bằng bảng băm đóng. h2(x).6. c) Cài đặt các phép toán từ điển trên bảng băm đóng: Với mỗi giá khoá x.h1(x).5. Ví dụ . Dictionary = array[ 0 . ….Tìm d. Để dễ dàng minh hoạ các giá trị Deleted và Empty.Giải quyết đụng độ trong bảng băm đóng bằng chiến lƣợc băm lại tuyến tính Trong bảng băm đóng. Tư tưởng để tìm ra các vị trí đó là thăm dò lần lượt các vị trí h(x). vậy không có e trong bảng băm. Chúng ta tìm kiếm e tại các vị trí 5. Nếu chúng ta chấp nhận phép xoá thì chúng ta qui ước rằng phần tử bị xóa sẽ được thay bởi một giá trị đặc biệt.. … cho tới khi hoặc tìm được vị trí có chứa x . phép kiểm tra một thành viên (thủ tục MEMBER (x.Tìm phần tử e trong bảng băm trên. Deleted = -1000. thủ tục này cho phép thăm dò các vị trí trong bảng. giả sử rằng ta cần cài đặt từ điển gồm các chuỗi 10 kí tự. {Gia dinh gia tri cho o da bi xoa} Empty=1000. Tuy nhiên.Bộ môn CNPM – Khoa CNTT – ĐHTN . gọi là Deleted. Ta có thể qui ước: Empty là chuỗi 10 dấu + và Deleted là chuỗi 10 dấu *.. vì h(d)=3 ta khởi đầu tại vị trí này và duyệt qua các “rổ” 5. Var T : Dictionary. rồi lần lượt qua các vị trí h 1(x).. . hoặc tìm ra vị trí trống đầu tiên 91 Cấu trúc dữ liệu . Delete. N-1] of elementType. Empty cũng là một giá trị đặc biệt cho ta biết ô trống.Với mỗi giá trị khoá x. Điều đó được thực hiện bởi thủ tục Location + Cách làm: . xuất phát từ vị trí được xác định bởi giá trị băm h(x).. Member ta đều phải xác định vị trí trong bảng có chứa x.A)) phải xét dãy các “rổ” h(x). “rổ” 6 là chứa Empty. nói chung điều đó chỉ đúng với trường hợp ta không hề xoá đi một phần tử nào trong bảng băm.0 b 1 2 3 a 4 d 5 c 6 7 Hình 5.. hoăc vị trí trong bảng cần đặt x vào. Phần tử d được tìm thấy tại “rổ” 5. {Gia dinh gia tri cho o chua su dung} type ElementType = integer.5.1 . giá trị Deleted không bằng với bất kỳ một phần tử nào trong tập hợp đang xét vào nó cũng phải khác giá trị Empty. h1(x). Bởi vì nếu hk(x) là vị trí trống được gặp đầu tiên thì x không thể được tìm gặp ở một vị trí nào xa hơn nữa.h2(x).

If (T[k]<>x) then 92 Cấu trúc dữ liệu . Begin Location(x. Begin Location(x. var T : Dictionary): boolean. 2. var T : Dictionary). Tham biến k: Lưu vị trí mà tại đó quá trình thăm dò dừng lại. End. var k.j). var k. J : = I. While (k <>i)and(T[k]<>x)and(T[k]<>enpty)do Begin If (T[k] = deleted )and(T[j]<>deleted)then j := k. { I ghi lại giá trị băm đầu tiên h(x)} Begin I := h(x).j : integer.j : integer).k. hoặc tìm ra vị trí trống đầu tiên).Thủ tục tìm xem x có thuộc từ điển T hay không? Function Member(x : keytype. j : integer.Quá trình thăm dò sẽ dừng lại nếu đi qua toàn bộ bảng mà không thành công (Không tìm ra vị trí chứa x.j).Thủ tục xác định vị trí đặt x Procedure Location(x: keytype. If (T[i] = x )or(T[i] = empty )then k := I Else Begin K := (i+1)mod n. End. 3.k. If (T[k]= empty)and(T[j]<>deleted)then j := k. Var I : integer. K := (k+1)mod N.Thủ tục thêm một phần tử x vào từ điển T Procedure Insert (x : KeyType..Bộ môn CNPM – Khoa CNTT – ĐHTN . If T[k]= x then Member := true Else Member := false. var k. End. Tham biến j: Lưu vị trí loại bỏ (Deleted) đầu tiên hoặc vị trí trống đầu tiên mà quá trình thăm dò phát hiện ra nếu trong bảng còn có các vị trí như thế 1. End.

3. Phân đoạn có thể là tách sau đó thì gấp: 93 Cấu trúc dữ liệu . c) Phƣơng pháp tách gấp Đối với các khoá dài và kích thước thay đổi người ta thường dùng phương pháp phân đoạn. Kết quả có thể ngẫu nhiên hơn nếu B là một số nguyên tố. If T[k] = x then T[k] := deleted. nói chung mỗi đoạn có độ dài bằng độ dài của kết quả hàm băm. Ví dụ: x h(x) g ồm 3 số ở giữa x2 5502 367 1256 2983 29181605 00135689 01552516 08898289 181 hoặc 816 135 356 552 525 898 982 Vì các chữ số ở giữa phụ thuộc vào tất cả các chữ số có mặt trong khoá do vậy các khoá có khác nhau đôi chút thì hàm băm cho kết quả khác nhau. Tức là hàm băm có dạng: H(x)= x mod B Phương pháp này rõ ràng là rất đơn giản nhưng nó có thể không cho kết quả ngẫu nhiên lắm.3 Các phương pháp xác định hàm băm a) Phƣơng pháp chia "Lấy phần dư của giá trị khoá khi chia cho số “rổ”" . tức là phân khoá ra thành nhiều đoạn có kích thước bằng nhau từ một đầu ( trừ đoạn tại đầu cuối ). Begin Location(x. 5. j : integer. var k. b) Phƣơng pháp nhân "Lấy khoá nhân với chính nó rồi chọn một số chữ số ở giữa làm kết quả của hàm băm".k.Bộ môn CNPM – Khoa CNTT – ĐHTN . Chẳng hạn B=1000 thì H(x) chỉ phụ thuộc vào ba số cuối cùng của khoá mà không phụ thuộc vào các số đứng trước.If (T[j] = deleted)or(T[j] = empty )then T[j] := x Else Writeln(„bảng đầy‟) Else Writeln(„ bảng đã có x‟). var T : Dictionary).j).5. 4 – Thủ tục xoá phần tử x thuộc từ điển T Procedure Delete (x : keyType. End. End.

….Cách làm: tách khóa ra từng đoạn rồi xếp các đoạn thành hàng được canh thẳng một đầu rồi có thể gấp (thực hiện một phép toán nào đó.Bộ môn CNPM – Khoa CNTT – ĐHTN . chúng lại) rồi áp dụng phương pháp chia để có kết quả băm. trừ. 94 Cấu trúc dữ liệu . 392 mod 1000 = 392 là kết quả băm khoá đã cho. ví dụ như:cộng. ví dụ: khoá 17056329 tách thành 329 056 017 cộng lại ta được 392.

Sign up to vote on this title
UsefulNot useful