Bài giảng Cấu trúc dữ liệu & Thuật toán: Chương 7 - Cây (Trees) - TS. Nguyễn Hồ Mẫn Rạng

Người đăng

Ẩn danh
64
0
0

Phí lưu trữ

30 Point

Tóm tắt

I. Khám phá cấu trúc dữ liệu và thuật toán DSA CH7 Trees

Trong lĩnh vực khoa học máy tính, cấu trúc dữ liệu và thuật toán là nền tảng cho việc xử lý thông tin hiệu quả. Chương 7 (CH7) tập trung vào Trees (Cây), một cấu trúc dữ liệu phi tuyến tính cực kỳ quan trọng, mô phỏng các mối quan hệ phân cấp. Không giống như các cấu trúc tuyến tính như danh sách hay mảng, cây cho phép tổ chức dữ liệu một cách tự nhiên hơn cho các bài toán như biểu diễn cây gia phả, cấu trúc thư mục hệ điều hành, hay các thuật toán định tuyến mạng. Bài viết này sẽ đi sâu vào các khái niệm cơ bản, các loại cây phổ biến như cây nhị phân, cây tìm kiếm nhị phân, và các ứng dụng thực tiễn của chúng. Việc nắm vững DSA CH7 Trees không chỉ giúp giải quyết các vấn đề phức tạp mà còn là chìa khóa để tối ưu hóa hiệu suất tìm kiếm, sắp xếp và quản lý dữ liệu. Theo tài liệu của Dr. Nguyen Ho Man Rang, việc hiểu rõ bản chất của cây và các phương thức duyệt cây là yêu cầu cơ bản để tiếp cận các cấu trúc phức tạp hơn như cây AVL hay B-Tree, vốn là trọng tâm trong các kỹ thuật tìm kiếm nâng cao.

1.1. Định nghĩa cây Tree và các thuật ngữ cơ bản nhất

Theo định nghĩa của Dr. Nguyen Ho Man Rang, một cây (Tree) bao gồm một tập hợp hữu hạn các phần tử, được gọi là nút (nodes), và một tập hợp hữu hạn các đường có hướng, gọi là nhánh (branches), dùng để kết nối các nút. Cấu trúc này có một nút đặc biệt không có nhánh đi vào, được gọi là nút gốc (root). Mọi nút khác, ngoại trừ gốc, đều có đúng một nhánh đi vào. Một nút có thể có không, một hoặc nhiều nhánh đi ra. Các thuật ngữ cơ bản cần nắm vững bao gồm: nút cha (parent) là nút có nhánh đi ra, nút con (child) là nút có nhánh đi vào từ một nút khác. Các nút có cùng một nút cha được gọi là nút anh em (siblings). Một đường đi (path) là một chuỗi các nút liên tiếp, trong đó mỗi nút kề với nút tiếp theo. Sự hiểu biết về các khái niệm nền tảng này là bước đầu tiên để làm việc với bất kỳ loại cấu trúc dữ liệu cây nào.

1.2. Phân biệt các loại nút nút gốc nút lá và nút nội

Trong một cấu trúc dữ liệu cây, các nút được phân loại dựa trên vị trí và số lượng nhánh của chúng. Nút gốc (root) là nút duy nhất trong cây có bậc vào (indegree) bằng 0, đây là điểm khởi đầu của mọi đường đi trong cây. Ngược lại, nút lá (leaf) là bất kỳ nút nào có bậc ra (outdegree) bằng 0, tức là chúng không có nút con và là điểm kết thúc của các nhánh. Tất cả các nút không phải là gốc cũng không phải là lá được gọi là nút nội (internal node). Một nút nội vừa là con của một nút khác, vừa là cha của ít nhất một nút khác. Ví dụ, trong sơ đồ tổ chức công ty, giám đốc điều hành là nút gốc, nhân viên không quản lý ai là nút lá, và các trưởng phòng là nút nội. Việc xác định chính xác vai trò của từng nút giúp triển khai các thuật toán duyệt cây và thao tác trên cây một cách hiệu quả.

1.3. Các đặc trưng quan trọng bậc độ cao và cây con

