6天通吃树结构—— 第四天 伸展树

 

      我们知道AVL树为了保持严格的平衡,所以在数据插入上会呈现过多的旋转,影响了插入和删除的性能,此时AVL的一个变种

伸展树(Splay)就应运而生了,我们知道万事万物都遵循一个“八二原则“,也就是说80%的人只会用到20%的数据,比如说我们

的“QQ输入法”,平常打的字也就那么多,或许还没有20%呢。

 

一:伸展树

 1:思想

    伸展树的原理就是这样的一个”八二原则”,比如我要查询树中的“节点7”,如果我们是AVL的思路,每次都查询“节点7”,那么当这

棵树中的节点越来越多的情况下就会呈现下旋,所以复杂度只会递增,伸展树的想法就是在第一次查询时树里面会经过一阵痉挛把

“节点7”顶成“根节点”,操作类似AVL的双旋转,比如下图:

6天通吃树结构—— 第四天 伸展树

当我们再次查询同样的”数字7“时,直接在根节点处O(1)取出,当然这算是一个最理想的情况,有时痉挛过度,会出现糟糕的”链表“,

也就退化了到O(N),所以伸展树讲究的是”摊还时间“,意思就是说在”连续的一系列操作中的平均时间“,当然可以保证是log(N)。

 

2:伸展方式

    不知道大家可否记得,在AVL中的旋转要分4个情况,同样伸展树中的伸展需要考虑6种情况,当然不考虑镜像的话也就是3种情况,

从树的伸展方向上来说有“自下而上”和“自上而下"的两种方式,考虑到代码实现简洁,我还是说下后者。

 

<1> 自上而下的伸展

      这种伸展方式会把树切成三份,L树,M树,R树,考虑的情况有:单旋转,“一字型”旋转,“之字形”旋转。

①: 单旋转

6天通吃树结构—— 第四天 伸展树

从图中我们可以看到,要将“节点2”插入到根上,需要将接近于“节点2”的数插入到根上,也就是这里的“节点7”,首先树被分成了3份,

初始情况,L和R树是“空节点”,M是整棵树,现在需要我们一步一步拆分,当我们将“节点2”试插入到“节点7”的左孩子时,发现“节点7”

就是父节点,满足“单旋转”情况,然后我们将整棵树放到“R树”中的left节点上,M此时是一个逻辑上的空节点,然后我们将R树追加到

M树中。L树追加到M的左子树中,最后我们将“节点2”插入到根节点上。说这么多有点拗口,伸展树比较难懂,需要大家仔细品味一下。

 

②: 一字型

一字型旋转方式与我们AVL中的“单旋转”类似,首先同样我们切成了三份,当我们"预插入20时”,发现20的“父节点”是根的右孩子,

而我们要插入的数字又在父节点的右边,此时满足”一字型“旋转,我们将7,10两个节点按照”右右情况”旋转,旋转后“节点10"的

左孩子放入到L树的right节点,"节点10”作为中间树M,最后将20插入根节点。

6天通吃树结构—— 第四天 伸展树

③: 之字形

6天通吃树结构—— 第四天 伸展树

 

之字形有点类似AVL中的“双旋转”,不过人家采取的策略是不一样的,当我们试插入“节点9”,同样发现“父节点”是根的右儿子,并且

“节点9”要插入到父节点的内侧,根据规则,需要将“父节点10”作为M树中的根节点,“节点7”作为L树中的right节点,然后M拼接L和R,

最后将节点9插入到根上。

 

3:基本操作

①:节点定义

我们还是采用普通二叉树中的节点定义,也就没有了AVL那么烦人的高度信息。

 1     public class BinaryNode<T>

 2     {

 3         // Constructors

 4         public BinaryNode(T theElement) : this(theElement, null, null) { }

 5 

 6         public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt)

 7         {

 8             element = theElement;

 9             left = lt;

10             right = rt;

11         }

12 

