根插入方法中,我们通过旋转把插入节点带到树根位置。这里,我们考虑如何使得旋转操作使树达到平衡。我们不考虑递归地使用把新近插入节点带到树顶部的单一旋转操作,而考虑把节点从作为树根的孙子(根的两代,也就是与根相距两层)之一的一个位置带到树顶部的两个旋转操作。首先,我们执行一次旋转,使节点成为树根的一个孩子。然后,执行另一次旋转,使它成为树根。根据从根到正被插入的节点的两个链接是否以相同的方式定向,存在两种本质不同的情况。当从根到正被插入的节点的两个链接以相同方式定向(例如:节点到根需要经过两个左链接,或者节点到根需要经过两个右链接)时,我们可以直接在树根执行两次相同的旋转操作,将相应节点移动到树根位置。当从根到正被插入的节点的两个链接以不同方式定向(例如:节点到根需要经过一个左链接和一个右链接,或者节点到根需要经过一个右链接和一个左链接)时,我们可以使用标准根插入方法。通过以上方式建立的二叉搜索树就是发散二叉搜索树。发散插入和标准根插入之间的区别也许显得不太合理,但它非常重要:发散操作消除了最坏情况下的二次水平性能,而这正是标准二叉搜索树的主要缺陷。
发散插入和之前根插入算法的不同之处仅在于一个实质细节:如果搜索路径的方向是“左-左”或“右-右”,则该节点可用一次双重旋转从树的顶部而不是树的底部带到树根。
对于源自树根的搜索路径,本程序检查其中的两个步骤的四种可能性,并执行适当的旋转操作:
左-左:在树根右旋转两次;
左-右:在左孩子左旋转,然后在树根右旋转;
右-右:在树根左旋转两次;
右-左:在右孩子右旋转,然后在树根左旋转。
1 private: 2 void splay(link& h, Item x) 3 { if (h == 0) 4 { h == new node(x, 0, 0, 1); return;} 5 if (x.key() < h->item.key()) 6 { link& hl = h->l; int N = h->N; 7 if (hl == 0) 8 { h == new node(x, 0, h, N+1); return;} 9 if (x.key() < hl->item.key()) 10 { splay(hl->l, x); rotR(h);} 11 else { splay(hl->r, x); rotL(hl);} 12 rotR(h); 13 } 14 else 15 { link &hr = h->r; int N = h->N; 16 if (hr == 0) 17 { h = new node(x, h, 0, N+1); return;} 18 if (hr->item.key() < x.key()) 19 {splay(hr->r, x); rotL(h);} 20 else { splay(hr->l, x); rotR(hr);} 21 rotL(h); 22 } 23 } 24 public: 25 void insert(Item item) 26 {splay(head, item);}