发散二叉搜索树
根插入方法中,我们通过旋转把插入节点带到树根位置。这里,我们考虑如何使得旋转操作使树达到平衡。我们不考虑递归地使用把新近插入节点带到树顶部的单一旋转操作,而考虑把节点从作为树根的孙子(根的两代,也就是与根相距两层)之一的一个位置带到树顶部的两个旋转操作。首先,我们执行一次旋转,使节点成为树根的一个孩子。然后,执行另一次旋转,使它成为树根。根据从根到正被插入的节点的两个链接是否以相同的方式定向,存在两种本质不同的情况。当从根到正被插入的节点的两个链接以相同方式定向(例如:节点到根需要经过两个左链接,或者节点到根需要经过两个右链接)时,我们可以直接在树根执行两次相同的旋转操作,将相应节点移动到树根位置。当从根到正被插入的节点的两个链接以不同方式定向(例如:节点到根需要经过一个左链接和一个右链接,或者节点到根需要经过一个右链接和一个左链接)时,我们可以使用标准根插入方法。通过以上方式建立的二叉搜索树就是发散二叉搜索树。发散插入和标准根插入之间的区别也许显得不太合理,但它非常重要:发散操作消除了最坏情况下的二次水平性能,而这正是标准二叉搜索树的主要缺陷。
发散插入和之前根插入算法的不同之处仅在于一个实质细节:如果搜索路径的方向是“左-左”或“右-右”,则该节点可用一次双重旋转从树的顶部而不是树的底部带到树根。
对于源自树根的搜索路径,本程序检查其中的两个步骤的四种可能性,并执行适当的旋转操作:
左-左:在树根右旋转两次;
左-右:在左孩子左旋转,然后在树根右旋转;
右-右:在树根左旋转两次;
右-左:在右孩子右旋转,然后在树根左旋转。
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);}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?