1. Trang chủ
  2. » Công Nghệ Thông Tin

CẤU TRÚC DỮ LIỆU (ĐH HÀNG HẢI)

80 197 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 80
Dung lượng 1,11 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Học phần Cấu trúc dữ liệu nhằm cung cấp kiến thức và rèn luyện kỹ năng thực hành cấu trúc dữ liệu cho sinh viên. Kết cấu của bài giảng gồm có 4 chương: Chương I : Khái niệm liên quan đến CTDL. Chương II : Các kiểu dữ liệu trừu tượng cơ bản. Chương III: Cây (tree). Chương IV: Bảng băm (hash table).

Trang 1

BỘ GIAO THÔNG VẬN TẢI

TRƯỜNG ĐẠI HỌC HÀNG HẢI

BỘ MÔN: KHOA HỌC MÁY TÍNH

KHOA: CÔNG NGHỆ THÔNG TIN

CẤU TRÚC DỮ LIỆU

TÊN HỌC PHẦN : CẤU TRÚC DỮ LIỆU

MÃ HỌC PHẦN : 17207

TRÌNH ĐỘ ĐÀO TẠO : ĐẠI HỌC CHÍNH QUY

DÙNG CHO SV NGÀNH : CÔNG NGHỆ THÔNG TIN

HẢI PHÒNG - 2008

Trang 2

MỤC LỤC

CHƯƠNG 1 CÁC KHÁI NIỆM MỞ ĐẦU 1

1.1 Giải thuật và cấu trúc dữ liệu 1

1.2 Cấu trúc dữ liệu và các vấn đề liên quan 1

1.3 Ngôn ngữ diễn đạt giải thuật 2

1.4 Kiểu dữ liệu, cấu trúc dữ liệu, kiểu dữ liệu trừu tượng 3

CHƯƠNG 2 CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG CƠ BẢN 6

2 1 Ngăn xếp - Stack 6

2.1.1 Khái niệm 6

2.1.2 Các thao tác của ngăn xếp 6

2.1.3 Ví dụ về hoạt động của một stack 7

2.1.4 Cài đặt stack bằng mảng 7

2.1.5 Ứng dụng của stack 10

2.2 Hàng đợi - Queue 12

2.2.1 Khái niệm 12

2.2.2 Các thao tác cơ bản của một hàng đợi 13

2.2.3 Cài đặt hàng đợi sử dụng mảng 13

2.2.4 Ví dụ về hoạt động của hàng đợi với cài đặt bằng mảng vòng tròn 16

2.2.5 Ứng dụng củ a hàng đợi 16

2.3 Danh sách liên kết – Linked list 17

2.3.1 Định nghĩa 17

2.3.2 Các thao tác trên danh sách liên kết 17

2.3.3 Cài đặt danh sách liên kết sử dụng con trỏ 18

2.3.4 Các kiểu danh sách liên kết khác 25

2.3.5 Một số ví du ̣ sử du ̣ng cấu trúc danh sách liên kết 26

2.3.6 Cài đặt stack và queue bằng con trỏ 26

2.4 Bài tập áp dụng 26

CHƯƠNG 3 CÂY (TREE) 28

3.1 Định nghĩa 28

3.1.1 Đồ thị (Graph) 28

3.1.2 Cây (tree) 29

3.3 Cây tìm kiếm nhi ̣ phân (Binary Search Tree - BST) 31

3.3.1 Định nghĩa 31

3.3.2 Khở i ta ̣o cây rỗng 32

3.3.3 Chèn thêm một nút mới vào cây 32

3.3.4 Xóa bỏ khỏi cây một nút 33

3.3.5 Tìm kiếm trên cây 34

3.3.6 Duyệt cây 35

3.3.7 Cài đặt cây BST 36

3.4.Cây cân bằng – AVL 39

CHƯƠNG 4 BẢNG BĂM (HASH TABLE) 54

4 1 Định nghĩa bảng băm 54

4.1.1.Định nghĩa : 54

4.1.2.Kích thước của bảng băm : 55

4.1.3 Phân loại : 55

4.1.4.Các phép toán trên bảng băm : 57

4.2.Hàm băm và các loại hàm băm : 57

4.2.1.Hàm băm (Hash Function): 57

4.2.2.Một số loại hàm băm : 58

Trang 3

4.3.Xung đột và cách xử lý xung đột 61

4.3.1 Định nghĩa : 61

4.3.2.Hệ số tải (Load Factor - ) : 61

4.3.3.Một số phương pháp xử lý xung đột : 61

4.3.4 Đánh giá : 71

4.4.4.Kết luận : 72

4.5 Bài tập áp dụng 72

TÀI LIỆU THAM KHẢO 75

Trang 4

Tên học phần: Cấu trúc dữ liệu Loại học phần: 2

Bộ môn phụ trách giảng dạy: Khoa học Máy tính Khoa phụ trách: CNTT

TS tiết Lý thuyết Thực hành/Xemina Tự học Bài tập lớn Đồ án môn học

Điều kiện tiên quyết:

Sinh viên phải học xong các học phần sau mới được đăng ký học phần này:

Toán cao cấp, Toán rời rạc, Ngôn ngữ C, Tin học đại cương

Mục tiêu của học phần:

Cung cấp kiến thức và rèn luyện kỹ năng thực hành cấu trúc dữ liệu cho sinh viên

Nội dung chủ yếu

- Những vấn đề cơ bản về cấu trúc dữ liệu;

- Các cấu trúc dữ liệu cơ bản

- Danh sách liên kết;

- Ngăn xếp, hàng đợi;

1.1 Giải thuật và cấu trúc dữ liệu

1.2 Giải thuật và các vấn đề liên quan

1.3 Ngôn ngữ diễn đạt giải thuật

1.4 Kiểu dữ liệu, cấu trúc dữ liệu, kiểu dữ liệu trừu

tượng

Chương II : Các kiểu dữ liệu trừu tượng cơ bản 12 6 6

2.1 Danh sách

2.1.1 Khái niệm danh sách

2.1.2 Các phép toán trên danh sách

2.1.3 Cài đặt danh sách

2.1.4 Các dạng danh sách liên kết (DSLK): DSLK

Trang 5

TÊN CHƯƠNG MỤC

PHÂN PHỐI SỐ TIẾT

3.2.1 Biểu diễn cây tổng quát

3.2.2 Duyệt cây tổng quát

3.2.3 Vài ví dụ áp dụng

3.3 Cây nhị phân

3.3.1 Định nghĩa và tính chất

3.3.2 Lưu trữ cây

3.3.3 Duyệt cây

3.3.4 Cây nhị phân nối vòng

3.4 Các phép toán thực hiện trên cây nhị phân

3.4.1 Dựng cây

3.4.2 Duyệt cây để tìm kiếm

3.4.3 Sắp xếp cây nhị phân

3.5 Cây tìm kiếm nhị phân (binary search tree)

3.5.1 Khái niệm, cài đặt

4.3 Các phương pháp giải quyết xung đột

4.4 Đánh giá hiệu quả các phương pháp băm

4.5 Bài tập áp dụng

Nhiệm vụ của sinh viên :

Tham dự các buổi thuyết trình của giáo viên, tự học, tự làm bài tập do giáo viên giao, tham dự các bài kiểm tra định kỳ và cuối kỳ

Tài liệu học tập :

1 Đinh Mạnh Tường, Cấu trúc dữ liệu và thuật toán, Nhà xuất bản ĐH QG Hà Nội,

2004

2 Đỗ Xuân Lôi, Cấu trúc dữ liệu và giải thuật, Nhà xuất bản ĐH QG Hà Nội, 2004

3 Robert Sedgewick, Cẩm nang thuật toán, NXB Khoa học kỹ thuật, 2000

Hình thức và tiêu chuẩn đánh giá sinh viên:

- Hình thức thi cuối kỳ : Thi viết

- Sinh viên phải đảm bảo các điều kiện theo Quy chế của Nhà trường và của Bộ

Thang điểm: Thang điểm chữ A, B, C, D, F

Điểm đánh giá học phần: Z = 0,3X + 0,7Y

Bài giảng này là tài liệu chính thức và thống nhất của Bộ môn Khoa học máy tính,

Khoa Công nghệ thông tin và được dùng để giảng dạy cho sinh viên

Ngày phê duyệt: / /20

Trưởng Bộ môn: ThS Nguyễn Hữu Tuân (ký và ghi rõ họ tên)

Trang 6

CHƯƠNG 1 CÁC KHÁI NIỆM MỞ ĐẦU

1.1 Giải thuật và cấu trúc dữ liệu

Ðể giải một bài toán trong thực tế bằng máy tính 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 đon 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ụ : 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 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

Ðối với một bài toán đã được hình thức hoá, chúng ta có thể tìm kiếm cách giải trong thuật ngữ của mô hình đó và xác định có hay không một chưong trình có sẵn để giải Nếu không có một chương trình như vậy thì ít nhất chúng ta cũng có thể tìm được những gì đã biết

về mô hình và dùng các tính chất của mô hình để xây dựng một giải thuật tố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 chưỗi hữu hạn các chỉ thị (instruction) mà mỗi chỉ thị có một ý nghĩa rõ ràng và thực hiện được trong một lượng thời gian hữu hạn

Nhưng xét cho cùng, giải thuật chỉ phản ánh các phép xử lý, còn đói tượng để xử lý trong máy tính chính là dữ liệu (data ), chúng biểu diễn các thông tin cần thiết cho bài toán: các dữ liệu vào, các dữ liệu ra, dữ liệu trung gian, … Không thể nói tới giải thuật mà không nghĩ tới: giải thuật đó được tác động trên dữ liệu nào, còn xét tới dữ liệu thì phải biết dữ liệu

