1. Trang chủ
  2. » Giáo án - Bài giảng

Giáo trình cấu trúc dữ liệu và thuật toán

94 507 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 94
Dung lượng 1,18 MB

Nội dung

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

Trang 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 T hái ngu yê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 h u ậ 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ả

Trang 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

Trang 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

Trang 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

Trang 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ể

Trang 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 ì n h 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

Trang 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

Trang 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

Trang 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

Trang 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 ì n h

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

Trang 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 ự n g 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

Trang 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

Trang 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

Trang 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ụ:

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

Trang 15

Ví du: Giả sử có kiểu dữ liệu mẫu tự = <Vc ,Oc> với

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:

Trang 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ư :

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ừ

Trang 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,

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

Trang 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

Trang 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

Trang 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

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

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)), g(n) còn được gọi là cấp độ phức tạp tính toán

Trang 21

Hàm g(n) thường chọn là: logn; n; n2; n3: ta gọi chung là hàm đa thức; g(n) = 2n 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

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

Trang 22

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 tha 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

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

{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 1] > a[j] cũng tốn O(1) thời gian, do đó lệnh {3} tốn O(1) thời gian

a[j Vòng lặp {2} thực hiện (na[j i) lần, mỗi lần O(1) do đó vòng lặp {2} tốn O((na[j i).1) =

Trang 23

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;

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

Trang 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 }

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

Trang 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

Trang 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

Trang 27

danh sách theo thứ tự xuất hiện của nó

- Danh sách con : Nếu L = (a1 , a2, , an ) 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 o n g 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

Trang 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

Trang 29

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

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

Trang 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

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

Trang 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)

For i:= P-1 to L.count -1do

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;

Trang 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 =

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

Trang 33

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ì:

Trang 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ể 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)

* 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

Trang 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

- 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

Trang 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:

Trang 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

Trang 38

Else

If p<=length(L) then Begin

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:

Trang 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)

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

Trang 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):

{ M^ PPtr^ LPtr := M^ LPtr;

M^ LPtr^ RPtr := M^ Rptr ;

(1) (2)

{ R:= R^ LPtr ; R^ RPtr := nil ; Nếu : M =R :

Dispose (M);

Ngày đăng: 13/04/2017, 22:37

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w