Bậc của một nút (degree of a node) là tổng số nhánh liên kết với nút đó, bao gồm cả nhánh vào và nhánh ra. Bậc của cây (level) được định nghĩa là khoảng cách từ một nút đến nút gốc. Nút gốc có bậc là 0. Các nút anh em luôn ở cùng một bậc. Độ cao (height) của một cây được xác định bằng bậc của nút lá nằm trên đường đi dài nhất từ gốc cộng với 1. Đây là một thông số quan trọng để đánh giá hiệu suất của cây, đặc biệt là trong các cây tìm kiếm nhị phân. Cuối cùng, một cây con (subtree) là bất kỳ cấu trúc được kết nối nào nằm bên dưới nút gốc. Mỗi nút trong cây có thể được xem là gốc của một cây con riêng. Ví dụ, trong một cây biểu diễn hệ thống file, mỗi thư mục là gốc của một cây con chứa các file và thư mục con bên trong nó.

II. Hướng dẫn chi tiết về Cây Nhị Phân Binary Trees trong DSA

Trong số các loại cây, Cây Nhị Phân (Binary Trees) là một trong những cấu trúc dữ liệu được nghiên cứu và ứng dụng rộng rãi nhất. Đặc điểm định danh của một cây nhị phân là mỗi nút chỉ có thể có tối đa hai nút con, thường được gọi là con trái và con phải. Cấu trúc này đơn giản nhưng đủ mạnh để giải quyết nhiều bài toán phức tạp. Một cây nhị phân có thể rỗng, hoặc bao gồm một nút gốc cùng với hai cây nhị phân con là cây con trái và cây con phải. Việc hiểu rõ các thuộc tính như độ cao, tính cân bằng, và các loại cây nhị phân đặc biệt như cây hoàn chỉnh (complete tree) hay cây gần hoàn chỉnh (nearly complete tree) là rất cần thiết. Những thuộc tính này ảnh hưởng trực tiếp đến độ phức tạp của các thuật toán thao tác trên cây, đặc biệt là tìm kiếm và duyệt. Tài liệu DSA CH7 Trees nhấn mạnh rằng việc triển khai hiệu quả các thuật toán duyệt cây là kỹ năng cốt lõi khi làm việc với cây nhị phân.

2.1. Các thuộc tính và đặc điểm của một cây nhị phân

Một cây nhị phân có nhiều thuộc tính quan trọng. Với N nút, độ cao tối thiểu của cây là H_min = floor(log₂N) + 1, trong khi độ cao tối đa có thể là H_max = N (trường hợp cây bị suy biến thành danh sách liên kết). Số nút tối đa trong một cây nhị phân có độ cao H là N_max = 2^H - 1. Một khái niệm quan trọng là tính cân bằng (balance). Hệ số cân bằng của một cây được tính bằng hiệu số độ cao giữa cây con trái và cây con phải (B = H_L - H_R). Một cây được coi là cân bằng nếu hệ số cân bằng của mọi nút là -1, 0, hoặc 1 và các cây con của nó cũng cân bằng. Ngoài ra, cây nhị phân hoàn chỉnh (complete binary tree) là cây mà tất cả các tầng đều được lấp đầy, trừ tầng cuối cùng, và các nút ở tầng cuối cùng được lấp đầy từ trái sang phải. Các thuộc tính này quyết định hiệu quả của việc lưu trữ và truy xuất dữ liệu.

2.2. Kỹ thuật duyệt cây nhị phân theo chiều sâu và chiều rộng

Có hai chiến lược chính để duyệt cây (tree traversal): duyệt theo chiều sâu và duyệt theo chiều rộng. Duyệt theo chiều sâu (Depth-first traversal) xử lý theo một đường đi từ gốc xuống nút con xa nhất trước khi quay lại xử lý các nút anh em. Có ba phương pháp duyệt theo chiều sâu phổ biến: Preorder, Inorder, và Postorder. Ngược lại, duyệt theo chiều rộng (Breadth-first traversal) xử lý các nút theo từng tầng. Nó bắt đầu từ gốc, sau đó đến tất cả các con của gốc, rồi đến tất cả các cháu, và cứ thế tiếp tục. Phương pháp này thường được triển khai bằng cách sử dụng cấu trúc dữ liệu hàng đợi (Queue). Việc lựa chọn kỹ thuật duyệt phụ thuộc vào bài toán cụ thể. Ví dụ, duyệt theo chiều rộng rất hữu ích trong việc tìm đường đi ngắn nhất từ gốc đến một nút nào đó.

2.3. Phân tích 3 phương pháp duyệt sâu Preorder Inorder Postorder

