定義:每一個節點不能有多於兩個節點
性質:一個平均二叉樹的深度要比節點個數\(N\)小得多,平均深度爲\(O(\sqrt {N})\),最壞爲\(N-1\), 特別地,二叉查找樹的平均深度爲\(O(logN)\).
因爲二叉查找最多有兩個子節點,因此能夠直接保連接到它們鏈。node
節點包括三部分:算法
應用:編譯器的設計領域
表達式數:樹葉爲操做數,其餘節點爲操做符,當全部的操做時二元的則恰好是二叉樹,也有一些更復雜的狀況,好比有些一目的操做符,或者須要多個操做數。
經過對樹葉進行先序遍歷、後序便利、中序遍歷,依次能夠獲得前綴表達式(節點、左子樹、右子樹),後綴表達式(左子樹、右子樹、節點),中綴表達式(左子樹,節點,右子樹)數據結構
構造表達樹算法:把後綴表達式轉化爲表達式樹
輸入爲一個後綴表達式,一次一個符號地讀入表達式
若符號是操做數,咱們就創建一個單節點樹,並將它推入棧中,若是符號是操做符,就熊棧中彈出兩顆樹\(T_1\)和\(T_2\)(\(T_1\)先彈出)並造成一顆新的樹,該樹的根就是操做符,它的左右兒子分別是\(T_2\)和\(T_1\)(注意這裏的順序)。而後這顆新樹壓入棧中。函數
特色:使得二叉樹成文二叉查找樹的性質是:對於樹中的每一個節點\(X\),它的左子樹中全部項的值小於\(X\)中的項,而它的右子樹中全部項的值大於X中的項。
注:根據以上的要求可知,二叉查找樹要求全部的項都可以排序。實現接口Comparable,經過compareTo比較,經過該方法返回0判斷兩項相等(而不是使用equal),可使用一個函數對象進行比較也就是比較器Comparatorthis
做用:若是在樹T中存在含有項X的節點,那麼這個操做須要返回true,不然返回false
若是T是空集,直接返回false,若存儲在T出的項是X,,返回true,不然,對樹T的左子樹或右子樹進行一次遞歸調用(依賴X與存儲在T中項的關係,X小於T中的項則對左子樹進行調用)
實現方法:spa
findMin方法:遞歸調用左子樹
findMiax方法:遞歸調用右子樹設計
能夠像用contains那樣沿着樹查找,若是找到X,則什麼也不用作,不然,將X插入到遍歷路徑上的最後一點。若爲空樹,新建一個節點,不然遞歸的插入x到適當的子樹中(這裏相似於contains)3d
上面的這種刪除方法有助因而的左子樹比右子樹深度深,咱們能夠隨機選取右子樹的最小元素或者左子樹的最大元素來代替被刪除的元素以消除這種不平衡問題。code
懶惰刪除:當一個元素要被刪除時,他仍然被留在樹中,而只是被標記刪除。對象
懶惰好處:
內部路徑長:全部節點的深度的和
在沒有刪除或者使用懶惰刪除的狀況下,咱們能夠斷言:上述的操做的平均時間都是\(O(log(N))\),(平均是針對向二叉查找樹中全部可能的插入序列進行的)
若向數中插入預先排好序的數據,就會花費二次時間,代價不少,由於樹將只有沒有左兒子的節點組成,因此須要有平衡的附加條件。
定義:帶有平衡條件的二叉查找樹,每一個節點的左子樹和右子樹的高度最多差1的二叉查找樹(空樹的高度爲-1)。每一個節點(在其節點結構中)保留高度信息。
高度爲\(h\)的AVL樹中,最少節點數\(S(h)=S(h-1)+S(h-2)+1\)
對AVL插入,致使數的平衡破壞的狀況,對於第一個平衡被破壞的節點(即最深的節點)\(\alpha\),出現不平衡的四種狀況:
1和4發生在「外邊」能夠經過單旋轉調整平衡
2和3發生在「內部」須要經過雙旋轉調整平衡
情形1和4是關於\(\alpha\)鏡面對稱的,後面的旋轉也可看出這一點
\(k_2\)爲第一個平衡被破壞的節點
情形1:
情形4
\(k_3\)爲第一個平衡被破壞的節點
情形2
情形3
與二叉查找樹的插入刪除相似,只是多了一個平衡。部分代碼以下:
/** * Internal method to remove from a subtree. * @param x the item to remove. * @param t the node that roots the subtree. * @return the new root of the subtree. */ private AvlNode<AnyType> remove( AnyType x, AvlNode<AnyType> t ) { if( t == null ) return t; // Item not found; do nothing int compareResult = x.compareTo( t.element ); if( compareResult < 0 ) t.left = remove( x, t.left ); else if( compareResult > 0 ) t.right = remove( x, t.right ); else if( t.left != null && t.right != null ) // Two children { t.element = findMin( t.right ).element; t.right = remove( t.element, t.right ); } else t = ( t.left != null ) ? t.left : t.right; return balance( t ); } // Assume t is either balanced or within one of being balanced private AvlNode<AnyType> balance( AvlNode<AnyType> t ) { if( t == null ) return t; if( height( t.left ) - height( t.right ) > ALLOWED_IMBALANCE ) if( height( t.left.left ) >= height( t.left.right ) ) t = rotateWithLeftChild( t ); else t = doubleWithLeftChild( t ); else if( height( t.right ) - height( t.left ) > ALLOWED_IMBALANCE ) if( height( t.right.right ) >= height( t.right.left ) ) t = rotateWithRightChild( t ); else t = doubleWithRightChild( t ); t.height = Math.max( height( t.left ), height( t.right ) ) + 1; return t; } /** * Internal method to insert into a subtree. * @param x the item to insert. * @param t the node that roots the subtree. * @return the new root of the subtree. */ private AvlNode<AnyType> insert( AnyType x, AvlNode<AnyType> t ) { if( t == null ) return new AvlNode<>( x, null, null ); int compareResult = x.compareTo( t.element ); if( compareResult < 0 ) t.left = insert( x, t.left ); else if( compareResult > 0 ) t.right = insert( x, t.right ); else ; // Duplicate; do nothing return balance( t ); } /** * Return the height of node t, or -1, if null. */ private int height( AvlNode<AnyType> t ) { return t == null ? -1 : t.height; } /** * Rotate binary tree node with left child. * For AVL trees, this is a single rotation for case 1. * Update heights, then return new root. */ private AvlNode<AnyType> rotateWithLeftChild( AvlNode<AnyType> k2 ) { AvlNode<AnyType> k1 = k2.left; k2.left = k1.right; k1.right = k2; k2.height = Math.max( height( k2.left ), height( k2.right ) ) + 1; k1.height = Math.max( height( k1.left ), k2.height ) + 1; return k1; } /** * Double rotate binary tree node: first left child * with its right child; then node k3 with new left child. * For AVL trees, this is a double rotation for case 2. * Update heights, then return new root. */ private AvlNode<AnyType> doubleWithLeftChild( AvlNode<AnyType> k3 ) { k3.left = rotateWithRightChild( k3.left ); return rotateWithLeftChild( k3 ); }
AVL數的節點聲明,是一個嵌套類:
private static class AvlNode<AnyType> { // Constructors AvlNode( AnyType theElement ) { this( theElement, null, null ); } AvlNode( AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt ) { element = theElement; left = lt; right = rt; height = 0; } AnyType element; // The data in the node AvlNode<AnyType> left; // Left child AvlNode<AnyType> right; // Right child int height; // Height }
在前面的運行時間的討論中,咱們能夠看到,在二叉查找樹中,咱們沒法保證人與單個的操做的時間界爲\(O(log(N))\),可是某些自調整樹結構能夠保證連續的M次操做在最壞的情形下花費時間爲\(O(M log(N))\).
伸展樹:保證從空樹開始連續M次對數的操做最多花費\(O(M log(N))\)時間。
堆還運行時間:通常來講,當M次操做的序列總的最壞運行時間爲\(O(M f(N))\)時,咱們就說它的堆還運行時間爲\(O( f(N))\)。
一棵伸展樹每次操做的堆還代價爲\(O(log(N))\)
二叉查找樹的問題在於,雖然一系列訪問總體是壞的操做有可能發生,可是很罕見。
思路:只要一個節點被訪問,它就必須被移動,不然,一旦發現一個深層的節點,咱們就有可能不斷對它進行訪問。
伸展樹的思想:當一個節點被訪問時,他就要通過一系列AVL樹的旋轉被推到根上。注意,若是一個節點很深,那麼在其路徑上就存在許多也相對較深的節點,經過從新狗仔能夠減小對全部這些節點的進一步訪問,並且在實際中,一個節點被訪問,極可能會在不久再次被訪問,並且伸展樹不要求保留高度或平衡信息,所以它在某種程度上節省空間並簡化代碼。
缺點:在將訪問節點推向樹根的過程當中可能會會將另外的節點推向很深的深度,使得M個操做時間須要\(\Omega (M \cdot N)\)
令X是訪問路徑上的一個(非根)節點,具體操做以下:
展開的優點:展開操做不只將訪問的節點移動到根處,並且還把訪問路徑上的大部分節點的深度大體減少一半(某些淺的節點最多向下推後兩層)
展開樹的基本的和關鍵的性質:當訪問路徑長而致使超出正常查找時間的時候這些旋轉將對將來的操做有益;而當耗時不多的時候,這些旋轉則不那麼有益甚至有害
展開帶來的新的節點刪除方法:經過要被訪問的節點來執行刪除。這種刪除將節點推到根處,若是刪除該節點這獲得兩個子樹\(T_L\)和\(T_R\)(左子樹和右子樹)。若是咱們知道\(T_L\)中最大的元素,那麼這個元素就被旋轉到\(T_L\)的根下,而此時\(T_L\)將有一個沒有有兒子的根,咱們可使\(T_R\)爲其右兒子完成刪除。
使用B樹的緣由因爲磁盤訪問相較於cpu中指令的執行是很是緩慢的,爲了處理的速度,儘可能減小磁盤訪問時很是有必要的,而二叉查找樹的速度不能低於\(logN\),那麼有更多的分支就能夠下降速度,因此就考慮構建M叉查找樹。爲了避免讓M叉查找樹退化成二叉查找樹甚至是鏈表,使用B樹實現。
性質:原則上,B樹保證只有少數的磁盤訪問
階爲M的B樹是一顆具備下列特性的樹: