重学数据结构系列之——平衡树之SB Tree(Size Blanced Tree)

平衡树

1.定义

对于每个结点,左右两个子树的高度差的绝对值不超过1,或者叫深度差不超过1

为什么会出现这样一种树呢?

假如我们按照1-n的顺序插入到二叉排序树中,那么二叉排序树就退化成了一个有序链表,效率大大降低。

2.有关概念

所有平衡树基本由以下三个特征组成:

1.自平衡条件

2.旋转操作

3.旋转的触发

平衡树通过设置合理的自平衡条件,使得二叉排序树的查找、插入等操作的性能不至于退化到 O(n)O(n),并且在进行二叉排序树的查找、插入等操作时进行判断,如果满足其中某个旋转的触发条件,则进行对应的旋转操作。

左旋和右旋:(我的理解)

左旋:右边的深度大于左边深度2以上,把它变成平衡树,叫左旋(因为要把右边的某些元素挪到左边)

右旋:左边的深度大于右边深度2以上,把它变成平衡树,叫右旋(因为要把左边的某些元素挪到右边)

多旋:1.先左旋后右旋 2.先右旋在左旋

SB Tree

这个其实不是非常熟悉,程序是计蒜客的,大概理解吧,做点笔记,不过注释都是自己的理解

1.定义

他也是平衡树的一种。每个结点所在子树的结点个数不小于其兄弟的两个孩子所在子树的结点个数。

还有SBTree 只有在插入时才可能触发调整。

据说他有个可以查找第k大的元素这个特殊的特点,为什么会有这个特点呢。

首先说个概念: 树的大小:所含结点的个数, 还有的话SB Tree也是属于排序树

SB Tree的结点的数据结构中用size储存了以当前结点为根构成的树的大小,那么它的父亲就排在size+1位了,具体看代码更好理解

2.实现

下面没注释的函数请看这个(二叉排序树):http://blog.csdn.net/u012763794/article/details/50967631

#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
//不过我们这里代替NULL指针的是ZERO(权值为0的结点)的地址,我们看到下面SBTNode的构造函数初始化的时候用的也是ZPTR指针
//这里用这个,二叉树又用NULL,好像有点矛盾,知道原因的可以告诉我,谢谢
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.运行结果

时间: 2024-12-02 13:52:01

重学数据结构系列之——平衡树之SB Tree(Size Blanced Tree)的相关文章

D&amp;F学数据结构系列——红黑树

红黑树 定义:一棵二叉查找树如果满足下面的红黑性质,则为一棵红黑树: 1)每个结点不是红的就是黑的 2)根结点是黑的 3)每个叶结点是黑的 4)如果一个结点是红的,它的两个儿子都是黑的(即不可能有两个连续的红色结点) 5)对于每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点 性质: 这些约束确保了红黑树的关键特性: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长.结果是这个树大致上是平衡的.因为操作比如插入.删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上

D&amp;F学数据结构系列——B树(B-树和B+树)介绍

B树 定义:一棵B树T是具有如下性质的有根树: 1)每个节点X有以下域: a)n[x],当前存储在X节点中的关键字数, b)n[x]个关键字本身,以非降序存放,因此key1[x]<=key2[x]<=...<=keyn[x][x], c)leaf[x],是一个布尔值,如果x是叶子的话,则它为TRUE,如果x为一个内节点,则为FALSE. 2)每个内节点包含n[x]+1个指向其子女的指针c1[x],c2[x],...,cn[x]+1[x].叶节点没有子女,故它们的ci域无意义. 3)各关键

D&amp;F学数据结构系列——二叉堆

二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元2i+1中,它的父亲在[i/2](向下取整)中. 因此,一个数据结构将由一个数组.一个代表最大值的整数.以及当前的堆的大小组成.一个典型的优先队列(priority queue)如下: 1 #ifndef _BinHeap_H 2 struct HeapStruct; 3 type

D&amp;F学数据结构系列——前驱和后继

前驱和后继 本文所述为二叉排序树的前驱和后继,如果想了解二叉排序树的概念,可以参考我的博文*** 给定一个二叉查找树中的结点,有时候要求找出在中序遍历顺序下它的后继.如果所有的关键字均不同,则某一结X点的后继就是所有(结点值)大于X的结点中最小的那个. 包含两种情况: 情况一:结点X的右子树非空,则X的后继是其右子树中最左的结点 情况二:结点X的右子树为空,设X的后继为Y.则Y是X的最低祖先结点,且Y的左儿子也是X的祖先(X自身也可以看做是X的祖先) 1 BT* TreeSuccessor(BT

重学数据结构笔记

大学上数据结构课都逃了,后来发现其重要,期间又自己抱起书学了一遍,因为当时懒,自学的成果也不大,如今工作有一年了,也写过几个项目越来越感觉数据结构对个人思想的重要,如今趁裸辞的空窗期,好好恶补下,顺便写个笔记总结下.仅为个人笔记用所以不排版写到哪算哪. 1,数据结构,是相互之间存在一种或多种关系的数据元素的集合从不同角度讨论,有不同的分类物理结构:顺序存储结构,链式存储结构逻辑结构:线性结构,集合结构,树形结构,图形结构2.数据类型,是指一组性质相同的值得类型的集合以及在此集合上的一些操作抽象数

重学Golang系列(一): 深入理解 interface和reflect

前言 interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface.本文即是对Go语言中的interface和reflect基础概念和用法的一次梳理,也算是我阶段学习的总结,以期温故而知新. interface(接口) 定义 在Go语言中,如果自定义类型(比如struct)实现了某个interface中的所有方法,那么就可以说这个类型实现了这个接口.接口可如下定义: type 接口名称 interface { method1(参数列表)

重学数据结构 --- 分类+稀疏数组

一.数据结构的分类 1. 数据结构两大类 线性结构和非线性结构 1) 线性结构 线性结构是最常见的数据结构,特点是元素间存在一对一的线性关系. 线性结构又分两种,一种是顺序存储(称为顺序表),另外一种是链式存储(称为链表).顺序表中的存储元素的连续的.链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息. 常见的线性结构有:数组.队列.链表和栈(这里只是讲个大概,具体内容后面的文章会展开阐述). 2) 非线性结构 非线性结构就是结点元素可能存在多个直接前趋和多个直接后续(

重学数据结构笔记(2)

4.栈和队列栈(stack),是仅限定在表尾进行插入和删除操作的线性表 (一种特殊的线性表,有前驱后继关系)我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何元素的称为空栈,栈又称为先进后出的线性表简称LIFO结构先进后出,后进先出,最先进栈的元素不一定最后出,因为栈对元素的删除和增加位置有了限制,可时间没限制例子:1,2,3依次放入栈中,可能是 1进 1出 2进 2出 3进,3出 所以进栈顺序是123出栈顺序也是123,其他情况相似.但是一定不会有312出栈顺序(自己想为什么,很重要

重学数据结构笔记(3)

5.串,由零个或多个字符组成的序列,又叫字符串串的比较是通过组成字符串的字符之间的编码来进行的,而字符串编码指的是字符在对应字符集中的符号.串的存储结构与线性表相同分两种串的顺序存储结构串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的.按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区域,一般使用定长数组来定义串的顺序存储方式其实是有问题的,因为串的插入和替换都会引起长度超过数组长度.串的链式存储结构,与线性表链式存储结构相似,不过每个节点存一个字符,太浪费空间,可以一