Ba phương pháp duyệt theo chiều sâu là nền tảng của nhiều thuật toán cây. Preorder (NLR - Node-Left-Right): xử lý nút gốc trước, sau đó duyệt đệ quy cây con trái, và cuối cùng duyệt đệ quy cây con phải. Phương pháp này hữu ích để sao chép một cây hoặc tạo ra một biểu thức tiền tố (prefix expression) từ một cây biểu thức. Inorder (LNR - Left-Node-Right): duyệt đệ quy cây con trái, sau đó xử lý nút gốc, và cuối cùng duyệt đệ quy cây con phải. Điểm đặc biệt của Inorder là khi áp dụng trên một cây tìm kiếm nhị phân, nó sẽ trả về các phần tử theo thứ tự tăng dần. Postorder (LRN - Left-Right-Node): duyệt đệ quy cây con trái, sau đó duyệt cây con phải, và cuối cùng mới xử lý nút gốc. Phương pháp này thường được sử dụng để xóa một cây khỏi bộ nhớ, vì nó đảm bảo các nút con được xử lý trước nút cha.

III. Bí quyết tối ưu tìm kiếm với Cây Tìm Kiếm Nhị Phân BST

Để giải quyết bài toán tìm kiếm hiệu quả, Cây Tìm Kiếm Nhị Phân (Binary Search Tree - BST) được ra đời. Đây là một dạng đặc biệt của cây nhị phân với các thuộc tính nghiêm ngặt giúp tối ưu hóa thời gian tìm kiếm. Một BST là một trong những cách triển khai hiệu quả nhất cho danh sách có thứ tự. Nó kết hợp ưu điểm của tìm kiếm nhị phân trên mảng (tốc độ nhanh) và của danh sách liên kết (chèn, xóa nhanh). Theo Dr. Nguyen Ho Man Rang, đặc tính cốt lõi của BST là: với mọi nút, tất cả các giá trị trong cây con trái đều nhỏ hơn giá trị của nút đó, và tất cả các giá trị trong cây con phải đều lớn hơn hoặc bằng giá trị của nút đó. Quy tắc này được áp dụng đệ quy cho tất cả các cây con. Nhờ vậy, việc tìm kiếm một phần tử có thể loại bỏ một nửa số nút ở mỗi bước, cho độ phức tạp trung bình là O(log N).

3.1. Đặc điểm và quy tắc xây dựng một Binary Search Tree

Một Binary Search Tree (BST) hợp lệ phải tuân thủ ba quy tắc chính: 1) Tất cả các mục trong cây con trái của một nút phải nhỏ hơn giá trị của nút gốc đó. 2) Tất cả các mục trong cây con phải của một nút phải lớn hơn hoặc bằng giá trị của nút gốc đó. 3) Mỗi cây con (trái và phải) cũng phải là một cây tìm kiếm nhị phân. Các quy tắc này đảm bảo rằng dữ liệu trong cây luôn được sắp xếp theo một trật tự nhất định. Khi duyệt một BST theo phương pháp Inorder (LNR), kết quả thu được sẽ là một danh sách các phần tử đã được sắp xếp theo thứ tự tăng dần. Đây là một cách hiệu quả để kiểm tra tính hợp lệ của một BST. Tuy nhiên, hiệu suất của BST phụ thuộc rất nhiều vào hình dạng của cây. Trong trường hợp xấu nhất, nếu dữ liệu được chèn vào theo thứ tự, cây sẽ bị suy biến thành một danh sách liên kết, và thời gian tìm kiếm sẽ trở thành O(N).

3.2. Triển khai thuật toán tìm kiếm đệ quy và lặp trong BST

Việc tìm kiếm trong một BST có thể được thực hiện bằng hai phương pháp chính: đệ quy và lặp. Thuật toán tìm kiếm đệ quy (Recursive Search) hoạt động dựa trên nguyên tắc của BST. Bắt đầu từ gốc, so sánh giá trị cần tìm với giá trị của nút hiện tại. Nếu giá trị cần tìm nhỏ hơn, tiếp tục tìm kiếm trong cây con trái. Nếu lớn hơn, tìm kiếm trong cây con phải. Quá trình dừng lại khi tìm thấy giá trị hoặc khi đến một nút rỗng (null). Thuật toán tìm kiếm lặp (Iterative Search) sử dụng một vòng lặp để thực hiện cùng một logic. Nó duy trì một con trỏ đến nút hiện tại và di chuyển sang trái hoặc phải dựa trên kết quả so sánh, cho đến khi tìm thấy phần tử hoặc con trỏ trở thành null. Phương pháp lặp thường hiệu quả hơn về mặt sử dụng bộ nhớ vì nó không yêu cầu không gian trên stack cho các lời gọi đệ quy.