ấy cần được giải thuật gì tác động để đưa ra kết quả mong muốn Như vậy, giữa cấu trúc dữ liệu và giải thuật có mối liên quan mật thiết với nhau

1.2 Cấu trúc dữ liệu và các vấn đề liên quan

Trong một bài toán, dữ liệu bao gồm một tập các phần tử cơ sở, được gọi là dữ liệu nguyên tử Dữ liệu nguyên tử có thể là một chữ số, một ký tự, … cũng có thể là một số, một xâu, … tùy vào bài toán Trên cơ sở các dữ liệu nguyên tử, các cung cách khả dĩ theo đó lien kết chúng lại với nhau, sẽ đãn đến các cấu trúc dữ liệu khác nhau

Lựa chọn một cấu trúc dữ liệu thích hợp để tổ chức dữ liệu vào và trên cơ sở đó xây dựng được giải thuật xử lý hữu hiệu đưa tới kết quả mong muốn cho bài toán (dữ liệu ra), là một khâu quan trọng

Cách biểu diễn một cấu trúc dữ liệu trong bộ nhớ được gọi là cấu trúc lưu trữ Đây chính

là cách cài đặt cấu trúc ấy trên máy tính và trên cơ sở các cấu trúc lưu trữ này mà thực hiện các phép xử lý Có thể có nhiều cấu trúc lưu trữ khác nhau cho cùng một cấu trúc dữ liệu và ngược lại

Khi đề cập tới cấu trúc lưu trũ, cần phân biệt: cấu trúc lưu trữ tương ứng với bộ nhớ trong – lưu trữ trong; cấu trúc lưu trữ ứng với bộ nhớ ngoài – lưu trữ ngoài Chúng có đặc điểm và cách xử lý riêng

Trang 7

1.3 Ngôn ngữ diễn đạt giải thuật

Việc sử dụng một ngôn ngữ lập trình bậc cao để diễn đạt giải thuật, như Pascal, C, C++,

… sẽ gặp một số hạn chế sau:

- Phải luôn tuân thủ các quy tắc chặt chẽ về cú pháp của ngôn ngữ khiến cho việc trình bày về giải thuật và cấu trúc dữ liệu có thiên hướng nặng nề, gò bó

- Phải phụ thuộc vào cấu trúc dữ liệu tiền định của ngôn ngữ nên có lúc không thể hiện được đầy đủ các ý về cấu trúc mà ta muỗn biểu đạt

Một khi đã có mô hình thích hợp cho bài toán, ta cần hình thức hoá một giải thuật, một

cấu trúc dữ liệu trong thuật ngữ của mô hình đó Khởi đầu là viết những mệnh đề tổng quát

rồi tinh chế dần thành những chuỗi mệnh đề cụ thể hơn, cuối cùng là các chỉ thị thích hợp trong một ngôn ngữ lập trình

Ở bước này, nói chung, ta có một giải thuật, một cấu trúc dữ liệu tương đói rõ ràng, nó gần giống như một chương trình được viết trong ngôn ngữ lập trình, nhưng nó không phải là một chương trình chạy được vì trong khi viết giải thuật ta không chú trọng nặng đến cú pháp

của ngôn ngữ và các kiểu dữ liệu còn ở mức trừu tượng chứ không phải là các khai báo cài đặt

kiểu trong ngôn ngữ lập trình

Chẳng hạn với giải thuật tô màu đồ thị GREEDY, giả sử đồ thị là G, giải thuật sẽ xác

định một tập hợp Newclr các đỉnh của G được tô cùng một màu, mà ta gọi là màu mới C ở

trên Ðể tiến hành tô màu hoàn tất cho đồ thị G thì giải thuật này phải được gọi lặp lại cho đến khi toàn thể các đỉnh đều được tô màu

void GREEDY ( GRAPH *G, SET *Newclr )

{

Newclr = ; /*1*/

for (mỗi đỉnh v chưa tô màu của G) /*2*/

if (v không được nối với một đỉnh nào trong Newclr) /*3*/

{

đánh dấu v đã được tô màu; /*4*/

thêm v vào Newclr; /*5*/

} }

Trong thủ tục bằng ngôn ngữ giả này chúng ta đã dùng một số từ khoá của ngôn ngữ C xen lẫn các mệnh đề tiếng Việt Ðiều đặc biệt nữa là ta dùng các kiểu GRAPH, SET có vẻ xa

lạ, chúng là các "kiểu dữ liệu trừu tượng" mà sau này chúng ta sẽ viết bằng các khai báo thích hợp trong ngôn ngữ lập trình cụ thể Dĩ nhiên, để cài đặt thủ tục này ta phải cụ thể hoá dần những mệnh đề bằng tiếng Việt ở trên cho đến khi mỗi mệnh đề tương ứng với một doạn mã

thích hợp của ngôn ngữ lập trình Chẳng hạn mệnh đề if ở /*3*/ có thể chi tiết hoá hơn nữa

for (mỗi đỉnh w trong Newclr) /*3.2*/

if (có cạnh nối giữa v và w) /*3.3*/

found=1; /*3.4*/

if (found==0)/*3.5*/

{

đánh dấu v đã được tô màu; /*4*/

thêm v vào Newclr; /*5*/

}

Trang 8

} }

GRAPH và SET ta coi như tập hợp Có nhiều cách để biểu diễn tập hợp trong ngôn ngữ lập trình, để đơn giản ta xem các tập hợp như là một danh sách (LIST) các số nguyên biểu diễn chỉ số của các đỉnh và kết thúc bằng một giá trị đặc biệt NULL Với những qui ước như vậy ta có thể tinh chế giải thuật GREEDY một bước nữa như sau:

void GREEDY ( GRAPH *G, LIST *Newclr )

w=đỉnh đầu tiên trong newclr;

while( w<>null) && (found=0)

Ðánh dấu v đã được tô màu;

Thêm v vào Newclr;

} v= đỉnh chưa tô màu kế tiếp trong G;

} }

1.4 Kiểu dữ liệu, cấu trúc dữ liệu, kiểu dữ liệu trừu tượng

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 di những chi tiết, làm nổi bật cái tổng thể Trừu tượng hóa có thể thực hiện trên hai khía cạnh là trừu tượng hóa dữ liệu và trừu tượng hóa chương trình

Trừu tượng hóa chương trình

Trừu tượng hóa chương trình là sự định nghĩa các chương trình con để tạo ra các phép toán trừu tượng (sự tổng quát hóa của các phép toán nguyên thủy) Chẳng hạn ta có thể tạo ra một chương trình con Matrix_Mult để thực hiện phép toán nhân hai ma trận Sau khi Matrix_mult đã được tạo ra, ta có thể dùng nó như một phép toán nguyên thủy (chẳng hạn phép cộng hai số)

Trừu tượng hóa chương trình cho phép phân chia chương trình thành các chương trình con Sự phân chia này sẽ che dấu tất cả các lệnh cài đặt chi tiết trong các chương trình con Ở cấp độ chương trình chính, ta chỉ thấy lời gọi các chương trình con và điều này được gọi là sự bao gói

Ví dụ như một chương trình quản lý sinh viên được viết bằng trừu tượng hóa có thể là:

void main()

{

Nhap(Lop);

Trang 9

Chương trình được viết theo cách gọi các phép toán trừu tượng có lệ thuộc vào cách cài đặt kiểu dữ liệu không?

Trừu tượng hóa dữ liệu

Trừu tượng hóa dữ liệu là định nghĩa các kiểu dữ liệu trừu tượng

Một kiểu dữ liệu trừu tượng là một mô hình toán học cùng với một tập hợp các phép toán (operator) trừu tượng được định nghĩa trên mô hình đó Ví dụ tập hợp số nguyên cùng

với các phép toán hợp, giao, hiệu là một kiểu dữ liệu trừu tượng

Trong một ADT các phép toán có thể thực hiện trên các đói tượng (toán hạng) không chỉ thuộc ADT đó, cũng như kết quả không nhất thiết phải thuộc ADT Tuy nhiên, phải có ít nhất một toán hạng hoặc kết quả phải thuộc ADT đang xét

ADT là sự tổng quát hoá của các kiểu dữ liệu nguyên thưỷ

Ðể minh hoạ ta có thể xét bản phác thảo cuối cùng của thủ tục GREEDY Ta đã dùng một danh sách (LIST) các số nguyên và các phép toán trên danh sách newclr là:

- Tạo một danh sách rỗng

- Lấy phần tử đầu tiên trong danh sách và trả về giá trị null nếu danh sách rỗng

- Lấy phần tử kế tiếp trong danh sách và trả về giá trị null nếu không còn phần tử kế tiếp

Ðiều này cho thấy sự thuận lợi của ADT, đó là ta có thể định nghĩa một kiểu dữ liệu tuỳ

ý cùng với các phép toán cần thiết trên nó rồi chúng ta dùng như là các đói tượng nguyên thuỷ Hơn nữa chúng ta có thể cài đặt một ADT bằng bất kỳ cách nào, chương trình dùng chúng cũng không thay đổi, chỉ có các chương trình con biểu diễn cho các phép toán của ADT là thay đổi

Cài đặt ADT là sự thể hiện các phép toán mong muốn (các phép toán trừu tượng) thành

các câu lệnh của ngôn ngữ lập trình, bao gồm các khai báo thích hợp và các thủ tục thực hiện

các phép toán trừu tượng Ðể cài đặt ta chọn một cấu trúc dữ liệu thích hợp có trong ngôn

ngữ lập trình hoặc là một cấu trúc dữ liệu phức hợp được xây dựng lên từ các kiểu dữ liệu cơ bản của ngôn ngữ lập trình

Sự khác nhau giữa kiểu dữ liệu và kiểu dữ liệu trừu tượng là gì?

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

Trang 10

structure), kiểu dữ liệu trừu tượng (abstract data type) nghe 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 sơ cấp 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 toán học cùng với một tập hợp các phép toán 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 ngữ 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

