Chương 7 DDT

You might also like

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 38

Cây tổng hợp

Các chuyên gia về năng suất nói rằng những đột phá đến từ tư duy “phi tuyến

tính”. TRONG chương này, chúng ta thảo luận về một trong những cấu trúc dữ liệu phi

tuyến quan trọng nhất trong máy tính—cây. Cấu trúc cây thực sự là một bước đột phá

trong tổ chức dữ liệu, vì chúng cho phép chúng tôi triển khai một loạt thuật toán nhanh

hơn nhiều so với khi sử dụng cấu trúc dữ liệu tuyến tính, chẳng hạn như danh sách, vectơ

và chuỗi. Cây cối cũng cung cấp một tổ chức tự nhiên cho dữ liệu và do đó đã trở thành

cấu trúc phổ biến trong hệ thống tập tin, giao diện đồ họa người dùng, cơ sở dữ liệu, trang

Web và các máy tính khác hệ thống.

Không phải lúc nào các chuyên gia về năng suất cũng hiểu rõ ý nghĩa của tư duy

“phi tuyến tính”, nhưng khi chúng ta nói rằng cây cối là “phi tuyến tính”, chúng ta đang

đề cập đến một tổ chức mối quan hệ phong phú hơn các mối quan hệ “trước” và “sau”

đơn giản giữa các đối tượng theo trình tự. Các mối quan hệ trong cây có tính phân cấp,

với một số các đối tượng “ở trên” và một số đối tượng “ở dưới” những đối tượng khác.

Trên thực tế, thuật ngữ chính đối với cấu trúc dữ liệu cây xuất phát từ cây gia phả, với

các thuật ngữ “cha”, “con”,“tổ tiên” và “con cháu” là những từ phổ biến nhất được sử

dụng để mô tả mối quan hệ tàu thuyền. Chúng tôi đưa ra một ví dụ về cây gia đình trong

Hình 7.1.
Hình 7.1: Cây gia phả cho thấy một số hậu duệ của Abraham, như được ghi lại trong

Sáng Thế Ký, chương 25–36.


7.1.1 Định nghĩa và tính chất của cây
Cây là một kiểu dữ liệu trừu tượng lưu trữ các phần tử theo thứ bậc. Với ngoại lệ- của

phần tử trên cùng, mỗi phần tử trong cây có một phần tử cha và bằng 0 hoặc nhiều yếu tố

trẻ em hơn. Một cái cây thường được hình dung bằng cách đặt các phần tử bên trong

hình bầu dục hoặc hình chữ nhật và bằng cách vẽ ra mối liên hệ giữa cha mẹ và con cái

với những đường thẳng. Chúng ta thường gọi phần tử trên cùng là gốc của cây, nhưng nó

được coi là phần tử cao nhất, với các phần tử khác là được kết nối bên dưới (ngay đối

diện với cây thực vật).

Định nghĩa cây chính thức


Về mặt hình thức, chúng ta định nghĩa cây T là một tập hợp các nút lưu trữ các phần tử

trong phần cha-con mối quan hệ với các tính chất sau:
-Nếu T khác rỗng thì nó có một nút đặc biệt, gọi là gốc của T, không có cha mẹ.

-Mỗi nút v của T khác với nút gốc có một nút cha duy nhất w; mọi nút có cha w là con

của w.

--Lưu ý: rằng theo định nghĩa của chúng tôi, một cây có thể trống, nghĩa là nó không có

bất kỳ nút nào.Quy ước này cũng cho phép chúng ta định nghĩa một cây theo cách đệ

quy, chẳng hạn như rằng cây T trống hoặc chứa nút r, được gọi là gốc của T và một tập

hợp cây (có thể trống) có gốc là con của r.

Các mối quan hệ nút khác:

 Hai nút là con của cùng một cha mẹ là anh em. Nút v là ở ngoài nếu v

không có con.

 Nút v là nút nội nếu nó có một hoặc nhiều nút con. Bên ngoài các nút còn

được gọi là lá.

Ví dụ 7.1: Trong hầu hết các hệ điều hành, các tệp được sắp xếp theo thứ bậc thành thư

mục lồng nhau (còn gọi là thư mục), được trình bày cho người dùng dưới dạng của một

cái cây. (Xem Hình 7.3.) Cụ thể hơn, các nút bên trong của cây là được liên kết với các

thư mục và các nút bên ngoài được liên kết với các tệp thông thường. Trong hệ điều hành

UNIX và Linux, gốc của cây được đặt một cách thích hợp được gọi là “thư mục gốc” và

được biểu thị bằng ký hiệu “/”.


Hình 7.3: Cây đại diện cho một phần của hệ thống tập tin.

Nút u là tổ tiên của nút v nếu u = v hoặc u là tổ tiên của nút cha của v.

Ngược lại, chúng ta nói rằng nút v là hậu duệ của nút u nếu u là nút tổ tiên của v.

Ví dụ, trong Hình 7.3, cs252/ là tổ tiên của các giấy tờ/, và pr3 là một hậu duệ của

cs016/. Cây con của T có gốc tại nút v là cây gồm tất cả các con cháu của v trong T (kể

cả chính v).

Trong Hình 7.3, cây con có gốc tại cs016/ bao gồm các nút cs016/, điểm, bài tập về nhà/,

chương trình/, hw1, hw2,hw3, pr1, pr2 và pr3.

Các cạnh và đường dẫn trong cây


- Một cạnh của cây T là một cặp nút (u,v) sao cho u là cha của v, hoặc ngược lại

- Đường đi của T là một chuỗi các nút sao cho hai nút liên tiếp bất kỳ trong dãy tạo

thành một cạnh.


Cây đặt hàng
Một cây được sắp xếp nếu có thứ tự tuyến tính được xác định cho các nút con

của mỗi nút; nghĩa là, chúng ta có thể xác định các nút con của một nút là nút thứ nhất,

thứ hai, thứ ba, v.v. Thứ tự như vậy được xác định bởi cách sử dụng cây và thường được

biểu thị bằng cách vẽ cây có các cây anh em được sắp xếp từ trái sang phải, tương ứng

với mối quan hệ tuyến tính của chúng. Cây có thứ tự thường biểu thị mối quan hệ thứ tự

tuyến tính tồn tại giữa các cây anh em bằng cách liệt kê chúng theo trình tự hoặc trình

vòng lặp trong theo đúng thứ tự.

• Hàm cây

Cây ADT lưu trữ các phần tử tại các nút của cây.

Thay vì, mỗi nút của cây được liên kết với một đối tượng vị trí, cung cấp công khai truy

cập vào các nút. Vì lý do này, khi thảo luận về giao diện chung của các chức năng của

ADT, chúng tôi sử dụng ký hiệu p (chứ không phải v) để làm rõ rằng đối số cho chức

năng là một vị trí chứ không phải một nút. Tuy nhiên, do mối liên hệ chặt chẽ giữa hai

đối tượng này, chúng ta thường làm mờ đi sự khác biệt giữa chúng và sử dụng các thuật

ngữ “Vị trí” và “nút” thay thế cho cây.

Chương 6, chúng ta khai thác khả năng của C++ trong việc nạp chồng toán tử

