Biểu diễn cây nhị phân bằng mảng một chiều¶
Tóm lược nội dung
Bài này trình bày cách biểu diễn cây nhị phân bằng mảng một chiều.
Khái quát¶
Có nhiều cách để biểu diễn cây nhị phân:
- Danh sách kề
- Ma trận kề
- Tập hợp lồng nhau
- Chuỗi biểu thức
- Danh sách liên kết
- Mảng một chiều
Bài này chỉ đề cập cách biểu diễn bằng mảng một chiều.
Mô tả cách biểu diễn¶
Nguyên lý lưu trữ
Mỗi nút của cây nhị phân được ánh xạ tương ứng với một phần tử trong mảng một chiều, trong đó:
- Nút gốc luôn được lưu trữ tại chỉ số
0. - Các nút tiếp theo được đánh chỉ số liên tục, bắt đầu từ
1, theo cấp độ từ cao xuống thấp, trong cùng cấp độ thì từ trái sang phải.
Như vậy, đối với một nút nằm tại vị trí \(i\) (chỉ số \(i\)) trong mảng, ta có các công thức xác định mối quan hệ như sau:
- Chỉ số của nút con trái: \(2i + 1\)
- Chỉ số của nút con phải: \(2i + 2\)
-
Chỉ số của nút cha: \(\lfloor (i - 1) / 2 \rfloor\), với \(i \gt 0\) (1)
- \(\lfloor \text{ } \rfloor\) là ký hiệu lấy phần nguyên của phép chia.
Ví dụ:
Cho mảng một chiều như sau:
block
A(("A [0]"))
B(("B [1]"))
C(("C [2]"))
D(("D [3]"))
E(("E [4]"))
F(("F [5]"))
G(("G [6]"))
Mảng một chiều trên có thể được dùng để biểu diễn cây nhị phân dưới đây:
flowchart TD
A(("A [0]")) --> B(("B [1]"))
B --> D(("D [3]"))
B --> E(("E [4]"))
A --> C(("C [2]"))
C --> F(("F [5]"))
C --> G(("G [6]"))
Cây nhị phân trên có:
- Nút
Alà nút gốc, được lưu trữ tại chỉ số0trong mảng. - Nút
Bđược lưu tại chỉ số1. - Nút con trái của nút
Blà nútD, có chỉ số là3(do \(2 \times 1 + 1 = 3\)) - Nút con phải của nút
Blà nútA, có chỉ số là4(do \(2 \times 1 + 2 = 4\)) - Nút cha của nút
Blà nútA, có chỉ số là0(do \(\lfloor (2 - 1) / 2 \rfloor = 0\))
Ưu điểm và hạn chế¶
Ưu điểm:
- Cho phép truy xuất ngẫu nhiên với độ phức tạp \(O(1)\). Ta có thể tìm ra ngay vị trí của nút con hoặc nút cha bằng cách tính chỉ số mà không cần phải duyệt qua các nút trung gian.
- Ta không cần lưu trữ các biến liên kết, tức con trỏ. Điều này giúp giảm dung lượng bộ nhớ.
Hạn chế:
- Nếu cây bị lệch hoặc phân bố không đều, mảng sẽ phải duy trì nhiều vị trí trống nhằm bảo toàn logic của các công thức tính chỉ số. Điều này làm ảnh hưởng đến hiệu suất về mặt không gian lưu trữ.
Do đó, mảng một chiều là cách thức hiệu quả khi áp dụng cho cây nhị phân hoàn toàn và cây nhị phân hoàn mỹ.
Phân biệt các kiểu cây nhị phân
Ba khái niệm sau về cây nhị phân có thể gây nhầm lẫn.
| Cây nhị phân đầy đủ | Cây nhị phân hoàn toàn | Cây nhị phân hoàn mỹ | |
|---|---|---|---|
| Thuật ngữ tiếng Anh | full binary tree | complete binary tree | perfect binary tree |
| Định nghĩa | Mỗi nút trong cây có đúng 0 hoặc 2 nút con. Không có nút nào chỉ có một nút con. | - Mọi cấp độ của cây đều được lấp đầy, ngoại trừ cấp độ cuối cùng có thể không. - Ở cấp độ cuối cùng, các nút phải được điền từ trái sang phải một cách liên tục. |
- Tất cả các nút trong đều có 2 nút con. - Tất cả các nút lá đều ở cùng một mức. |
Chương trình minh hoạ¶
Chương trình sau đây minh hoạ cách biểu diễn cây nhị phân bằng mảng một chiều.
Định nghĩa lớp cây nhị phân¶
Định nghĩa lớp BinaryTree gồm hai thuộc tính:
- Thuộc tính
tree: là mộtlistcủa Python, đóng vai trò là mảng một chiều lưu trữ các nút của cây nhị phân. - Thuộc tính
size: lưu kích thước thực tế của mảng, tức số lượng nút của cây nhị phân.
Định nghĩa các phương thức của lớp¶
1. Viết hàm get_left_child() (1) dùng để lấy chỉ số của nút con trái.
- Khi hàm được định nghĩa bên trong một lớp thì nó được gọi là phương thức.
Hàm gồm có:
- Tham số
self: là tham số bắt buộc của bất kỳ phương thức nào trong Python, dùng để tham chiếu đến chính đối tượng đang vận hành, cho phép truy xuất các thuộc tính của lớp. - Tham số
index: biểu thị chỉ số của nút cha đang xét, tức vị trí hiện tại trong mảng. - Giá trị trả về là chỉ số của nút con bên trái.
Lưu ý:
Khi đề cập đến "chỉ số" là đang nói đến chỉ số hoặc vị trí (của một nút) trong mảng một chiều. Còn cây nhị phân thì không có khái niệm "chỉ số".
2. Viết hàm get_right_child() dùng để lấy chỉ số của nút con phải.
Hàm gồm có:
- Tham số
self - Tham số
index: biểu thị chỉ số của nút cha đang xét. - Giá trị trả về là chỉ số của nút con bên phải.
3. Viết hàm get_parent() dùng để lấy chỉ số của nút cha.
Hàm gồm có:
- Tham số
self - Tham số
index: biểu thị chỉ số của nút con đang xét. - Giá trị trả về là chỉ số của nút cha.
4. Viết hàm get_node_value() dùng để lấy dữ liệu của nút (tức giá trị của phần tử) dựa trên chỉ số của nút trong mảng.
Viết chương trình chính¶
1. Khởi tạo mảng nodes gồm 7 phần tử, mỗi phần tử chứa một ký tự.
Khởi tạo cây nhị phân bt (1) có các nút lấy từ mảng nodes (2).
-
btlà một thực thể (instance) của lớpBinaryTree. -
Với dòng lệnh
self.tree = data_array, hệ thống không tạo ra một bản sao mới của mảng, mà tạo ra một tham chiếu. Do đó, về mặt bản chất,nodesvàself.treechỉ là hai cái "nhãn tên" trỏ về cùng một danh sách trên bộ nhớ RAM.
2. Cho người dùng nhập chỉ số của nút cần xét, lưu vào biến i.
3. Đối với nút con trái:
- Gọi phương thức
get_left_child()ra để lấy chỉ số của nút con trái, lưu vào biếnli. - Gọi phương thức
get_node_value()ra để lấy giá trị của nút con trái, lưu vào biếnlv. - In ra chỉ số và giá trị của nút con trái.
4. Thực hiện tương tự đối nút con phải và nút cha.
5. Chạy chương trình trên, nhập vào 1, tức nút B, kết quả như sau:
Nhập chỉ số của một nút: 1
Nút con trái của nút B là D
Nút con phải của nút B là E
Nút cha của nút B là A
Mã nguồn¶
Code đầy đủ được đặt tại: