1、 概述

二叉查找树(Binary Search Tree,也叫二叉排序树,即Binary Sort Tree)能够支持多种动态集合操作,它可以用来表示有序集合、建立索引等,因而在实际应用中,二叉排序树是一种非常重要的数据结构。

从算法复杂度角度考虑,我们知道,作用于二叉查找树上的基本操作(如查找,插入等)的时间复杂度与树的高度成正比。对一个含n个节点的完全二叉树,这些操作的最坏情况运行时间为O(log n)。但如果因为频繁的删除和插入操作,导致树退化成一个n个节点的线性链(此时即为一个单链表),则这些操作的最坏情况运行时间为O(n)。为了克服以上缺点,很多二叉查找树的变形出现了,如红黑树、AVL树,Treap树等。

本文介绍了二叉查找树的一种改进数据结构–伸展树(Splay Tree)。它的主要特点是不会保证树一直是平衡的,但各种操作的平摊时间复杂度是O(log n),因而,从平摊复杂度上看,二叉查找树也是一种平衡二叉树。另外,相比于其他树状数据结构(如红黑树,AVL树等),伸展树的空间要求与编程复杂度要小得多。

2、 基本操作

伸展树的出发点是这样的:考虑到局部性原理(刚被访问的内容下次可能仍会被访问,查找次数多的内容可能下一次会被访问),为了使整个查找时间更小,被查频率高的那些节点应当经常处于靠近树根的位置。这样,很容易得想到以下这个方案:每次查找节点之后对树进行重构,把被查找的节点搬移到树根,这种自调整形式的二叉查找树就是伸展树。每次对伸展树进行操作后,它均会通过旋转的方法把被访问节点旋转到树根的位置。

为了将当前被访问节点旋转到树根,我们通常将节点自底向上旋转,直至该节点成为树根为止。“旋转”的巧妙之处就是在不打乱数列中数据大小关系(指中序遍历结果是全序的)情况下,所有基本操作的平摊复杂度仍为O(log n)。

伸展树主要有三种旋转操作,分别为单旋转,一字形旋转和之字形旋转。为了便于解释,我们假设当前被访问节点为X,X的父亲节点为Y(如果X的父亲节点存在),X的祖父节点为Z(如果X的祖父节点存在)。

(1)单旋转

节点X的父节点Y是根节点。这时,如果X是Y的左孩子,我们进行一次右旋操作;如果X 是Y 的右孩子,则我们进行一次左旋操作。经过旋转,X成为二叉查找树T的根节点,调整结束。

(2)一字型旋转

节点X 的父节点Y不是根节点,Y 的父节点为Z,且X与Y同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次左左旋转操作或者右右旋转操作。

(3)之字形旋转

节点X的父节点Y不是根节点,Y的父节点为Z,X与Y中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次左右旋转操作或者右左旋转操作。

3、伸展树区间操作

在实际应用中,伸展树的中序遍历即为我们维护的数列,这就引出一个问题,怎么在伸展树中表示某个区间?比如我们要提取区间[a,b],那么我们将a前面一个数对应的结点转到树根,将b 后面一个结点对应的结点转到树根的右边,那么根右边的左子树就对应了区间[a,b]。原因很简单,将a 前面一个数对应的结点转到树根后, a 及a 后面的数就在根的右子树上,然后又将b后面一个结点对应的结点转到树根的右边,那么[a,b]这个区间就是下图中B所示的子树。

利用区间操作我们可以实现线段树的一些功能,比如回答对区间的询问(最大值,最小值等)。具体可以这样实现,在每个结点记录关于以这个结点为根的子树的信息,然后询问时先提取区间,再直接读取子树的相关信息。还可以对区间进行整体修改,这也要用到与线段树类似的延迟标记技术,即对于每个结点,额外记录一个或多个标记,表示以这个结点为根的子树是否被进行了某种操作,并且这种操作影响其子结点的信息值,当进行旋转和其他一些操作时相应地将标记向下传递。

与线段树相比,伸展树功能更强大,它能解决以下两个线段树不能解决的问题:

