Giáo trình Cấu trúc dữ liệu và giải thuật - Trần Hạnh Nhi, Dương Anh Đức Phần 2

Giáo trình cấu trúc dữ liệu và giải thuật Trần Hạnh Nhi Dương Anh Đức phần 2 cung cấp kiến thức chuyên sâu và ứng dụng thực tiễn cho sinh viên.

Trường đại học

Trường Đại Học

Người đăng

Ẩn danh

Thể loại

Giáo Trình

2023

123
2
0

Phí lưu trữ

35 Point

Tóm tắt

I. Review giáo trình CTDL GT Trần Hạnh Nhi Dương Anh Đức P2

Giáo trình Cấu trúc dữ liệu và giải thuật của Trần Hạnh Nhi và Dương Anh Đức, đặc biệt là phần 2, đóng vai trò nền tảng cho sinh viên ngành Công nghệ thông tin. Tài liệu này tập trung vào các cấu trúc dữ liệu động, một khái niệm then chốt giúp giải quyết những hạn chế của cấu trúc dữ liệu tĩnh. Các cấu trúc tĩnh như mảng có kích thước cố định, gây khó khăn khi xử lý các bài toán có dữ liệu thay đổi liên tục. Như tài liệu gốc đã nêu: “Các đối tượng dữ liệu... có đặc điểm chung là không thay đổi được kích thước, cấu trúc trong quá trình sống, do vậy thường cứng nhắc, gò bó”. Phần 2 của giáo trình Dương Anh Đức giới thiệu các giải pháp linh hoạt hơn, cho phép chương trình sử dụng bộ nhớ hiệu quả và mô tả chính xác hơn các đối tượng trong thế giới thực. Nội dung chính xoay quanh danh sách liên kết, Stack, Queue và các thuật toán sắp xếp, thuật toán tìm kiếm phức tạp hơn. Việc nắm vững kiến thức từ sách CTDL&GT KHTN này không chỉ là yêu cầu để qua môn mà còn là bước đệm vững chắc cho các môn học chuyên ngành sau này như cơ sở dữ liệu, trí tuệ nhân tạo và lý thuyết đồ thị.

1.1. Tầm quan trọng của cấu trúc dữ liệu động trong lập trình

Cấu trúc dữ liệu động mang lại sự linh hoạt vượt trội so với cấu trúc tĩnh. Chúng cho phép cấp phát và giải phóng bộ nhớ trong quá trình chạy, giúp tối ưu hóa tài nguyên hệ thống. Trong thực tế, dữ liệu thường không cố định. Ví dụ, một danh sách sinh viên có thể thêm hoặc bớt thành viên. Sử dụng mảng tĩnh sẽ dẫn đến lãng phí bộ nhớ hoặc không đủ dung lượng. Giáo trình nhấn mạnh rằng cấu trúc dữ liệu động giải quyết vấn đề này bằng cách cho phép chương trình "thay đổi về cấu trúc, độ lớn". Khả năng này cực kỳ quan trọng khi xây dựng các ứng dụng phức tạp, nơi không thể dự đoán trước quy mô dữ liệu. Hơn nữa, tổng vùng nhớ cho biến tĩnh thường bị giới hạn (ví dụ 64kb trong một số môi trường), trong khi cấu trúc động sử dụng vùng nhớ Heap, lớn hơn nhiều. Điều này mở ra khả năng xử lý các tập dữ liệu khổng lồ, một yêu cầu cơ bản trong khoa học dữ liệu và phát triển phần mềm hiện đại.

1.2. Nội dung cốt lõi sách CTDL GT KHTN Danh sách liên kết

Danh sách liên kết là cấu trúc dữ liệu động cơ bản nhất được trình bày trong giáo trình Cấu trúc dữ liệu và giải thuật Trần Hạnh Nhi Dương Anh Đức phần 2. Thay vì lưu trữ các phần tử liền kề trong bộ nhớ như mảng, mỗi phần tử (node) trong danh sách liên kết chứa dữ liệu và một con trỏ (liên kết) đến phần tử tiếp theo. Tài liệu gốc mô tả hai hình thức tổ chức danh sách: liên kết “ngầm” (mảng) và liên kết “tường minh” (danh sách móc nối). Chính liên kết tường minh này tạo nên sự linh động. Nó cho phép các thao tác thêm, xóa phần tử được thực hiện một cách tự nhiên và hiệu quả mà không cần di dời các phần tử khác. Cuốn sách CTDL&GT KHTN đi sâu vào các biến thể như danh sách liên kết đơn, danh sách liên kết kép và danh sách liên kết vòng, cùng với việc cài đặt chi tiết các thao tác trên chúng.

1.3. Đối tượng và mục tiêu học tập của giáo trình Dương Anh Đức