hội thảo (“*”) để truy cập phần tử được liên kết với một chức vụ. Cho một biến vị trí p,

phần tử liên quan được truy cập bởi *p.Điều này có thể được sử dụng cho cả việc đọc và

sửa đổi giá trị của phần tử. Nó rất hữu ích để lưu trữ các bộ sưu tập các vị trí.

Ví dụ: các nút con của một nút trong cây có thể được hiển thị cho người dùng dưới dạng

danh sách như vậy. Ta xác định danh sách vị trí là một danh sách có các thành phần là

vị trí cây.
Sức mạnh thực sự của phát hiện cây trí tuệ từ khả năng tiếp cận các vị trí lân cận

các yếu tố của cây. Cho vị trí p của cây T, ta xác định như sau:

-p.parent():Trả về cha mẹ của p; lỗi xảy ra nếu p là gốc.

-p.children():Trả về danh sách vị trí chứa nút con của nút p.

Nếu p là một nút bên ngoài thì p.children() trả về một giá trị trống danh sách.

-p.isRoot():Trả về true nếu p là gốc và ngược lại là false.

-p.isExternal():Trả về true nếu p là bên ngoài và sai nếu không.

-p.isInternal(), hàm này sẽ chỉ cần trả về phần bù của p.isExternal().

Cây cung cấp các chức năng:

- Hai hàm đầu tiên, size(kích thước) và empty(trống), chỉ là các hàm tiêu chuẩn mà

chúng ta đã xác định cho các loại vùng chứa khác mà chúng ta đã thấy.

- Hàm root mang lại vị trí của gốc và các vị trí tạo ra một danh sách chứa tất cả các

nút của cây.

-size(kích thước):Trả về số nút trong cây.

-empty(trống):Trả về true nếu cây trống và sai nếu không.

-root(nguồn gốc): Trả về vị trí cho gốc của cây, lỗi xảy ra nếu cây trống rỗng.

-positions(vị trí): Trả về danh sách vị trí của tất cả các nút của cây.

Thay vì, muốn mô tả các chức năng cập nhật cây khác nhau kết hợp với các ứng

dụng của cây ở các chương tiếp theo. Trên thực tế, chúng ta có thể tưởng tượng ra nhiều

loại các hoạt động cập nhật cây ngoài những hoạt động được đưa ra trong cuốn sách này.
7.1.3 giao diện cây C++
template <typename E> // loại phần tử cơ sở

class Position<E> { // một vị trí nút

E& operator*(); // lấy phần tử

Position parent() const; // lấy cha mẹ

PositionList children() const; // lấy con của nút

bool isRoot() const; // Nút gốc

bool isExternal() const; // nút bên ngoài?

};

Đoạn mã 7.1: Giao diện không chính thức cho một vị trí trong cây (không phải

là một giao diện hoàn chỉnh lớp C++).

template <typename E> // loại phần tử cơ sở

class Tree<E> {

public: // các loại công khai

class Position; // một vị trí nút

class PositionList; // một danh sách các vị trí

public: // chức năng công cộng

int size() const; // số nút

bool empty() const; // cây có trống không?

Position root() const; // lấy gốc

PositionList positions()const; // lấy vị trí của tất cả các nút

Đoạn mã 7.2: Giao diện không chính thức cho cây ADT (không phải là một lớp

hoàn chỉnh).
7.1.4 Cấu trúc liên kết cho cây thông thường

Chúng ta biểu diễn mỗi nút của T bởi một đối tượng vị trí p (xem Hình 7.5(a))

với các trường sau: một tham chiếu đến phần tử của nút, một liên kết tới nút cha của nút

và một số loại bộ sưu tập (ví dụ: danh sách hoặc mảng) để lưu trữ các liên kết đến nút con

của nút. Nếu p là gốc của T thì trường cha của p là NULL. T và số nút của T trong các

biến nội. được minh họa bằng sơ đồ trong Hình 7.5(b).

Hình 7.5: Cấu trúc liên kết của cây tổng quát: (a) cấu trúc nút; (b) cái phần cấu

trúc dữ liệu được liên kết với một nút và các nút con của nó.

Bảng 7.1 tóm tắt hiệu suất triển khai cấu trúc liên kết

của một cái cây. Phân tích này được để lại dưới dạng bài tập (C-7.27), nhưng chúng tôi

lưu ý rằng, bằng cách sử dụng container để lưu trữ các nút con của mỗi nút p, chúng ta có

thể triển khai các nút con (p) bằng cách sử dụng trình vòng lặp cho vùng chứa để liệt kê

các phần tử của nó.


7.2 Thuật toán truyền tải cây
Trong phần này, chúng tôi trình bày các thuật toán để thực hiện tính toán truyền

tải trên một cây bằng cách truy cập nó thông qua các hàm ADT của cây.

7.2.1 Chiều sâu và chiều cao

Cho p là một nút của cây T. Độ sâu của p là số tổ tiên của p, không bao gồm chính p.Lưu

ý rằng định nghĩa này hàm ý rằng độ sâu của nghiệm của T là 0. Độ sâu của nút p cũng

có thể được xác định đệ quy như sau:

 Nếu p là nghiệm thì độ sâu của p là 0