(1) 在a后面插入一些数。方法是:首先利用要插入的数构造一棵伸展树,接着,将a 转到根,并将a 后面一个数对应的结点转到根结点的右边,最后将这棵新的子树挂到根右子结点的左子结点上。

(2)  删除区间[a,b]内的数。首先提取[a,b]区间,直接删除即可。

4、实现

代码全部来自【参考资料2】。

(1)旋转操作

// node 为结点类型,其中ch[0]表示左结点指针,ch[1]表示右结点指针

// pre 表示指向父亲的指针

// Rotate函数用于(左/右)旋转x->pre

void Rotate(node *x, int d) // 旋转操作,d=0 表示左旋,d=1 表示右旋

{

node *y = x->pre;

Push_Down(y), Push_Down(x);

// 先将Y 结点的标记向下传递(因为Y 在上面),再把X 的标记向下传递

y->ch[! d] = x->ch[d];

if (x->ch[d] != Null) x->ch[d]->pre = y;

x->pre = y->pre;

if (y->pre != Null)

if (y->pre->ch[0] == y) y->pre->ch[0] = x; else y->pre->ch[1] = x;

x->ch[r] = y, y->pre = x, Update(y); // 维护Y 结点

if (y == root) root = x; // root 表示整棵树的根结点

}

(2)splay操作

void Splay(node *x, node *f) // Splay 操作,表示把结点x 转到结点f 的下面

{

for (Push_Down(x) ; x->pre != f; ) // 一开始就将X 的标记下传

if (x->pre->pre == f) // 父结点的父亲即为f,执行单旋转

if (x->pre->ch[0] == x) Rotate(x, 1); else Rotate(x, 0);

else

{

node *y = x->pre, *z = y->pre;

if (z->ch[0] == y)

if (y->ch[0] == x)

Rotate(y, 1), Rotate(x, 1); // 一字形旋转

else

Rotate(x, 0), Rotate(x, 1); // 之字形旋转

else

if (y->ch[1] == x)

Rotate(y, 0), Rotate(x, 0); // 一字形旋转

else

Rotate(x, 1), Rotate(x, 0); // 之字形旋转

}

Update(x); // 最后再维护X 结点

}

(3)将第k个数转到要求的位置

// 找到处在中序遍历第k 个结点,并将其旋转到结点f 的下面

void Select(int k, node *f)

{

int tmp;

node *t;

for (t = root; ; ) // 从根结点开始

{

Push_Down(t); // 由于要访问t 的子结点,将标记下传

tmp = t->ch[0]->size; // 得到t 左子树的大小

if (k == tmp + 1) break; // 得出t 即为查找结点,退出循环

if (k <= tmp) // 第k 个结点在t 左边,向左走

t = t->ch[0];

else // 否则在右边,而且在右子树中,这个结点不再是第k 个

k -= tmp + 1, t = t->ch[1];

}

Splay(t, f); // 执行旋转

}

5、 应用

(1)数列维护问题

题目:维护一个数列,支持以下几种操作:

1. 插入:在当前数列第posi 个数字后面插入tot 个数字;若在数列首位插入,则posi 为0。

2. 删除:从当前数列第posi 个数字开始连续删除tot 个数字。

3. 修改:从当前数列第posi 个数字开始连续tot 个数字统一修改为c 。

4. 翻转:取出从当前数列第posi 个数字开始的tot 个数字,翻转后放入原来的位置。

5. 求和:计算从当前数列第posi 个数字开始连续tot 个数字的和并输出。

6. 求和最大子序列:求出当前数列中和最大的一段子序列,并输出最大和。

(2)轻量级web服务器lighttpd中用到数据结构splay tree.

6、 参考资料

(1)杨思雨《伸展树的基本操作与应用》

(2)Crash《运用伸展树解决数列维护问题》

