樹堆(Treap = tree+heap)
Treap是用來排序(Sort)的一種資料結構(Data Structure)。
reap是隨機查詢二叉平衡樹。 Treap發音為tree+ Heap。顧名思義, Treap把 BST和 Heap結合了起來。它和 BST一樣滿足許多優美的性質,而引入堆目的就是為了維護平衡。 Treap在 BST的基礎上,添加了一個修正值。在滿足 BST性質的上,Treap節點的修正值還滿足最小堆性質。最小堆性質可以被描述為每個子樹根節點都小於等於其子節點。
(1) Treap的特點 1. Treap簡明易懂。Treap只有兩種調整方式,左旋和右旋。而且即使沒有嚴密的數學證明和分析,Treap的構造方法啊,平衡原理也是不難理解的。只要能夠理解 BST和堆的思想,理解 Treap當然不在話下。 2. Treap易於編寫。Treap只需維護一個滿足堆序的修正值,修正值一經生成無需修改。相 比較其他各種平衡樹, Treap擁有最少的調整方式,僅僅兩種相互對稱的旋轉。所以 Treap當之無愧是最易於編碼除錯的一種平衡樹。 3. Treap穩定性佳。Treap的平衡性雖不如 AVL,紅黑樹, SBT等平衡樹,但是 Treap也不會退化,可以保證期望 O(logN)的深度。Treap的穩定性取決於隨機數發生器。 4. Treap具有嚴密的數學證明。Treap期望 O(logN)的深度,是有嚴密的數學證明的。但這不是介紹的重點,大多略去。 5. Treap具有良好的實踐效果。各種實際應用中, Treap的穩定性表現得相當出色,沒有因為任何的構造出的資料而退化。於是在資訊學競賽中,不少選手習慣於使用 Treap,均取得了不俗的表現。
一棵treap是一棵修改了結點順序的二叉查詢樹,如圖,顯示一個例子,通常樹內的每個結點x都有一個關鍵字值key[x],另外,還要為結點分配priority[x],它是一個獨立選取的隨機數。
假設所有的優先順序是不同的,所有的關鍵字也是不同的。treap的結點排列成讓關鍵字遵循二叉查詢樹性質,並且優先順序(有的地方也叫修正值,是一個隨機數)遵循最小堆順序性質:1.如果left是u的左孩子,則key[left] < key[u].
2.如果right是u的右孩子,則key[right] > key[u].
3.如果child是u的孩子,則priority[child] > priority[u].
4.如果priority[vi] < priority[vj].則vi相對於vj而言更接近根節點。
這兩個性質的結合就是為什麼這種樹被稱為“treap”的原因,因為它同時具有二叉查詢樹和堆的特徵。(在關鍵字上它滿足二叉排序樹,在優先順序上他滿足小頂堆)。
用以下方式考慮treap會有幫助。假設插入關聯關鍵字的結點x1,x2,...,xn到一棵treap內。結果的treap是將這些結點以它們的優先順序(隨機選取)的順序插入一棵正常的二叉查詢樹形成的,亦即priority[xi] < priority[xj]表示xi在xj之前被插入。
在演算法導論的12.4節中,其證明了隨機構造的二叉查詢樹的期望高度為O(lgn),因而treap的期望高度亦是O(lgn)。
1.treap插入操作:
1.按照二叉樹的插入方法,將結點插入到樹中
2.根據堆的性質(我們這裡為最小堆)和優先順序的大小調整結點位置。
2.treap刪除操作:
1.找到相應的結點
2.若該結點為葉子結點,則直接刪除;
若該結點為只包含一個葉子結點的結點,則將其葉子結點賦值給它;
若該結點為其他情況下的節點,則進行相應的旋轉,直到該結點為上述情況之一,然後進行刪除。
3、如何使treap平衡
Treap中的節點不僅滿足BST的性質,還滿足最小堆的性質。因此需要通過旋轉來調整二叉樹的結構,在維護Treap的旋轉操作有兩種:左旋和右旋,(注意:無論怎麼旋轉二叉查詢樹的性質是不能改變的)4.查詢
和一般的二叉搜尋樹一樣,但是由於Treap的隨機化結構,可以證明Treap中查詢的期望複雜度是O(log n)。
5.分離
要把一個Treap按大小分成兩個Treap,只要在需要分開的位置加一個虛擬節點,然後旋至根節點刪除,左右兩個子樹就是得出的兩個Treap了。根據二叉搜尋樹的性質,這時左子樹的所有節點都小於右子樹的節點。時間相當於一次插入操作的複雜度,也就是O(log n)。
6、查詢最大值和最小值
根據Treap的性質可以看出最左非空子節點就是最小值,同理最右非空子節點就是最大值(同樣也是BST的性質)7、前驅與後繼
定義:前驅,查詢該元素在平衡樹中不大於該元素的最大元素;後繼查詢該元素在平衡樹中不小於該元素的最小元素。 從定義中看出,求一個元素在平衡樹中的前驅和後繼,這個元素不一定是平衡樹中的值,而且如果這個元素就是平衡樹中的值,那麼它的前驅與後繼一定是它本身。 求前驅的基本思想:貪心逼近法。在樹中查詢,一旦遇到一個不大於這個元素的值的節點,更新當前的最優的節點,然後在當前節點的右子樹中繼續查詢,目的是希望能找到一個更接近於這個元素的節點。如果遇到大於這個元素的值的節點,不更新最優值,節點的左子樹中繼續查詢。直到遇到空節點,查詢結束,當前最優的節點的值就是要求的前
驅。求後繼的方法與上述相似,只是要找不小於這個元素的值的節點。
演算法說明: 求前驅:
1. 從根節點開始訪問,初始化最優節點為空節點;
2. 如果當前節點的值不大於要求前驅的元素的值,更新最有節點為當前節點,訪問當前節點的右子節點;
3. 如果當前節點的值大於要求前驅的元素的值,訪問當前節點的左子節點;
4. 如果當前節點是空節點,查詢結束,最優節點就是要求的前驅。
求後繼:
1. 從根節點開始訪問,初始化最優節點為空節點;
2. 如果當前節點的值不小於要求前驅的元素的值,更新最有節點為當前節點,訪問當前節點的左子節點;
3. 如果當前節點的值小於要求前驅的元素的值,訪問當前節點的右子節點;
4. 如果當前節點是空節點,查詢結束,最優節點就是要求的後繼。
8、當前節點子樹的大小
Treap 是一種排序的資料結構,如果我們想查詢第 k 小的元素或者詢問某個元素在 Treap 中從小到大的排名時,我們就必須知道每個子樹中節點的個數。我們稱以一個子樹的所有節點的權值之和,為子樹的大小。由於插入、刪除、旋轉等操作,會使每個子樹的大小改變,所以我們必須對子樹的大小進行動態的維護。 對於旋轉,我們要在旋轉後對子節點和根節點分別重新計算其子樹的大小。 對於插入,新建立的節點的子樹大小為 1。在尋找插入的位置時,每經過一個節點,都要先使以它為根的子樹的大小增加 1,再遞迴進入子樹查詢。 對於刪除,在尋找待刪除節點,遞迴返回時要把所有的經過的節點的子樹的大小減少 1。要注意的是,刪除之前一定要保證待刪除節點存在於 Treap 中。 下面給出左旋操作如何計運算元樹大小的程式碼,右旋很類似。 //這裡需要注意的是,每個節點可能有重複的,重複的數目是用cnt來記錄的,因此最後需要加上cnt9、查詢第K小元素
首先,在一個子樹中,根節點的排名取決於其左子樹的大小,如果根節點有權值 cnt,則根節點 P 的排名是一個閉區間 A,且 A = [P->left->size + 1,P->left->size + P->cnt]。根據此,我們可以知道,如果查詢排名第 k 的元素,k∈A,則要查詢的元素就是 P 所包含元素。如果 k<A,那麼排名第 k 的元素一定在左子樹中,且它還一定是左子樹的排名第 k 的元素。如果 k>A,則排名第 k 的元素一定在右子樹中,是右子樹排名第 k-(P->left->size + P->cnt)的元素(P->left->size指的是p節點左子樹的大小)演算法思想: 1. 定義 P 為當前訪問的節點,從根節點開始訪問,查詢排名第 k 的元素;
2. 若滿足 P->left->size + 1 <=k <= P->left->size + P->cnt,則當前節點包含的元素就是排名第 k 的元素;
3. 若滿足 k <P->left->size+ 1,則在左子樹中查詢排名第 k 的元素;
4. 若滿足 k >P->left->size + P->cnt,則在右子樹中查詢排名第 k-(P->left->size + P->cnt)的元素。
10、求某個元素的排名
演算法思想: 1. 定義 P 為當前訪問的節點,cur 為當前已知的比要求的元素小的元素個數。從根節點開始查詢要求的元素,初始化 cur 為 0;2. 若要求的元素等於當前節點元素,要求的元素的排名為區間[P->left->size + cur + 1, P->left->size + cur + P->cnt]內任意整數;
3. 若要求的元素小於當前節點元素,在左子樹中查詢要求的元素的排名;
4. 若要求的元素大於當前節點元素,更新 cur 為 cur + P->left->size+P->cnt,在右子樹中查詢要求的元素的排名。
下面是程式碼實現:
package com.yc.tree;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Random;
public class THead <T extends Comparable<T>>{
public class Node{
T data;
int priority; //隨機權值(修改值fix)
Node parent;
Node left;
Node right;
public Node(T data, int priority, Node parent, Node left, Node right){
this.data = data;
this.priority = priority;
this.parent = parent;
this.left = left;
this.right = right;
}
public String toString(){
return "[data="+data+",priority="+priority+"]";
}
}
//根
private Node root;
//
private Random rd;
//
private static final int DEFAULT_RD = 1 << 10;
public THead(){
root = null;
}
public THead(T data){
rd = new Random();
root = new Node(data, rd.nextInt(DEFAULT_RD), null, null, null);
}
public THead(T data, int priority){
root = new Node(data, priority, null, null, null);
}
//按照隨機的修改值新增節點
public void add(T data){
rd = new Random();
add(data, rd.nextInt(DEFAULT_RD));
}
//按照指定的修改值新增節點
public void add(T data, int priority){
Node newNode = new Node(data, priority, null, null, null);
if(root == null){
root = newNode;
}else{
//找新插入節點的所屬的父節點
Node current = root;
Node parent = current;
int result = 0;
while(current != null){
parent = current;
result = data.compareTo(current.data);
if(result > 0){ //新插入節點的data域大於當前節點的data域(這裡也埋下了當data域相等時會把新節點往左子樹插)
current = current.right;
}else{
current = current.left;
}
}
if(result > 0){
parent.right = newNode;
}else{
parent.left = newNode;
}
newNode.parent = parent;
parent = null; //釋放臨時節點
reBalanceForAdd(newNode);
}
}
//刪除節點
public void remove(T data){
Node node = getNode(data);
if(node != null){
if(node.left == null && node.right == null){ //如果是葉子節點
removeNeitherLeftAndRight(node);
}else if(node.left != null && node.right == null){ //只有左子節點
removeOnlyHasLeft(node);
}else if(node.right != null && node.left == null){ //只有右子節點
removeOnlyHasRight(node);
}else{ //既有左子樹又有右子樹
removeHaveLeftAndRight(node);
}
}
}
//最大值
public T maxData(){
Node current = root;
Node maxNode = current;
while(current != null){
maxNode = current;
current = current.right;
}
if(maxNode != null){
return maxNode.data;
}else{
return null;
}
}
//最小值
public T minData(){
Node current = root;
Node minNode = current;
while(current != null){
minNode = current;
current = current.left;
}
if(minNode != null){
return minNode.data;
}else{
return null;
}
}
//前驅
public T frontData(T data){
Node current = root;//當前節點
Node frontNode = null;//前驅節點
int result = 0;
while(current != null){
result = data.compareTo(current.data);
if(result == 0){
return current.data;
}else if(result > 0){
frontNode = current;
current = current.right;
}else{
current = current.left;
}
}
if(frontNode != null){
return frontNode.data;
}
return null;
}
//後繼
public T descendantData(T data){
Node current = root;//當前節點
Node frontNode = null;//後繼節點
int result = 0;
while(current != null){
result = data.compareTo(current.data);
if(result == 0){
return current.data;
}else if(result < 0){
frontNode = current;
current = current.left;
}else{
current = current.right;
}
}
if(frontNode != null){
return frontNode.data;
}
return null;
}
//8、當前節點子樹的大小
//9、查詢第K小元素
//10、求某個元素的排名
//對於上面的三個問題需要在Node節點新增兩個域Size(表示子樹中節點個數),ctn(表示子樹中重複Data域的節點個數)
//這裡就不做了,希望有興趣的同學去完成
private void removeNeitherLeftAndRight(Node node){
if(root == node){
root = null;
}else{
if(node == node.parent.left){
node.parent.left = null;
node.parent = null;
}else{
node.parent.right = null;
node.parent = null;
}
}
}
private void removeOnlyHasLeft(Node node){
node.left.parent = node.parent;
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = node.left;
}else{
node.parent.right = node.left;
}
}
if(root == node){
root = node.left;
}
node.parent = node.left = node.right = null;
}
private void removeOnlyHasRight(Node node){
node.right.parent = node.parent;
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = node.right;
}else{
node.parent.right = node.right;
}
}
if(root == node){
root = node.right;
}
node.parent = node.left = node.right = null;
}
private void removeHaveLeftAndRight(Node node){
int result = 0;
while(node.left != null && node.right != null){
result = node.left.priority - node.right.priority;
if(result <= 0){ //左子節點的修改值小於等於右子節點,則進行右旋
type_right(node.left);
}else{
type_left(node.right);
}
}
if(node.left == null && node.right == null){ //如果是葉子節點
removeNeitherLeftAndRight(node);
}else if(node.left != null && node.right == null){ //只有左子節點
removeOnlyHasLeft(node);
}else if(node.right != null && node.left == null){ //只有右子節點
removeOnlyHasRight(node);
}
}
//新增節點後都THead進行修復
private void reBalanceForAdd(Node node) {
//1.遞迴
if(node.parent != null){
int result = node.priority - node.parent.priority;
if(result < 0){
if(node == node.parent.left){
type_right(node);
reBalanceForAdd(node);
}else if(node == node.parent.right){
type_left(node);
reBalanceForAdd(node);
}
}else{
return;
}
}
//2.迴圈(樓主不會寫QAQ.)
/*int result = node.priority - node.parent.priority;
while(node.parent != null && result < 0){
if(result < 0){
if(node == node.parent.left){
type_right(node);
}else{
type_left(node);
}
}
}*/
}
/**
* 節點右旋
* @param node
*
* │ │
* p─┘ └─l
* ││ -> ││
* l─┘└ ─┘└─p
* ││ ││
* ─┘└ ─┘└
*
*/
private void type_right(Node l){
Node p = l.parent;
l.parent = p.parent;
if(p.parent != null){
if(p == p.parent.left){
p.parent.left = l;
}else{
p.parent.right = l;
}
}
p.left = l.right;
if(l.right != null){
l.right.parent = p;
}
l.right = p;
p.parent = l;
if(root == p){
root = l;
}
}
/**
* 節點左旋
* @param r
*
* │ │
* └─p r─┘
* ││ -> ││
* ─┘└─r p─┘└─
* ││ ││
* ─┘└─ ─┘└─
*/
private void type_left(Node r){
Node p = r.parent;
r.parent = p.parent;
if(p.parent != null){
if(p == p.parent.left){
p.parent.left = r;
}else{
p.parent.right = r;
}
}
p.right = r.left;
if(r.left != null){
r.left.parent = p;
}
r.left = p;
p.parent = r;
if(root == p){
root = r;
}
}
//獲得指定元素的節點
private Node getNode(T data){
Node current = root;
if(current == null){
return null;
}else{
int result = 0;
while(current != null){
result = data.compareTo(current.data);
if(result > 0){
current = current.right;
}else if(result < 0){
current = current.left;
}else{
return current;
}
}
return null;
}
}
//廣度優先遍歷
public List<Node> breadthFirstSearch(){
return cBreadthFirstSearch(root);
}
private List<Node> cBreadthFirstSearch(Node node) {
List<Node> nodes = new ArrayList<Node>();
Deque<Node> deque = new ArrayDeque<Node>();
if(node != null){
deque.offer(node);
}
while(!deque.isEmpty()){
Node first = deque.poll();
nodes.add(first);
if(first.left != null){
deque.offer(first.left);
}
if(first.right != null){
deque.offer(first.right);
}
}
return nodes;
}
public static void main(String[] args) {
THead<Integer> tree = new THead<Integer>();
tree.add(12, 6);
tree.add(8, 12);
tree.add(23, 20);
tree.add(16, 3);
tree.add(45, 18);
tree.add(2, 7);
tree.add(9, 42);
tree.add(16, 15);
System.out.println( tree.breadthFirstSearch());
System.out.println("9的前驅是:" + tree.frontData(9));
System.out.println("9的後繼是:" + tree.descendantData(9));
tree.remove(12);
System.out.println( tree.breadthFirstSearch());
}
}
測試結果為:
[[data=16,priority=3], [data=12,priority=6], [data=45,priority=18], [data=2,priority=7], [data=16,priority=15], [data=23,priority=20], [data=8,priority=12], [data=9,priority=42]]
9的前驅是:9
9的後繼是:9
[[data=16,priority=3], [data=2,priority=7], [data=45,priority=18], [data=8,priority=12], [data=23,priority=20], [data=16,priority=15], [data=9,priority=42]]