3.3. Thao tác tìm nút nhỏ nhất và lớn nhất trong cây BST

Nhờ vào thuộc tính sắp xếp của cây tìm kiếm nhị phân, việc tìm giá trị nhỏ nhất hoặc lớn nhất trở nên rất đơn giản. Để tìm nút có giá trị nhỏ nhất, chỉ cần bắt đầu từ nút gốc và liên tục đi theo con trỏ sang trái cho đến khi gặp một nút không có con trái. Nút đó chính là nút chứa giá trị nhỏ nhất. Tương tự, để tìm nút có giá trị lớn nhất, ta bắt đầu từ gốc và liên tục đi theo con trỏ sang phải cho đến khi gặp một nút không có con phải. Các thuật toán này, findSmallestBSTfindLargestBST như được mô tả trong tài liệu gốc, có độ phức tạp thời gian là O(H), với H là chiều cao của cây. Trong một cây cân bằng, điều này tương đương với O(log N), cực kỳ hiệu quả.

IV. Ứng dụng thực tiễn của cấu trúc dữ liệu cây Cây Biểu Thức

Ngoài việc lưu trữ và tìm kiếm, cấu trúc dữ liệu cây còn có nhiều ứng dụng mạnh mẽ khác, một trong số đó là Cây Biểu Thức (Expression Trees). Đây là một ứng dụng chuyên biệt của cây nhị phân được sử dụng để biểu diễn các biểu thức toán học. Trong một cây biểu thức, các nút lá là các toán hạng (operands) như số hoặc biến, trong khi các nút nội và nút gốc là các toán tử (operators) như +, -, *, /. Cấu trúc này loại bỏ sự cần thiết của dấu ngoặc đơn và sự mơ hồ về thứ tự ưu tiên của các phép toán. Bằng cách duyệt cây theo các thứ tự khác nhau (Preorder, Inorder, Postorder), chúng ta có thể dễ dàng chuyển đổi biểu thức sang các dạng ký pháp khác nhau như tiền tố (Prefix), trung tố (Infix), và hậu tố (Postfix). Đây là một công cụ cơ bản trong thiết kế trình biên dịch và các ứng dụng tính toán khoa học, minh họa cho tính linh hoạt của DSA CH7 Trees.

4.1. Cấu tạo và vai trò của Cây Biểu Thức Expression Trees

Một Cây Biểu Thức được xây dựng để mỗi cây con là một biểu thức con (sub-expression). Ví dụ, với biểu thức (a + b) * c, toán tử * sẽ là nút gốc. Cây con trái của nó sẽ là biểu thức a + b, với + là gốc và a, b là các nút lá. Cây con phải sẽ là nút lá c. Vai trò chính của cây biểu thức là cung cấp một cách biểu diễn không mơ hồ cho các phép tính. Thứ tự thực hiện các phép toán được xác định một cách tự nhiên bởi cấu trúc của cây: một phép toán chỉ được thực hiện sau khi tất cả các biểu thức con của nó đã được tính toán. Điều này làm cho việc phân tích và đánh giá biểu thức trở nên đơn giản và có hệ thống, một nhiệm vụ quan trọng trong quá trình biên dịch mã nguồn thành mã máy.

4.2. Cách duyệt cây để tạo biểu thức Infix Postfix và Prefix

Vẻ đẹp của Cây Biểu Thức nằm ở việc có thể tạo ra ba dạng ký pháp toán học phổ biến chỉ bằng cách thay đổi thuật toán duyệt cây. Duyệt theo thứ tự Inorder (LNR) trên cây biểu thức sẽ tạo lại biểu thức trung tố (Infix) ban đầu, tuy nhiên cần thêm dấu ngoặc đơn một cách thủ công để đảm bảo đúng thứ tự ưu tiên. Duyệt theo thứ tự Postorder (LRN) sẽ tạo ra biểu thức hậu tố (Postfix hay Ký pháp Ba Lan ngược), nơi các toán tử đi sau các toán hạng của chúng. Dạng này rất hữu ích cho các máy tính dựa trên stack. Cuối cùng, duyệt theo thứ tự Preorder (NLR) sẽ tạo ra biểu thức tiền tố (Prefix hay Ký pháp Ba Lan), nơi các toán tử đi trước các toán hạng. Mỗi cách duyệt này tương ứng với một thuật toán (infix, postfix, prefix trong tài liệu gốc) và phục vụ các mục đích tính toán khác nhau.

16/07/2025
Cấu trúc dữ liệu và thuật toán dsa ch7 trees