伸展树算法c语言,数据结构之伸展树详解相关推荐

  1. c语言线段树建树程序,c语言数据结构之线段树详解;例题:校门外的树(poj2808或者vijos1448)...

    线段树:它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个结点. 也就是说线段树的每一个结点对应一个区间,其中根节点对应区间[1,n] 对于线段树中的每一个非叶子节点[a,b],它的左儿子 ...

  2. 伸展树算法c语言,数据结构伸展树介绍及C语言的实现方法

    概要 本章介绍伸展树.它和"二叉查找树"和"AVL树"一样,都是特殊的二叉树.在了解了"二叉查找树"和"AVL树"之后, ...

  3. 数据结构_线段树 详解+模板

  4. C语言哈夫曼树压缩/解压器

    C语言哈夫曼树压缩/解压器 小编是大一的菜鸡,这个题目是数据结构的一个实验题,为了完成这个作业,查找了各种资料,借鉴了很多人的代码,前后折腾了三天左右.代码可能跟网上的不一样,大佬路过请不要踩我. 温 ...

  5. c语言线性表库函数大全,数据结构(C语言版)-线性表习题详解

    <数据结构(C语言版)-线性表习题详解>由会员分享,可在线阅读,更多相关<数据结构(C语言版)-线性表习题详解(23页珍藏版)>请在人人文库网上搜索. 1.数 据 结 构 ,线 ...

  6. Android 驱动(12)---Linux DTS(Device Tree Source)设备树详解

    Linux DTS(Device Tree Source)设备树详解 Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) Linux DTS(Device Tr ...

  7. HBase数据结构与基本语法详解

    HBase数据结构与基本语法详解.背景: 阅读新闻 [日期:2019-01-06] 来源:Linux社区 作者:Linux [字体:大 中 小] HBase中的表一般有这样的特点: 1 大:一个表可以 ...

  8. 机器学习算法(二十五):KD树详解及KD树最近邻算法

    目录 1 KD树 1.1 什么是KD树 1.2 KD树的构建 1.3 KD树的插入 1.4 KD树的删除 1.5 KD树的最近邻搜索算法 1.5.1 举例:查询点(2.1,3.1) 1.5.2 举例: ...

  9. 高通平台8953 Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇)

    本系列导航: 高通平台8953  Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) 高通平台8953 Linux DTS(Device Tree Source ...

最新文章

  1. 人天生就会除法,宾大最新研究:儿童啥都不学也能算出来
  2. 特来电CMDB应用实践
  3. 《大话数据结构》读书笔记
  4. web设计页面跳转的方法
  5. windows下使用pip安装Python Web框架webpy
  6. JSP简单练习-EL获取表单数据
  7. JQuery中的层级选择器
  8. Java 常见的 30 个误区与细节!
  9. 论文浅尝 | NumNet: 一种带有数学推理的机器阅读理解模型
  10. java jdbc封装_JDBC封装-Java(新手)
  11. Python案例:四种方式编程求解一元二次方程
  12. 201521123060 《Java程序设计》第12周学习总结
  13. 印度大量投资太阳能已取得成效 足以媲美煤炭
  14. LaTex使用Excel实现快速插入表格
  15. python3内存分析_调试和分析 - tracemalloc —- 跟踪内存分配 - 《Python 3.7 标准库》 - 书栈网 · BookStack...
  16. VMClean(vmware卸载清理,解决卸载MSI问题)
  17. 数据库导出数据字典(MySQL)
  18. 好文:中国Saas蜕变史
  19. iso文件用什么打开?还有iso是什么东西?
  20. 【GCC编译优化系列】宏定义名称与函数同名是一种什么骚操作?

热门文章

  1. PCL函数库摘要——关键点
  2. android打包证书文件在线生成
  3. 全新系列手机 配索尼4800万摄像头
  4. 01、如何用DAP仿真器下载程序
  5. 过程控制工程,离子膜烧碱一次盐水精制的膜过滤器压力控制
  6. 微信小程序 小星星样式
  7. 纯html加css象棋棋盘,纯CSS实现的国际象棋棋盘
  8. 为什么使用服务器端渲染 (SSR)?
  9. 宇宙人工智能计算机程序,人工智能令整个宇宙变成一个玄计算机,宇宙是虚拟的...
  10. 笔记本服务器管理器在哪个文件夹,笔记本云服务器在哪个文件夹