平衡树主要关心的是使树不要倾向一方,理想情况下,叶节点只出如今一两个层次上。所以,新近到达的元素威胁到树的平衡,就要当即在局部从新构造树(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节点以及其父节点、祖父节点属于同构仍是异构,决定下一步的操做。
"半张开"策略的优点在于使树更倾向于平衡的同时兼顾到元素的访问频率。
以上讨论的自适应树策略既能够用到普通二叉树也能够用到二叉查找树上,由于旋转的操做不会改变二叉查找树的性质。
综上所述,自适应树是对平衡树很好的替代方式,由于不须要专门维护树的平衡,操做也很简单易懂,对于中等数量的数据很是友好。
自适应树的相关问题已经探讨完毕,更多内容须要在实践中摸索,毕竟,实践出真知。