Bài tập:

1 Tìm hiểu các kiểu dữ liệu cơ sở trong C

2 Tìm hiểu các cấu trúc dữ liệu mảng, cấu trúc trong C và thực hiện một số bài tập cơ bản như nhập, xuất

Trang 11

CHƯƠNG 2 CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG CƠ BẢN

2 1 Ngăn xếp - Stack

2.1.1 Khái niệm

Khái niệm: Ngăn xếp (stack) là một tập hợp các phần tử (items) cùng kiểu được tổ

chức mô ̣t cách tuần tự (chính vì thế một số tài liệu còn định nghĩa ngăn xếp là một danh sách tuyến tính các phần tử với các thao tác truy câ ̣p ha ̣n chế tới các phần tử của danh sách đó) trong đó phần tử được thêm vào cuối cùng của tâ ̣p hợp sẽ là phần tử bi ̣ loa ̣i bỏ đầu tiên khỏi

tâ ̣p hợp Các ngăn xếp thường được gọi là các cấu trúc LIFO (Last In First Out)

Ví dụ về ngăn xếp: Chồng tài liê ̣u của mô ̣t công chức văn phòng, chồng đĩa … là các

ví dụ về ngăn xếp

Chú ý: Phần tử duy nhất có thể truy câ ̣p tới của mô ̣t ngăn xếp là phần tử mới được

thêm vào gần đây nhất (theo thời gian) của ngăn xếp

2.1.2 Các thao tác của ngăn xếp

Đối với một ngăn xếp chỉ có 2 thao tác cơ bản, thao tác thứ nhất thực hiê ̣n thêm mô ̣t phần tử vào stack go ̣i là push, thao tác thứ hai là đo ̣c giá tri ̣ của mô ̣t phần tử và loa ̣i bỏ nó khỏi stack go ̣i là pop

Để nhất quán với các thư viê ̣n cài đă ̣t cấu trúc stack chuẩn STL (và một số tài liệu cũng phân chia như vậy), ta xác đi ̣nh các thao tác đối với mô ̣t stack gồm có:

1 Thao tác push(d) sẽ đặt phần tử d lên đỉnh của stack

2 Thao tác pop() loại bỏ phần tử ở đỉnh stack

3 Thao tác top() sẽ trả về giá trị phần tử ở đỉnh stack

4 Thao tác size() cho biết số phần tử hiê ̣n ta ̣i đang lưu trong stack

Ngoài hai thao tác cơ bản trên chúng ta cần có mô ̣t số thao tác phu ̣ trợ khác: chẳng ha ̣n làm thế nào để biết là một stack không có phần tử nào – tức là rỗng (empty) hay là đầy (full) tức là không thể thêm vào bất cứ mô ̣t phần tử nào khác nữa Để thực hiê ̣n điều này người ta thường thêm hai thao tác tiến hành kiểm tra là empty() và full()

Để đảm bảo không xảy ra tình tra ̣ng go ̣i là stack overflow (tràn stack – không thể thêm vào stack bất cứ phần tử nào) chúng ta có thể cho hàm push trả về chẳng ha ̣n 1 trong trường

hơ ̣p thực hiê ̣n thành công và 0 nếu không thành công

Trang 12

2.1.3 Ví dụ về hoạt động của một stack

Giả sử chúng ta có một stack kích thước bằng 3 (có thể chứa được tối đa 3 phần tử) và các phần tử của stack là các số nguyên trong khoảng từ -100 đến 100 Sau đây là minh ho ̣a các thao tác đối với stack và kết quả thực hiện của các thao tác đó

Thao tác Nô ̣i dung stack Kết quả

2.1.4 Cài đặt stack bằng mảng

Cấu trúc dữ liê ̣u stack có thể cài đă ̣t bằng cách sử dụng một mảng và một số nguyên top_idx để chứ a chỉ số của phần tử ở đỉnh stack

Ngăn xếp rỗng khi top_idx = -1 và đầy khi top_idx = n-1 trong đó n là kích thước của mảng

Khi thực hiê ̣n thao tác push chúng ta tăng top_idx lên 1 và ghi dữ liệu vào vị trí tương ứng của mảng

Khi thực hiê ̣n thao tác pop chúng ta chỉ viê ̣c giảm chỉ số top_idx đi 1

Ví dụ về ngăn xếp cài đặt bằng mảng:

Giả sử chúng ta sử dụng mảng E[0 4] để chứa các phần tử của stack và biến top_idx

để lưu chỉ số của phần tử ở đỉnh stack Trong bảng sau cô ̣t cuối cùng kết quả là giá tri ̣ trả về của việc gọi hàm

Thao tác top_idx E[0] E[1] E[2] E[3] E[4] Kết quả

Trang 13

Cài đặt của stack bằng ngôn ngữ C nhƣ sau (áp dụng stack cho bài toán chuyển số từ

cơ số 10 sang cơ số 2):

#include <stdio.h>

#include <stdlib.h>

const int MAX_ELEMENT = 100; // so phan tu toi da cua stack la 100

// khai bao stack chua cac so nguyen

int top(const stack *s);

int size(const stack *s);

int empty(const stack *s);

int full(const stack *s);

// ham giai phong bo nho danh cho stack

push(&s, n%2);

n /= 2;

} while(!empty(&s)) {

bit = top(&s);

pop(&s);

printf("%d", bit);

} clear(&s);

return 0;

Trang 14

Cứ mỗi phần tử của stack cần

có thêm 2 byte (1 con trỏ)

Xin cấp phát mô ̣t vùng nhớ có kích thước cố đi ̣nh và có thể mô ̣t phần

Trang 15

Nếu kích thước của mô ̣t phần

tử lớn thì không đáng kể

nhưng nếu là kiểu int thì kích

thước sẽ tăng gấp đôi

trong số đó không bao giờ được dùng đến và nếu như kích thước của một phần tử lớn thì vùng nhớ lãng phí này cũng rất lớn

Không có giới ha ̣n về số phần

tử của stack

Kích thước tối đa của stack được xác

đi ̣nh ngay khi nó được ta ̣o ra

2.1.5 Ứng dụng của stack

Chúng ta có thể sử dụng thuật toán sau đây:

while not end of Input

S = next sumbol if(s is opening symbol)

push(s) else // s là dấu đóng ngoă ̣c

if(stack.empty)

Báo lỗi else

R = stack.top() stack.pop() if(!match(s,r))

Báo lỗi If(!stack.empty())

Báo lỗi Ví dụ:

End of Input, stack rỗng => biểu thứ c là đúng

Ví dụ: Input = { ( ) ( { ) } } (sai)

Input = { ( { } ) { } ( ) }

Trang 16

Ví dụ 2

Sử du ̣ng Stack để chuyển đổi các dạng biểu thức đại số Trong ví du ̣ này chúng ta sẽ xem xét các thuâ ̣t toán sử du ̣ng stack để chuyển đổi từ biểu đa ̣i số ở da ̣ng trung tố (dạng thông thường, hay còn go ̣i là infix notation) thành các biểu thức ở dạng tiền tố (prefix notation, hay còn gọi là biểu thức Balan – Polish notation) và biểu thức hậu tố (postfix notation, hay biểu thức Balan ngược)

Biểu thức đa ̣i số là mô ̣t sự kết hợp đúng đắn giữa các toán ha ̣ng (operand) và các toán tử (operator) Toán hạng là các số liệu có thể thực hiện được các thao tác tính toán toán học Toán hạng cũng có thể là các biến số x, y, z hay các hằng số Toán tử là một ký hiệu chỉ ra thao tác tính toán toán ho ̣c hay logic giữa các toán ha ̣ng, chẳng ha ̣n như các toán ha ̣ng +, -, *, /, ^ (toán tử mũ hóa) Việc đi ̣nh nghĩa các biểu thức đa ̣i số mô ̣t cách chă ̣t chẽ về lý thuyết là như sau:

 Mô ̣t toán ha ̣ng là mô ̣t biểu thức hợp lê ̣

 Nếu expression1 và expression2 là hai biểu thức hợp lệ và op là một toán tử thì

mô ̣t kết hợp hợp lê ̣ giữa biểu thức expression 1 với biểu thức expression 2 sử du ̣ng toán tử op sẽ cho ta một biểu thức đại số hợp lệ

Theo đi ̣nh nghĩa trên ta có x + y*z là mô ̣t biểu thức đa ̣i số hợp lê ̣ nhưng *x y z+ không phải là mô ̣t biểu thức hợp lê ̣ Trong các biểu thức đa ̣i số người ta có thể sử du ̣ng các dấu đóng và mở ngoă ̣c

Mô ̣t biểu thức đa ̣i số có thể được biểu diễn bằng 3 dạng khác nhau

Biểu thức trung tố (infix notation): đây là da ̣ng biểu diễn phổ biến nhất của các biểu thức đa ̣i số, trong các biểu thức trung tố, toán tử nằm giữa các toán hạng Ví dụ như 2 + 3 * (7 – 3)