 Ngược lại, độ sâu của p bằng một cộng với độ sâu của phần tử cha của p

Dựa trên định nghĩa trên, độ sâu thuật toán đệ quy (T, p) được hiển thị trong đoạn mã 7.3,

tính toán độ sâu của nút được tham chiếu bởi vị trí p của T bằng gọi chính nó một cách đệ

quy trên phần tử cha của p và thêm 1 vào giá trị được trả về.

int depth(const Tree& T, const Position& p) {

if (p.isRoot())

return 0; // gốc có độ sâu 0

else

return 1 + depth(T, p.parent()); // 1 + (độ sâu của cha mẹ)


Chiều cao của nút p trong cây T cũng được xác định đệ quy.

 Nếu p ở ngoài thì chiều cao của p bằng 0

 Ngược lại, chiều cao của p bằng một cộng với chiều cao tối đa của con của p

`int height1(const Tree& T) {

int h = 0;

PositionList nodes = T.positions(); // danh sách tất cả các nút

for (Iterator q = nodes.begin(); q != nodes.end(); ++q) {

if (q−>isExternal())

h = max(h, depth(T, *q)); // đạt được độ sâu tối đa giữa các lá

return h;

Đoạn mã 7.6: Triển khai hàm cao trong C++.

Ta thấy thuật toán chiều cao không hiệu quả lắm. Vì thuật toán gọi cao

độ sâu nhịp điệu (p) trên mỗi nút bên ngoài p của T, thời gian chạy của chiều cao1 được

tính bằng O(n+∑p(1+dp)), trong đó n là số nút của T, dp là độ sâu của nút p và E là tập

hợp các nút bên ngoài của T. Trong trường hợp xấu nhất, tổng sum ∑p(1+ dp) tỷ lệ thuận

với n2,Do đó, thuật toán cao chạy trong O(n2)

Chiều cao thuật toán 2, được hiển thị trong Đoạn mã 7.8, tính toán chiều cao của

cây T theo cách hiệu quả hơn bằng cách sử dụng định nghĩa đệ quy về chiều cao.
int height2(const Tree& T, const Position& p) {

if (p.isExternal()) return 0; // lá có chiều cao 0

int h = 0;

PositionList ch = p.children(); // danh sách trẻ em

for (Iterator q = ch.begin(); q != ch.end(); ++q)

h = max(h, height2(T, *q));

return 1 + h; // 1 + chiều cao tối đa của trẻ

Đoạn mã 7.8: Phương thức chiều cao 2 được viết bằng C++.

Thuật toán chiều cao 2 hiệu quả hơn chiều cao 1 (từ Đoạn mã 7.5).Thuật toán có

tính đệ quy và nếu ban đầu nó được gọi trên gốc của T, nó sẽ cuối cùng được gọi trên mỗi

nút của T. Do đó, chúng ta có thể xác định thời gian chạy của phương pháp này bằng

cách tính tổng, trên tất cả các nút, lượng thời gian dành cho mỗi nút (ở phần không đệ

quy). Xử lý từng nút ở trẻ em (p) mất O(cp) thời gian, trong đó cp biểu thị số nút con của

nút p. Ngoài ra, vòng lặp while có các lần lặp cp và mỗi lần lặp của vòng lặp mất O(1)

thời gian cộng với thời gian cho lệnh gọi đệ quy lên phần tử con của p. Do đó, thuật toán

có chiều cao 2 tiêu tốn thời gian O(1+ cp) tại mỗi nút p và thời gian chạy của nó là

O(∑p(1 + cp)). Để hoàn thành việc phân tích, ta sử dụng tính chất sau.

Mệnh đề 7.5: Cho T là một cây có n nút và cp là số lượng con của nút p của T.

Khi đó ∑pcp = n−1.

Giải thích: Mỗi nút của T, ngoại trừ nút gốc, là con của một nút khác nút và do đó đóng

góp một đơn vị vào tổng trên. Theo Mệnh đề 7.5, thời gian chạy của thuật toán có độ cao

2, khi được gọi trên nghiệm của T, là O(n), trong đó n là số nút của T.
Duyệt cây T là một cách có hệ thống để truy cập hoặc “thăm” tất cả các nút của

T. Trong phần này, chúng tôi trình bày một sơ đồ duyệt cơ bản cho cây, được gọi là duyệt

theo thứ tự trước. Trong phần tiếp theo, chúng ta nghiên cứu một sơ đồ truyền tải cơ bản

khác, được gọi là truyền tải sau thứ tự.

Trong phép duyệt cây T theo thứ tự trước, gốc của T được thăm trước và sau đó

mới đến các cây con có gốc tại các cây con của nó được duyệt đệ quy.

Nếu cây được đặt hàng thì các cây con được duyệt theo thứ tự của các cây con.Hành

động cụ thể liên quan đến “lượt truy cập” của một nút phụ thuộc vào ứng dụng của quá

trình truyền tải này và có thể liên quan đến bất cứ điều gì từ việc tăng bộ đếm đến thực

hiện một số thao tác phức tạp tính toán cho nút này. Mã giả cho việc duyệt thứ tự trước

của cây con có gốc tại nút được tham chiếu bởi vị trí p được hiển thị trong Đoạn mã 7.9.

Ban đầu gọi thủ tục này bằng lệnh gọi trước (T,T.root()).

Algorithm preorder(T, p):

perform the “visit” action for node p

for each child q of p do

recursively traverse the subtree rooted at q by calling preorder(T,q)

Đoạn mã 7.9: Thứ tự trước của thuật toán để thực hiện việc duyệt theo thứ tự

trước của cây con của cây T có gốc tại nút p.Thuật toán duyệt theo thứ tự trước rất hữu

ích trong việc tạo ra thứ tự tuyến tính của các nút của cây trong đó nút cha luôn phải đứng

trước con của họ trong thứ tự. Thứ tự như vậy có một số ứng dụng khác nhau. Chúng ta

khám phá một ví dụ đơn giản của ứng dụng như vậy trong ví dụ tiếp theo.
Hình 7.6: Duyệt cây theo thứ tự trước, trong đó các nút con của mỗi nút được sắp xếp từ

trái sang phải.

Việc duyệt theo thứ tự trước cũng là một cách hiệu quả để truy cập vào tất cả các

nút của cây. Để chứng minh điều này, chúng ta hãy xem xét thời gian chạy của quá trình

duyệt cây theo thứ tự trước T với n nút với giả định rằng việc truy cập một nút mất O(1)

thời gian. Các phân tích thuật toán duyệt theo thứ tự trước thực sự tương tự như thuật

toán chiều cao 2 (Đoạn mã 7.8), được nêu trong Phần 7.2.1. Tại mỗi nút p, điểm không

phần chữ thảo của thuật toán duyệt thứ tự trước yêu cầu thời gian O(1+ cp), trong đó cp

là số con của p. Do đó, theo Dự luật 7.5, tổng thời gian chạyviệc duyệt thứ tự trước của T

là O(n).

Thuật toán preorderPrint(T, p), được triển khai trong C++ trong Đoạn mã 7.10,

thực hiện việc in thứ tự trước cây con của nút p của T, nghĩa là nó thực hiện duyệt cây

con có gốc tại p và in ra phần tử được lưu trữ tại nút khi nút được truy cập. Hãy nhớ lại

rằng, đối với cây có thứ tự T, hàm T.children(p) trả về một trình vòng lặp truy cập các

phần tử con của p theo thứ tự. Chúng tôi giả định rằng Iterator đây là loại vòng lặp. Cho

một iterator q, vị trí liên quan được cho bởi *q.


void preorderPrint(const Tree& T, const Position& p) {

cout << *p; // in phần tử

PositionList ch = p.children(); // danh sách nút con

for (Iterator q = ch.begin(); q != ch.end(); ++q) {

cout << " ";

preorderPrint(T, *q);

Đoạn mã 7.10: Phương thức preorderPrint(T, p) thực hiện việc in thứ tự trước

của các phần tử trong cây con tương ứng với vị trí p của T.

Có một biến thể thú vị của hàm preorderPrint tạo ra một biểu diễn khác nhau của toàn bộ

cây. Biểu diễn chuỗi ngoặc đơn P(T) của cây T được định nghĩa đệ quy như sau. Nếu T

bao gồm một nút duy nhất được tham chiếu bởi vị trí p, sau đó

P(T) = *p.

Nếu không thì,

P(T) = *p+"("+P(T1) +P(T2) +···+P(Tk)+")",

trong đó p là vị trí gốc của T và T1,T2,...,Tk là các cây con có gốc tại con của p, được sắp

xếp theo thứ tự nếu T là cây có thứ tự.

Lưu ý rằng định nghĩa của P(T) là đệ quy. Ngoài ra, chúng tôi đang sử dụng “+”

ở đây để biểu thị nối chuỗi. (Nhắc lại kiểu chuỗi ở Phần1.1.3.)biểu diễn trong dấu ngoặc

đơn của cây Hình 7.2 được thể hiện trong Hình 7.7. Biểu diễn cây của Hình 7.2 được thể

hiện trong Hình 7.7.


Electronics R’Us (

R&D

Sales (

Domestic

International (

Canada

S.America

Overseas ( Africa Europe Asia Australia ) ) )

Purchasing

Manufacturing ( TV CD Tuner ) )

Hình 7.7: Biểu diễn trong ngoặc đơn của cây Hình 7.2. Thụt lề, dòng dấu ngắt và khoảng

trắng đã được thêm vào cho rõ ràng.

Lưu ý rằng, về mặt kỹ thuật, có một số tính toán xảy ra giữa và sau các lệnh gọi

đệ quy tại các nút con trong thuật toán trên. Tuy nhiên, ta vẫn coi thuật toán này là truyền

tải theo thứ tự trước, vì thuật toán chính hành động in nội dung của nút xảy ra trước các

lệnh gọi đệ quy.

Hàm parenPrint trong C++, được trình bày trong Đoạn Mã 7.11, là một biến thể

của chức năng preorderPrint (Đoạn mã 7.10). Nó thực hiện định nghĩa được đưa ra ở trên

để xuất ra một biểu diễn chuỗi ngoặc đơn của cây T. Đầu tiên, nó in phần tử liên kết với

mỗi nút. Đối với mỗi nút bên trong, trước tiên chúng tôi in “(”, theo sau bằng cách biểu

diễn trong ngoặc đơn của mỗi phần tử con của nó, theo sau là “)”.
void parenPrint(const Tree& T, const Position& p) {

cout << *p; // in phần tử của nút

if (!p.isExternal()) {

PositionList ch = p.children(); // danh sách nút con

cout << "( "; // mở

for (Iterator q = ch.begin(); q != ch.end(); ++q) {

if (q != ch.begin()) cout << " "; // in dấu phân cách

parenPrint(T, *q); // nút con tiếp theo

cout << " )"; // đóng

Đoạn mã 7.11: Triển khai thuật toán parenPrint trong C++.

7.2.3 Truyền tải thứ tự sau


Một thuật toán duyệt cây quan trọng khác là duyệt theo thứ tự sau. Thuật toán

này có thể được coi là đối lập với việc duyệt theo thứ tự trước, bởi vì nó đệ quy duyệt qua

các cây con có gốc tại các cây con trước tiên, sau đó mới đến thăm gốc.Tuy nhiên, nó

tương tự như việc duyệt thứ tự trước ở chỗ chúng ta sử dụng nó để giải quyết một vấn đề

cụ thể. vấn đề bằng cách chuyên biệt hóa một hành động liên quan đến “lượt truy cập”

của nút p.
Tuy nhiên, giống như việc duyệt theo thứ tự trước, nếu cây được sắp thứ tự,

chúng ta thực hiện các lệnh gọi đệ quy cho các nút con của nút p theo thứ tự được chỉ

định của chúng. Mã giả cho truyền tải sau thứ tự được đưa ra trong Đoạn mã 7.12.

Algorithm postorder(T, p):

for each child q of p do

recursively traverse the subtree rooted at q by calling postorder(T,q)

perform the “visit” action for node p

Đoạn mã 7.12: Thuật toán postorder để thực hiện việc duyệt postorder cây con của cây T

có gốc tại nút p.

Tên của quá trình duyệt thứ tự sau xuất phát từ thực tế là phương thức truyền tải

này truy cập vào nút p sau khi nó đã truy cập tất cả các nút khác trong cây con có gốc tại

p. (Xem Hình 7.8.)

Hình 7.8: Duyệt thứ tự sau của cây có thứ tự trong Hình 7.6.

Việc phân tích thời gian chạy của việc duyệt theo thứ tự sau cũng tương tự như

việc phân tích thời gian chạy của việc duyệt theo thứ tự trước. (Xem Phần 7.2.2.) Tổng

thời gian dành cho các phần không đệ quy của thuật toán tỷ lệ thuận với thời gian dành

cho việc truy cập các nút con của mỗi nút trong cây. Do đó, việc duyệt cây T với n nút sẽ
mất O(n) thời gian, giả sử rằng việc truy cập mỗi nút mất O(1) thời gian. Nghĩa là, quá

trình duyệt thứ tự sau chạy theo thời gian tuyến tính. Trong Đoạn mã 7.13, chúng tôi

trình bày một hàm C++ postorderPrint thực hiện việc duyệt thứ tự sau của cây T. Hàm

này in phần tử được lưu trữ tại một nút khi nó được truy cập.

void postorderPrint(const Tree& T, const Position& p) {

PositionList ch = p.children(); // danh sách nút con

for (Iterator q = ch.begin(); q != ch.end(); ++q) {

postorderPrint(T, *q);

cout << " ";

cout << *p; } // in phần tử

Đoạn mã 7.13: Hàm postorderPrint(T, p), in ra các phần tử của cây con ở vị trí p của T.

Phương pháp duyệt thứ tự sau rất hữu ích để giải quyết các bài toán trong đó

chúng ta muốn tính một số thuộc tính cho mỗi nút p trong cây, nhưng việc tính thuộc tính

đó cho p yêu cầu chúng ta phải tính cùng thuộc tính đó cho các nút con của p. Một ứng

dụng như vậy được minh họa trong ví dụ sau.

Ví dụ 7.7: Xét cây hệ thống tệp T, trong đó các nút bên ngoài biểu thị các tệp và

các nút bên trong biểu thị các thư mục (Ví dụ 7.1). Giả sử chúng ta muốn tính toán dung

lượng ổ đĩa được sử dụng bởi một thư mục, được tính đệ quy bằng tổng của các giá trị

sau (xem Hình 7.9):

 Kích thước của chính thư mục

• Kích thước của các tập tin trong thư mục

• Không gian được sử dụng bởi các thư mục con


Hình 7.9: Cây trong Hình 7.3 biểu thị một hệ thống tệp, hiển thị tên và kích thước của

tệp/thư mục được liên kết bên trong mỗi nút và không gian đĩa được sử dụng bởi thư mục

liên kết phía trên mỗi nút bên trong.

Sau khi duyệt qua các cây con của nút bên trong p, chúng ta tính toán không

gian được sử dụng bởi p bằng cách cộng các kích thước của chính thư mục p và của các

tệp chứa trong p, để không gian được sử dụng bởi mỗi phần tử con bên trong của p, được

tính bằng cách duyệt thứ tự hậu đệ quy của các phần tử con của p.

Thuật toán diskSpace, được trình bày trong Đoạn Mã 7.14, thực hiện duyệt theo

thứ tự sau của cây hệ thống tệp T, in tên và không gian đĩa được sử dụng bởi thư mục liên

kết với mỗi nút bên trong của T. Khi được gọi trên gốc của cây T, diskSpace chạy trong

thời gian O(n), trong đó n là số nút của cây, với điều kiện là các hàm phụ trợ name(p) và

size(p) mất O(1) thời gian.

int diskSpace(const Tree& T, const Position& p) {

int s = size(p); // bắt đầu với kích thước của p

if (!p.isExternal()) { // nếu p là nội


PositionList ch = p.children(); // danh sách con của p

for (Iterator q = ch.begin(); q != ch.end(); ++q)

s += diskSpace(T, *q); // tính tổng không gian của cây con

cout << name(p) << ": " << s << endl; // in tóm tắt

return s;

Đoạn mã 7.14: Hàm diskSpace, in tên và dung lượng ổ đĩa được thư mục liên kết

với p sử dụng, cho mỗi nút bên trong p của cây hệ thống tệp T. Hàm này gọi tên và kích

thước của các hàm phụ trợ, cần được xác định để trả về tên và kích thước của tệp/thư mục

được liên kết với một nút.

Truyền tải theo thứ tự trước rất hữu ích khi chúng ta muốn thực hiện một hành

động cho một nút và sau đó thực hiện đệ quy hành động đó cho các nút con của nó, và

truyền tải theo thứ tự sau rất hữu ích khi chúng ta muốn thực hiện một hành động trước

tiên trên các nút con của một nút và sau đó thực hiện hành động đó trên nút con của nút.

Mặc dù việc duyệt thứ tự trước và thứ tự sau là những cách phổ biến để truy cập

các nút của cây, chúng ta cũng có thể hình dung ra các cách duyệt khác. Ví dụ: chúng ta

có thể duyệt một cây để truy cập tất cả các nút ở độ sâu d trước khi truy cập các nút ở độ

sâu d+1. Việc duyệt như vậy, được gọi là duyệt theo chiều rộng, có thể được triển khai

bằng cách sử dụng hàng đợi, trong khi việc duyệt thứ tự trước và thứ tự sau sử dụng ngăn

xếp. (Ngăn xếp này tiềm ẩn trong việc chúng ta sử dụng đệ quy để mô tả các hàm này,

nhưng chúng ta cũng có thể làm cho việc sử dụng này trở nên rõ ràng để tránh đệ quy.)

Ngoài ra, cây nhị phân mà chúng ta sẽ thảo luận tiếp theo, hỗ trợ một phương pháp truyền

tải bổ sung được gọi là duyệt theo thứ tự.


7.3 Cây nhị phân
Cây nhị phân là cây có thứ tự trong đó mỗi nút có nhiều nhất hai nút con.

1. Mỗi nút có tối đa hai nút con.

2. Mỗi nút con được gắn nhãn là nút con trái hoặc nút con phải.

3. Con trái đứng trước con phải theo thứ tự các con của một nút.

Cây con có gốc ở nút con trái hoặc phải của nút bên trong được gọi tương ứng là

cây con trái hoặc cây con phải của nút đó. Cây nhị phân là phù hợp nếu mỗi nút có 0

hoặc 2 nút con. Một số người cũng coi những cây như vậy là cây nhị phân đầy đủ. Vì

vậy, trong một cây nhị phân thích hợp, mỗi nút bên trong có đúng hai nút con. Cây nhị

phân không phù hợp là không phù hợp.

Ví dụ 7.8: Một lớp cây nhị phân quan trọng xuất hiện trong các bối cảnh mà

chúng ta muốn biểu diễn một số kết quả khác nhau có thể xảy ra từ việc trả lời một loạt

câu hỏi có hoặc không. Mỗi nút nội bộ được liên kết với một câu hỏi Bắt đầu từ gốc,

chúng ta đi đến nút con trái hoặc phải của nút hiện tại, tùy thuộc vào câu trả lời cho câu

hỏi là “Có” hay “Không”. Với mỗi quyết định, chúng ta đi theo một cạnh từ nút cha đến

nút con, cuối cùng dò tìm một đường dẫn trong cây từ gốc đến nút bên ngoài. Những cây

nhị phân như vậy được gọi là cây quyết định, bởi vì mỗi nút p bên ngoài trong cây như

vậy thể hiện một quyết định phải làm gì nếu các câu hỏi liên quan đến tổ tiên của p được

trả lời theo cách dẫn đến p. Cây quyết định là cây nhị phân thích hợp.

Hình 7.10 minh họa cây quyết định cung cấp các khuyến nghị cho nhà đầu tư tiềm năng.
Hình 7.10: Cây quyết định cung cấp lời khuyên đầu tư.

Ví dụ 7.9: Một biểu thức số học có thể được biểu diễn bằng một cây có các nút

bên ngoài được liên kết với các biến hoặc hằng và các nút bên trong của nó được liên kết

với một trong các toán tử +, −, × và /. (Xem Hình 7.11.) Mỗi nút trong cây như vậy có

một giá trị liên kết với nó.

• Nếu một nút là bên ngoài thì giá trị của nó là biến hoặc hằng.

• Nếu một nút là bên trong thì giá trị của nó được xác định bằng cách áp dụng phép toán

của nó cho các giá trị của các nút con của nó.

Cây biểu thức số học như vậy là cây nhị phân thực sự, vì mỗi toán tử +, −, × và /

lấy chính xác hai toán hạng. Tất nhiên, nếu chúng ta cho phép các toán tử một ngôi, như

phủ định (-), như trong “−x,” thì chúng ta có thể có một cây nhị phân không đúng.
Hình 7.11: Cây nhị phân biểu diễn một biểu thức số học. Cây này biểu thị biểu

thức ((((3+1)×3)/((9−5)+2))−((3×(7−4))+6)). Giá trị được liên kết với nút bên trong có

nhãn “/” là 2.

Định nghĩa cây nhị phân đệ quy

Ngẫu nhiên, chúng ta cũng có thể định nghĩa cây nhị phân theo cách đệ quy sao cho cây

nhị phân trống hoặc bao gồm:

• Nút r, gọi là gốc của T và lưu trữ một phần tử

• Cây nhị phân, gọi là cây con trái của T

• Cây nhị phân, gọi là cây con bên phải của T

7.3.1 Cây nhị phân ADT


Trong phần này, chúng tôi giới thiệu kiểu dữ liệu trừu tượng cho cây nhị phân.

Giống như ADT cây trước đây của chúng ta, mỗi nút của cây lưu trữ một phần tử và được

liên kết với đối tượng vị trí, cung cấp quyền truy cập công cộng vào các nút. Bằng cách

nạp chồng toán tử tham chiếu, phần tử được liên kết với vị trí p có thể được truy cập bởi

*p. Ngoài ra, vị trí p còn hỗ trợ các thao tác sau.

p.left(): Trả về con trái của p; một tình trạng lỗi xảy ra nếu p là một nút bên

ngoài.
p.right(): Trả về con phải của p; một tình trạng lỗi xảy ra nếu p là một nút bên

ngoài.

p.parent(): Trả về cha của p; lỗi xảy ra nếu p là gốc.

p.isRoot(): Trả về true nếu p là gốc và ngược lại là false.

p.isExternal(): Trả về true nếu p là bên ngoài và sai nếu không.

Cây cũng cung cấp các hoạt động tương tự như cây tiêu chuẩn ADT. Như size(),

empty(), root(), positions().

7.3.2 Giao diện cây nhị phân C++


Ta trình bày một giao diện C++ không chính thức cho cây nhị phân ADT. Bắt

đầu trong Đoạn mã 7.15 bằng cách trình bày một giao diện C++ không chính thức cho

lớp Vị trí, đại diện cho một vị trí trong cây. Nó khác với giao diện cây ở Phần 7.1.3 ở chỗ

thay thế hàm thành viên cây con bằng hai hàm left và right.

template <typename E> // loại phần tử cơ sở

class Position<E> { // một vị trí nút

public:

E& operator*(); // lấy phần tử

Position left() const; // lấy con trái

Position right() const; // lấy con phải

Position parent() const; // lấy cha mẹ

bool isRoot() const; // nút gốc?

bool isExternal() const; // một nút bên ngoài?

};

Đoạn mã 7.15: Giao diện không chính thức cho cây nhị phân ADT (không phải là lớp C+

+ hoàn chỉnh).
Tiếp theo, trong Đoạn mã 7.16, trình bày giao diện C++ không chính thức cho

cây nhị phân. Để giữ cho giao diện đơn giản nhất có thể, ta bỏ qua việc xử lý lỗi và do đó

ta không khai báo bất kỳ ngoại lệ nào sẽ được đưa ra.

template <typename E> // loại phần tử cơ sở

class BinaryTree<E> { // cây nhị phân

public: // public types

class Position; // một vị trí nút

class PositionList; // một danh sách các vị trí

public: // member functions

int size() const; // số nút

bool empty() const; // cây có trống không?

Position root() const; // lấy nút gốc

PositionList positions() const; // danh sách các nút

};

Cây nhị phân có một số thuộc tính thú vị liên quan đến mối quan hệ giữa chiều

cao và số nút của chúng. Chúng ta biểu thị tập hợp tất cả các nút của cây T, ở cùng độ sâu

d, là mức d của T. Trong cây nhị phân, mức 0 có một nút (gốc), cấp 1 có nhiều nhất hai

nút (con của nút gốc), cấp 2 có nhiều nhất là bốn nút, v.v. (Xem Hình 7.12.) Nói chung,

cấp d có nhiều nhất là các nút 2d.

Mệnh đề 7.10: Cho T là cây nhị phân khác rỗng và gọi n, nE, nI và h lần lượt là số

nút, số nút ngoài, số nút trong và chiều cao của T. Khi đó T có các tính chất sau:

1. h+1 ≤ n ≤ 2h+1 −1

2. 1 ≤ nE ≤ 2h

3. h ≤ nI ≤ 2h −1
4. log(n+1)−1 ≤ h ≤ n−1

Ngoài ra, nếu T đúng thì nó có các thuộc tính sau:

1. 2h+1 ≤ n ≤ 2h+1 −1

2. h+1 ≤ nE ≤ 2h

3. h ≤ nI ≤ 2h −1

4. log(n+1)−1 ≤ h ≤ (n−1)/2

Hình 7.12: Số nút tối đa ở các cấp của cây nhị phân.

Ngoài các thuộc tính của cây nhị phân ở trên, chúng ta còn có mối quan hệ sau

đây giữa số lượng nút bên trong và nút bên ngoài trong một cây nhị phân thích hợp.

Mệnh đề 7.11: Trong cây nhị phân T khác rỗng, số nút bên ngoài nhiều hơn số nút bên

trong một.

Giải thích: Chúng ta có thể thấy điều này bằng cách sử dụng lập luận dựa trên quy nạp.

Nếu cây bao gồm một nút gốc duy nhất thì rõ ràng chúng ta có một nút bên ngoài và

không có nút bên trong nào, do đó mệnh đề đúng. Mặt khác, nếu chúng ta có hai hoặc
nhiều hơn thì gốc có hai cây con. Vì các cây con nhỏ hơn cây ban đầu nên chúng ta có thể

giả định rằng chúng thỏa mãn mệnh đề. Như vậy, mỗi cây con có nhiều hơn một nút bên

ngoài so với các nút bên trong. Giữa hai nút này có nhiều nút bên ngoài hơn hai nút bên

trong. Tuy nhiên, gốc của cây là một nút bên trong. Khi chúng ta xem xét gốc và cả hai

cây con cùng nhau, sự khác biệt giữa số lượng nút bên ngoài và bên trong là 2−1 = 1, đây

chính là điều chúng ta mong muốn.

Lưu ý rằng mối quan hệ trên không đúng, nói chung, đối với cây nhị phân và cây

không nhị phân không đúng, mặc dù có những mối quan hệ thú vị khác có thể xảy ra khi

chúng ta khám phá trong bài tập (C-7.9).

7.3.4 Cấu trúc liên kết cho cây nhị phân

Trong phần này, cách triển khai cây nhị phân T dưới dạng cấu trúc được liên kết,

được gọi là LinkedBinaryTree. Chúng ta biểu diễn mỗi nút v của T bằng một đối tượng

nút lưu trữ phần tử liên quan và các con trỏ tới nút cha và hai nút con của nó. (Xem Hình

7.13.) Để đơn giản, chúng ta giả sử cây là đúng, nghĩa là mỗi nút có 0 hoặc 2 nút con.

Hình 7.13: Một nút trong cấu trúc dữ liệu được liên kết để biểu diễn cây nhị phân.

Trong Hình 7.14, chúng tôi trình bày biểu diễn cấu trúc liên kết của cây nhị phân. Cấu

trúc lưu trữ kích thước của cây, nghĩa là số lượng nút trong cây và một con trỏ tới gốc của
cây. Phần còn lại của cấu trúc bao gồm các nút được liên kết với nhau một cách thích

hợp. Nếu v là nút gốc của T thì con trỏ tới nút cha là NULL và nếu v là nút bên ngoài thì

con trỏ tới nút con của v là NULL.

Hình 7.14: Một ví dụ về cấu trúc dữ liệu được liên kết để biểu diễn cây nhị phân.

Chúng ta bắt đầu bằng việc xác định các thành phần cơ bản tạo nên lớp

LinkedBinaryTree. Thực thể cơ bản nhất là cấu trúc Nút, được hiển thị trong Đoạn mã

7.17, đại diện cho một nút của cây.

struct Node { // một nút của cây

Elem elt; // giá trị phần tử

Node* par; // nút cha

Node* left; // nút con trái

Node* right; // nút con phải

Node() : elt(), par(NULL), left(NULL), right(NULL) { } // xây dựng

};

Đoạn mã 7.17: Nút cấu trúc thực hiện một nút của cây nhị phân. Nó được lồng trong

phần được bảo vệ của lớp BinaryTree.


class Position { // vị trí trên cây

private:

Node* v; // con trỏ tới nút

public:

Position(Node* v = NULL) : v( v) { } // constructor

Elem& operator*() // lấy phần tử

{ return v−>elt; }

Position left() const // lấy nút con trái

{ return Position(v−>left); }

Position right() const // lấy nút con phải

{ return Position(v−>right); }

Position parent() const // lấy nút cha

{ return Position(v−>par); }

bool isRoot() const // nút gốc?

{ return v−>par == NULL; }

bool isExternal() const // nút bên ngoài?

{ return v−>left == NULL && v−>right == NULL; }

friend class LinkedBinaryTree; // cấp quyền truy cập cho cây

};

typedef std::list<Position> PositionList; // danh sách các vị trí

Đoạn Mã 7.18: Lớp Vị trí thực hiện một vị trí trong cây nhị phân. Nó được lồng trong

phần công khai của lớp Cây nhị phân được liên kết.

Hầu hết các hàm của lớp Vị trí chỉ đơn giản liên quan đến việc truy cập các

thành viên thích hợp của cấu trúc Nút. Chúng tôi cũng đã bao gồm một khai báo về lớp
Danh sách vị trí, dưới dạng danh sách các vị trí STL. Danh sách này được sử dụng để thể

hiện các tập hợp các nút. Để giữ cho mã đơn giản, chúng tôi đã bỏ qua việc kiểm tra lỗi

và thay vì sử dụng các mẫu, chúng tôi chỉ cung cấp định nghĩa kiểu cho loại phần tử cơ

sở, được gọi là Elem.

typedef int Elem; // base element type

class LinkedBinaryTree {

protected:

// insert Node declaration here. . .

public:

// insert Position declaration here. . .

public:

LinkedBinaryTree(); // constructor

int size() const; // number of nodes

bool empty() const; // is tree empty?

Position root() const; // get the root

PositionList positions() const; // list of nodes

void addRoot(); // add root to empty tree

void expandExternal(const Position& p); // expand external node

Position removeAboveExternal(const Position& p); // remove p and parent

// housekeeping functions omitted. . .

protected: // local utilities

void preorder(Node* v, PositionList& pl) const; // preorder utility

private:

Node* root; // pointer to the root


int n; // number of nodes

};

Đoạn mã 7.19: Triển khai lớp Cây nhị phân được liên kết.

Dữ liệu riêng cho lớp LinkedBinaryTree bao gồm một con trỏ gốc tới nút gốc và

một biến n, chứa số lượng nút trong cây.Ngoài các chức năng của ADT, còn có một số

hàm cập nhật, addRoot, ExpandExternal và RemoveAboveExternal, cung cấp các phương

tiện để xây dựng và sửa đổi cây.

LinkedBinaryTree::LinkedBinaryTree() // constructor

:_root(NULL), n(0) { }

int LinkedBinaryTree::size() const // number of nodes

{ return n; }

bool LinkedBinaryTree::empty() const // is tree empty?

{ return size() == 0; }

LinkedBinaryTree::Position LinkedBinaryTree::root() const // get the root

{ return Position( root); }

void LinkedBinaryTree::addRoot() // add root to empty tree

{_root = new Node; n = 1; }

Đoạn mã 7.20: Các hàm thành viên đơn giản cho lớp LinkedBinaryTree.

Hàm cập nhật cây nhị phân

Ngoài các hàm giao diện BinaryTree và addRoot, lớp LinkedBi-naryTree còn bao gồm

các hàm cập nhật sau đây với vị trí p. Cái đầu tiên được sử dụng để thêm các nút vào cây

và cái thứ hai được sử dụng để loại bỏ các nút.


expandExternal(p): Chuyển đổi p từ một nút bên ngoài thành một nút bên trong

bằng cách tạo hai nút bên ngoài mới và biến chúng lần lượt là con trái và con phải của p;

một điều kiện lỗi xảy ra nếu p là một nút bên trong.

RemoveAboveExternal(p): Loại bỏ nút bên ngoài p cùng với nút cha q của nó,

thay thế q bằng nút anh em của p (xem Hình 7.15, trong đó nút của p là w và nút của q là

v); tình trạng lỗi xảy ra nếu p là nút bên trong hoặc p là nút gốc.

Hình 7.15: Thao tác RemoveAboveExternal(p), loại bỏ nút bên ngoài w mà p

tham chiếu tới và nút cha v của nó.

// expand external node

void LinkedBinaryTree::expandExternal(const Position& p) {

Node* v = p.v; // p’s node

v−>left = new Node; // add a new left child

v−>left−>par = v; // v is its parent

v−>right = new Node; // and a new right child

v−>right−>par = v; // v is its parent

n += 2; // two more nodes

Đoạn mã 7.21: Hàm mở rộngExternal(p) của lớp LinkedBinaryTree.


Hàm ExpandExternal(p) được hiển thị trong Đoạn mã 7.21. Đặt v là nút liên kết

của p, nó tạo ra hai nút mới. Một đứa trở thành con trái của v và đứa kia trở thành con

phải của v. Hàm tạo của Node khởi tạo các con trỏ của nút thành NULL, vì vậy chúng ta

chỉ cần cập nhật các liên kết chính của nút mới.

Hiệu suất triển khai LinkedBinaryTree

Phân tích thời gian chạy của các hàm của lớp LinkedBinaryTree, lớp này sử

dụng biểu diễn cấu trúc được liên kết.

• Mỗi hàm vị trí trái, phải, cha, isRoot và isExternal mất O(1) thời gian.

• Bằng cách truy cập vào biến thành viên n, biến này lưu trữ số lượng nút của T, kích

thước hàm và làm trống mỗi lần chạy trong thời gian O(1).

• Root hàm truy cập chạy trong thời gian O(1).

• Các hàm cập nhật ExpandExternal và RemoveAboveExternal chỉ truy cập một số nút

không đổi, vì vậy cả hai đều chạy trong thời gian O(1).

• Vị trí hàm được thực hiện bằng cách thực hiện duyệt theo thứ tự trước, mất O(n) thời

gian. Các nút được duyệt qua cây nhị phân được thêm vào danh sách STL trong thời gian

O(1). Do đó, vị trí hàm mất O(n) thời gian.

7.3.5 Cấu trúc dựa trên vectơ cho cây nhị phân

Cấu trúc đơn giản để biểu diễn cây nhị phân T dựa trên cách đánh số các nút của

T. Với mọi nút v của T, cho f(v) là số nguyên được xác định như sau:

• Nếu v là nghiệm của T thì f(v) = 1

• Nếu v là nút con trái của nút u thì f(v) = 2 f(u)

• Nếu v là nút con bên phải của nút u thì f(v) = 2 f(u) +1
Hình 7.17: Biểu diễn cây nhị phân T bằng vectơ S.

Bảng 7.3: Thời gian chạy của cây nhị phân T được triển khai bằng vectơ S.
Chúng ta biểu thị số nút của T bằng n và N biểu thị kích thước của S. Khoảng cách
sử dụng là O(N), tức là O(2n) trong trường hợp xấu nhất.

7.3.6 Truyền cây nhị phân


Giống như cây thông thường, việc tính toán trên cây nhị phân thường liên quan
đến việc duyệt.
 Duyệt trước cây nhị phân
 Truyền tải thứ tự sau của cây nhị phân
Cây tìm kiếm nhị phân
Cho S là một tập hợp có các phần tử có quan hệ thứ tự. Ví dụ: S có thể là một tập hợp các
số nguyên. Cây tìm kiếm nhị phân cho S là cây nhị phân thích hợp T sao cho:
• Mỗi nút bên trong p của T lưu trữ một phần tử của S, ký hiệu là x(p)
• Với mỗi nút trong p của T, các phần tử lưu trong cây con bên trái của p nhỏ hơn hoặc
bằng x(p) và các phần tử lưu trong cây con bên phải của p lớn hơn hoặc bằng x(p)
Các nút bên ngoài của T không lưu trữ bất kỳ phần tử nàoViệc duyệt theo thứ tự
các nút bên trong của cây tìm kiếm nhị phân T sẽ truy cập các phần tử theo thứ tự không
giảm. (Xem Hình 7.19.)

Hình 7.19: Cây tìm kiếm nhị phân lưu trữ các số nguyên. Đường nét đứt màu xanh
lam được đi ngang khi tìm kiếm (thành công) cho 36. Đường nét đứt màu xanh lam được
đi ngang khi tìm kiếm (không thành công) cho 70.
Sử dụng phương pháp truyền tải thứ tự để vẽ cây
Việc duyệt theo thứ tự cũng có thể được áp dụng cho bài toán tính toán bản vẽ cây
nhị phân. Chúng ta có thể vẽ cây nhị phân T bằng thuật toán gán tọa độ x và y cho nút p
của T bằng hai quy tắc sau (xem Hình 7.20).
• x(p) là số nút được truy cập trước p trong lần duyệt thứ tự của T.
• y(p) là độ sâu của p trong T.

Hình 7.20: Thuật toán vẽ thứ tự cho cây nhị phân.


7.3.7 Mẫu hàm mẫu
Mẫu hàm mẫu mô tả một cơ chế tính toán chung có thể chuyên biệt cho một ứng
dụng cụ thể bằng cách xác định lại các bước nhất định
Euler Tour với mẫu hàm mẫu: Theo mẫu hàm mẫu, chúng ta có thể thiết kế một
thuật toán, template-EulerTour, thực hiện hành trình duyệt Euler chung của cây nhị phân.
Khi được gọi trên nút p, hàm templateEulerTour gọi một số hàm phụ trợ khác ở các giai
đoạn khác nhau của quá trình truyền tải. Trước hết, nó tạo ra cấu trúc 3 phần tử r để lưu
trữ kết quả tính toán gọi hàm phụ trợ initRe-sult. Tiếp theo, nếu p là một nút bên ngoài,
templateEulerTour gọi hàm phụ trợ VisitExternal, nếu không (p là một nút bên trong)
templateEulerTour thực hiện các bước sau:
• Gọi hàm phụ trợ VisitLeft, thực hiện các phép tính liên quan đến việc gặp nút
bên trái
• Gọi đệ quy chính nó ở con bên trái
• Gọi hàm phụ trợ VisitBelow, thực hiện các tính toán liên quan đến việc gặp nút
từ bên dưới
• Gọi đệ quy chính nó trên cây con bên phải
• Gọi hàm phụ trợ VisitRight, thực hiện các phép tính liên quan đến việc gặp nút
bên phải
Cuối cùng, templateEulerTour trả về kết quả tính toán bằng cách gọi hàm phụ trợ
returnResult. Mẫu hàm EulerTour có thể được xem như một khuôn mẫu hoặc “bộ khung”
của một chuyến tham quan Euler. (Xem Đoạn Mã 7.30.)
Algorithm templateEulerTour(T, p):
r ← initResult()
if p.isExternal() then
r.finalResult ← visitExternal(T, p,r)
else
visitLeft(T, p,r)
r.leftResult ← templateEulerTour(T, p.left())
visitBelow(T, p,r)
r.rightResult ← templateEulerTour(T, p.right())
visitRight(T, p,r)
return returnResult(r)
Đoạn mã 7.30: Hàm templateEulerTour để tính toán truyền tải Eulertour
tổng quát của cây con của cây nhị phân T bắt nguồn từ nút p, theo mẫu hàm mẫu.
Hàm này gọi các hàm initResult, VisitExter-nal, VisitLeft, VisitBelow, VisitRight
và ReturnResult.
Trong ngữ cảnh hướng đối tượng, chúng ta có thể viết một lớp EulerTour
như sau:
• Chứa hàm templateEulerTour
• Chứa tất cả các hàm phụ trợ được gọi bởi templateEulerTour dưới dạng
phần giữ chỗ trống (nghĩa là không có hướng dẫn hoặc trả về NULL)
• Chứa một hàm thực thi gọi templateEulerTour(T,T.root())
Bản thân lớp EulerTour không thực hiện bất kỳ tính toán hữu ích nào. Tuy
nhiên, chúng ta có thể mở rộng nó bằng cơ chế kế thừa và ghi đè các hàm trống để
thực hiện các tác vụ hữu ích.
Ví dụ về hàm mẫu
• Mở rộng lớp EulerTour
• Ghi đè hàm initResult bằng cách trả về một mảng gồm ba số
• Ghi đè hàm VisitExternal bằng cách trả về giá trị được lưu tại nút
• Ghi đè hàm VisitRight bằng cách kết hợp r.leftResult và r.rightResult với
toán tử được lưu trữ tại nút và đặt r.finalResult bằng kết quả của thao tác
• Ghi đè hàm returnResult bằng cách trả về r.finalResult Cách tiếp cận này
nên được so sánh với việc triển khai trực tiếp thuật toán được hiển thị trong Đoạn
mã 7.26. Ví dụ thứ hai, chúng ta có thể in biểu thức liên kết với cây biểu thức số
học bằng cách sử dụng lớp PrintExpression mới:
• Mở rộng lớp EulerTour
• Ghi đè hàm VisitExternal bằng cách in giá trị của biến hoặc hằng số liên
kết với nút
• Ghi đè chức năng VisitLeft bằng cách in “(”
• Ghi đè hàm VisitBelow bằng cách in toán tử liên kết với nút
• Ghi đè chức năng VisitRight bằng cách in “)”
7.3.8 Biểu diễn cây tổng quát bằng cây nhị phân
Một biểu diễn thay thế của cây tổng quát T thu được bằng cách chuyển đổi
T thành cây nhị phân T. (Xem Hình 7.22.) Chúng ta giả sử rằng T được sắp xếp
thứ tự hoặc nó được sắp xếp tùy ý. Sự chuyển đổi như sau:
• Với mỗi nút u của T, có một nút bên trong u′ của T′ liên kết với u
• Nếu u là một nút ngoài của T và không có nút anh em ngay sau nó thì các
nút con của u′ trong T′ là các nút ngoài
• Nếu u là nút trong của T và v là con đầu tiên của u trong T thì v′
là con trái của bạn' ở T
• Nếu nút v có anh em w ngay sau nó thì w′ là con phải của v′ trong T
Lưu ý rằng các nút bên ngoài của T’ không được liên kết với các nút của T
và chỉ đóng vai trò giữ chỗ (do đó, thậm chí có thể rỗng).

Hình 7.22: Biểu diễn cây bằng cây nhị phân: (a) cây T; (b) cây nhị phân T’ liên kết
với T. Các cạnh nét đứt kết nối các nút của T’ liên kết với các nút anh em của T.
Dễ dàng duy trì sự tương ứng giữa T và T', và biểu diễn các phép toán trong T
dưới dạng các phép toán tương ứng trong T'. Một cách trực quan, chúng ta có thể nghĩ về
sự tương ứng dưới dạng chuyển đổi T thành T′ lấy mỗi tập hợp anh chị em {v1,v2,...,vk}
trong T với cha mẹ v và thay thế nó bằng một chuỗi con bên phải có gốc tại v1, sau đó trở
thành con trái của v.

You might also like