Treap=Tree+Heap。Treap是一棵二叉排序樹,它的左子樹和右子樹分別是一個Treap,和通常的二叉排序樹不一樣的是, Treap記錄一個額外的數據, 就是優先級。Treap在以關鍵碼構成二叉排序樹的同時,還知足堆的性質(在這裏咱們假設節點的優先級大於該節點的孩子的優先級)。可是這裏要注意的是Treap和二叉堆有一點不一樣,就是 二叉堆必須是徹底二叉樹,而Treap不必定是徹底二叉樹。 該博文主要講解了Treap樹相關的知識點及其實現。html
Treap
咱們都知道,在二叉查找樹中,當插入的數據爲隨機的時候,其有較好的性能使得孩子節點大致上左右均勻分佈。可是,當插入的數據爲有序的時候,其會退化爲一個鏈表的狀態,從而達不到效果。通常,咱們有AVL或者紅黑樹以及Splay等平衡二叉樹結構。可是,其在實現上難度較高。爲此,引入了Treap,treap採用了二叉堆的性質來保持二叉樹的平衡性。由於對於一個堆而言,其須要知足以下的性質:一個節點的兩個孩子節點的值都小於節點的自己的值。可是,對於一棵二叉查找樹而言,其須要知足一棵樹的左孩子節點的值小於根節點的值而且右孩子節點的值大於根節點的值。這顯然出現了衝突。爲此,咱們須要增長一個變量,用於知足這樣的性質。咱們將用於知足二叉搜索樹的性質的值稱之爲key,用於知足堆的性質的值稱之爲priority(優先級)。java
每一個節點的key咱們是沒法改變的,必需要按照要求來,可是爲了保持treap的平衡性,咱們只能從priority上作文章。其實也並無複雜之處,就是讓每一個節點的priority都取一個隨機值,這樣咱們就能夠保證這棵樹「基本平衡」。node
treap的基本操做
爲了要知足二叉堆和平衡樹的性質,treap中有兩種操做用於將不知足二叉堆的性質的二叉查找樹進行相應的調整,使其知足相應的性質。這兩個操做分別爲左旋和右旋:app
旋轉操做的目的是使得不知足堆序的兩個節點經過調整位置,使其從新知足堆序。且不改變二叉查找樹的性質。dom
ps:咱們知道,堆有兩種形式,一種是大頂堆,一種是小頂堆,如下討論的,咱們採用小頂堆的方式對其進行操做函數
treap的相關操做
在對treap樹進行相關的操做以前,咱們先定義treap的節點類:性能
class Node{ /** * 元素的關鍵字值 */ T key; /** * 節點的優先級,用於知足堆的性質 */ int priority; /** * 該節點的左右孩子節點 */ Node left; Node right; /** * 一個隨機數生成器,用於隨機生成節點的元素的優先級 */ Random random=new Random(); public Node(T key){ this(key,null,null); } public Node(T key,Node left,Node right){ this.key=key; this.left=left; this.right=right; this.priority=random(); }
treap相關的操做有插入、刪除、查找。this
查找:
treap的查找操做並不影響treap樹相關的性質,其只須要按照普通的二叉查找樹的檢索方式進行查找便可。url
具體代碼以下:spa
/** * 用於treap樹的查找操做 * @param key 須要進行查找的關鍵字 * @return 查找的相應的節點,當沒有找到對應的節點時,其返回null */ public Node search(T key){ Node temp=this.root; while(temp!=null){ int cmp=key.compareTo(temp.key); if(cmp<0){ temp=temp.left; } else if(cmp>0){ temp=temp.right; } else{ break; } } return temp; }
插入:
在Treap中插入元素,與在BST中插入方法類似。首先找到合適的插入位置,而後創建新的節點,存儲元素。可是要注意創建新的節點的過程當中,會隨機地生成一個修正值,這個值可能會破壞堆序,所以咱們要根據須要進行恰當的旋轉。具體方法以下:
-
從根節點開始插入;
-
若是要插入的值小於等於當前節點的值,在當前節點的左子樹中插入,插入後若是左子節點的修正值小於當前節點的修正值,對當前節點進行右旋;
-
若是要插入的值大於當前節點的值,在當前節點的右子樹中插入,插入後若是右子節點的修正值小於當前節點的修正值,對當前節點進行左旋;
-
若是當前節點爲空節點,在此創建新的節點,該節點的值爲要插入的值,左右子樹爲空,插入成功。
其過程以下例:
具體代碼以下:
/** * 用於往treap樹中插入相應的節點 * @param key 節點元素的值 */ public void insert(T key){ //當節點元素存在的時候 if(key==null||search(key)!=null){ return; } this.root=insert(this.root,key); } /** * 用於插入元素相應的節點值 * @param node 須要進行比較的節點 * @param key 須要進行插入的鍵值 * @return 樹對應的根節點 */ private Node insert(Node node,T key){ if(node==null){ node=new Node(key); } else if(key.compareTo(node.key)<0){ node.left=insert(node.left,key); //當知足狀況的時候,對其進行旋轉操做 if(node.left.priority<node.priority){ node=rotateRight(node); } } else{ node.right=insert(node.right,key); //當知足狀況的時候,對其進行旋轉操做 if(node.right.priority<node.priority){ node=rotateLeft(node); } } return node; }
刪除:
與BST同樣,在Treap中刪除元素要考慮多種狀況。咱們能夠按照在BST中刪除元素一樣的方法來刪除Treap中的元素,即用它的後繼(或前驅)節點的值代替它,而後刪除它的後繼(或前驅)節點並對其進行相應的旋轉調整操做使其符合Treap中堆性質的要求。爲了避免使Treap向一邊偏沉,咱們須要隨機地選取是用後繼仍是前驅代替它,並保證兩種選擇的機率均等。上述方法指望時間複雜度爲O(logN),可是這種方法並無充分利用Treap已有的隨機性質,而是從新得隨機選取代替節點。咱們給出一種更爲通用的刪除方法,這種方法是基於旋轉調整的。首先要在Treap樹中找到待刪除節點的位置,而後分狀況討論:
狀況一, 該節點爲葉節點或鏈節點(即只有一個孩子節點的節點),則該節點是能夠直接刪除的節點。若該節點有非空子節點,用非空子節點代替該節點的,不然用空節點代替該節點,而後刪除該節點。
狀況二, 該節點有兩個非空子節點。咱們的策略是經過旋轉,使該節點變爲能夠直接刪除的節點。若是該節點的左子節點的修正值小於右子節點的修正值,右旋該節點,使該節點降爲右子樹的根節點,而後訪問右子樹的根節點,繼續討論;反之,左旋該節點,使該節點降爲左子樹的根節點,而後訪問左子樹的根節點,繼續討論,直到變成能夠直接刪除的節點。(也就是讓該節點的左右孩子節點中的最小優先級的節點稱爲該節點)
其過程以下例:
具體代碼以下:
/** * 節點的刪除操做 * @param key 須要進行刪除的節點的鍵值 */ public void remove(T key){ //用於判空操做 if(key==null||search(key)==null){ return; } this.root=remove(this.root,key); } /** * 刪除對應的節點 * @param node 開始進行比較的節點 * @param key 進行刪除的節點的關鍵字 * @return 其子樹的根節點 */ private Node remove(Node node,T key){ if(node==null){ return null; } //左子樹 if (key.compareTo(node.key)<0){ node.left=remove(node.left,key); } //右子樹 if(key.compareTo(node.key)>0){ node.right=remove(node.right,key); } //相等的狀況,即刪除的節點爲該節點時 if(key.compareTo(node.key)==0){ //當存在左右孩子節點的時候 if(node.left!=null&&node.right!=null){ //若是左孩子優先級低就右旋 if(node.left.priority<node.right.priority){ node=rotateRight(node); } else{ node=rotateLeft(node); } //旋轉後繼續進行刪除操做 node=remove(node,key); } else{ //當其爲根節點的時候 if(node.left==null&&node.right==null){ return null; } //當其爲單分支樹的時候 node=node.left==null?node.right:node.left; } } return node; }
完整代碼以下:
import java.util.Random; /** * @author 學徒 *用於實現Treap */ public class Treap<T extends Comparable<T>>{ /** * 樹的根節點 */ private Node root; /** * 樹的節點類 */ private class Node{ /** * 元素的關鍵字值 */ T key; /** * 節點的優先級,用於知足堆的性質 */ int priority; /** * 該節點的左右孩子節點 */ Node left; Node right; /** * 一個隨機數生成器,用於隨機生成節點的元素的優先級 */ Random random=new Random(); public Node(T key){ this(key,null,null); } public Node(T key,Node left,Node right){ this.key=key; this.left=left; this.right=right; this.priority=random(); } /** * 用於隨機獲取節點的優先級的隨機數生成函數 * @return 隨機數值 * 參考自:https://blog.csdn.net/chen_tr/article/details/50924073 隨機數的生成。這樣作,聽說可使得隨機數的值 * 能夠不發生重複 */ private int random(){ int seed=random.nextInt(); return (int)(seed*48271L%Integer.MAX_VALUE); } } /** * 用於其相應的左旋操做 * @param node 進行旋轉的節點 * @return 旋轉後的根節點 */ private Node rotateLeft(Node node){ Node temp=node.right; node.right=temp.left; temp.left=node; return temp; } /** * 用於其相應的右旋操做 * @param node 進行旋轉的節點 * @return 旋轉後的根節點 */ private Node rotateRight(Node node){ Node temp=node.left; node.left=temp.right; temp.right=node; return temp; } /** * 用於treap樹的查找操做 * @param key 須要進行查找的關鍵字 * @return 查找的相應的節點,當沒有找到對應的節點時,其返回null */ public Node search(T key){ Node temp=this.root; while(temp!=null){ int cmp=key.compareTo(temp.key); if(cmp<0){ temp=temp.left; } else if(cmp>0){ temp=temp.right; } else{ break; } } return temp; } /** * 用於往treap樹中插入相應的節點 * @param key 節點元素的值 */ public void insert(T key){ //當節點元素存在的時候 if(key==null||search(key)!=null){ return; } this.root=insert(this.root,key); } /** * 用於插入元素相應的節點值 * @param node 須要進行比較的節點 * @param key 須要進行插入的鍵值 * @return 樹對應的根節點 */ private Node insert(Node node,T key){ if(node==null){ node=new Node(key); } else if(key.compareTo(node.key)<0){ node.left=insert(node.left,key); //當知足狀況的時候,對其進行旋轉操做 if(node.left.priority<node.priority){ node=rotateRight(node); } } else{ node.right=insert(node.right,key); //當知足狀況的時候,對其進行旋轉操做 if(node.right.priority<node.priority){ node=rotateLeft(node); } } return node; } /** * 節點的刪除操做 * @param key 須要進行刪除的節點的鍵值 */ public void remove(T key){ //用於判空操做 if(key==null||search(key)==null){ return; } this.root=remove(this.root,key); } /** * 刪除對應的節點 * @param node 開始進行比較的節點 * @param key 進行刪除的節點的關鍵字 * @return 其子樹的根節點 */ private Node remove(Node node,T key){ if(node==null){ return null; } //左子樹 if (key.compareTo(node.key)<0){ node.left=remove(node.left,key); } //右子樹 if(key.compareTo(node.key)>0){ node.right=remove(node.right,key); } //相等的狀況,即刪除的節點爲該節點時 if(key.compareTo(node.key)==0){ //當存在左右孩子節點的時候 if(node.left!=null&&node.right!=null){ //若是左孩子優先級低就右旋 if(node.left.priority<node.right.priority){ node=rotateRight(node); } else{ node=rotateLeft(node); } //旋轉後繼續進行刪除操做 node=remove(node,key); } else{ //當其爲根節點的時候 if(node.left==null&&node.right==null){ return null; } //當其爲單分支樹的時候 node=node.left==null?node.right:node.left; } } return node; } }