Size Balanced Tree
Size Balanced Tree(SBT)是目前速度最快的平衡二叉搜索树,且能够进行多种搜索操作,区间操作;和AVL、红黑树、伸展树、Treap类似,SBT也是通过对节点的旋转来维持树的平衡,而相比其他平衡树,SBT维持平衡所需要的额外数据很少,只需要维持以当前节点为根的子树的大小;且SBT的编写复杂度低。因此具有空间优势、速度优势、编写优势。
SBT的节点
SBT节点维持很少的额外信息,只需要知道以当前节点为根的子树的大小。
1 2 3 4 5 6 7 8 9 10 | struct TreeNode{ int data; TreeNode* child[2]; int size; //以该节点为根的子树的大小(节点的个数) TreeNode( int d){ data = d; child[0] = child[1] = NULL; size = 1; } }; |
SBT的平衡性质
一棵平衡的SBT树满足如下要求:
记S[t]为以节点t为根的子树的大小,则对于每个节点T,记其左子节点L, 右子节点R, 左子结点的左子结点LL, 左子结点的右子节点LR, 右子节点的左子结点RL, 右子节点的右子节点RR。
则有, S[L] >= max(S[RL], S[RR]), S[R] >= max(S[LL], S[LR]).
即任何一个节点的size均大于等于其侄子节点的size。 (侄子节点:定义为一个节点的兄弟节点的两个子节点)
SBT的维护操作
一棵平衡的SBT在进行插入和删除之后,可能会不再平衡,此时需要进行维护操作,维护操作需要进行左旋或者右旋操作,旋转操作和其他平衡树的旋转类似(具体见zig-zag旋转) .
SBT的非平衡情况分为两类:左子结点和左子结点的侄子节点不平衡或者右子节点和右子节点的侄子节点不平衡。这里以右子节点和右子节点的侄子节点为例,进行Maintain操作。
失衡情形1: S[LL] > S[R]
(1)执行 RightRotate(T),得到如下结果
(2)此时以T为根的树可能不平衡,递归调用Maintain(T)
(3)此时T成为平衡SBT, 再次对L调用Maintain(L)将整体变为平衡SBT
失衡情形2: S[LR] > S[R]
(1)执行 LeftRotate(L),得到如下结果
(2)执行 RightRotate(T),得到如下结果
(2)此时以B和R为根的树可能不平衡,递归调用Maintain(B)、Maintain(R)
(3)此时T成为平衡SBT, 再次对L调用Maintain(L)将整体变为平衡SBT
由于Maintain操作是个递归执行的函数,貌似可能会出现无限循环,但实际上,陈启峰在论文里分析过了,Maintain操作的平坦复杂度为O(1)。因此Maintain操作不会出现无法结束的情况。
SBT的其他操作
和其他的二叉搜索树一样,SBT支持插入、删除、查找等操作。插入和删除操作可能会破坏SBT的平衡性质,因此,需要在普通的插入和删除之后对节点进行维护,即调用Maintain函数。
实现(c++)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #include<iostream> using namespace std; struct TreeNode{ int data; TreeNode* child[2]; int size; int count; TreeNode( int d){ data = d; child[0] = child[1] = NULL; size = count = 1; } void Update(){ size = count; if (child[0]){ size += child[0]->size; } if (child[1]){ size += child[1]->size; } } }; struct SBT{ TreeNode* root; SBT() :root(NULL){}; void Rotate(TreeNode*& node, int dir){ TreeNode* ch = node->child[dir]; node->child[dir] = ch->child[!dir]; ch->child[!dir] = node; node = ch; } //返回node节点为根的子树的大小 int GetSize(TreeNode* node){ if (node) return node->size; return 0; //对于空节点,直接返回0 } //维持平衡 void Maintain(TreeNode*& node, bool flag){ TreeNode* R = node->child[1]; TreeNode* L = node->child[0]; TreeNode* LL = NULL,*LR = NULL,*RL = NULL,*RR = NULL; if (L){ LL = L->child[0]; LR = L->child[1]; } if (R){ RL = R->child[0]; RR = R->child[1]; } if (flag == false ){ //左边维护 if (GetSize(LL) > GetSize(R)){ //失衡情况1 Rotate(node, 0); } else if (GetSize(LR) > GetSize(R)){ //失衡情况2 Rotate(L, 1); Rotate(node, 0); } else { return ; //不失衡,直接返回 } } else { if (GetSize(RR) > GetSize(L)){ Rotate(node, 1); } else if (GetSize(RL) > GetSize(L)){ Rotate(R, 0); Rotate(node, 1); } else { return ; } } Maintain(node->child[0], false ); //继续将 左子树维持平衡,注意这里不能直接使用L,因为之前进行了旋转操作 Maintain(node->child[1], true ); //继续将 右子树维持平衡 Maintain(node, true ); //再维持 node Maintain(node, false ); } void Insert(TreeNode*& node, int data){ if (!node){ node = new TreeNode(data); return ; } else if (node->data == data){ node->count++; node->Update(); //更新本节点以及其祖先节点的size return ; } else { int dir = node->data < data; Insert(node->child[dir], data); Maintain(node, ! dir); //如果新插入的数据 小于 当前节点的数据,则被插入左子树, //此时左子树的左右子节点的size可能大于右子节点,因此Maintain(x, false) node->Update(); } } void Delete(TreeNode*& node, int w){ if (!node){ return ; } if (node->data == w){ if (node->child[0] && node->child[1]){ TreeNode* succ = node->child[1]; while (succ->child[0]){ succ = succ->child[0]; } node->data = succ->data; succ->data = w; Delete(node, w); } else { TreeNode* tmp_node = NULL; if (node->child[0]) tmp_node = node->child[0]; else tmp_node = node->child[1]; delete node; node = tmp_node; } } Maintain(node, false ); node->Update(); } };<br> |
参考:
SBT-陈启峰
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步