Giáo trình này được biên soạn chủ yếu cho sinh viên các ngành Khoa học Máy tính, Kỹ thuật Phần mềm và Công nghệ Thông tin. Mục tiêu chính là trang bị cho người học kiến thức sâu sắc về cách tổ chức dữ liệu một cách hiệu quả. Sau khi hoàn thành phần 2, sinh viên phải có khả năng phân tích bài toán để lựa chọn cấu trúc dữ liệu phù hợp. Họ cần thành thạo việc cài đặt và sử dụng các cấu trúc như danh sách liên kết, Stack, Queue. Quan trọng hơn, họ phải hiểu và đánh giá được độ phức tạp thuật toán (Big O notation) của các thao tác trên từng cấu trúc. Đây là kỹ năng cốt lõi để viết ra những chương trình không chỉ đúng mà còn tối ưu về tốc độ và bộ nhớ, một yêu cầu bắt buộc đối với các lập trình viên chuyên nghiệp.

II. Khó khăn khi tự học Cấu trúc dữ liệu và giải thuật P2

Việc tiếp cận phần 2 của giáo trình Cấu trúc dữ liệu và giải thuật đặt ra nhiều thách thức, ngay cả với những sinh viên đã nắm vững kiến thức cơ bản. Khái niệm con trỏ và quản lý bộ nhớ động là rào cản lớn đầu tiên. Việc thao tác trực tiếp với địa chỉ bộ nhớ đòi hỏi sự cẩn thận và hiểu biết sâu sắc để tránh các lỗi nghiêm trọng như rò rỉ bộ nhớ hoặc truy cập vùng nhớ không hợp lệ. Bên cạnh đó, việc phân tích độ phức tạp thuật toán không còn đơn giản như trên các cấu trúc tĩnh. Sinh viên phải xem xét các thao tác duyệt qua con trỏ thay vì truy cập trực tiếp qua chỉ số. Một khó khăn khác là sự thiếu hụt các bài tập cấu trúc dữ liệu và giải thuật có lời giải chi tiết, khiến việc tự kiểm tra và củng cố kiến thức trở nên khó khăn. Để vượt qua những trở ngại này, người học cần một phương pháp tiếp cận có hệ thống, kết hợp giữa việc đọc kỹ giáo trình Dương Anh Đức và thực hành liên tục với code C++ CTDL&GT.

2.1. Hiểu sai khái niệm con trỏ và cấp phát bộ nhớ động

Con trỏ là khái niệm trừu tượng và dễ gây nhầm lẫn. Nhiều người học gặp khó khăn trong việc phân biệt giữa biến con trỏ (lưu địa chỉ) và biến động (vùng nhớ được con trỏ trỏ tới). Các thao tác như p->pNext trong danh sách liên kết có thể trở nên khó hình dung nếu không có nền tảng vững chắc. Sách định nghĩa rõ: “Biến thuộc kiểu con trỏ... là biến mà giá trị của nó là địa chỉ của một vùng nhớ”. Các lỗi phổ biến bao gồm việc quên giải phóng bộ nhớ (dùng new mà không có delete), dẫn đến rò rỉ bộ nhớ, hoặc truy cập vào con trỏ NULL, gây sập chương trình. Để khắc phục, người học cần vẽ sơ đồ các con trỏ và các vùng nhớ trên giấy để trực quan hóa mọi thao tác thay đổi liên kết trước khi viết mã.

2.2. Phân tích độ phức tạp thuật toán Big O notation khó khăn

Phân tích độ phức tạp thuật toán trên cấu trúc dữ liệu động phức tạp hơn trên mảng. Với mảng, truy cập một phần tử bất kỳ là O(1). Nhưng với danh sách liên kết, để truy cập phần tử thứ n, ta phải duyệt từ đầu, mất O(n) thời gian. Sự khác biệt này ảnh hưởng lớn đến hiệu năng của các thuật toán. Ví dụ, một thuật toán tìm kiếm tuyến tính trên danh sách liên kết luôn có độ phức tạp O(n). Giáo trình đề cập đến việc đánh giá hiệu năng của các thuật toán sắp xếp như Quick Sort hay Merge Sort, đòi hỏi người học phải hiểu cách các thao tác trên con trỏ ảnh hưởng đến tổng số phép toán. Nắm vững Big O notation là chìa khóa để lựa chọn giải thuật tối ưu cho từng bài toán cụ thể.

2.3. Thiếu bài tập cấu trúc dữ liệu và giải thuật có lời giải

Lý thuyết suông là không đủ. Việc áp dụng kiến thức vào giải quyết các bài toán cụ thể là cách học hiệu quả nhất. Tuy nhiên, tìm kiếm nguồn bài tập cấu trúc dữ liệu và giải thuật có lời giải chất lượng, đặc biệt là các bài toán liên quan đến danh sách liên kết, cây, hay đồ thị, là một thách thức. Nhiều sinh viên có thể hiểu lý thuyết nhưng lại bế tắc khi bắt đầu cài đặt. Các tài liệu như slide bài giảng CTDL&GT KHTN thường cung cấp ví dụ, nhưng không đủ đa dạng. Việc tự giải bài tập, đối chiếu với các lời giải mẫu và phân tích các cách tiếp cận khác nhau sẽ giúp củng cố kiến thức và phát triển tư duy giải thuật một cách toàn diện. Đây là một phần quan trọng trong quá trình tự học và ôn luyện.