13         public T element;

14 

15         public BinaryNode<T> left;

16 

17         public BinaryNode<T> right;

18     }

②:伸展

     这里为了编写代码方便,我采用的是逻辑nullNode节点,具体伸展逻辑大家可以看上面的图。

 1         #region 伸展

 2         /// <summary>

 3         /// 伸展

 4         /// </summary>

 5         /// <param name="Key"></param>

 6         /// <param name="tree"></param>

 7         /// <returns></returns>

 8         public BinaryNode<T> Splay(T Key, BinaryNode<T> tree)

 9         {

10             BinaryNode<T> leftTreeMax, rightTreeMin;

11 

12             header.left = header.right = nullNode;

13 

14             leftTreeMax = rightTreeMin = header;

15 

16             nullNode.element = Key;

17 

18             while (true)

19             {

20                 int compareResult = Key.CompareTo(tree.element);

21 

22                 if (compareResult < 0)

23                 {

24                     //如果成立,说明是”一字型“旋转

25                     if (Key.CompareTo(tree.left.element) < 0)

26                         tree = rotateWithLeftChild(tree);

27 

28                     if (tree.left == nullNode)

29                         break;

30 

31                     //动态的将中间树的”当前节点“追加到 R 树中,同时备份在header中

32                     rightTreeMin.left = tree;

33 

34                     rightTreeMin = tree;

35 

36                     tree = tree.left;

37                 }

38                 else if (compareResult > 0)

39                 {

40                     //如果成立,说明是”一字型“旋转

41                     if (Key.CompareTo(tree.right.element) > 0)

42                         tree = rotateWithRightChild(tree);

43 

44                     if (tree.right == nullNode)

45                         break;

46 

47                     //动态的将中间树的”当前节点“追加到 L 树中,同时备份在header中

48                     leftTreeMax.right = tree;

49 

50                     leftTreeMax = tree;

51 

52                     tree = tree.right;

53                 }

54                 else

55                 {

56                     break;

57                 }

58             }

59 

60             /* 剥到最后一层,来最后一次切分 */

61             //把中间树的左孩子给“左树”

62             leftTreeMax.right = tree.left;

63 

64             //把中间树的右孩子给“右树”

65             rightTreeMin.left = tree.right;

66 

67             /* 合并操作 */

68             //将头节点的左树作为中间树的左孩子

69             tree.left = header.right;

70 

71             //将头结点的右树作为中间树的右孩子

72             tree.right = header.left;

73 

74             return tree;

75         }

76         #endregion

③:插入

插入操作关键在于我们要找到接近于”要插入点“的节点,然后顶成“根节点”,也就是上面三分图中的最后一分。

 1 #region 插入

 2         /// <summary>

 3         /// 插入

 4         /// </summary>

 5         /// <param name="Key"></param>

 6         public void Insert(T Key)

 7         {

 8             if (newNode == null)

 9                 newNode = new BinaryNode<T>(default(T));

10 

11             newNode.element = Key;

12 

13             if (root == nullNode)

14             {

15                 newNode.left = newNode.right = nullNode;

16 

17                 root = newNode;

18             }

19             else

20             {

21                 root = Splay(Key, root);

22 

23                 int compareResult = Key.CompareTo(root.element);

24 

25                 if (compareResult < 0)

26                 {

27                     newNode.left = root.left;

28 

29                     newNode.right = root;

30 

31                     root.left = nullNode;

32 

33                     root = newNode;

34                 }

35                 else

36                     if (compareResult > 0)

37                     {

38                         newNode.right = root.right;

39 

40                         newNode.left = root;

41 

42                         root.right = nullNode;

43 

44                         root = newNode;

45                     }

46                     else

47                         return;

48             }

49 

50             newNode = null;

51         }

52         #endregion

