重学数据结构系列之——平衡树之SB Tree(Size Blanced Tree)
学习来源:计蒜客
平衡树
1.定义
假如我们依照1-n的顺序插入到二叉排序树中,那么二叉排序树就退化成了一个有序链表,效率大大减少。
2.有关概念
全部平衡树基本由下面三个特征组成:
1.自平衡条件
2.旋转操作
3.旋转的触发
平衡树通过设置合理的自平衡条件,使得二叉排序树的查找、插入等操作的性能不至于退化到 O(n)O(n),而且在进行二叉排序树的查找、插入等操作时进行推断。假设满足当中某个旋转的触发条件,则进行相应的旋转操作。
左旋和右旋:(我的理解)
左旋:右边的深度大于左边深度2以上,把它变成平衡树,叫左旋(由于要把右边的某些元素挪到左边)
右旋:左边的深度大于右边深度2以上,把它变成平衡树。叫右旋(由于要把左边的某些元素挪到右边)
多旋:1.先左旋后右旋 2.先右旋在左旋
SB Tree
1.定义
2.实现
#include <iostream> using namespace std; class SBTNode {
public:
//size:子树大小(就是以当前结点为根构成的树有多少结点)
//data:权值。就是树上的结点储存的值
//value:应该是暂时储存权值的
int data, size, value;
SBTNode * lchild, * rchild, * father;
//构造函数,參数分别为 权值,以当前结点为根的树的大小,父亲结点
SBTNode(int init_data, int init_size = 0, SBTNode * init_father = NULL);
~SBTNode();
//以下依次是
//二叉排序树的插入,搜索,找前驱,找后继,移除某个度为0或1的结点,移除某个权值的点。找出第k大的元素
void insert(int value);
SBTNode * search(int value);
SBTNode * predecessor();
SBTNode * successor();
void remove_node(SBTNode * delete_node);
bool remove(int value);
int select(int k);
}; class BinaryTree {
private:
SBTNode * root;
public:
BinaryTree();
~BinaryTree();
//以下依次是
//二叉树的插入 查找 删除结点 找出第k大的树。都是以上面的结点类的函数为基础的
void insert(int value);
bool find(int value);
bool remove(int value);
int select(int k);
}; //这里搞了个权值为0的结点,避免在边界情况时对空指针(NULL)进行特判,所以将全部原本指向空指针的情况都改为指向一个 ZPTR,并将其 size 设置为 0,从而减少代码复杂度。
SBTNode ZERO(0);
SBTNode * ZPTR = &ZERO; SBTNode::SBTNode(int init_data, int init_size, SBTNode * init_father) {
data = init_data;
size = init_size;
lchild = ZPTR;
rchild = ZPTR;
father = init_father;
} SBTNode::~SBTNode() {
if (lchild != ZPTR) {
delete lchild;
}
if (rchild != ZPTR) {
delete rchild;
}
} //左旋:将右孩子变为“根结点”(当前子树的根结点)。右孩子的左孩子就变成原来的根结点的右孩子
//以下凝视中:node(原来的根结点)的右孩子用“根结点”来说
SBTNode * left_rotate(SBTNode * node) {
//用temp保存“根结点”
SBTNode * temp = node->rchild;
//“根结点”的左孩子 变成node(原来的根结点)的右孩子
node->rchild = temp->lchild;
//更新“根结点”原来的左孩子的父亲为node(原来的根结点)
temp->lchild->father = node;
//node(原来的根结点) 就变成“根结点”的左孩子
temp->lchild = node;
//“根结点”的父亲更新为node(原来的根结点)的父亲
temp->father = node->father;
//node(原来的根结点)的父亲更新为“根结点”
node->father = temp;
//“根结点”的大小更新为node(原来的根结点)的大小(这里的大小是以该结点为根构成的树的结点的个数)
temp->size = node->size;
//node(原来的根结点)的大小更新为 它左孩子和右孩子的大小再在上本身1
node->size = node->lchild->size + node->rchild->size + 1;
//返回左旋后的根结点
return temp;
} //右旋:将左孩子变为“根结点”(当前子树的根结点)。左孩子的右孩子就变成原来的根结点的左孩子
//以下凝视中:node(原来的根结点)的左孩子用“根结点”来说
//反正这里跟上差点儿相反
SBTNode * right_rotate(SBTNode * node) {
//用temp保存“根结点”
SBTNode * temp = node->lchild;
//“根结点”的右孩子 变成node(原来的根结点)的左孩子
node->lchild = temp->rchild;
//更新“根结点”原来的右孩子的父亲为node(原来的根结点)
temp->rchild->father = node;
//node(原来的根结点) 就变成“根结点”的右孩子
temp->rchild = node;
//“根结点”的父亲更新为node(原来的根结点)的父亲
temp->father = node->father;
//node(原来的根结点)的父亲更新为“根结点”
node->father = temp;
//“根结点”的大小更新为node(原来的根结点)的大小(这里的大小是以该结点为根构成的树的结点的个数)
temp->size = node->size;
//node(原来的根结点)的大小更新为 它左孩子和右孩子的大小再在上本身1
node->size = node->lchild->size + node->rchild->size + 1;
//返回右旋后的根结点
return temp;
} //利用上面的左右旋进行调整的函数
//flag为false:处理左子树更高的情况,否则处理右子树更高的情况
//node:要调整的子树的根结点
SBTNode * maintain(SBTNode * node, bool flag) {
//左子树比右子树高(或者叫深度要深)
if (flag == false) {
//LL型:左子树的左子树的元素个数大于右子树的元素个数。应进行右旋
if (node->lchild->lchild->size > node->rchild->size) {
//右旋并更新子树的根结点
node = right_rotate(node);
}
//LR型:左子树的右子树的元素个数大于右子树的元素个数
//那么先对左子树进行左旋,就变成LL型。再右旋就可以
else if (node->lchild->rchild->size > node->rchild->size) {
//左旋并更新左子树的根结点
node->lchild = left_rotate(node->lchild);
//右旋并更新根节点
node = right_rotate(node);
} else {
//说明平衡了,返回根节点
return node;
}
//右子树比左子树高(或者叫深度要深)
} else {
//RR型:右子树的右子树的元素个数大于左子树的元素个数,应进行左旋
if (node->rchild->rchild->size > node->lchild->size) {
//左旋并更新根节点
node = left_rotate(node);
}
//RL型: 右子树的左子树的元素个数大于左子树的元素个数
//那么先对右子树进行右旋,变成RR型,在左旋就可以
else if (node->rchild->lchild->size > node->lchild->size) {
//右旋并更新左子树的根结点
node->rchild = right_rotate(node->rchild);
//左旋并更新根节点
node = left_rotate(node);
} else {
//说明平衡了。返回根节点
return node;
}
}
//以下为递归调用。由于有时上面的调整过后。左子树和右子树的某个结点还是不平衡 //递归调用,处理可能左子树的左子树高度更高的情况
//false表示左子树较高
node->lchild = maintain(node->lchild, false);
//其右子树的右子树高度更高的情况
node->rchild = maintain(node->rchild, true);
//最后再对子树根结点的左右子树递归进行调整
node = maintain(node, false);
node = maintain(node, true);
//返回调整后的子树的根结点
return node;
} SBTNode * insert(SBTNode * node, int value) {
if (value == node->data) {
return node;
} else {
node->size++;
if (value > node->data) {
if (node->rchild == ZPTR) {
node->rchild = new SBTNode(value, 1, node);
} else {
node->rchild = insert(node->rchild, value);
}
} else {
if (node->lchild == ZPTR) {
node->lchild = new SBTNode(value, 1, node);
} else {
node->lchild = insert(node->lchild, value);
}
}
}
return maintain(node, value > node->data);
} SBTNode * SBTNode::search(int value) {
if (data == value) {
return this;
} else if (value > data) {
if (rchild == ZPTR) {
return ZPTR;
} else {
return rchild->search(value);
}
} else {
if (lchild == ZPTR) {
return ZPTR;
} else {
return lchild->search(value);
}
}
} SBTNode * SBTNode::predecessor() {
SBTNode * temp = lchild;
while (temp != ZPTR && temp->rchild != ZPTR) {
temp = temp->rchild;
}
return temp;
} SBTNode * SBTNode::successor() {
SBTNode * temp = rchild;
while (temp != ZPTR && temp->lchild != ZPTR) {
temp = temp->lchild;
}
return temp;
} void SBTNode::remove_node(SBTNode * delete_node) {
SBTNode * temp = ZPTR;
if (delete_node->lchild != ZPTR) {
temp = delete_node->lchild;
temp->father = delete_node->father;
delete_node->lchild = ZPTR;
} if (delete_node->rchild != ZPTR) {
temp = delete_node->rchild;
temp->father = delete_node->father;
delete_node->rchild = ZPTR;
}
if (delete_node->father->lchild == delete_node) {
delete_node->father->lchild = temp;
} else {
delete_node->father->rchild = temp;
}
temp = delete_node;
while (temp != NULL) {
temp->size--;
temp = temp->father;
}
delete delete_node;
} bool SBTNode::remove(int value) {
SBTNode * delete_node, * current_node;
current_node = search(value);
if (current_node == ZPTR) {
return false;
}
size--;
if (current_node->lchild != ZPTR) {
delete_node = current_node->predecessor();
} else if (current_node->rchild != ZPTR) {
delete_node = current_node->successor();
} else {
delete_node = current_node;
}
current_node->data = delete_node->data;
remove_node(delete_node);
return true;
} int SBTNode::select(int k) {
//rank表示当前结点在子树的排位
int rank = lchild->size + 1;
//若rank等于第k小的k。说明就是要找的值。直接返回权值就可以
if (rank == k) {
return data;
}else if (k < rank) {
//小于rank。就表明要找比当前结点更小的。就在左边查找
return lchild->select(k);
}else{
//大于就在右边咯
//这里为什么看k - rank呢。由于我们已经把前rank排除了。
//相当于我们要在右子树(把他当做一颗新的树去查找),所以排位当然要减去rank了
return rchild->select(k - rank);
}
} BinaryTree::BinaryTree() {
root = NULL;
} BinaryTree::~BinaryTree() {
if (root != NULL) {
delete root;
}
} void BinaryTree::insert(int value) {
if (root == NULL) {
//初始化时仅仅有根结点,所以子树大小为1
root = new SBTNode(value, 1);
} else {
root = ::insert(root, value);
}
} bool BinaryTree::find(int value) {
if (root->search(value) == NULL) {
return false;
} else {
return true;
}
} bool BinaryTree::remove(int value) {
return root->remove(value);
} int BinaryTree::select(int k) {
return root->select(k);
} int main() {
BinaryTree binarytree;
int arr[10] = { 16, 9, 20, 3, 18, 15, 6, 30, 7, 25 };
for (int i = 0; i < 10; i++) {
binarytree.insert(arr[i]);
}
cout<<"请输入第k小的元素:"<<endl;
int k;
cin >> k;
cout<<endl<<"第"<<k<<"小的元素为:"<<endl;
cout << binarytree.select(k) << endl;
return 0;
}
3.执行结果
重学数据结构系列之——平衡树之SB Tree(Size Blanced Tree)的更多相关文章
- 重学c#系列——字典(十一)
前言 重学c#系列继续更新,简单看一下字典的源码. 看源码主要是解释一下江湖中的两个传言: 字典foreach 顺序是字典添加的顺序 字典删除元素后,字典顺序将会改变 正文 那么就从实例化开始看起,这 ...
- 重学c#系列——对c#粗浅的认识(一)
前言 什么是c#呢? 首先你是如何读c#的呢?c sharp?或者c 井? 官方读法是:see sharp. 有没有发现开发多年,然后感觉名字不对. tip:为个人重新整理,如学习还是看官网,c# 文 ...
- 重学ES系列之新型数据结构Map应用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- D&F学数据结构系列——B树(B-树和B+树)介绍
B树 定义:一棵B树T是具有如下性质的有根树: 1)每个节点X有以下域: a)n[x],当前存储在X节点中的关键字数, b)n[x]个关键字本身,以非降序存放,因此key1[x]<=key2[x ...
- D&F学数据结构系列——二叉排序树
二叉排序树(Binary Sort Tree) 定义:对于树中的每个结点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值. 二叉查找树声明: #ifndef _ ...
- D&F学数据结构系列——二叉堆
二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿 ...
- D&F学数据结构系列——红黑树
红黑树 定义:一棵二叉查找树如果满足下面的红黑性质,则为一棵红黑树: 1)每个结点不是红的就是黑的 2)根结点是黑的 3)每个叶结点是黑的 4)如果一个结点是红的,它的两个儿子都是黑的(即不可能有两个 ...
- 重学Golang系列(一): 深入理解 interface和reflect
前言 interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface.本文即是对Go语言中的interface和reflect基础概念和用法的一 ...
- 重学c#系列——c# 托管和非托管资源(三)
前言 c# 托管和非托管比较重要,因为这涉及到资源的释放. 现在只要在计算机上运行的,无论玩出什么花来,整个什么概念,逃不过输入数据修改数据输出数据(计算机本质),这里面有个数据的输入,那么我们的内存 ...
随机推荐
- 6.12---前提两个对象的成员必须一致,才能将有数据的对象将数据传给反射获取的对象conver(有数据对象,目标对象)
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)/ ...
- 实现div左右上下都居中
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Redis应用场景[分享]
Redis应用场景[分享] 1.取最新N个数据的操作2.排行榜应用 取TOPN操作3.需要精确设定过期时间的应用4.计数器应用(文章阅读数.评论数)5.Uniq操作,获取某段时间所有数据排重值6.实时 ...
- HDU_1022_Train Problem I
Train Problem I Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)T ...
- Java多线程学习笔记(四)——Thread类中方法介绍
currentThread():返回代码正在被哪个线程调用. public class CurrentThreadWay { public static void main(String[] args ...
- 安卓app测试之Monkey日志分析
转:原文:https://blog.csdn.net/a136332462/article/details/76066909 一.一般测试结果分析-搜索关键字: 1.无响应问题可以在日志中搜索 “A ...
- MySQL单表数据不超过500万:是经验数值,还是黄金铁律?
今天,探讨一个有趣的话题:MySQL 单表数据达到多少时才需要考虑分库分表?有人说 2000 万行,也有人说 500 万行.那么,你觉得这个数值多少才合适呢? 曾经在中国互联网技术圈广为流传着这么一个 ...
- yii 在lnmp下访问问题
lnmp大坑 /usr/local/nginx/conf/fastcgi.conf 文件里面
- attack on titans(动态规划递推,限制条件,至少转至多方法,进击的巨人)
题目意思: 给n个士兵排队,每个士兵三种G.R.P可选,求至少有m个连续G士兵,最多有k个连续R士兵的排列的种数. 原题 Attack on Titans Time Limit: 2 Seconds ...
- 关于C++中字符串输入get与getline的区别
最近使用C++中自己老是忘记的一个点,get与getline的区别. 1.get与getline get和getline所属iostream类,作用是读取一整行,通过换行符确定读取结束,他们都可以读取 ...