III. Hướng dẫn chi tiết danh sách liên kết trong giáo trình

Giáo trình Cấu trúc dữ liệu và giải thuật của Dương Anh Đức và Trần Hạnh Nhi trình bày rất chi tiết về danh sách liên kết, nền tảng của cấu trúc dữ liệu động. Một danh sách liên kết được tạo thành từ các nút (node), mỗi nút gồm hai thành phần chính: thành phần dữ liệu (Info) và thành phần mối liên kết (pNext). Thành phần pNext là một con trỏ, lưu trữ địa chỉ của nút kế tiếp. Nhờ cấu trúc này, các nút không cần nằm liền kề nhau trong bộ nhớ, tạo ra sự linh hoạt tối đa. Giáo trình định nghĩa một cách tổng quát: typedef struct tagNode { Data Info; struct tagNode* pNext; } NODE;. Việc quản lý toàn bộ danh sách chỉ cần thông qua một con trỏ trỏ đến nút đầu tiên (pHead). Cuốn sách CTDL&GT KHTN này hướng dẫn cặn kẽ cách xây dựng các thao tác cơ bản như thêm, xóa, tìm kiếm và duyệt danh sách, là những kỹ năng không thể thiếu để làm việc với các cấu trúc dữ liệu phức tạp hơn.

3.1. Cài đặt danh sách liên kết đơn và các thao tác cơ bản

Việc cài đặt danh sách liên kết đơn bắt đầu bằng việc định nghĩa cấu trúc của một nút và một danh sách. Các thao tác cơ bản được giáo trình mô tả chi tiết bằng thuật toán và mã giả. Thao tác chèn một phần tử có thể thực hiện ở đầu danh sách (AddFirst), cuối danh sách (AddTail), hoặc sau một nút cho trước (AddAfter). Tương tự, thao tác xóa cũng có thể xóa ở đầu, sau một nút, hoặc xóa một nút có giá trị cụ thể. Thuật toán tìm kiếm trên danh sách liên kết đơn là tìm kiếm tuần tự, duyệt qua từng nút từ đầu cho đến khi tìm thấy hoặc hết danh sách. Việc duyệt danh sách (ProcessList) là thao tác nền tảng để xử lý tất cả các phần tử, ví dụ như in danh sách hoặc tính tổng. Các đoạn code C++ CTDL&GT minh họa giúp người đọc dễ dàng chuyển từ thuật toán sang cài đặt thực tế.

3.2. So sánh danh sách liên kết đơn kép và danh sách vòng

Ngoài danh sách liên kết đơn, giáo trình còn giới thiệu hai biến thể quan trọng khác. Danh sách liên kết kép (Doubly Linked List) là loại danh sách mà mỗi nút có hai con trỏ: một trỏ tới nút phía sau (pNext) và một trỏ tới nút phía trước (pPrev). Cấu trúc này cho phép duyệt danh sách theo cả hai chiều, thuận lợi cho một số thuật toán nhưng tốn thêm bộ nhớ cho con trỏ pPrev. Danh sách liên kết vòng (Circular Linked List) là danh sách mà nút cuối cùng không trỏ tới NULL mà trỏ ngược lại nút đầu tiên, tạo thành một vòng khép kín. Mỗi loại danh sách có ưu và nhược điểm riêng, và việc lựa chọn loại nào phụ thuộc vào yêu cầu cụ thể của bài toán đang giải quyết.

3.3. Kỹ thuật quản lý bộ nhớ với biến động trong C C

Làm việc với danh sách liên kết gắn liền với quản lý bộ nhớ động. Giáo trình nhấn mạnh tầm quan trọng của các thao tác cấp phát và giải phóng bộ nhớ. Trong C++, toán tử new được dùng để cấp phát bộ nhớ cho một nút mới, và delete được dùng để giải phóng vùng nhớ khi nút đó không còn được sử dụng. Tài liệu gốc nêu rõ: “Biến động... có thể được cấp phát hoặc giải phóng bộ nhớ khi người sử dụng yêu cầu”. Việc quản lý bộ nhớ cẩn thận là tối quan trọng. Nếu một nút bị xóa khỏi danh sách nhưng không được delete, vùng nhớ của nó sẽ bị chiếm dụng vĩnh viễn trong suốt quá trình chạy chương trình, gây ra hiện tượng rò rỉ bộ nhớ (memory leak). Đây là một trong những lỗi khó phát hiện và nguy hiểm nhất trong lập trình.

IV. Bí quyết tối ưu thuật toán sắp xếp từ giáo trình CTDL GT