④:删除

  删除操作也要将节点伸展到根上,然后进行删除,逻辑很简单。

 1  #region 删除

 2         /// <summary>

 3         /// 删除

 4         /// </summary>

 5         /// <param name="Key"></param>

 6         public void Remove(T Key)

 7         {

 8             BinaryNode<T> newTree;

 9 

10             //将删除结点顶到根节点

11             root = Splay(Key, root);

12 

13             //不等于说明没有找到

14             if (root.element.CompareTo(Key) != 0)

15                 return;

16 

17             //如果左边为空,则直接用root的右孩子接上去

18             if (root.left == nullNode)

19             {

20                 newTree = root.right;

21             }

22             else

23             {

24                 newTree = root.left;

25 

26                 newTree = Splay(Key, newTree);

27 

28                 newTree.right = root.right;

29             }

30             root = newTree;

31         }

32         #endregion

 

总的运行代码如下:

View Code
  1 using System;

  2 using System.Collections.Generic;

  3 using System.Linq;

  4 using System.Text;

  5 

  6 namespace DataStructSplay

  7 {

  8     public class BinaryNode<T>

  9     {

 10         public BinaryNode(T theElement) : this(theElement, null, null) { }

 11 

 12         public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt)

 13         {

 14             element = theElement;

 15             left = lt;

 16             right = rt;

 17         }

 18 

 19         public T element;

 20 

 21         public BinaryNode<T> left;

 22 

 23         public BinaryNode<T> right;

 24     }

 25 

 26     public class SplayTree<T> where T : IComparable

 27     {

 28         public BinaryNode<T> root;

 29 

 30         public BinaryNode<T> nullNode;

 31 

 32         public BinaryNode<T> header = new BinaryNode<T>(default(T));

 33 

 34         public BinaryNode<T> newNode;

 35 

 36         public SplayTree()

 37         {

 38             nullNode = new BinaryNode<T>(default(T));

 39 

 40             nullNode.left = nullNode.right = nullNode;

 41 

 42             root = nullNode;

 43         }

 44 

 45         #region 插入

 46         /// <summary>

 47         /// 插入

 48         /// </summary>

 49         /// <param name="Key"></param>

 50         public void Insert(T Key)

 51         {

 52             if (newNode == null)

 53                 newNode = new BinaryNode<T>(default(T));

 54 

 55             newNode.element = Key;

 56 

 57             if (root == nullNode)

 58             {

 59                 newNode.left = newNode.right = nullNode;

 60 

 61                 root = newNode;

 62             }

 63             else

 64             {

 65                 root = Splay(Key, root);

 66 

 67                 int compareResult = Key.CompareTo(root.element);

 68 

 69                 if (compareResult < 0)

 70                 {

 71                     newNode.left = root.left;

 72 

 73                     newNode.right = root;

 74 

 75                     root.left = nullNode;

 76 

 77                     root = newNode;

 78                 }

 79                 else

 80                     if (compareResult > 0)

 81                     {

 82                         newNode.right = root.right;

 83 

 84                         newNode.left = root;

 85 

 86                         root.right = nullNode;

 87 

 88                         root = newNode;

 89                     }

 90                     else

 91                         return;

 92             }

 93 

 94             newNode = null;

 95         }

 96         #endregion

 97 

 98         #region 是否包含

 99         /// <summary>

100         /// 是否包含

101         /// </summary>

102         /// <param name="Key"></param>

103         /// <returns></returns>

104         public bool Contains(T Key)

105         {

106             if (isEmpty())

107                 return false;

108 

109             root = Splay(Key, root);

110 

111             return root.element.CompareTo(Key) == 0;

112         }

113         #endregion

114 

115         #region 判断是否为空

116         /// <summary>

117         /// 判断是否为空

118         /// </summary>

119         /// <returns></returns>

120         public bool isEmpty()

121         {

122             return root == nullNode;

123         }

124         #endregion

125 

126         #region 伸展

127         /// <summary>

128         /// 伸展

129         /// </summary>

130         /// <param name="Key"></param>

131         /// <param name="tree"></param>

132         /// <returns></returns>

133         public BinaryNode<T> Splay(T Key, BinaryNode<T> tree)

134         {

135             BinaryNode<T> leftTreeMax, rightTreeMin;

136 

137             header.left = header.right = nullNode;

138 

139             leftTreeMax = rightTreeMin = header;

140 

141             nullNode.element = Key;

142 

143             while (true)

144             {

145                 int compareResult = Key.CompareTo(tree.element);

146 

147                 if (compareResult < 0)

148                 {

149                     //如果成立,说明是”一字型“旋转

150                     if (Key.CompareTo(tree.left.element) < 0)

151                         tree = rotateWithLeftChild(tree);

152 

153                     if (tree.left == nullNode)

154                         break;

155 

156                     //动态的将中间树的”当前节点“追加到 R 树中,同时备份在header中

157                     rightTreeMin.left = tree;

158 

159                     rightTreeMin = tree;

160 

161                     tree = tree.left;

162                 }

163                 else if (compareResult > 0)

164                 {

165                     //如果成立,说明是”一字型“旋转

166                     if (Key.CompareTo(tree.right.element) > 0)

167                         tree = rotateWithRightChild(tree);

168 

169                     if (tree.right == nullNode)

170                         break;

171 

172                     //动态的将中间树的”当前节点“追加到 L 树中,同时备份在header中

173                     leftTreeMax.right = tree;

174 

175                     leftTreeMax = tree;

176 

177                     tree = tree.right;

178                 }

179                 else

180                 {

181                     break;

182                 }

183             }

184 

185             /* 剥到最后一层,来最后一次切分 */

186             //把中间树的左孩子给“左树”

187             leftTreeMax.right = tree.left;

188 

189             //把中间树的右孩子给“右树”

190             rightTreeMin.left = tree.right;

191 

192             /* 合并操作 */

193             //将头节点的左树作为中间树的左孩子

194             tree.left = header.right;

195 

196             //将头结点的右树作为中间树的右孩子

197             tree.right = header.left;

198 

199             return tree;

200         }

201         #endregion

202 

203         #region 删除

204         /// <summary>

205         /// 删除

206         /// </summary>

207         /// <param name="Key"></param>

208         public void Remove(T Key)

209         {

210             BinaryNode<T> newTree;

211 

212             //将删除结点顶到根节点

213             root = Splay(Key, root);

214 

215             //不等于说明没有找到

216             if (root.element.CompareTo(Key) != 0)

217                 return;

218 

219             //如果左边为空,则直接用root的右孩子接上去

220             if (root.left == nullNode)

221             {

222                 newTree = root.right;

223             }

224             else

225             {

226                 newTree = root.left;

227 

228                 newTree = Splay(Key, newTree);

229 

230                 newTree.right = root.right;

231             }

232             root = newTree;

233         }

234         #endregion

235 

236         #region 右旋转

237         /// <summary>

238         /// 右旋转

239         /// </summary>

240         /// <param name="k1"></param>

241         /// <returns></returns>

242         public BinaryNode<T> rotateWithRightChild(BinaryNode<T> k1)

243         {

244             BinaryNode<T> k2 = k1.right;

245             k1.right = k2.left;

246             k2.left = k1;

247             return k2;

248         }

249         #endregion

250 

251         #region 左旋转

252         /// <summary>

253         /// 左旋转

254         /// </summary>

255         /// <param name="k2"></param>

256         /// <returns></returns>

257         public BinaryNode<T> rotateWithLeftChild(BinaryNode<T> k2)

258         {

259             BinaryNode<T> k1 = k2.left;

260             k2.left = k1.right;

261             k1.right = k2;

262             return k1;

263         }

264         #endregion

265     }

266 }

伸展树可以总结成一幅图:

6天通吃树结构—— 第四天 伸展树

你可能感兴趣的:(树)