zoukankan      html  css  js  c++  java
  • Size Balanced Tree

      Size Balanced Tree(SBT)是目前速度最快的平衡二叉搜索树,且能够进行多种搜索操作,区间操作;和AVL、红黑树、伸展树、Treap类似,SBT也是通过对节点的旋转来维持树的平衡,而相比其他平衡树,SBT维持平衡所需要的额外数据很少,只需要维持以当前节点为根的子树的大小;且SBT的编写复杂度低。因此具有空间优势、速度优势、编写优势。

    SBT的节点

        SBT节点维持很少的额外信息,只需要知道以当前节点为根的子树的大小。

        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] 
    sbt失衡1_1 
    (1)执行 RightRotate(T),得到如下结果 
    sbt失衡1_2 
    (2)此时以T为根的树可能不平衡,递归调用Maintain(T) 
    sbt失衡1_3 
    (3)此时T成为平衡SBT, 再次对L调用Maintain(L)将整体变为平衡SBT 
    sbt失衡1_4

        失衡情形2:    S[LR] > S[R] 
    sbt失衡2_1 
    (1)执行 LeftRotate(L),得到如下结果 
    sbt失衡2_2 
    (2)执行 RightRotate(T),得到如下结果 
    sbt失衡2_3 
    (2)此时以B和R为根的树可能不平衡,递归调用Maintain(B)、Maintain(R) 
    sbt失衡2_4 
    (3)此时T成为平衡SBT, 再次对L调用Maintain(L)将整体变为平衡SBT 
    sbt失衡2_5

        由于Maintain操作是个递归执行的函数,貌似可能会出现无限循环,但实际上,陈启峰在论文里分析过了,Maintain操作的平坦复杂度为O(1)。因此Maintain操作不会出现无法结束的情况。

    SBT的其他操作

        和其他的二叉搜索树一样,SBT支持插入、删除、查找等操作。插入和删除操作可能会破坏SBT的平衡性质,因此,需要在普通的插入和删除之后对节点进行维护,即调用Maintain函数。

    实现(c++)

    #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();
    	}
    
    };

     参考: 
    SBT-陈启峰

  • 相关阅读:
    mysql5.7版本centos8环境修改my.cnf配置文件
    mysql5.7使用r2dbc持久层框架性能飙升,一小时插入623万数据,平均每秒插入1723条
    mysql5.7决定SQL中IN条件是否走索引的成本计算,mysql中的index dive是在两个区间之间计算有多少条记录的方式
    mysql5.7的SQL执行成本计算,IO成本和CPU成本,单表查询成本,多表连接查询成本,执行成本决定mysql是否走索引,.OPTIMIZER_TRACE,cost_info
    mysql5.7基于块的嵌套循环连接(Block Nested-Loop Join)
    时间复杂度计算法
    mysql5.7索引合并:交集、并集,EXPLAIN例子
    mysql5.7分区表
    mysql5.7的随机IO和顺序IO
    MySQL5.7二级索引+回表的MRR优化技术
  • 原文地址:https://www.cnblogs.com/gtarcoder/p/4724288.html
Copyright © 2011-2022 走看看