Phần 2 của giáo trình Cấu trúc dữ liệu và giải thuật Trần Hạnh Nhi Dương Anh Đức dành một chương quan trọng để phân tích các thuật toán sắp xếp trên danh sách liên kết. Sắp xếp trên danh sách liên kết có những đặc thù riêng so với sắp xếp trên mảng. Giáo trình đưa ra hai phương pháp tiếp cận chính. Phương án 1 là hoán vị nội dung (vùng Info) của các nút, giữ nguyên cấu trúc liên kết. Phương án này đơn giản nhưng không hiệu quả nếu dữ liệu trong mỗi nút lớn. Phương án 2, hiệu quả hơn, là thay đổi các mối liên kết (vùng pNext), tức là sắp xếp lại thứ tự các nút mà không cần sao chép dữ liệu. Cách tiếp cận này tận dụng được ưu điểm của danh sách liên kết. Các thuật toán nổi tiếng như Quick Sort, Merge Sort khi cài đặt trên danh sách liên kết thể hiện bản chất của chúng một cách rất rõ ràng và hiệu quả, như được phân tích chi tiết trong tài liệu ôn thi cuối kỳ CTDL&GT này.

4.1. Phân tích Quick Sort và Merge Sort trên danh sách liên kết

Quick Sort và Merge Sort là hai thuật toán sắp xếp hiệu quả hàng đầu. Khi áp dụng trên danh sách liên kết, chúng có những cách cài đặt rất đặc trưng. Với Quick Sort, thuật toán chọn một phần tử làm chốt (pivot), sau đó chia danh sách thành hai danh sách con: một chứa các phần tử nhỏ hơn chốt và một chứa các phần tử lớn hơn chốt, rồi đệ quy trên hai danh sách con. Giáo trình mô tả việc này thực hiện bằng cách thay đổi các con trỏ pNext. Tương tự, Merge Sort hoạt động bằng cách chia danh sách thành hai nửa, đệ quy sắp xếp từng nửa, sau đó trộn (merge) hai danh sách con đã sắp xếp lại với nhau. Thao tác trộn trên danh sách liên kết đặc biệt hiệu quả vì chỉ cần điều chỉnh các con trỏ mà không cần dùng mảng phụ.

4.2. Khi nào nên dùng Radix Sort cho cấu trúc dữ liệu động

Radix Sort là một thuật toán sắp xếp không dựa trên so sánh, hoạt động bằng cách phân loại các số theo từng chữ số. Khi cài đặt trên mảng, Radix Sort đòi hỏi bộ nhớ phụ rất lớn. Tuy nhiên, giáo trình chỉ ra rằng thuật toán này rất phù hợp với danh sách liên kết. Ý tưởng là sử dụng một mảng gồm 10 danh sách liên kết (gọi là các "lô" hoặc "bucket"), tương ứng với các chữ số từ 0 đến 9. Ở mỗi bước, thuật toán duyệt qua danh sách gốc, tách từng phần tử và chèn vào cuối lô tương ứng dựa trên chữ số ở vị trí đang xét. Sau đó, các lô được nối lại với nhau để tạo thành danh sách mới. Radix Sort đặc biệt hiệu quả khi sắp xếp các số nguyên có độ dài cố định.

4.3. Đánh giá hiệu năng và độ phức tạp thuật toán sắp xếp

Việc lựa chọn thuật toán sắp xếp phù hợp đòi hỏi phải hiểu rõ độ phức tạp thuật toán của chúng. Quick SortMerge Sort đều có độ phức tạp trung bình là O(n log n), rất hiệu quả cho các tập dữ liệu lớn. Tuy nhiên, trường hợp xấu nhất của Quick Sort có thể lên tới O(n^2), trong khi Merge Sort luôn ổn định ở O(n log n). Các thuật toán đơn giản hơn như Selection Sort có độ phức tạp O(n^2) và chỉ phù hợp với danh sách nhỏ. Radix Sort có độ phức tạp O(d*n), trong đó d là số chữ số, rất nhanh cho các số nguyên. Phân tích Big O notation giúp lập trình viên đưa ra quyết định sáng suốt, cân bằng giữa tốc độ, bộ nhớ và độ phức tạp khi cài đặt.

V. Ứng dụng Stack và Queue trong Cấu trúc dữ liệu Giải thuật

Stack (ngăn xếp) và Queue (hàng đợi) là hai cấu trúc dữ liệu trừu tượng tuyến tính có ứng dụng vô cùng rộng rãi trong khoa học máy tính. Giáo trình Cấu trúc dữ liệu và giải thuật Trần Hạnh Nhi Dương Anh Đức phần 2 trình bày chúng như những dạng đặc biệt của danh sách. Điểm khác biệt cốt lõi nằm ở cơ chế truy xuất phần tử. Stack hoạt động theo nguyên tắc LIFO (Last-In, First-Out - Vào sau, ra trước), giống như một chồng đĩa. Queue hoạt động theo nguyên tắc FIFO (First-In, First-Out - Vào trước, ra trước), tương tự như một hàng người xếp hàng. Cả hai cấu trúc này đều có thể được cài đặt bằng mảng hoặc danh sách liên kết. Việc hiểu rõ nguyên tắc hoạt động và ứng dụng của chúng là rất quan trọng, ví dụ như trong việc khử đệ quy, quản lý tiến trình hệ điều hành, hay các thuật toán duyệt đồ thị như duyệt đồ thị theo chiều sâu DFSduyệt đồ thị theo chiều rộng BFS.

