Giới thiệu
Trong khoa học máy tính, cây tìm kiếm ưu tiên (priority search tree) là một cấu trúc dữ liệu dạng cây để lưu trữ các điểm trong không gian hai chiều (Oxy). Ban đầu cây tìm kiếm ưu tiên được giới thiệu bởi Edward McCreight năm 1985. Ban đầu cây tìm kiếm ưu tiên là sự mở rộng của hàng đợi ưu tiên (priority queue) với mục đích cải thiện thời gian tìm kiếm từ: đến ( + ) trong đó n là số điểm trong cây và s là số trong tổng số điểm được trả về bởi tìm kiếm. Sau đó, cây tìm kiếm ưu tiên được sử dụng để lưu trữ một tập hợp các điểm 2 chiều được sắp xếp theo mức độ ưu tiên(priority) và theo một giá trị khóa (key value). Điều này được thực hiện bằng cách tạo kết hợp giữa hàng đợi ưu tiên (priority queue) và cây tìm kiếm nhị phân (binary search tree). Kết quả là một cây trong đó mỗi nút đại diện cho một điểm trong tập dữ liệu gốc. Điểm được chứa bởi nút là điểm có mức độ ưu tiên thấp nhất. Ngoài ra, mỗi nút còn chứa một giá trị khóa dùng để chia các điểm còn lại (thường là trung vị của các khóa, không kể điểm của nút) thành cây con trái và phải. Các điểm được chia bằng cách so sánh các giá trị khóa của chúng với khóa nút, ủy nhiệm các giá trị có khóa thấp hơn cho cây con bên trái và các giá trị lớn hơn cho cây con bên phải.
Hình 1: Ví dụ về cây tìm kiếm ưu tiên:
Cách xây dựng cây PST
Cây tìm kiếm ưu tiên là một cấu trúc dữ liệu lưu trữ các điểm trên không gian 2 chiều. Đầu tiên, chúng ta xem xét n các điểm trên cùng một mặt phẳng, mỗi điểm đều có tọa độ x và tọa độ y biểu diễn trên hình 2. Bài toán xây dựng cây tìm kiếm ưu tiên được mô tả như sau:
Đầu vào: Ta có n điểm thuộc mặt phẳng = { │ } và các điểm .
Đầu ra: Cây tìm kiếm ưu tiên lưu trữ các điểm trong không gian 2 chiều.
Trình tự:
- Nếu S = NULL thì trả về NULL.
- Tìm điểm có tọa độ y là nhỏ nhất đặt làm root (gốc), = min { .y ∊S }
- Tìm đường trung tuyến (median) chia đôi các điểm về 2 phía trái và phải. Ở phía bên trái tìm điểm = min { .y ∊ \ {} } gán làm con của nút cha ở bước 2, thực hiện tương tự với phía bên phải xem hình 3.
- Đệ quy lại PST(s) với phía bên trái.
- Đệ quy lại PST(s) với phái bên phải.
- Kết thúc.
Hình 2: Biểu diễn các điểm trên mặt phẳng Oxy
Hình 3: Mô tả xác dựng cây PST
Mã giải được viết bằng pascal:
procedure construct_PST(RPSTPtr): CONST k = 30000, FirstKey = 0, LastKey = k - 1; FirstNonKey = LastKey + 1; TYPE KeyRange First_Key… Last_Key; KeyBound First_Key… FirstNonKey; Pair= RECORD x, y: KeyRange END; RPSTPtr = RPST; RPST=RECORD p: Pair; left, right: RPSTPtr END;
Thêm nút mới vào cây
Kế tiếp, cũng đến với vấn đề thêm một nút mới vào cây. Giả sử có một nút mới với tọa độ và cần phải thêm vào cây. Như vậy sẽ cần tính toán lại xem có sự thay đổi ở gốc cây (root) và các đường trung tuyến (median). Sau đó sẽ so sánh xem nút mới có thể chèn vào gốc cây hoặc là các nút ở phía bên trái, phải.
Mã giải pascal thêm nút mới vào cây:
PROCEDURE InsertPair(VAR t: RPSTPtr; newPr: Pair; lowerX: KeyRange; upperX: KeyBound); VAR p: Pair; middleX: KeyRange BEGIN IF t NIL THEN BEGIN NEW(t); (* tao nut tren cay *) t.p := newPr; t.left := NIL; t.right := NIL; END ELSE IF t.p.x <> newPr.x (* gia su bien x la du nhat *) THEN BEGIN IF newPr.y < t.p.y THEN (* neu y_new < y_old *) BEGIN p := t.p; t.p := newPr END (*tao nut moi*) ELSE p := newPr; middleX := (lowerX+upperX) DIV 2; IF p.x < middleX THEN InsertPair(t.left, p, lowerX, middleX) ELSE InsertPair(t.right, p, middleX, upperX); END; (* neu nut da ton tai thi khong them *) END; (* ket thuc ")
Xóa nút ở trên cây
Kế tiếp, cũng đến với vấn đề thêm một nút mới vào cây. Giả sử có một nút cũ với tọa độ và cần phải xóa khỏi cây. Như vậy sẽ cần tính toán lại xem có sự thay đổi ở gốc cây, các nút cha, các đường trung tuyến (median) và cập nhập lại cây.
Mã giải pascal xóa một nút ở trên cây:
PROCEDURE DeletePair(VAR t: RPSTPtr; oldPr: Pair; lowerX: KeyRange; upperX: KeyBound); VAR middleX: KeyRange BEGIN IF t <> NULL THEN BEGIN IF t.p.x == oldPr.x (* co duy nhat 1 bien x *) THEN BEGIN (* tim vi tri can xoa *) IF t.left <> NULL THEN BEGIN IF t.right <> NULL THEN BEGIN (* nut o ben trai hoac ben phai cua cay con *) IF t.left.p.y < t.right.p.y THEN BEGIN (* nut o ben trai *) t.p := t.left.p; DeletePai r( t.left, t.p, lowerX, upperX); END ELSE BEGIN (* nut o ben phai *) t.p := t.right.p; DeletePair(t.right, t.p, lowerX, upperX); END; END ELSE BEGIN (* nut chi o ben trai *) t.p := t.left.p; DeletePair(t.left, t.p, lowerX, upperX); END; END ELSE BEGIN IF t.right <> NULL THEN BEGIN (* nut chi o ben phai *) t.p := t.right.p; DeletePair(t.right, t.p, lowerX, upperX); END ELSE BEGIN (* nut khong co cay con *) DISPOSE(t); t := NIL; END; END; END ELSE BEGIN (* xoa nut khoi cay con *) middleX := (lowerX+upperX) DIV 2; IF oldPr.x < middleX THEN DeletePair(t.left, oldPr, lowerX, middleX) ELSE DeletePair(t.right, oldPr, middleX, upperX); END; END; (* ELSE nut này khong co trong cay nen khong the xoa *) END; (* ket thuc *) TYPE CondPair RECORD valid: BOOLEAN; p: Pair; END;
Truy vấn các điểm trên cây
Tiếp theo, hãy xem xét vấn đề tìm kiếm trên cây. Cây tìm kiếm ưu tiên có thể giúp tìm kiếm các điểm thỏa mãn các điều kiện trên mặt phẳng Oxy, điều đó khác so với cây tìm kiếm nhị phân (BST) là chỉ tìm kiếm 1 điểm trên cây. Khi tìm kiếm ta cần xác định được phạm vi tìm kiếm dưới dạng hình chữ nhật có giới hạn và và độ ưu tiên y. Trong hình dưới đây hãy xem xét vấn đề tìm kiếm các điểm trên cây tìm kiếm ưu tiên với làm gốc.
Hình 4: Mô tả phạm vi tìm kiếm
Hình 5: Mô tả phương pháp tìm kiếm
Mã giải pascal thuật toán tìm kiếm:
procedure PST( tree, x_min, x_max, y_p): IF tree <> NULL THEN IF y_p > tree.node.y THEN IF tree.node.x >= x_min AND tree.node.x <= x_max THEN report tree.node; IF x_min < tree.node.middleX THEN PST(tree.left, x_min, x_max, y_p); IF x_max > tree.node.middleX THEN PST(tree.left, x_min, x_max, y_p); ELSE END; ELSE END;
Thuật toán triển khai với Python:
def query_priority_search_tree(tree, x_min, x_max, y_priority): if tree is None: return if y_priority < tree.node.y: return if tree.node.x in range(x_min, x_max + 1): result_query.append(tree.node.name) if x_min < tree.node.middleX: query_priority_search_tree(tree.left, x_min, x_max, y_priority) # tim ben trai if x_max > tree.node.middleX: query_priority_search_tree(tree.right, x_min, x_max, y_priority) # tim ben phai
Bài toán tìm kiếm điểm có y nhỏ nhất
Bài toán đưa ra vấn đề đó là tìm kiếm một điểm có tọa độ y nhỏ nhất trong một phạm vi hình chữ nhật cho trước trên mặt phẳng Oxy. Bằng mắt thường có thể thấy ngay điểm cần tìm trong phạm vị với số lượng ít các điểm trên mặt phẳng. Nếu tìm tuần tự so sánh từng điểm với nhau thì độ phức tạp của thuật toán sẽ là . Vì thế ta sử dụng cấu trúc dữ liệu cây tìm kiếm ưu tiên để thực hiện việc tìm kiếm với độ phức tạp từ đến ( + ).
Mã giải thuật toán:
FUNCTION MinYlnXRange(t: RPSTPtr; xO, xl: KeyRange; lowerX: KeyRange; upperX: KeyBound): CondPair; VAR c, cRight: CondPair; middleX: KeyRange BEGIN IF t <> NIL THEN IF (xO <= t.p.x) AND (t.p.x <= xl) THEN (" pham vi tim kiem ") BEGIN c.valid := TRUE; c.p := t.p; END ELSE BEGIN middleX := (lowerX+upperX) DIV 2; IF xO < middleX THEN c := MinYlnXRange(t.left, xO, xl, lowerX, middleX) ELSE c.valid := FALSE; IF middleX <= xl THEN cRight := MinYlnXRange(t.right, xO, xl, middleX, upperX) ELSE cRight.valid := FALSE; IF NOT c.valid OR (cRight.valid AND (cRight.p.y < c.p.y)) THEN c := cRight; END ELSE c.valid := FALSE; (" cay con rong ") MinYInXRange := c; END: (" ket thuc ")
Bài toán tìm điểm có x lớn nhất
Bài toán đưa ra vấn đề đó là tìm kiếm một điểm có tọa độ x lớn nhất trong một phạm vi hình chữ nhật cho trước trên mặt phẳng Oxy. Bằng mắt thường có thể thấy ngay điểm cần tìm trong phạm vị với số lượng ít các điểm trên mặt phẳng. Nếu tìm tuần tự so sánh từng điểm với nhau thì độ phức tạp của thuật toán sẽ là . Vì thế ta sử dụng cấu trúc dữ liệu cây tìm kiếm ưu tiên để thực hiện việc tìm kiếm với độ phức tạp từ đến ( + ).
Mã giải thuật toán:
FUNCTION MaxXlnRectangle(t: RPSTPtr; xO, xl, yl: KeyRange; lowerX: KeyRange; upperX: KeyBound): CondPair; VAR c: CondPai r; middleX: KeyRange BEGIN IF t <> NIL THEN BEGIN IF t.p.y > yl THEN (* Khong co nut nao trong cay con nay nam trong tim kiem *) c.valid := FALSE ELSE BEGIN middleX := (lowerX+upperX) DIV 2; IF middleX < xl THEN (* ket qua chi o cay con phai*) c := MaxXInRectangle(t.right, xO, xl, yl, middleX, upperX) ELSE c.valid := FALSE; IF (NOT c.valid) AND (xO <= middleX) THEN (* ket qua chi o cay con trai *) c := MaxXlnRectangle(t.left, xO, xl, yl, lowerX, middleX); IF (xO <= t.p.x) AND (t.p.x <= xl) AND ((NOT c.valid) OR (c.p.x < t.p.x)) THEN (* t.p is best of all in the search rectangle *) BEGIN c.valid := TRUE; c.p := t,.p; END; END END ELSE c.valid := FALSE; (* cay con rong *) MaxXInRectangle := c; END; (* ket thuc *)
Tải về mã nguồn
Xem toàn bộ source code bằng python tại github
Tham khảo
[1] McCreight, Edward (May 1985). “Priority search trees”. SIAM Journal on Scientific Computing.
[2] D.T. Lee. “Interval, Range, and Priority Search Tree”. Academia Sinica.
[3] Dina Q Goldin Karon (March 8, 1993). “Priority Search Tree”. Computational Geometry.