平衡樹主要關心的是使樹不要傾向一方,理想情況下,葉節點只出如今一兩個層次上。所以,新近到達的元素威脅到樹的平衡,就要當即在局部從新構造樹(AVL方法)或從新建立樹(DSW方法),從而糾正這一問題。而後,這樣的從新構造是否老是必要?二叉查找樹用來快速插入、檢索和刪除元素,重要的是執行這些操做的速度而不是樹的形狀。經過平衡樹能夠提升效率,但這不是惟一可用的方法。
還記得數據結構與算法-鏈表(下)中的自組織鏈表嗎?在二叉樹的組織結構中也有相似的自適應樹概念。它們都是基於一個觀點出發而衍生出來的算法:
並不是全部的元素使用的頻率都相同。
例如,若是樹中第10層的一個元素不經常使用,整個程序的效率將不會因訪問這一層而受到太大的影響。然而,若是要常常訪問這個元素,該元素位於第10層仍是靠近根節點就有很大的區別。所以,自適應樹中的策略是沿着樹向上移動經常使用的元素,以此方式對樹進行從新構造,從而造成一種"優先樹"。
根據前移法和換位法,咱們爲二叉樹提出兩種可行的策略
訪問過元素P以後,從P節點開始不停的旋轉,直到P節點成爲新的根節點
訪問過元素P以後,將P節點圍繞其父節點旋轉一次便可,根節點除外
使用單一旋轉策略,常常訪問的元素最終上移到靠近根的地方,這樣,之後訪問會比之前的訪問更快。更重要的一點,即便全部請求的機率相同時,單一旋轉技術平均查找時間也僅僅是

,算法複雜度介於
O(n)
和
O(lgn)
之間,能夠接受。
至於「移動到根部」策略,已經肯定訪問節點的效率是
(2ln2)lgn
,這一結果在任何機率分佈下都成立(也就是說,該結果與特定請求的機率無關)。
單一旋轉策略沒有改進的餘地,由於它的操做比較簡單,僅僅是旋轉一次便可。
"移動到根部"策略還有改進的餘地,由於在移動到根部的過程當中涉及到屢次旋轉。若是咱們能在將節點移動到根部的過程當中使二叉樹的形狀儘量平衡,那麼整體來看效率會有很大提高。算法
"移動到根部"策略的一個修改版本稱爲"張開"策略,該策略根據子節點、父節點和祖父節點之間的連接關係,成對地使用單一旋轉。首先,根據被訪問節點R,其父節點Q及祖父節點P之間的關係,分爲3種狀況:
對於異構配置,首先旋轉R節點,此時,R成了新的父節點,繼續旋轉R便可。能夠發現,和"移動到根部"策略是同樣的。
對於同步配置,首先旋轉R的父節點即Q,而後再旋轉R節點便可。
能夠發現,"移動到根部"策略和"張開"策略的差異只有一點,就是在同構配置時旋轉的方式不一樣。
splaying(P, Q, R) {
while R不是根節點 {
if Q是根節點
將R圍繞Q旋轉一次便可
else if 同構配置
將Q圍繞P旋轉一次,而後將R圍繞Q旋轉一次便可
else
將R圍繞Q旋轉一次,而後將R圍繞P旋轉一次便可
}
}
複製代碼
事實證實,"張開"策略很是有效,既能把元素移動到根部,也能使整個樹趨於平衡,對下一次訪問元素有着積極的影響。經過計算,使用"張開"策略構造二叉樹的效率與平衡樹的效率至關,等於
O(mlgn)
,其中m是指節點的m次訪問。
"張開"策略既關注元素的訪問頻率也兼顧到樹的平衡。在一些元素比其餘元素更經常使用的環境中,該策略執行的很好。可是,若是靠近根部的元素與最底層元素的訪問頻率差很少,"張開"策略可能並非最好的選擇。換而言之,咱們但願有一種策略重點強調樹的平衡也能兼顧到元素的訪問頻率。這就是"半張開"策略。
"半張開"策略是"張開"策略的一個修改版本,差異只有一點。假設已知被訪問節點R,其父節點Q及祖父節點P,對於同構配置,"張開"策略須要執行兩次旋轉,最終R取代了P的位置。可是,對於"半張開"策略,只執行一次旋轉,就是將Q圍繞P旋轉便可。關鍵邏輯是R節點以後再也不旋轉了,Q節點成了新的R節點,判斷Q節點以及其父節點、祖父節點屬於同構仍是異構,決定下一步的操做。
"半張開"策略的優點在於使樹更傾向於平衡的同時兼顧到元素的訪問頻率。
以上討論的自適應樹策略既能夠用到普通二叉樹也能夠用到二叉查找樹上,由於旋轉的操做不會改變二叉查找樹的性質。
綜上所述,自適應樹是對平衡樹很好的替代方式,由於不須要專門維護樹的平衡,操做也很簡單易懂,對於中等數量的數據很是友好。
自適應樹的相關問題已經探討完畢,更多內容須要在實踐中摸索,畢竟,實踐出真知。