5.1. Cơ chế LIFO của Stack và ứng dụng khử đệ quy

Stack được định nghĩa trong giáo trình là "một vật chứa... làm việc theo cơ chế LIFO". Hai thao tác chính trên Stack là Push (thêm một phần tử vào đỉnh) và Pop (lấy một phần tử ra khỏi đỉnh). Do đặc tính "vào sau ra trước", Stack là công cụ lý tưởng để quản lý các lệnh gọi hàm trong chương trình (call stack). Một ứng dụng quan trọng khác là khử đệ quy. Bất kỳ thuật toán đệ quy nào cũng có thể được chuyển thành dạng lặp bằng cách sử dụng một Stack để lưu trữ các trạng thái hoặc các tham số của các lần gọi đệ quy. Ngoài ra, Stack còn được dùng trong các bài toán duyệt cây, duyệt đồ thị theo chiều sâu (DFS), và tính toán giá trị biểu thức hậu tố.

5.2. Nguyên tắc FIFO của Queue và bài toán quản lý tiến trình

Ngược lại với Stack, Queue tuân theo nguyên tắc "vào trước ra trước". Các thao tác chính là Enqueue (thêm một phần tử vào cuối hàng đợi) và Dequeue (lấy một phần tử ra khỏi đầu hàng đợi). Nhờ tính công bằng này, Queue được sử dụng rộng rãi trong các hệ điều hành để quản lý các tiến trình đang chờ CPU, quản lý các yêu cầu in ấn, hoặc trong các bộ đệm (buffer) dữ liệu. Trong lĩnh vực giải thuật, Queue là cấu trúc dữ liệu không thể thiếu để cài đặt thuật toán duyệt đồ thị theo chiều rộng (BFS), một kỹ thuật dùng để tìm đường đi ngắn nhất trên các đồ thị không có trọng số. Các bài toán mô phỏng hệ thống xếp hàng trong thực tế cũng thường sử dụng Queue.

5.3. Triển khai Stack và Queue bằng mảng và danh sách liên kết

Giáo trình phân tích hai cách triển khai phổ biến cho Stack và Queue. Sử dụng mảng để cài đặt rất đơn giản và hiệu quả về tốc độ truy cập, nhưng có nhược điểm là kích thước cố định, có thể gây lãng phí hoặc thiếu bộ nhớ. Sử dụng danh sách liên kết để cài đặt mang lại sự linh hoạt, kích thước có thể thay đổi động theo nhu cầu. Với Stack, việc thêm/xóa ở đầu danh sách liên kết (AddFirst/RemoveFirst) là các thao tác O(1), rất phù hợp. Với Queue, cần quản lý cả con trỏ đầu (pHead) và cuối (pTail) của danh sách để đảm bảo các thao tác Enqueue (AddTail) và Dequeue (RemoveFirst) đều có độ phức tạp thuật toán là O(1).

VI. Tổng hợp tài liệu ôn thi cuối kỳ CTDL GT từ giáo trình

Để chuẩn bị tốt nhất cho kỳ thi, việc hệ thống hóa kiến thức từ giáo trình Cấu trúc dữ liệu và giải thuật Trần Hạnh Nhi Dương Anh Đức phần 2 là cực kỳ cần thiết. Đây là một tài liệu ôn thi cuối kỳ CTDL&GT toàn diện, bao quát các chủ đề quan trọng nhất của học phần. Sinh viên cần tập trung vào các khái niệm cốt lõi: sự khác biệt giữa cấu trúc tĩnh và động, nguyên tắc hoạt động của con trỏ và bộ nhớ Heap. Nắm vững việc cài đặt danh sách liên kết và các thao tác trên nó là yêu cầu bắt buộc. Bên cạnh đó, cần hiểu rõ cơ chế LIFO/FIFO và ứng dụng của Stack/Queue. Một phần quan trọng khác là các thuật toán sắp xếp trên danh sách liên kết, không chỉ biết cách cài đặt mà còn phải phân tích được độ phức tạp thuật toán của chúng. Việc ôn tập có hệ thống sẽ giúp sinh viên tự tin đối mặt với các câu hỏi lý thuyết và bài tập lập trình trong đề thi.

6.1. Slide bài giảng CTDL GT KHTN và các điểm nhấn chính