Biểu thức tiền tố (prefix notation): dạng biểu diễn này do nhà toán học người Balan Jan Lukasiewicz đưa ra vào những năm 1920 Trong da ̣ng biểu diễn này, toán tử đứng trước các toán hạng Ví dụ như + * 2 3 7

Biểu thức hâ ̣u tố (postfix notation): hay còn go ̣i là biểu thức Balan ngược, toán tử đứng sau các toán ha ̣ng Ví dụ như 2 3 – 7 *

Câu hỏi mà chúng ta có thể đă ̣t ra ngay lâ ̣p tức ở đây là: tại sao lại cần sử dụng tới các dạng biểu diễn tiền tố và hậu tố trong khi chúng ta vẫn quen và vẫn sử dụng được các biểu thức ở da ̣ng trung tố

Lý do là các biểu thức trung tố không đơn giản và dễ dàng khi tính giá trị của chúng như chúng ta vẫn tưởng Để tính giá tri ̣ của mô ̣t biểu thức trung tố chúng ta cần tính tới đô ̣ ưu tiên của các toán tử của biểu thức và các qui tắc kết hợp Độ ưu tiên của các toán tử và các qui tắc kết hơ ̣p sẽ quyết đi ̣nh tới quá trình tính toán giá tri ̣ của mô ̣t biểu thức trung tố

Chúng ta có bảng độ ưu tiên của các toán tử thường gặp như sau:

() + (một ngôi), - (mô ̣t ngôi), ! + (cộng), - (trừ)

<, <=, >, >=

==, !=

Trang 17

&&

||

Khi đã biết đô ̣ ưu tiên toán tử chúng ta có thể tính toán các biểu thức chẳng hạn 2 + 4

* 5 sẽ bằng 22 vì trước hết cần lấy 4 nhân vớ i 5, sau đó kết quả nhâ ̣n đươ ̣c đem cô ̣ng với 2 vì phép nhân có độ ưu tiên cao hơn phép cộng Nhưng với biểu thức 2*7/3 thì ta không thể tính đươ ̣c vì phép nhân và phép chia có đô ̣ ựu tiên bằng nhau, khi đó cần sử du ̣ng tới các qui tắc kết hơ ̣p các toán tử Qui tắc kết hợp sẽ cho chúng ta biết thứ tự thực hiê ̣n các toán tửu có cùng

đô ̣ ưu tiên Chẳng ha ̣n chúng ta có qui tắc kết hợp trái, nghĩa là các toán tử cùng độ ưu tiên sẽ đươ ̣c thực hiê ̣n từ trái qua phải, hay qui tắc kết hợp phải Nếu theo qui tắc kết hợp trái thì phép toán trên sẽ có kết quả là 4 (lấy kết quả nguyên)

Vì những vấn đề liên quan tới độ ưu tiên toán tử và các qui luật kết hợp nên chúng ta thường sử du ̣ng các da ̣ng biểu diễn tiền tố và hâ ̣u tố trong viê ̣c tính toán các biểu thức đa ̣i số

Cả biểu thức hậu tố và tiền tố đều có một ưu điểm hơn so với cách biểu diễn trung tố: đó là khi tính toán các biểu thức ở da ̣ng tiền tố và hâ ̣u tố chúng ta không cần phải để ý tới đô ̣

ưu tiên toán tử và các luâ ̣t kết hợp Tuy nhiên so với biểu thức trung tố, các biểu thức tiền tố

và hậu tố khó hiểu hơn và vì thế nên khi biểu diễn chúng ta vẫn sử dụng dạng biểu thức trung tố, nhưng khi tính toán sẽ sử du ̣ng da ̣ng tiền tố hoă ̣c hâ ̣u tố, điều này yêu cầu cần có các thuật toán chuyển đổi từ dạng trung tố sang dạng tiền tố hoặc hậu tố

Viê ̣c chuyển đổi của chúng ta có thể thực hiê ̣n bằng cách sử du ̣ng cấu trúc stack hoă ̣ cây biểu thức (chương 5), phần này chúng ta sẽ chỉ xem xét các thuâ ̣t toán sử du ̣ng stack vì thuâ ̣t toán sử du ̣ng cây biểu thức khá phức ta ̣p

Thuâ ̣t toán chuyển đổi biểu thức da ̣ng trung tố thành da ̣ng hâ ̣u tố sử du ̣ng stack

Ví dụ 3

Phân tích đô ̣ ưu tiên toán tử

Chúng ta có thể sử dụng cấu trúc stack để phân tích và lượng giá các biểu thức toán học kiểu như:

đơ ̣i Các hàng đợi thường được gọi là các cấu trúc FIFO (First In First Out)

Các ví dụ thực tế về hàng đợi mà chúng ta có thể thấy trong cuộc sống hàng ngày đó là đoàn người xếp hàng chờ mua vé tầu, danh sách các cuô ̣c he ̣n của mô ̣t giám đốc, danh sách các công viê ̣c cần làm của mô ̣t người …

Trang 18

Cũng có thể định nghĩa hàng đợi là một danh sách tuyến tính các phần tử giống nhau với mô ̣t số thao tác ha ̣n chế tới các phần tử trên danh sách đó

2.2.2 Các thao tác cơ bản của một hàng đợi

Tương tự như cấu trúc ngăn xếp, chúng ta định nghĩa các thao tác trên hàng đợi tuân theo cài đă ̣t chuẩn của hàng đợi trong thư viê ̣n STL và các tài liê ̣u khác, gồm có:

1 push(d): thêm phần tử d vào vi ̣ trí ở cuối hàng đợi

2 pop(): loại bỏ phần tử ở đầu hàng đợi

3 front(): trả về giá trị phần tử ở đầu hàng đợi

4 back(): trả về giá trị phần tử ở cuối hàng đợi

5 size(): trả về số phần tử đang ở trong hàng đợi

6 empty(): kiểm tra hàng đơ ̣i có rỗng hay không

7 full(): kiểm tra hàng đơ ̣i đầy (chỉ cần khi cài đặt hàng đợi bằng mảng)

2.2.3 Cài đặt hàng đợi sử dụng mảng

Để cài đă ̣t cấu trúc hàng đợi chúng ta có thể sử du ̣ng mảng hoă ̣c sử du ̣ng con trỏ (phần này sẽ học sau phần danh sách liên kết):

 Ta lưu các phần tử của hàng đợi trong mô ̣t mảng data Đầu của hàng đợi là phần tử đầu tiên, và đuôi được chỉ ra bằng cách sử dụng một biến tail

 push(d) được thực hiê ̣n mô ̣t cách dễ dàng: tăng tail lên 1 và chèn phần tử vào vị trí đó

 pop() được thực hiê ̣n không hiê ̣u quả: tất cả các phần tử đều sẽ bi ̣ dồn về đầu mảng

do đó đô ̣ phức ta ̣p là O(n)

Làm thế nào chúng ta có thể cải thiện tình hình này?

Thay vì chỉ sử du ̣ng mô ̣t biến chỉ số tail chúng ta sử dụng hai biến tail và head, khi cần loại bỏ (pop) mô ̣t phần tử khỏi hàng đợi chúng ta sẽ tăng biến head lên 1:

Tuy vâ ̣y vẫn còn có vấn đề, đó là sau n lần push() (n là kích thước mảng) mảng sẽ đầy kể cả trong trường hợp nó gần như rỗng về mặt logic Để giải quyết vấn đề này chúng ta sẽ sử dụng lại các phần tử ở đầu mảng Khi push() mô ̣t phần tử mới tail sẽ được tăng lên 1 nhưng nếu như nó ở cuối mảng thì sẽ đă ̣t nó bằng 0

Vấn đề mới nảy sinh ở đây là làm thế nào chúng ta có thể xác định được khi nào hàng

đơ ̣i rỗng hoă ̣c đầy?

Trang 19

Cách giải quyết đơn giản là ta sẽ dùng một biến lưu số phần tử thực sự của hàng đợi

để giải quyết cho tất cả các thao tác kiểm tra hàng đợi rỗng, đầy hoă ̣c lấy số phần tử của hàng

đơ ̣i

#include <stdio.h>

#include <stdlib.h>

const int MAX_ELEMENT = 100; // so phan tu toi da cua queue la 100

// khai bao queue chua cac so nguyen

int front(const queue *q);

int back(const queue *q);

int size(const queue *q);

int empty(const queue *q);

int full(const queue *q);

// ham giai phong bo nho danh cho queue

d = front(&q);

printf("%d ", d);

pop(&q);

} clear(&q);

return 0;

Trang 21

int empty(const queue *q)

2.2.4 Ví dụ về hoạt động của hàng đợi với cài đặt bằng mảng vòng tròn

Ta giả sử mảng lưu các phần tử của hàng đợi là E[0 3], các biến head, tail lưu vi ̣ trí của phần tử ở đầu và cuối hàng đợi, cô ̣t R là cô ̣t kết quả thực hiê ̣n các thao tác trên hàng đợi, các dấu ? tương ứng với giá tri ̣ bất kỳ

Thao tác head tail E[0] E[1] E[2] E[3] R

2.2.5 Ứng dụng của hàng đợi

Trong các hê ̣ điều hành:

 Hàng đợi các công việc hoặc các tiến trình đang đợi để được thực hiện

 Hàng đợi các tiến trình chờ các tín hiệu từ các thiết bị IO

 Các file đươ ̣c gửi tới máy in

Mô phỏng các hê ̣ thống hàng đợi thời trong thực tế

 Các khách hàng trong các cửa hàng tạp hóa, trong các hê ̣ thống ngân hàng

 Các đơn đặt hàng của một công ty

