Dhhh ctdlgt bai giang cau truc du lieu

2,620 views

Published on

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,620
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
135
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Dhhh ctdlgt bai giang cau truc du lieu

  1. 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 BÀI GIẢNG CẤU TRÚC DỮ LIỆUTÊN HỌC PHẦN : CẤU TRÚC DỮ LIỆUMÃ HỌC PHẦN : 17207TRÌNH ĐỘ ĐÀO TẠO : ĐẠI HỌC CHÍNH QUYDÙNG CHO SV NGÀNH : CÔNG NGHỆ THÔNG TIN HẢI PHÒNG - 2008
  2. 2. MỤC LỤCCHƢƠ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................................................... 3CHƢƠ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 Đinh nghia ............................................................................................................... 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 ............................................................................................................. 26CHƢƠNG 3. CÂY (TREE). ..................................................................................................... 28 3.1. Đinh 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. Đinh nghia .............................................................................................................. 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 ..................................................................................................... 39CHƢƠ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 i
  3. 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 ............................................................................................................. 72TÀI LIỆU THAM KHẢO. ....................................................................................................... 75 ii
  4. 4. Tên học phần: Cấu trúc dữ liệu Loại học phần: 2Bộ môn phụ trách giảng dạy: Khoa học Máy tính Khoa phụ trách: CNTTMã học phần: 17207 Tổng số TC: 3TS tiết Lý thuyết Thực hành/Xemina Tự học Bài tập lớn Đồ án môn học 60 30 30 0 0 0Đ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; - Cấu trúc cây; - Bảng băm, ...Nội dung chi tiết của học phần: PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KTChƣơng I : Khái niệm liên quan đến CTDL 2 2 01.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ừutƣợng.Chƣơng II : Các kiểu dữ liệu trừu tƣợng cơ bản 12 6 62.1. Danh sách2.1.1. Khái niệm danh sách2.1.2. Các phép toán trên danh sách2.1.3. Cài đặt danh sách2.1.4. Các dạng danh sách liên kết (DSLK): DSLKđơn, vòng, kép, …2.2. Ngăn xếp (stack)2.2.1. Khái niệm2.2.2. Cài đặt ngăn xếp bởi mảng, DSLK2.2.3. Ứng dụng2.3. Hàng đợi (queue)2.3.1. Khái niệm2.3.2. Cài đặt hàng đợi bởi mảng, DSLK2.3.3. Ứng dụng2.4. Bài tập áp dụngChƣơng III: Cây (tree). 18 9 8 13.1. Khái niệm.3.2. Cây tổng quát. iii
  5. 5. PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT3.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ất3.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ây3.4.2. Duyệt cây để tìm kiếm3.4.3. Sắp xếp cây nhị phân3.5. Cây tìm kiếm nhị phân (binary search tree)3.5.1. Khái niệm, cài đặt.3.5.2. Cây AVL3.6. Bài tậpChƣơng IV: Bảng băm (hash table) 14 7 6 14.1. Khái niệm4.2. Các loại hàm băm4.3. Các phƣơng pháp giải quyết xung đột4.4. Đánh giá hiệu quả các phƣơng pháp băm4.5. Bài tập áp dụngNhiệ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) iv
  6. 6. CHƢƠNG 1. CÁC KHÁI NIỆM MỞ ĐẦU1.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àitoá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ờirõ 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ầuhế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ựctế, 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ìnhthứ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ìnhtoá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ánggiề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ạnhnố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 trongthuậ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ếukhô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ếtvề 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àitoá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ờigian 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ôngnghĩ 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ệunguyê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ộtxâ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 đó lienkế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âydự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ínhlà 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ệncá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. 1
  7. 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ìnhbà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ộtcấ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átrồ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ợptrong 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ápcủ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 đặtkiể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 đếnkhi 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ữ Cxen 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ẻ xalạ, 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íchhợ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ầnnhữ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ữanhƣ sau: void GREEDY ( GRAPH *G, SET *Newclr ) { Newclr= ; /*1*/ for (mỗi đỉnh v chƣa tô màu của G) /*2*/ { int found=0; /*3.1*/ 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*/ } 2
  8. 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ểudiễ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 ) { int found; int v,w ; Newclr= ; v= đỉnh đầu tiên chƣa đƣợc tô màu trong G; while (v<>null) { found=0; w=đỉnh đầu tiên trong newclr; while( w<>null) && (found=0) { if ( có cạnh nối giữa v và w ) found=1; else w= đỉnh kế tiếp trong newclr; } if (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ểuhơ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óacó 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éptoá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 ramột chƣơng trình con Matrix_Mult để thực hiện phép toán nhân hai ma trận. Sau khiMatrix_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ạnphé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ìnhcon. 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); 3
  9. 9. Xu_ly (Lop); Xuat (Lop); } Trong chƣơng trình trên, Nhap, Xu_ly, Xuat là các phép toán trừu tƣợng. Chúng che dấubên trong rất nhiều lệnh phức tạp mà ở cấp độ chƣơng trình chính ta không nhìn thấy đƣợc.Còn Lop là một biến thuộc kiểu dữ liệu trừu tƣợng mà ta sẽ xét sau. 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áchcà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éptoá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ùngvớ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ấtmộ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ùngmộ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. - h. Nếu chúng ta viết các chƣơng trình con thực hiện các phép toán này, thì ta dễ dàng thaycác mệnh đề hình thức trong giải thuật bằng các câu lệnh đơn giản Câu lệnh Mệnh đề hình thức MAKENULL(newclr) newclr=; w=FIRST(newclr) w=phần tử đầu tiên trong newclr w=NEXT(w,newclr) w=phần tử kế tiếp trong newclr INSERT( v,newclr) Thêm v vào newclr Ð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ênthuỷ. 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ùngchú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ủaADT 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ànhcá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ệncá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ônngữ 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 4
  10. 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 32767cù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ểuBoolean, 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ệucủ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éptoá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ảithự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ệutrừ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à tathƣờ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 5
  11. 11. CHƢƠNG 2. CÁC KIỂU DỮ LIỆU TRỪU TƢỢNG CƠ BẢN2. 1. Ngăn xế p - Stack2.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áchtuyế n tinh 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ỏitâ ̣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 đia … 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 đƣơ ̣cthê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ô ̣tphầ 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ỏistack 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ệucũng phân chia nhƣ vậy), ta xác đinh 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 ̣nlà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 tathƣờ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 tinh 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ƣờnghơ ̣p thƣ̣c hiê ̣n thành công và 0 nế u không thành công. 6
  12. 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 ̣acá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ả Khởi ta ̣o () push(55) (55) 1 push(-7) (-7, 55) 1 push(16) (16, -7, 55) 1 pop (-7, 55) 16 push(-8) (-8, -7, 55) 1 push(23) (-8, -7, 55) 0 pop (-7, 55) -8 pop (55) -7 pop () 55 pop () 1012.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êntop_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ủamả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ả khởi ta ̣o -1 ? ? ? ? ? push(55) 0 55 ? ? ? ? 1 push(-7) 1 55 -7 ? ? ? 1 push(16) 2 55 -7 16 ? ? 1 pop 1 55 -7 16 ? ? 16 push(-8) 2 55 -7 -8 ? ? 1 pop 1 55 -7 -8 ? ? -8 pop 0 55 -7 -8 ? ? -7 Chú ý rằng trong minh họa này chúng ta thấy rằng một số giá trị vẫn còn trong mảngnhƣng chúng đƣơ ̣c xem nhƣ không có trong stack vì không có thao tác nào truy câ ̣p tới chúng .Nói chúng thì phần tử E[i] đƣơ ̣c xem là rác nế u nhƣ i>top_idx. Tại sao chúng ta không xóa bỏcác phần tử này (chẳ ng ha ̣n nhƣ đă ̣t chúng bằng các giá trị mặc định nào đó?). 7
  13. 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 typedef struct { int * data; // khai bao mang dong int top_idx; } stack; // ham khoi tao stack rong void init(stack *s); void push(stack * s, int d); void pop(stack *s); 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 void clear(stack *s); int main() { int n; int bit; stack s; init(&s); printf("Nhap so nguyen n = "); scanf("%d", &n); while(n) { push(&s, n%2); n /= 2; } while(!empty(&s)) { bit = top(&s); pop(&s); printf("%d", bit); } clear(&s); return 0; 8
  14. 14. } void init(stack *s) { s->data = (int*)malloc(MAX_ELEMENT * sizeof(int)); s->top_idx = -1; } void clear(stack *s) { if(s->data != NULL) free(s->data); s->top_idx = -1; } void push(stack *s, int d) { s->data[++s->top_idx] = d; } void pop(stack *s) { s->top_idx --; } int top(const stack *s) { return s->data[s->top_idx]; } int size(const stack *s) { return s->top_idx+1; } int empty(const stack * s) { return (s->top_idx==-1)?(1):(0); } int full(const stack * s) { return (s->top_idx==MAX_ELEMENT-1)?(1):(0); } Cấ u trúc stack có thể cài đă ̣t bằ ng mảng theo các cách khác , hoă ̣c cài đă ̣t bằ ng con trỏ(chúng ta sẽ học về phầ n cài đă ̣t này sau phầ n danh sách liên kế t ). Stack có thể đƣơ ̣c cài đă ̣t bằ ng cả mảng và danh sách liên kế t vâ ̣y khi nào chúng ta sƣ̉dụng mảng và khi nào dùng danh sách liên kết? Danh sách liên kế t Mảng Cƣ́ mỗi phầ n tƣ̉ của stack cầ n Xin cấ p phát mô ̣t vùng nhớ có kich́ có thêm 2 byte (1 con trỏ). thƣớc cố đinh và có thể mô ̣t phầ n ̣ 9
  15. 15. Nế u kích thƣớc của mô ̣t phầ n trong số đó không bao giờ đƣơ ̣c dùng tƣ̉ lớn thì không đáng kể đến và nếu nhƣ kích thƣớc của một nhƣng nế u là kiể u int thì kích phầ n tƣ̉ lớn thì vùng nhớ lang phí này ̃ thƣớc sẽ tăng gấ p đôi. cũng rất lớn. Không có giới ha ̣n về số phầ n Kích thƣớc tối đa của stack đƣợc xác tƣ̉ của stack. đinh ngay khi nó đƣơ ̣c ta ̣o ra. ̣2.1.5 Ứng dụng của stack Ví dụ 1 Stack có thể dùng để kiể m tra các că ̣p ký hiê ̣u cân bằ ng trong mô ̣t chƣơng trình (chẳ nghạn {}, (), []). Ví dụ {()} và {()({})} là các biểu thức đúng còn {((} và {(}) không phải là các biể uthƣ́c đúng. Chúng ta thấy rằng nếu nhƣ một biểu thức là đúng thì trong quá trình đọc các ký hiệucủa biểu thức đó nếu chúng ta thấy một ký hiệu đóng (chẳ ng ha ̣n ), } hay ]) thì ký hiệu nàyphải khớp với ký hiệu mở đƣợc đọc thấy gần nhất (theo thời gian), và khi đó việc sử dụngstack cho các bài toán nhƣ thế này là hoàn toàn hơ ̣p lý . 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ụ: 1. Input: {()} s = {, push{, s = (, push (, s = ), r = pop = (, r,s match s = }, r = pop = {, r,s match End of Input, stack rỗng => biể u thƣ́c là đúng. Ví dụ: Input = { ( ) ( { ) } } (sai) Input = { ( { } ) { } ( ) } 10
  16. 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ôngthƣờng, hay còn go ̣i là infix notation) thành các biểu thức ở dạng tiền tố (prefix notation, haycòn gọi là biểu thức Balan – Polish notation) và biểu thức hậu tố (postfix notation, hay biể uthƣ́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ántƣ̉ (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ỉ rathao 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 đinh nghia 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 express ion1 và expression 2 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 đinh nghia 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ácdấ 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ể uthƣ́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 BalanJan Lukasiewicz đƣa ra vào nhƣ̃ng năm 1920. Trong da ̣ng biể u diễn này , toán tử đứng trƣớccá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ácdạ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ểuthƣ́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úngnhƣ chúng ta vẫn tƣởng. Để tinh giá tri ̣của mô ̣t biể u thƣ́c trung tố chúng ta cầ n tinh 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 quitắ c kế t hơ ̣p sẽ quyế t đinh 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: Toán tử Độ ƣu tiên () + (mô ̣t ngôi), - (mô ̣t ngôi), ! + (cô ̣ng), - (trƣ̀ ) <, <=, >, >= ==, != 11
  17. 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ể tinh ́đƣơ ̣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ắ ckế 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 tathƣờng sƣ̉ du ̣ng các da ̣ng biể u diễn tiề n tố và hâ ̣u tố trong viê ̣c tinh 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 trungtố , nhƣng khi tinh 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 tich đô ̣ ƣ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ánhọc kiểu nhƣ: 5 * (( (9+8) * (4 * 6) ) + 7) Trƣớc hế t chúng ta chuyể n chúng thành da ̣ng hâ ̣u tố (postfix): 589+46**7+* Sau đó sƣ̉ du ̣ng mô ̣t stack để thƣ̣c hiê ̣n viê ̣c tính toán giá tri ̣của biể u thƣ́c hâ ̣u tố nhâ ̣nđƣơ ̣c. Ngoài ra các stack còn có thể dùng để cài đặt các thuật toán đệ qui và khử đệ qui cáccài đặt thuật toán.2.2. Hàng đợi - Queue2.2.1 Khái niệm Hàng đợi là một tập hợp các phần tử cùng kiểu đƣợc tổ chức một cách tuần tự (tuyế ntính) trong đó phầ n tƣ̉ đƣơ ̣c thêm vào đầ u tiên sẽ là phầ n tƣ̉ đƣơ ̣c loa ̣i bỏ đầ u tiên khỏi hàngđơ ̣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áchcác công viê ̣c cầ n làm của mô ̣t ngƣời … 12
  18. 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 nhauvớ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ântheo 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 đơị . 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). Ví dụ: Thao tác Nô ̣i dung Giá trị trả về Khởi ta ̣o () push(7) (7) push(8) ( 7, 8 ) push(5) ( 7, 8, 5 ) pop() ( 8, 5 ) 7 pop() (5) 82.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ầ nnà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ầ nloạ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à kich 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ƣngnế 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? 13
  19. 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 typedef struct { int * data; // khai bao mang dong int head; int tail; int cap; // luu so phan tu cua hang doi } queue; // ham khoi tao queue rong void init(queue *q); void push(queue * s, int d); void pop(queue *q); 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 void clear(queue *q); int main() { int a[] = {3, 5, 1, 8}; int n = 4; int i; int d; queue q; init(&q); for(i=0;i<n;i++) push(&q, a[i]); while(!empty(&q)) { d = front(&q); printf("%d ", d); pop(&q); } clear(&q); return 0; 14
  20. 20. }void init(queue *q){ q->data = (int*)malloc(MAX_ELEMENT * sizeof(int)); q->head = q->tail = -1; q->cap = 0;}void clear(queue *q){ if(q->data != NULL) free(q->data); q->head = q->tail = -1; q->cap = 0;}void push(queue *q, int d){ q->tail = (q->tail + 1) % MAX_ELEMENT; q->data[q->tail] = d; if(q->cap==0) // neu hang doi rong thi sau khi push // ca head va tail deu chi vao 1 phan tu q->head = q->tail; q->cap++;}void pop(queue *q){ q->head = (q->head + 1)%MAX_ELEMENT; q->cap--; if(q->cap==0) q->head = q->tail = -1;}int front(const queue *q){ return q->data[q->head];}int back(const queue *q){ return q->data[q->tail];}int size(const queue *q){ return q->cap;} 15
  21. 21. int empty(const queue *q) { return (q->cap==0)?(1):(0); } int full(const queue *q) { return (q->cap==MAX_ELEMENT-1)?(1):(0); }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 Khởi ta ̣o -1 -1 ? ? ? ? push(55) 0 0 55 ? ? ? push(-7) 0 1 55 -7 ? ? push(16) 0 2 55 -7 16 ? pop() 1 2 55 -7 16 ? 55 push(-8) 1 3 55 -7 16 -8 pop() 2 3 55 -7 16 -8 -7 pop() 3 3 55 -7 16 -8 16 push(11) 3 4 11 -7 16 -82.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  Phòng cấp cứu  Các cuộc gọi điện thoại hoă ̣c các đă ̣t hàng vé máy bay , các đặt hàng của khách hàng … Các ứng dụng khác: Thƣ́ tƣ̣ topo: với mô ̣t tâ ̣p các sƣ̣ kiê ̣n, và các cặp (a, b) trong đó sƣ̣ kiê ̣n a có đô ̣ ƣu tiêncao hơn so với sƣ̣ kiê ̣n b (bài toán lập lịch), duyệt đồ thị theo chiều rộng (Breadth FirstSearch). 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ôngthƣờ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àyxem nhƣ mô ̣t bài tâ ̣p để sinh viên tƣ̣ làm. 16
  22. 22. 2.3. Danh sách liên kế t – Linked list2.3.1 Đinh nghia ̣ ̃ 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ấ tlà 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ìnhvẽ 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ủadanh 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. 17
  23. 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ôngviê ̣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 nhau2.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 đinh nghia 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ử tailcủ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 danhsá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áchliê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èncác phần tử vào giƣ̃a danh sách. Chèn X vào giữa I và S: struct node * A; A = new node; Akey = X; Anext = Inext; Inext = A; 18
  24. 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ôngcầ 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ácmả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 contrỏ, 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 đinh 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ánhvớ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 danhsá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 thamchiế 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; 19
  25. 25. } NodeType;// khai bao kieu danh sachtypedef struct{ NodeType * head; NodeType * tail; // so phan tu cua danh sach int spt;}LList;// ham khoi tao danh sachvoid init(LList * list);// ham them 1 phan tu vao dau danh sachvoid push_front(LList *list, int d);// ham them mot phan tu vao cuoi danh sachvoid push_back(LList *list, int d);// ham xoa phan tu o cuoi danh sachint pop_back(LList * list);// ham xoa phan tu o dau danh sachint pop_front(LList * list);// ham tra ve phan tu dau tienint begin(const LList * list);// ham tra ve phan tu cuoi cungint 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 sachvoid printList(LList list);// ham sap xep danh sachvoid sort(LList *list);// ham tim kiem trong danh sachNodeType * find(LList *, int d);// giai phong toan bo danh sachvoid clear(LList * list);// ham tron hai danh sach, ket qua luu trong danh sach thu nhatvoid merge(LList *list1, const LList *list2);// ham kiem tra danh sach lien ket co rong khongint empty(const LList *list);int main(){ LList myList; LList list2; 20
  26. 26. init(&myList); init(&list2); push_front(&myList, 10); push_front(&myList, 1); push_front(&myList, 12); push_back(&myList, 20); push_back(&myList, 23); push_back(&myList, 25); sort(&myList); printList(myList); push_front(&list2, 14); push_front(&list2, 9); merge(&myList, &list2); printList(myList); printList(list2); clear(&myList); printList(myList); return 0;}void init(LList * list){ list->head = list->tail = NULL; list->spt = 0;}void printList(LList list){ NodeType * tmp; tmp = list.head; while(tmp!=NULL) { printf("%d ", tmp->data); tmp = tmp->next; } printf("n");}void push_front(LList * list, int d){ NodeType * tmp = (NodeType *)malloc(sizeof(NodeType)); tmp->data = d; if(list->spt==0) { tmp->next = NULL; list->head = list->tail = tmp; 21
  27. 27. } else { tmp->next = list->head; list->head = tmp; } list->spt = list->spt+1;}void push_back(LList * list, int d){ NodeType * tmp = (NodeType *)malloc(sizeof(NodeType)); tmp->data = d; tmp->next = NULL; if(list->spt==0) list->head = list->tail = tmp; else { list->tail->next = tmp; list->tail = tmp; } list->spt = list->spt+1;}NodeType * find(LList * list, int d){ NodeType * tmp = list->head; while(tmp!=NULL) { if (tmp->data==d) break; tmp = tmp->next; } return tmp;}int pop_back(LList * list){ NodeType * p, * q; int ret = -1; if(list->spt>0) { p = list->head; q = NULL; while(p->next!=NULL) 22
  28. 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;}int pop_front(LList * list){ int ret=-1; NodeType * tmp; if(list->spt>0) { tmp = list->head; if(list->spt==1) { // danh sach chi co 1 phan tu ret = list->head->data; list->head = list->tail = NULL; }else list->head = list->head->next; free(tmp); list->spt = list->spt - 1; } return ret;}// 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; 23
  29. 29. p = list->head; while(p!=NULL) { q = p->next; while(q!=NULL) { if(q->data < p->data) { tmp = q->data; q->data = p->data; p->data = tmp; } q = q->next; } p = p->next; }}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; } }}void merge(LList *list1, const LList *list2){ NodeType * tmp; tmp = list2->head; while(tmp) { push_back(list1, tmp->data); tmp = tmp->next; } 24
  30. 30. } int empty(const LList *list) { return (list->spt==0)?(1):(0); } int begin(const LList * list) { return list->head->data; } int end(const LList * list) { return list->tail->data; }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 đacủa danh trƣớc. Ngƣơ ̣c la ̣i mảng là các cấ u trúc có kích thƣớc cố đinh ̣ 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ớimô ̣t số cố đinh 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. Để tim đế 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ƣ̣ kich 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 đơngiả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ạitrừ 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áchthự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 danhsá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 25
  31. 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ỏ. Thayvà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 takhô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ƣngcũ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óavà 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ínhlà 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ê ̣csƣ̉ 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óabỏ 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ố đinh 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ƣ́acác đỉnh liền kề của một đỉnh của đồ thị.2.3.6. Cài đặt stack và 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ạnchế , 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 stackhay 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ênkế 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.2.4. Bài tập áp dụng 1. Viết khai báo và các chƣơng trình con cài đặt danh sách bằng mảng. Dùng cácchƣơng trình con này để viết: a. Chƣơng trình con nhận một đãy các số nguyên nhập từ bàn phím, lƣu trữ nó trongdanh sách theo thứ tự nhập vào. b. Chƣơng trình con nhận một đãy các số nguyên nhập từ bàn phím, lƣu trữ nó trong 26
  32. 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ủanó 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áctrƣờ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ẫncó 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ó trongmộ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ƣơngtrì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ìnhcon 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à trongtrƣờ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 đặtbằ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ó trongmộ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 danhsá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 danhsách: một danh sách gồm các số chẵn còn cái kia chứa các số lẻ. 27
  33. 33. CHƢƠNG 3. CÂY (TREE).3.1. Đinh nghia ̣ ̃ 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â ̣pcá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ênmă ̣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ƣơ ̣ngthƣ̣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 đinh của đồ thi ̣ , đố i với đinh 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 đinh 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ộttrạ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 đinh 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áiniê ̣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đinh 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ớinhau, 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 đinh ̉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ínhxác là các cung , ngƣơ ̣c la ̣i đồ nế u không phân biê ̣t hƣớng giƣ̃a các đinh trong các ca ̣nh nố i ̉giƣ̃a hai đinh 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ốigiƣ̃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 day các đinh v 1, v2, …, vk, trong đó các đinh v i, ̣ ̃ ̉ ̉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 28
  34. 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ầ nliên thông nhỏ hơn. 3.1.2. Cây (tree) Có nhiều cách định nghĩa cây khác nhau nhƣng ở đây chúng ta sẽ định nghĩa khái niệ mcây theo lý thuyế t đồ thi ̣ (graph theory). Cây là mô ̣t đồ thi ̣vô hƣớng , không có tro ̣ng số , liên thông và không có chu trinh . Ví dụ ̀hình vẽ sau là một cây: 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 tinh , 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 đƣợcgọi là các nút (nodes). Tƣ̀ gố c của cây đi xuố ng tấ t cả các đinh 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â ̣yquan 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ô ̣tnú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 đặtngƣờ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ácnú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 phia 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 đinh ̣đô ̣ 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ủangà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âytổ ng quát luôn có thể phân chia thành các cây nhi ̣phân. 29
  35. 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útcon 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 ƣ́ngdụng rô ̣ng rai 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ệtgọ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ấtmộ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ễnmộ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ểudiễ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ộtdoạ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ớinà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 coilà 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 Sách cũng đƣợc xem nhƣ một cây. C3 C1 C2 3.2 1.1 1.2 2.1 2.2 2.1.1 2.1.2 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àidƣờ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ộtnú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ộtnú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âylà chiều cao của nút gốc. Ðộ sâu của một nút là dộ dài dƀ

×