Bên cạnh giáo trình, slide bài giảng CTDL&GT KHTN là một nguồn tài liệu bổ trợ quý giá. Các slide thường tóm tắt nội dung một cách cô đọng, sử dụng hình ảnh và sơ đồ để minh họa các khái niệm phức tạp như thao tác trên con trỏ, quá trình hoạt động của các thuật toán sắp xếp, hoặc sự thay đổi của Stack và Queue. Khi ôn thi, việc kết hợp đọc giáo trình để hiểu sâu và xem lại slide để hệ thống hóa kiến thức là một chiến lược hiệu quả. Các điểm nhấn chính cần chú ý trong slide bao gồm định nghĩa các cấu trúc dữ liệu, mã giả của các thuật toán quan trọng và bảng so sánh độ phức tạp của chúng.

6.2. Phương pháp giải bài tập Cấu trúc dữ liệu và giải thuật

Để giải quyết tốt các bài tập cấu trúc dữ liệu và giải thuật có lời giải, sinh viên cần rèn luyện một quy trình làm bài khoa học. Bước đầu tiên là đọc kỹ đề và xác định đúng cấu trúc dữ liệu cần sử dụng. Bước hai, hãy vẽ ra giấy để mô phỏng các thao tác, đặc biệt với danh sách liên kết. Việc trực quan hóa giúp tránh các lỗi logic về con trỏ. Bước ba, viết thuật toán hoặc mã giả trước khi lập trình. Bước cuối cùng mới là viết code C++ CTDL&GT hoàn chỉnh. Sau khi có lời giải, cần tự phân tích độ phức tạp thuật toán để xem giải pháp của mình đã tối ưu hay chưa. Luyện tập thường xuyên theo quy trình này sẽ hình thành tư duy giải thuật sắc bén.

6.3. Tầm nhìn phát triển từ kiến thức trong giáo trình

Kiến thức từ giáo trình Dương Anh Đức không chỉ dùng để thi qua môn. Nó là nền tảng cho rất nhiều lĩnh vực nâng cao trong khoa học máy tính. Nắm vững danh sách liên kết và cây nhị phân là điều kiện tiên quyết để học về các cấu trúc phức tạp hơn như bảng băm (hash table) hay cây cân bằng. Hiểu về các thuật toán duyệt là cơ sở để tiếp cận lý thuyết đồ thị và các thuật toán kinh điển như thuật toán Dijkstra, thuật toán Prim, hay thuật toán Kruskal. Các kỹ thuật thiết kế giải thuật như quy hoạch độngthuật toán tham lam cũng đòi hỏi một nền tảng vững chắc về các cấu trúc dữ liệu cơ bản. Do đó, đầu tư thời gian để học kỹ học phần này là một sự đầu tư cho tương lai sự nghiệp.

17/07/2025
Giáo trình cấu trúc dữ liệu và giải thuật trần hạnh nhi dương anh đức phần 2

Trích đoạn nội dung tài liệu

CHƯƠNG 3 CẤU TRÚC DỮ LIỆU ĐỘNG Mục tiêu *“#” Giới thiệu khái niệm cấu trúc dữ liệu động. *®” Danh sách liên kết: tổ chức, các thuật toán, ứr:g dụng. DAT VAN DE Với các cấu trúc dữ liệu được xây dựng từ các kiểu cơ sở như: kiếu thực, kiểu nguyên, kiểu ký tự. hoặc từ các cấu trúc đơn giản như mẩu tin, tập hợp, mảng.

lập trình viên có thể giải quyết hầu. hết các bài toán đặt ra. Các đối tượng dữ liệu được xác định thuộc những kiểu dữ liệu này có đặc điểm chung là không thay đổi được kích thước, cấu trúc trong quá trình sống, do vậy thường cứng nhắc, gò bó khiến đôi khi khó diễn tả được thực tế vốn. sinh động, phong phú.

Các kiểu dữ liệu kể trên được gọi là các kiểu dữ liệu tĩnh. Trong thực tế, một số đối tượng có thể được định nghĩa đệ qui, ví dy để mô tả đối tugng ‘con người' cắn thể hiện các thông tin tối thiểu như : = Họ tên * SốCMND 97 » _ Thông tin về cha, mẹ Để biểu diễn một đối tượng có nhiều thành phần thông tin như trên có thể sử dụng kiểu mẫu tin. Tuy nhiên, cần lưu ý cha, mẹ của một người cũng là các đối tượng kiểu NGƯỜI, do vậy vẻ nguyên tắc cẩn phải có định nghĩa như sau: typedef struct NGUOI( char ñoten(30]; intSo_CMND ; NGUOI Cha,Me; H 'Tuy nhiên với khai báo trên, các ngôn ngữ lập trình gặp khó khăn trong việc cài đặt không vượt qua được như xác định kích thước của đối tượng kiểu NGUOI ? 2. Một số đối tượng dữ liệu trong chu kỳ sống của nó có thể thay đổi vẻ cấu trúc, độ lớn, như danh sách các học viên trong một lớp học có thể tăng thêm, giảm đi.