Bài tập: Hãy viết chương trình chuyển đổi một biểu thức dạng infix (dạng thông

thường) đơn giản (không chứa các dấu ()) thành một biểu thức dạng tiền tố (prefix) Ví dụ này xem như một bài tâ ̣p để sinh viên tự làm

Trang 22

2.3 Danh sa ́ ch liên kết – Linked list

2.3.1 Đi ̣nh nghi ̃a

Danh sách liên kết (linked list) là một tập hợp tuyến tính các phần tử cùng kiểu gọi là các nút (node), mỗi nút có các đă ̣c điểm sau đây:

 Mỗi nút có ít nhất hai trường (field) mô ̣t trường go ̣i là trường dữ liê ̣u (data) và trường còn la ̣i là trường liên kết (link) trỏ tới (point to) (thường go ̣i là next)

 Trường liên kết của phần tử thứ i của danh sách sẽ trỏ tới phần tử thứ (i+1) của danh sách

 Phần tử đầu tiên của danh sách liên kết được go ̣i là head và phần tử cuối cùng đươ ̣c go ̣i là tail Head không chứa dữ liê ̣u và trường next của tail sẽ chỉ vào NULL

 Trường data là trườ ng chứa dữ liê ̣u mà chúng ta thực sự lưu trong danh sách liên kết

 Giá trị NULL và việc thực hiện trỏ tới (point to) của mỗi liên kết thực sự diễn ra như thế nào phu ̣ thuô ̣c nhiều vào viê ̣c cài đă ̣t cu ̣ thể danh sách liên kết

Có nhiều loại danh sách liên kết khác nhau tùy thuộc vào cấu trúc của mỗi phần tử trong danh sách (số trường liên kết với các phần tử khác trong danh sách) nhưng cơ bản nhất

là danh sách liên kết đơn (single linked list), mỗi phần tử có một trường liên kết như trên hình

vẽ minh họa, và khi chúng ta nói đến danh sách liên kết, nếu không có các chú giải đi kèm thì ngầm hiểu đó là danh sách liên kết đơn

2.3.2 Các thao tác trên danh sách liên kết

Tương tự như các cấu trúc cơ bản stack và queue, chúng ta định nghĩa các thao tác của danh sách liên kết dựa trên cài đă ̣t chuẩn của cấu trúc danh sách liên kết trong thư viê ̣n STL:

1 push_front(d): thêm một phần tử vào đầu danh sách

2 push_back(d): thêm một phần tử vào cuối danh sách

3 pop_front(): loại bỏ phần tử ở đầu danh sách

4 pop_back(): loại bỏ phần tử cuối danh sách

5 erase(): xỏa bỏ một phần tử khỏi danh sách

6 insert(): chèn một phần tử mới vào một vị trí cụ thể của danh sách

7 size(): cho biết số phần tử trong danh sách

8 empty(): kiểm tra danh sách rỗng

9 begin(): trả về phần tử ở đầu danh sách

10 end(): trả về phần tử ở cuối danh sách

11 sort(): sắp xếp danh sách theo trường khóa (là một trường con của trường dữ liệu)

12 merge(): trộn danh sách với mô ̣t danh sách khác

13 clear(): xóa bỏ toàn bộ các phần tử của danh sách

14 find(): tìm kiếm một phần tử trong danh sách theo khóa tìm kiếm

Trang 23

Các thao tác khác cũng có thể được cài đă ̣t với mô ̣t danh sách liên kết để làm cho công viê ̣c của các lâ ̣p trình viên trở nên dễ dàng hơn:

 Di chuyển mô ̣t phần tử trong danh sách

 Đổi hai phần tử cho nhau

2.3.3 Cài đặt danh sách liên kết sư ̉ du ̣ng con trỏ

typedef struct Node

{

// truong du lieu int data;

struct Node * next;

} NodeType;

Khởi ta ̣o danh sách:

NodeType * head, * tail;

head = new node;

headnext = NULL;

Các thao tác trên sẽ tạo ra một danh sách liên kết rỗng (empty – không chứa phần tử nào)

Trong cài đă ̣t này chúng ta cho tail chỉ vào NULL, thường được đi ̣nh nghĩa là 0 Do đó trường next của mô ̣t phần tử sẽ là 0, và khi đó chúng ta biết là chúng ta đang ở phần tử tail của danh sách

Ở đây trỏ tới (point to) có nghĩa là chúng ta thực sự sử dụng các con trỏ Chúng ta sẽ sớm thấy rằng chúng ta không cần thiết phải sử du ̣ng các con trỏ thực sự để cài đă ̣t mô ̣t danh sách liên kết

Chèn một nút (node) vào danh sách liên kết

Dễ dàng nhâ ̣n thấy rằng cách đơn giản nhất để chèn mô ̣t nút mới vào mô ̣t danh sách liên kết là đă ̣t nút đó ở đầu (hoă ̣c cuối) của danh sách Hoă ̣c cũng có thể chúng ta muốn chèn các phần tử vào giữa danh sách

Chèn X vào giữa I và S:

Trang 24

Để thƣ̣c hiê ̣n điều này chúng ta cần 2 tham chiếu tới hai nút trong danh sách và không

cần quan tâm tới đô ̣ dài (số phần tƣ̉) của danh sách Tuy nhiên thƣ̣c hiê ̣n viê ̣c này với các

mảng chắc chắn sẽ khác nhiều

Xóa một nút (node) khỏi danh sách liên kết

Xóa một nút khỏi danh sách liên kết rất đơn giản chúng ta chỉ cần thay đổi một con

trỏ, tuy nhiên vấn đề là chúng ta cần biết nút nào trỏ tới nút mà chúng ta đi ̣nh xóa Giả sử

chúng ta biết nút i trỏ tới nút x và chúng ta muốn xóa bỏ x:

inext = xnext;

Chỉ có một tham chiếu bị thay đổi không phụ thuộc vào đô ̣ dài của danh sách (so sánh

với cài đă ̣t bằng mảng, tuy vâ ̣y vẫn có vấn đề với cài đă ̣t trên)

Di chuyển (move) mô ̣t nút trong danh sách liên kết

Di chuyển mô ̣t nút trong danh sách liên kết bao gồm hai thao tác: xóa bỏ một nút sau

đó chèn vào mô ̣t nút Ví dụ chúng ta muốn di chuyển nút T từ cuối danh sách lên đầu danh

sách:

Mô ̣t lần nƣ̃a chúng ta thấy rằng thao tác di chuyển này chỉ đòi hỏi thay đổi 3 tham

chiếu và không phu ̣ thuô ̣c vào đô ̣ dài của danh sách (so sánh điều này với cài đă ̣t bằng mảng)

Cài đặt minh họa đầy đủ của danh sách liên kết đơn:

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

// khai bao cau truc cho mot nut cua danh sach

typedef struct Node

{

// truong du lieu int data;

struct Node * next;

Trang 25

// ham khoi tao danh sach

void init(LList * list);

// ham them 1 phan tu vao dau danh sach

void push_front(LList *list, int d);

// ham them mot phan tu vao cuoi danh sach

void push_back(LList *list, int d);

// ham xoa phan tu o cuoi danh sach

int pop_back(LList * list);

// ham xoa phan tu o dau danh sach

int pop_front(LList * list);

// ham tra ve phan tu dau tien

int begin(const LList * list);

// ham tra ve phan tu cuoi cung

int end(const LList * list);

void insertAfter(LList * list, NodeType * p);

void insertBefore(LList * list, NodeType * p);

void eraseAfter(LList * list, NodeType * p);

void eraseBefore(LList * list, NodeType * p);

// ham in danh sach

void printList(LList list);

// ham sap xep danh sach

void sort(LList *list);

// ham tim kiem trong danh sach

NodeType * find(LList *, int d);

// giai phong toan bo danh sach

void clear(LList * list);

// ham tron hai danh sach, ket qua luu trong danh sach thu nhat

void merge(LList *list1, const LList *list2);

// ham kiem tra danh sach lien ket co rong khong

int empty(const LList *list);

int main()

{

LList myList;

LList list2;

Trang 26

printf("%d ", tmp->data);

tmp = tmp->next;

} printf("\n");

tmp->next = NULL;

list->head = list->tail = tmp;

Trang 27

} else {

tmp->next = list->head;

list->head = tmp;

} list->spt = list->spt+1;

list->tail->next = tmp;

list->tail = tmp;

} list->spt = list->spt+1;

if (tmp->data==d)

break;

tmp = tmp->next;

} return tmp;

p = list->head;

q = NULL;

while(p->next!=NULL)

Trang 28

{

q = p;

p = p->next;

} if(q!=NULL) {

// danh sach chi co 1 phan tu q->next = NULL;

list->tail = q;

} else

list->head = list->tail = NULL;

ret = p->data;

free(p);

list->spt = list->spt-1;

} return ret;

tmp = list->head;

if(list->spt==1) {

// danh sach chi co 1 phan tu ret = list->head->data;

list->head = list->tail = NULL;

}

// sap xep dung thuat toan doi cho truc tiep (interchange sort)

void sort(LList * list)

{

// sƣ̉ du ̣ng thuâ ̣t toán sắp xếp nổi bo ̣t Bubble sort NodeType * p, * q;

int tmp;

Trang 29

p = list->head;

while(p!=NULL) {

q = p->next;

while(q!=NULL) {

if(q->data < p->data) {

void clear(LList * list)

{

NodeType * p, * q;

if(list->spt>0) {

p = list->head;

list->head = list->tail = NULL;

list->spt = 0;

while(p) {

q = p->next;

free(p);

p = q;

} }

push_back(list1, tmp->data);

tmp = tmp->next;

}

Trang 30

So sánh giữa danh sách liên kết và mảng

Danh sách liên kết có số phần tử có thể thay đổi và không cần chỉ rõ kích thước tối đa của danh trước Ngươ ̣c la ̣i mảng là các cấu trúc có kích thước cố đi ̣nh

Chúng ta có thể sắp xếp lại, thêm và xóa các phần tử khỏi danh sách liên kết chỉ với

mô ̣t số cố đi ̣nh các thao tác Với mảng các thao tác này thường tương đương với kích thước mảng

Để tìm đến phần tử thứ i trong mô ̣t danh sách liên kết chúng ta cần phải dò qua i-1 phần tử đứng trước nó trong danh sách (i-1 thao tác) trong khi với mảng để làm điều này chỉ mất 1 thao tác

Tương tự kích thước của mô ̣t danh sách không phải hiển nhiên mà biết được trong khi chúng ta luôn biết rõ kích thước của một mảng trong chương trình (tuy nhiên có mô ̣t cách đơn giản để khắc phục điều này)

2.3.4 Các kiểu danh sách liên kết khác

Danh sách liên kết đôi (double linked list) giống như một danh sách liên kết đơn ngoại trừ việc mỗi nút có thêm một trường previous trỏ vào nút đứng trước nó

Với danh sách liên kết đôi các thao tác như tìm kiếm, xóa bỏ một nút khỏi danh sách thực hiện dễ dàng hơn nhưng đồng thời cũng mất nhiều bộ nhớ hơn và số lượng các lệnh để thực hiện một thao tác trên danh sách liên kết đôi chắc chắn cũng xấp xỉ gấp đôi so với danh sách liên kết đôi

Ngoài ra còn có một loại danh sách liên kết khác được gọi là danh sách liên kết vòng (circular-linked list)

Nút cuối cùng trỏ tới nút đầu tiên

Danh sách liên kết vòng có thể là danh sách liên kết đơn hoặc danh sách liên kết đôi

Nó có thể được cài đặt với một đầu (head) cố định hoặc thay đổi

Trang 31

Việc cài đặt danh sách liên kết có thể không cần thiết sử dụng tới các con trỏ Thay vào đó chúng ta có thể sử dụng các mảng để cài đặt các danh sách liên kết, ở đây chúng ta không đi sâu vào xem xét cụ thể cài đặt một danh sách liên kết bằng mảng như thế nào nhưng cũng không quá khó để hình dung cách thức hoạt động của các danh sách kiểu như thế

Kết luận:

Danh sách liên kết là các cấu trúc dữ liệu rất giống với các mảng

Các thao tác chính thường được sử dụng đối với một danh sách liên kết là thêm, xóa

và tìm kiếm trên danh sách

Thao tác chèn và xóa có thể thực hiện với thời gian hằng số

Việc tìm một phần tử trong danh sách liên kết thường mất thời gian tuyến tính (xấp xỉ

độ dài danh sách) và trường hợp xấu nhất là đúng bằng độ dài của danh sách Đây cũng chính

là một trong những nhược điểm lớn nhất của danh sách liên kết

2.3.5 Mô ̣t số ví du ̣ sử du ̣ng cấu trúc danh sách liên kết

Các bài toán mà danh sách liên kết thường được sử dụng là các bài toán trong đó viê ̣c sử du ̣ng mảng sẽ là không thuâ ̣n lợi, chẳng ha ̣n mô ̣t bài toán yêu cầu các thao tác thêm, xóa bỏ xảy ra thường xuyên thì lựa chọn thông minh sẽ là sử dụng danh sách liên kết Mô ̣t ví du ̣ nữa là khi ta làm viê ̣c với các đồ thi ̣ thưa (các cạnh ít) lớn (nhưng số đỉnh nhiều), thay vì dùng

mô ̣t mảng hai chiều, ta sẽ dùng mô ̣t mảng các danh sách liên kết, mỗi danh sách liên kết chứa các đỉnh liền kề của một đỉnh của đồ thị

2.3.6 Cài đặt stack va ̀ queue bằng con trỏ

Về bản chất, các cấu trúc dữ liệu stack và queue là các cấu trúc danh sách liên kết hạn chế, các thao tác được giới hạn so với cấu trúc danh sách liên kết Vì thế có thể coi một stack hay queue là mô ̣t danh sách liên kết, và có thể lợi dụng cài đặt bằng con trỏ của danh sách liên kết để cài đă ̣t các cấu trúc stack và queue (sử du ̣ng con trỏ ) Phần này được để la ̣i xem như

mô ̣t bài tâ ̣p của sinh viên

Trang 32

danh sách theo thứ tự ngược với thứ tự nhập vào

c Viết chương trình con in ra màn hình các phần tử trong danh sách theo thứ tự của

nó trong danh sách

2 Tương tự như bài tập 1 nhưng cài đặt bằng con trỏ

3 Viết chương trình con sắp xếp một danh sách chứa các số nguyên, trong các trường hợp:

a Danh sách được cài đặt bằng mảng (danh sách đặc)

b Danh sách được cài đặt bằng con trỏ (danh sách liên kết)

4 Viết chương trình con thêm một phần tử trong danh sách đã có thứ tự sao cho ta vẫn có một danh sách có thứ tự bằng cách vận dụng các phép toán co bản trên danh sách

5 Viết chương trình con tìm kiếm và xóa một phần tử trong danh sách có thứ tự

6 Viết chương trình con nhận vào từ bàn phím một đãy số nguyên, lưu trữ nó trong một danh sách có thứ tự không giảm, theo cách sau: với mỗi phần tử được nhập vào chương trình con phải tìm vị trí thích hợp để xen nó vào danh sách cho dúng thứ tự Viết chương trình con trên cho trường hợp danh sách được cài đặt bằng mảng và cài đặt bằng con trỏ và trong trường hợp tổng quát (dùng các phép toán cơ bản trên danh sách)

7 Viết chương trình con loại bỏ các phần tử trùng nhau (giữ lại duy nhất 1 phần tử) trong một danh sách có thứ tự không giảm, trong hai trường hợp: cài đặt bằng mảng và cài đặt bằng con trỏ

8 Viết chương trình con nhận vào từ bàn phím một đãy số nguyên, lưu trữ nó trong một danh sách có thứ tự tang không có hai phần tử trùng nhau, theo cách sau: với mỗi phần tử được nhập vào chương trình con phải tìm kiếm xem nó có trong danh sách chưa, nếu chưa có thì xen nó vào danh sách cho đúng thứ tự Viết chương trình con trên cho trường hợp danh sách được cài đặt bằng mảng và cài đặt bằng con trỏ

9 Viết chương trình con trộn hai danh sách liên kết chứa các số nguyên theo thứ tự tăng để được một danh sách cũng có thứ tự tăng

10 Viết chương trình con xoá khỏi danh sách lưu trữ các số nguyên các phần tử là số nguyên lẻ, cũng trong hai trường hợp: cài đặt bằng mảng và bằng con trỏ

11 Viết chương trình con tách một danh sách chứa các số nguyên thành hai danh sách: một danh sách gồm các số chẵn còn cái kia chứa các số lẻ

Trang 33

CHƯƠNG 3 CÂY (TREE)

3.1 Đi ̣nh nghi ̃a

3.1.1 Đồ thị (Graph)

Trước khi xem xét khái niê ̣m thế nào là mô ̣t cây (tree) chúng ta nhắc lại khái niệm đồ thị (graph) đã đươ ̣c ho ̣c trong ho ̣c phần Toán rời ra ̣c : Đồ thị G bao gồm hai thành phần chính : tâ ̣p các đỉnh V (Vertices) và tập các cung E (hay ca ̣nh Edges ), thường viết ở da ̣ng G = <V, E> Trong đó tâ ̣p các đỉnh V là tâ ̣p các đối tượng cùng loa ̣i , đô ̣c lâ ̣p, chẳng ha ̣n như các điểm trên

mă ̣t phẳng to ̣a đô ̣, hoă ̣c tâ ̣p các thành phố , tâ ̣p các tra ̣ng thái của mô ̣t trò chơi , mô ̣t đối tượng thực như con người, … tất cả đều có thể là các đỉnh của mô ̣t đồ thi ̣ nào đó Tâ ̣p các cung E là

tâ ̣p các mối quan hê ̣ hai ngôi giữa các đỉnh của đồ thi ̣ , đối với đỉnh là các điểm thì đây có thể

là quan hệ về khoảng cách, tâ ̣p đỉnh là các thành phố thì đây có thể là quan hê ̣ về đường đi (có tồn ta ̣i đường đi trực tiếp nào giữa các thành phố hay không ), hoă ̣c nếu đỉnh là các tra ̣ng thái của một trò chơi thì cạnh có thể là cách biến đổi (transform) để đi từ trạng thái này sang một trạng thái khác , quá trình chơi chính là biến đổi từ trạng thái ban đầu tới trạng thái đích (có nghĩa là đi tìm một đường đi)

Ví dụ về đồ thị:

Hình 5.1 Đồ thị có 6 đỉnh và 7 cạnh, tham khảo từ wikipedia

Có rất nhiều vấn đề liên quan tới đồ thị , ở phần này chúng ta chì nhắc lại một số khái niê ̣m liên quan

Mô ̣t đồ thi ̣ được go ̣i là đơn đồ thi ̣ (simple graph) nếu như không có đường đi giữa hai đỉnh bất kỳ của đồ thi ̣ bi ̣ lă ̣p la ̣i , ngược la ̣i nếu như có đường đi nào đó bi ̣ lă ̣p la ̣i hoă ̣c tồn ta ̣i khuyên (self-loop), một da ̣ng cung đi từ 1 đỉnh đến chính đỉnh đó, thì đồ thị được gọi là đa đồ thị (multigraph)

Giữa hai đỉnh u , v trong đồ thi ̣ có đường đi trực tiếp thì u , v được go ̣i là liền kề với nhau, cạnh (u, v) được go ̣i là liên thuô ̣c với hai đỉnh u, v

Đồ thì được gọi là đồ thị có hướng (directed graph) nếu như các đường đi giữa hai đỉnh bất kỳ trong đồ thi ̣ phân biê ̣t hướng với nhau, khi đó các quan hê ̣ giữa các đỉnh được go ̣i chính xác là các cung , ngươ ̣c la ̣i đồ nếu không phân biê ̣t hướng giữa các đỉnh trong các ca ̣nh nối giữa hai đỉnh thì đồ thị được gọi là đồ thị vô hướng (undirected graph), khi đó ta nói tâ ̣p E là

tâ ̣p các ca ̣nh của đồ thi ̣

Các cung hay các cạnh của đồ thj có thể được gán các giá trị gọi là các trọng số

(weight), một đồ thi ̣ có thể là đồ thị có trọng số hoặc không có trọng số Ví dụ như đối với đồ thị mà các đỉnh là các thành phố ta có thể gán trọng số của các cung là độ dài đường đi nối giữa các thành phố hoă ̣c chi phí đi trên con đường đó …

Mô ̣t đường đi (path) trong đồ thi ̣ là mô ̣t dãy các đỉnh v1, v2, …, vk, trong đó các đỉnh vi,

vi+1 là liền kề với nhau Đường đi có đỉnh đầu trùng với đỉnh cuối được gọi là chu trình (cycle)

Giữa hai đỉnh của đồ thi ̣ có thể có các đường đi trực tiếp nếu chúng liền kề với nhau , hoă ̣c nếu có mô ̣t đường đi giữa chúng (gián tiếp ) thì hai đỉnh đó được gọi là liên thông (connected) vớ i nhau Mô ̣t đồ thi ̣ đươ ̣c go ̣i là liên thông nếu như hai đỉnh bất kỳ của nó đều

Trang 34

liên thông với nhau Nếu đồ thi ̣ không liên thông thì luôn có thể chia nó thành các thành phần liên thông nhỏ hơn

Hình 5.2 Cây, tham khảo từ wikipedia

Cấu trúc cây là mô ̣t cấu trúc đư ợc sử dụng rất rộng rãi trong cuộc sống hàng ngày và trên máy tính , chẳng ha ̣n cấu trúc tổ chức của mô ̣t công ty là mô ̣t cây phân cấp , cấu trúc của

mô ̣t web site cũng tương tự:

Hình 5.3 Cấu trúc web site wikipedia, tham khảo từ wikipedia

Cấu trúc tổ chức thư mu ̣c của hê ̣ điều hành là mô ̣t cây …

Trong cây luôn có mô ̣t nút đă ̣c biê ̣t go ̣i là gốc của cây (root), các đỉnh trong cây được gọi là các nút (nodes) Từ gốc của cây đi xuống tất cả các đỉnh liền kề với nó , các đỉnh này gọi là con của gốc , đến lượt các con của gốc lại có các nút con (child nodes) khác, như vâ ̣y quan hê ̣ giữa hai nút liền kề nhau trong cây là quan hê ̣ cha con , mô ̣t nút là cha (parent), mô ̣t nút là con (child), nút cha của cha của một nút được gọi là tổ tiên (ancestor) của nút đó

Các nút trong cây được phân biệt làm nhiều loại : các nút có ít nhất 1 nút con được gọi là các nút trong (internal nodes hay inner nodes), các nút không có nút con được go ̣i là các nút lá (leaf nodes ) Các nút lá không có các nút con nhưng để thuận tiện trong quá trình cài đặt người ta vẫn coi các nút lá có hai nút con giả , rỗng (NULL) đóng vai trò lính canh , gọi là các nút ngoài (external nodes)

Các nút trong cây được phân chia thành các tầng (level), nút gốc thuộc tầng 0 (level 0), sau đó các tầng tiếp theo sẽ được tăng lên 1 đơn vi ̣ so với tầng phía trên nó cho đến tầng cuối cùng Độ cao (height) của cây được tính bằng số tầng của cây , đô ̣ cao của cây sẽ quyết đi ̣nh

đô ̣ phức ta ̣p (số thao tác) khi thực hiê ̣n các thao tác trên cây

Mỗi nút trong của cây tổng quát có thể có nhiều nút con , tuy nhiên các ngh iên cứu của ngành khoa học máy tính đã cho thấy cấu trúc cây quan trọng nhất cần nghiên cứu chính là các cây nhị phân (binary tree ), là các cây là mỗi nút chỉ có nhiều nhất hai nút con Mô ̣t cây tổng quát luôn có thể phân chia thành các cây nhi ̣ phân

Trang 35

Các nút con của một nút trong cây nhị phân được gọi là nút con trái (left child) và nút con phải (right child)

Trong chương này chúng ta sẽ nghiên cứu mô ̣t số loa ̣i cây nhi ̣ phân cơ bản và được ứng

dụng rô ̣ng rãi nhất , đó là cây tìm kiếm nhi ̣ phân BST (Binary Search Tree), cây biểu thức (expression tree hay syntax tree) và cây cân bằng (balanced tree) AVL

Hoặc một cách định nghĩa khác (đọc thêm)

Cây là một tập hợp các phần tử gọi là nút (nodes) trong đó có một nút được phân biệt

gọi là nút gốc (root) Trên tập hợp các nút này có một quan hệ, gọi là mối quan hệ cha - con

(parenthood), để xác dịnh hệ thống cấu trúc trên các nút Mỗi nút, trừ nút gốc, có duy nhất một nút cha Một nút có thể có nhiều nút con hoặc không có nút con nào Mỗi nút biểu diễn một phần tử trong tập hợp dang xét và nó có thể có một kiểu nào đó bất kỳ, thường ta biểu

diễn nút bằng một kí tự, một chưỗi hoặc một số ghi trong vòng tròn Mối quan hệ cha con được biểu diễn theo qui ước nút cha ở dòng trên nút con ở dòng dưới và được nối bởi một

doạn thẳng Một cách hình thức ta có thể dịnh nghĩa cây một cách đệ qui như sau:

Ðịnh nghĩa

- Một nút đơn dộc là một cây Nút này cũng chính là nút gốc của cây

- Giả sử ta có n là một nút đơn độc và k cây T1, , Tk với các nút gốc tương ứng là n1, ,

nk thì có thể xây dựng một cây mới bằng cách cho nút n là cha của các nút n1, , nk Cây mới này có nút gốc là nút n và các cây T1, , Tk được gọi là các cây con Tập rỗng cũng được coi

là một cây và gọi là cây rỗng kí hiệu

Ví dụ: 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 Xét

cấu trúc thư mục trong tin học, cấu trúc này cũng được xem như một cây

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

Nếu n1

, , nk là một chưỗi các nút trên cây sao cho ni là nút cha của nút ni+1, với

i=1 k-1, thì chưỗi này gọi là một dường di trên cây (hay ngắn gọn là dường di ) từ n1

đến nk Ðộ dài

dường di được dịnh nghĩa bằng số nút trên dường di trừ 1 Như vậy dộ dài dường di từ một

nút đến chính nó bằng không

Nếu có dường di từ nút a đến nút b thì ta nói a là tiền bối (ancestor) của b, còn b gọi là

hậu duệ (descendant) của nút a Rõ ràng một nút vừa là tiền bối vừa là hậu duệ của chính nó

Tiền bối hoặc hậu duệ của một nút khác với chính nó gọi là tiền bối hoặc hậu duệ thực sự Trên cây nút gốc không có tiền bối thực sự Một nút không có hậu duệ thực sự gọi là nút lá (leaf) Nút không phải là lá ta còn gọi là nút trung gian (interior) Cây con của một cây là một

nút cùng với tất cả các hậu duệ của nó

Chiều cao của một nút là dộ dài dường di lớn nhất từ nút đó tới lá Chiều cao của cây

là chiều cao của nút gốc Ðộ sâu của một nút là dộ dài dường di từ nút gốc đến nút đó Các

nút có cùng một dộ sâu i ta gọi là các nút có cùng một mức i Theo dịnh nghĩa này thì nút gốc

Trang 36

C3 có chiều cao 0 Nút 2.1 có dộ sâu 2 Các nút C1,C2,C3 cùng mức 1

Thứ tự các nút trong cây

Nếu ta phân biệt thứ tự các nút con của cùng một nút thì cây gọi là cây có thứ tự, thứ

tự qui ước từ trái sang phải Như vậy, nếu kể thứ tự thì hai cây sau là hai cây khác nhau:

Trong trường hợp ta không phân biệt rõ ràng thứ tự các nút thì ta gọi là cây không có thứ tự Các nút con cùng một nút cha gọi là các nút anh em ruột (siblings) Quan hệ "trái sang phải" của các anh em ruột có thể mở rộng cho hai nút bất kỳ theo qui tắc: nếu a, b là hai anh

em ruột và a bên trái b thì các hậu duệ của a là "bên trái" mọi hậu duệ của b

3.3 Cây ti ̀m kiếm nhi ̣ phân (Binary Search Tree - BST)

3.3.1 Đi ̣nh nghi ̃a

Mỗi nút trong cây bất kỳ đều chứa các trường thông tin , trên mô ̣t cây tìm kiếm nhị phân mỗi nút là mô ̣t struct (bản ghi – record) gồm các trường: trường dữ liê ̣u data, trường khóa key

để so sánh với các nút khác, các liên kết tới các nút con của nút left và right

Để tâ ̣p trung vào các vấn đề thuâ ̣t toá n ta bỏ qua trường dữ liê ̣u , chỉ xem như mỗi nút

trên cây tìm kiếm nhi ̣ phân gồm có mô ̣t trường khóa key và hai trường liên kết left và right

Với các giả thiết trên ta đi ̣nh nghĩa cây tìm kiếm nhi ̣ phân như sau:

Cây tìm kiếm nhi ̣ phân là mô ̣t cây nhi ̣ phân (binary tree) mà mỗi nút x trong cây thỏa mãn bất đẳng thức kép sau:

key left child xkey xkey right child x

Trong đó left_child(x), right_child(x) là các nút con trái và phải của nút x , key() là hàm trả về giá trị khóa ở nút tương ứng

Ví dụ:

Hình 5.4 Cây tìm kiếm nhi ̣ phân BST, tham khảo từ wikipedia

Nhận xét:

- Trên cây BST không có hai nút cùng khoá

- Cây con của một cây BST là cây BST

Ưu điểm chính của cây tìm kiếm nhi ̣ phân là : nó cung cấp thuâ ̣t toán sắp xếp và tìm kiếm dựa trên kiểu duyê ̣t thứ tự giữa (in-order) mô ̣t cách rất hiê ̣u quả, và là cấu trúc dữ liệu cơ bản cho các cấu trúc dữ liệu cao cấp hơn (trừu tượng hơn) như tâ ̣p hợp (set), các mảng liên kết

A

A

Trang 37

(associative array), các ánh xạ map, và các cây cân bằng tối ưu như AVL , cây đỏ đen Chúng

ta sẽ xem xét ta ̣i sao cây tìm kiếm nhi ̣ phân la ̣i hiê ̣u quả như vâ ̣y

3.3.2 Khơ ̉ i ta ̣o cây rỗng

Thao tác đầu tiên là khai báo cấu trúc cây và khởi ta ̣o mô ̣t cây rỗng để bắt đầu thực hiê ̣n các thao tác khác

Ở đây ta giả sử cây tìm kiếm nhị phân chỉ chứa các khóa là các số nguyên dương

Khai báo cây tìm kiếm nhi ̣ phân trong ngôn ngữ C như sau:

// khai bao cau truc cay tim kiem nhi phan

typedef struct tree

3.3.3 Chèn thêm một nút mới vào cây

Để chèn mô ̣t nút mới vào cây ta xuất phát từ gốc của cây , ta go ̣i đó là nút đang xét Nếu như nút đang xét có khóa bằng với khóa cần chèn vào cây thì xảy ra hiê ̣n tượng trùng khóa , thuâ ̣t toán kết thúc với thông báo trùng khóa Nếu như nút đang xét là mô ̣t nút ngoài (external nodes) thì ta tạo một nút mới và gán các trường thông tin tương ứng cho nút đó , gán các con của nút đó bằng NULL

// them mot nut moi vao cay, gia tri khoa cua nut moi luu trong bien toan cuc newkey void insert(BSTree **root)

Thuâ ̣t toán trên sử du ̣ng bô ̣ nhớ (log n ) trong trườ ng hơ ̣p trung bình và (n) trong

trường hợp tồi nhất Độ phức tạp thuật toán bằng với độ cao của cây , tức là O (log n) trong trường hợp trung bình đối với hầu hết các cây, nhưng sẽ là (n) trong trườ ng hơ ̣p xấu nhất Cũng nên chú ý là các nút mới luôn được chèn vào các nút ngoài của cây tìm kiếm nhị phân, gốc củ a cây không thay đổi trong quá trình chèn thêm nút vào cây

Trang 38

3.3.4 Xóa bỏ khỏi cây một nút

Khi xóa bỏ mô ̣t nút X khỏi cây (dựa trên giá trị khóa), chúng ta chia ra một số trường

Hình minh họa:

Hình 5.5 Xóa nút trên cây BST, tham khảo từ wikipedia

Do các nút thực sự bi ̣ xóa trong trường hợp thứ ba sẽ có thể rơi vào trường hợp 1 hoă ̣c 2 (là các nút lá hoặc các nút chỉ có 1 con), đồng thờ i nút bi ̣ xóa sẽ có khóa nhỏ hơn hai con của

X nên trong cài đă ̣t ta nên tránh chỉ sử du ̣ng mô ̣t phương pháp , vì có thể dẫn tới tình huống mất tính cân bằng của cây

Viê ̣c cài đă ̣t thuâ ̣t toán xóa mô ̣t nút trên cây tìm kiếm nhi ̣ phân không đơn giản như viê ̣c

mô tả thuâ ̣t toán xóa ở trên Trước hết ta sẽ xuất phát từ gốc của cây để đi tìm nút chứa khóa cần xóa trên cây Trong quá trình này điều quan tro ̣ng là ta xác đi ̣nh rõ nút cần xóa (biến p trong đoa ̣n mã chương trình bên dưới ) là một nút lá, hay là mô ̣t nút chỉ có một con, hay là nút có đầy đủ cả hai con Dù trong trường hợp nào thì chúng ta cũng cần xác định nút cha của nút

p (nút q), và p là con trái hay con phải của q Để xác đi ̣nh các trường hơ ̣p trên ta sử du ̣ng mô ̣ t biến cờ f, f bằng 0 tương ứng với viê ̣c nút cần xóa là gốc của cây , f bằng 1 tương ứng với p là con phải của q, và f bằng 2 tương ứng với p là con trái của q

Cài đặt bằng C của thao tác xóa một nút khỏi cây BST:

// xoa bo mot khoa khoi cay

void del(BSTree ** root, int key)

Trang 39

3.3.5 Tìm kiếm trên cây

Viê ̣c tìm kiếm trên cây nhi ̣ phân tìm kiếm giống nhƣ khi ta thêm mô ̣t nút mới vào cây Dƣ̣a trên khóa tìm kiếm key ta xuất phát tƣ̀ gốc , gọi nút đang xét là X Nếu khóa của X bằng

Trang 40

với key, thì kết thúc và trả về X Nếu X là mô ̣t nút lá thì kết quả trả về NULL (cũng chính là X) Nếu khóa của X nhỏ hơn key thì ta lă ̣p la ̣i thao tác tìm kiếm với nút con phải của X , ngươ ̣c la ̣i thì tiến hành tìm kiếm với nút con trái của X

Độ phức tạp của thuật toán nà y bằng với đô ̣ phức ta ̣p của thuâ ̣t toán chèn mô ̣t nút mới vào cây

Cài đặt của thuật toán được để lại như một bài tập dành cho các bạn độc giả

3.3.6 Duyê ̣t cây

Duyê ̣t cây (tree travel) là thao tác duyệt qua (đến thăm) tất cả các nút trên cây

Có nhiều cách để duyệt một cây , chẳng ha ̣n như duyê ̣t theo chiều sâu (DFS), duyê ̣t theo chiều rô ̣ng (BFS), nhưng ở đây ta phân chia các cách duyê ̣t mô ̣t cây BST dựa trên thứ tự đến thăm nút gốc, nút con trái, và nút con phải của gốc

Cụ thể có ba cách duyệt một cây BST: duyê ̣t thứ tự trước, thứ tự giữa, thứ tự sau

Để minh ho ̣a kết quả của các cách duyê ̣t cây ta xét cây ví du ̣ sau :

Hình 5.6 Cây tìm kiếm nhi ̣ phân, tham khảo từ wikipedia

Duyê ̣t thứ tự trước (pre-order traversal):

 Thăm gốc (visit root)

Duyê ̣t cây con trái theo thứ tự trước

Duyê ̣t cây con phải theo thứ tự trước

Cụ thể thuật toán được cài đặt như sau:

// duyet theo thu tu truoc

void pre_order(BSTree *node)

Kết quả duyê ̣t cây theo thứ tự trước: 8, 3, 1, 6, 4, 7, 10, 14, 13

Trong cách duyê ̣t theo thứ tự trước, gốc của cây luôn được thăm đầu tiên

Duyê ̣t thứ tự giữa (in-order traversal):

Duyê ̣t cây con trái theo thứ tự giữa

 Thăm gốc

Ngày đăng: 09/04/2015, 21:02

HÌNH ẢNH LIÊN QUAN

Hình  III.1  -  Cây mục lục một quyển sách - CẤU TRÚC DỮ LIỆU (ĐH HÀNG HẢI)
nh III.1 - Cây mục lục một quyển sách (Trang 35)
Hình  III.2:  Hai cây có thứ tự khác nhau - CẤU TRÚC DỮ LIỆU (ĐH HÀNG HẢI)
nh III.2: Hai cây có thứ tự khác nhau (Trang 36)
CHƯƠNG 4. BẢNG BĂM (HASH TABLE) - CẤU TRÚC DỮ LIỆU (ĐH HÀNG HẢI)
4. BẢNG BĂM (HASH TABLE) (Trang 59)
Bảng băm hình chữ nhật đƣợc mô tả bởi một danh sách kề : - CẤU TRÚC DỮ LIỆU (ĐH HÀNG HẢI)
Bảng b ăm hình chữ nhật đƣợc mô tả bởi một danh sách kề : (Trang 61)
Hình  2.3  :  Bảng  băm  theo - CẤU TRÚC DỮ LIỆU (ĐH HÀNG HẢI)
nh 2.3 : Bảng băm theo (Trang 73)

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w