Khi đó nếu cố tình dùng những cấu trúc dữ liệu tĩnh đã biết như mảng. để biểu diễn những đối tượng đơ, lập trình viên phải sử dụng những thao tác phức tạp, kém tự nhiên khiến chương „ trình trở nên khó đọc, do đó khó bảo trì và nhất là khó có thể sử dụng bộ nhớ một cách có hiệu quả. Một lý do nữa làm cho các kiểu đữ liệu tĩnh không thể đáp ứng được nhu cầu của thực tế là tổng kích thước vùng nhớ dành cho tất cả các biến tĩnh chỉ là 64kb (1 Segment bộ nhớ). Khi có nhu cẩu dùng nhiễu bộ nhớ hơn ta phải sử dụng các cấu trúc dữ liệu động.

Cuối cùng, do bản chất của các dữ liệu tĩnh, chúng sẽ chiếm. 98 vùng nhớ đã dành cho chúng suốt quá trình hoạt động của chương trình. Tuy nhiên, trong thực tế, có thể xảy ra trường hợp một dữ liệu nào đó chỉ tổn tại nhất thời hay không thường xuyên trong quá trình hoạt động của chương trình. Vì vậy việc dùng các CTDL tĩnh sẽ không cho phép sử dụng hiệu quả bộ nhớ.

Do vậy, nhằm đáp ứng nhu câu thể hiện sát thực bản chất của dữ liệu cũng như xây dựng các thao tác hiệu quả trên dữ liệu, cần phải tìm cách tổ chức kết hợp dữ liệu với những hình thức mới linh động hơn, có thể thay đổi kích thước, cấu trúc trong; suốt thời gian. Các hình thức tổ chức dữ liệu như vậy được gọi là cấu £rúc diz ligu động. Chương này sẽ giới thiệu về các cấu trúc dữ liệu động và tập trung khảo sát cấu trúc đơn giản nhất thuộc loại này là “danh sách liên kết. KIỂU DỮ LIỆU CON TRỎ 1.

Biến không động (biến tĩnh, biến nửa tĩnh) Khi xây dựng chương trình, lập trình viên có thể xác định được ngay những đối tượng dữ liệu luôn cần được sử dụng, không có nhu cẩu thay đổi về số lượng kích thước. do đó có thể xác định cách thức lưu trữ chúng ngay từ đâu. Các đối tượng dữ liệu này sẽ được khai báo như các biến không động. Biến không động là những biến thỏa: « _ Được khai báo tường mình, + Tén tai khi vào phạm vi khai báo và chỉ: mất khi ra khỏi phạm vỉ này, « Được cấp phát vùng nhớ trong vùng dữ liệu (Data segment) hoặc là Stack (đối với biến nửa tĩnh - các biến 99 cục bộ).

« _ Kích thước không thay đổi trong suốt quá trình sống. Do được khai báo tường minh, các biến không động có một định danh đã được kết nối với địa chỉ vùng nhớ lưu trữ biến và được truy xuất trực tiếp thông qua định danh đó. Ví dụ : int a //a, b là các biến khong dong char (10); 2. Kiểu con trỏ « Cho trước kiểu T = <V,O>.

Kiểu con trỏ - ký hiệu “Tp”- chỉ đến các phần tử có kiểu “T” được định nghĩa: Tp = <Vp, Op> trong đó ~ Vp = lleác đia chỉ có thể lưu trữ những đối tượng có kiểu TỊ, NULLI (với NULL là một giá trị đặc biệt tượng. trưng cho một giá trị không biết hoặc không quan tâm) ~ Op= lcác thao tác định địa chỉ của một đối tượng thuộc kiểu T khi biết con trỏ chỉ đến đối tượng đó] (thường gồm các thao tác tạo một con trỏ chỉ đến một đối tượng. thuộc kiểu T; hủy một đối tượng dữ liệu thuộc kiểu T khi biét con trỏ chỉ đến đối tượng đó) © Nói một cách dễ hiểu, kiểu con trỏ là kiểu cơ sở dùng lưu địa chỉ của một đối tượng dữ liệu khác. « _ Biến thuộc kiểu con trỏ Tp là biến mà giá trị của nó là địa chỉ cuả một vùng nhớ ứng với một biến kiểu T, hoặc là giá 100 tri NULL.

l ưuý “#” Kích thước của biến con trỏ tùy thuộc vào qui ước số byte địa chỉ trong từng mô hình bộ nhớ của từng ngôn ngữ lập trình cụ thé. Ví dụ: -_ biến con tré trong Pascal có kích thước 4 byte (2 byte địa chi segment + 2 byte dja chi offset) - _ biến con trổ trong C có kích thước 2 hoặc 4 byte tùy vào con trỏ near (chỉ lưu địa chỉ offset) hay far (lưu cả segment lẫn offset) “# Cú pháp định nghĩa một kiểu con trỏ trong ngôn ngữ C : typedef «<kiểuconưỏ> *<kiéucd sd>; Ví dụ typedef int *intpointer; intpointer p; hoặc int*pz là những khai báo hợp lệ. Các thao tác cơ bản trên kiểu con trỏ(minh họa bằng C) 101 - _ Khi một biến con trỏ p lưu địa chỉ của đối tượng x, ta nói íp tré dén x’. - __ Gán địa chỉ của một vùng nhớ con trỏ p: p = <địa chỉ»; P= <dia chỉ» + <giá trị nguyén>; ~ __ Truy xuất nội dung của đối tượng do p trỏ đến (*p) 3.

Biến động Trong nhiễu trường hợp, tại thời điểm biên dịch không thể xác định trước kích thước chính xác của một số đối tượng dữ liệu đo sự tổn tại và tăng trưởng của chúng phụ thuộc vào ngữ cảnh của việc thực hiện chương trình. Các đối tượng dữ liệu có đặc điểm kể trên nên được khai báo như biến động. Biến động là những biến thỏa: - _ Biến không được khai báo tưởng mình. ~_ Có thể được cấp phát hoặc giải phóng bộ nhớ khi người sử dụng yêu cầu.

-_ Các biến này không theo qui tắc phạm vi (tĩnh). 'Vùng nhớ của biến được cấp phát trong Heap. Kích thước có thể thay đổi trong quá trình sống. Do không được khai báo tường minh nên các biến động không có một định danh được kết buộc với địa chỉ vùng nhớ cấp phát cho nó, do đó gặp khó khăn khi truy xuất đến một biến động.

Để giải quyết vấn đề, biến con trỏ (là biến không động) được sử dụng để trỏ đến biến động. Khi tạo ra một biến động, phải dùng một con trỏ để lưu địa chỉ của biến này và sau đó, truy xuất đến biến động thông qua biến con 102 trỏ đã biết định danh. Hai thao tác cơ bản trên biến động là tạo và hủy một biến. động do biến con trỏ 'p' trỏ đến: Tạo ra một biến động và cho con trỏ “p' chỉ đến nó Hầu hết các ngôn ngữ lập trình cấp cao đều cung cấp những thủ tục cấp phát vùng nhớ cho một biến động và cho một con trỏ giữ địa chỉ vùng nhớ đó.

Một số hàm cấp phát bộ nhớ của C : void* malloe(size) ; _ // trả vể con trỏ chỉ đến một vùng nhớ. 1í size byte vừa được cấp phát. void* ca1loe(n„ size) ;// trả về con trỏ chỉ đến một vùng nhớ. 4 vừa được cấp phát gồm n phẫn tử, 1 mỗi phân tử có kích thước size byte new //hàm cấp phát bộ nhớ trong C++ Hủy một biến động do p chỉ đến Ham free(p) huỷ vùng nhớ cấp phát bởi hàm malloe hoặc calloe do p trở tới Hàm delete p huỷ vùng nhớ cấp phát bởi hàm new do p trỏ tới Ví dụ: int* pì, P2; 4ƒ cấp phát vùng nhớ cho một biến động kiểu int pl = (int*)malloc(sizeof(int)); 108 pl? = 5; // đất giá trị năm cho biến dong pl // cấp phát biến động kiểu mảng gồm 10 phẩn tử kiểu int p2 = (int*)calloc(10, sizeof|int)); (p2+3)* = 0; / đặt giá trị 0 cho phẩn tử thứ 4 của mảng p2 free(pl); £free(p2); : Il.

DANH SÁCH LIÊN KET (LINK LIST) 1. Định nghĩa : Cho T là một kiểu được định nghiã trước, kiểu danh sách. Tx gồm các phần tử thuộc kiểu T được định nghĩa là: ‘Tx = <Vx, Ox> trong đó: Vx = (tap hợp có thứ tự các phần tử kiểu T được móc nối với nhau tieo trình tự tuyến tính]; * Ox = (Tao danh sách; Tìm một phẩn tử trong danh sách; Chèn một phản tử vào danh sách; Huỳ một phần tử khỏi danh sách ; Liệt kê danh sách, Sấp xếp danh sách .] Ví du: Hồ sơ các học sinh của một trường được tổ chức thành. danh sách gồm nhiều hồ sơ của từng học sinh; số lượng học sinh trong trường có thể thay đổi do vậy cản có các thao tác thêm, hủy một hổ sơ; để phục vụ công tác giáo.

vụ cẩn thực hiện các thao tác tìm hỗ sơ của một học sinh, in danh sách hỗ sơ. Các hình thức tổ chức danh sách. Có nhiễu hình thức tổ chức mối liên hệ tuần tự giữa các phần tử trong cùng một danh sách: 104 « Mối liên hệ giữa các phần tử được thể hiện ngắm: mỗi phần tử. trong danh sách được đặc trưng bằng chỉ số.

Cặp phần tử x„, Xiq1 được xác định là kế cận trong danh sách nhờ vào quan hệ giữa cặp chỉ số¡ và (i+1). Với hình thức tổ chức này, các phần.

Nội dung được bảo vệ bản quyền — Tải xuống đầy đủ