【开卷数据结构 】2-3树

这篇具有很好参考价值的文章主要介绍了【开卷数据结构 】2-3树。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

2-3树 的定义

 2-3树 的结点

 2-3树 的查找

参考代码

 2-3树 的插入

向 2 结点中插入新结点

向 3 结点中插入新结点

图解过程:

 参考代码:

 2-3树 的删除

1.p是r的左结点

2.p是r的中结点

3.p是r的右结点



【开卷数据结构 】2-3树

2-3树 的定义

Q:什么是二叉排序树

A:二叉排序树或者是一棵空树,或者是具有如下性质的二叉树

  • 1)若它的左子树不空,则 左子树 上所有结点的值 均小于 它的根结点的值
  • 2)若它的右子树不空,则 右子树 上所有结点的值 均大于 它的根结点的值
  • 3)左、右子树也分别是一棵二叉排序树

通过引入结点度大于 2 的排序树,可以得到一种插入算法和删除算法都比二叉排序树简单的树结构,且这些算法的时间复杂性仍是 O(logn) 。这种树结构称为2-3树2-3树的名字反映出 2-3树 具有如下性质: 一棵 2-3树 中的每个内部结点的度或者是2,或者是3。其中度为2的结点称为 2结点,度为3的结点称为 3结点。

Q:什么是2-3树

A:一棵 2-3树(2-3 tree) 是一棵查找树,该查找树或者为空,或者满足如下性质:

  • 1)每个内部结点或者是一个 2结点,或者是一个 3结点。一个 2结点 存放一个元素,而一个 3结点 存放两个元素。
  • 2)令左孩子和中孩子表示 2结点 的两个儿子。以左孩子为根的子树中所有结点的关键字值都小于该结点元素的关键字,而以中孩子为根的子树中所有结点的关键字值都大于该结点元素的关键字。
  • 3)令左孩子,中孩子和右孩子表示 3结点 的三个儿子。且有左孩子结点元素的关键字小于右孩子结点元素的关键字;以左孩子为根的子树中所有结点的关键字值都小于右孩子结点元素的关键字;以中孩子为根的子树中所有结点的关键字值都大于左孩子结点元素的关键字而小于右孩子结点元素的关键字;而以右孩子为根的子树中所有结点的关键字值都大于右孩子结点元素的关键字。
  • 4)所有叶子点都在树的同一层。

一棵2-3树

【开卷数据结构 】2-3树


 2-3树 的结点


 2-3树的结点如下所示:

struct two_three {
    element data_l, data_r;
    two_three *left_child;
    two_three *middle_child;
    two_three *right_child;
};

假设任何元素的关键字值都不等于 INT_MAX ,并且 2结点 的 data_r.key=INT_MAX ,其唯一的元素存放在 data_l 域中,其 left_child 域和 middle_child 域分别指向它的两个儿子,将right_child 域置为 NULL。

【开卷数据结构 】2-3树

 2-3树 的查找

2-3 树 的查找类似二叉平衡树的查找过程。

假设我们在 2-3树 t 上查找元素的关键字值等于 x 的结点,假定关键字值为整数。在进行查找时,一共有四种情况:

  • 1)x 小于第一个关键字。
  • 2)x 位于第一个关键字和第二个关键字之间。
  • 3)x 大于第二个关键字。
  • 4)x 等于结点中某个关键字

参考代码


two_three *search23(two_three *t, element x)
{
    while(t) {
        if (x < t->data_l) {
            t = t->left_child;
        } else if (x > t->data_l && x < t->data_r) {
            t = t->middle_child;
        } else if (x > t->data_r) {
            t = t->right_child;
        } else {
            return t;
        }
    }
    return NULL;
}

通过分析,可以发现 while 循环的重复次数不会超过 2-3树的高度。因此,如果树 t 包含 n 个结点,则函数 search23 的时间复杂度为O(logn)。

【开卷数据结构 】2-3树

 2-3树 的插入

若原 2-3树 为空,则直接插入结点,若原 2-3树 非空,在插入之前需要对带插入的结点进行一次查找操作。若树中已经有此结点则不进行插入操作。若没有此结点,进行插入操作,此时插入有两种情况。

  • 1)向 2 结点的树中插入新结点
  • 2)向 3 结点的树中插入新结点

向 2 结点中插入新结点


此时叶子结点里面就只有一个元素,那么插入之后正好两个元素,符合 2-3 树 的要求,直接插入。

插入关键字值为 30 的元素。

【开卷数据结构 】2-3树

 

向 3 结点中插入新结点


此时这个叶子结点里面已经有两个元素了,插入后就有三个元素,那么就把叶子结点中间的元素传给父结点,自己分裂成两个结点。再看父结点,如果接收之后父结点也变成了 3结点,那么就一直重复这个过程,直到根结点。

插入关键字值为 35 的元素。

【开卷数据结构 】2-3树

插入关键字值为 20 的元素。

【开卷数据结构 】2-3树

插入关键字值为 25 的元素。

【开卷数据结构 】2-3树

 

 参考代码:


two_three_ptr search23(two_three_ptr t,element x)
{
	while(t){
		switch(compare(x,t)){// 比较大小,返回情况1,2,3,4
			case 1:t=t->left_child;
					break;
			case 2:t=t->middle_child;
					break;
			case 3:t=t->right_child;
					break;
			case 1:return t;									
		}	
	}	
	return NULL:
} 
void insert23 (two_three_ptr *t,element y)
{
	two_three_ptr q,p,temp;
	if(!(*t))//如果树为空
		new_root(t,y,NULL);
	else{
		p=find_node(*t,y);
		if(!p){
			cout<<"该结点在树中"<<endl;
			exit(1); 
		}
		q=NULL;
		
		for(;;){
			if(p->data_r.key==INT_MAX){//二结点 
				put_in(&p,y,q);
				break;
			}
			else{//三结点 			
				split(p,&y,&q);
				if(p==*t){
					new_root(t,y,q);
					break;
				}
				else{
					 p=s->pop();
				}
			}
		}
	}
}

该函数调用了以下几个函数:

  • 1)new_root:输入新根的左子树,新根存放的单个元素和新根的中子树,返回指向新根的指针。
void new_root(two_three *&left, element y, two_three *middle)
{
    two_three *p = new two_three;
    p->left_child = left;
    p->middle_child = middle;
    p->right_child = NULL;
    p->data_l = y;
    p->data_r = MAX;
    left = p;
}
  • 2)find_node:在一棵非空的 2-3树 t 中查找关键字值为 y. key 的元素。如果 t 中存在关键字值为 y. key 的元素,则返回 NULL,否则返回查找过程中遇到的叶结点 p。此外, find_node还会创建一个全局的栈结构,以便得到从叶结点 p 到根结点 t 的路径上所有 p 的祖先结点。这个栈结构依次保存从最近祖先结点到最远祖先结点的结点表。由于在拆分结点时需要访问拆分结点的父结点,所以要使用这个结点表。
two_three *find_node(two_three *t, element x, stack *&s)
{
    while(t) {
        s->push(t);
        if (x < t->data_l && t->left_child) {
            t = t->left_child;
        } else if (x > t->data_l && x < t->data_r && t->middle_child) {
            t = t->middle_child;
        } else if (x > t->data_r && t->right_child) {
            t = t->right_child;
        } else if (x == t->data_l || x == t->data_r) {
            return NULL;
        } else {
            return s->pop();
        }
    }
}
  • 3)put_in:该函数将元素 y 插到只包含一个元素的结点 p 中,并将子树 q 直接放在 y 的右侧。因此,如果 y 变为 data_l,则 q 就变为 middle_child ,原来的 data_l 和 middle child 分别右移变成 data_r 和 right_child。如果 y 变为 data_r,则 q 就变成right_child。
void put_in(two_three *p, element x, two_three *q)
{
    if(p->data_l < x) {
        p->data_r = x;
        p->right_child = q;
    } else {
        p->right_child = p->middle_child;
        p->middle_child = q;
        p->data_r = p->data_l;
        p->data_l = x;
    }
}
  • 4)split:该函数的输入为包含两个元素的结点 p,其功能是创建一个新结点 q。新结点 q 将存放 p 中原来的两个元素与元素 y 三者中关键字值最大的元素。三者中关键字值最小的元素将成为 p 中的唯一元素。p 中原有的三个子树指针和指针 q 分别存放在 p 结点和新建结点的四个儿子域中。函数返回时,y 为关键字值处于中间大小的元素,q 为指向新建结点的指针。
void split(two_three *p, element &x, two_three *&q)
{
    element min, max;
    if(x < p->data_l) {
        min = x;
        max = p->data_r;
        x = p->data_l;
    } else if (x > p->data_r) {
        min = p->data_l;
        max = x;
        x = p->data_r;
    } else {
        min = p->data_l;
        max = p->data_r;
    }

    two_three *n = new two_three;
    n->data_l = max;
    n->data_r = MAX;
    n->left_child = p->right_child;
    n->middle_child = q;
    n->right_child = NULL;

    p->data_l = min;
    p->data_r = MAX;
    p->right_child = NULL;

    q = n;
}

【开卷数据结构 】2-3树

 2-3树 的删除

在删除之前需要对要删除的结点进行一次查找操作。若树中没有此结点则不进行删除操作。

如果要删除的元素不在叶子结点中,可以用一个叶子结点中的适当元素与待删除元素进行交换,从而将该删除操作转换成在叶结点上的删除操作。一般情况下,可以用被删除元素左子树中的关键字值最大的元素或者右子树中关键字值最小的元素与该元素进行交换。


如果要删除的叶子结点是 3结点 ,直接把元素删除了就可以了,不会破坏2-3 B树的任何性质。如果要删除的叶子结点是 2结点 ,直接删除的话就不符合 2-3树 的性质了。类似于平衡二叉树(AVL),此时就需要进行旋转或合并操作了。


根据要删除结点 p 是父结点 f 的左孩子,中间孩子还是右孩子,旋转,合并可以分为六种情况。

1.p是r的左结点旋转

【开卷数据结构 】2-3树

2.p是r的中结点旋转

【开卷数据结构 】2-3树

3.p是r的右结点旋转

【开卷数据结构 】2-3树

4.p是r的左结点合并

【开卷数据结构 】2-3树

 中结点和右结点的合并也差不多,这里就不在详细描述了。文章来源地址https://www.toymoban.com/news/detail-505265.html

到了这里,关于【开卷数据结构 】2-3树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【开卷数据结构 】图的应用——最短生成树

    目录 最小生成树 Prim算法实现最小生成树 kruskal算法实现最小生成树 Q:什么是广度优先搜索 A: 一个连通图的生成树含有图中全部的顶点,并且只含尽可能少的边 。若砍去它的一条边,则会使生成树变成非连通图。若给它增加一条边,则会形成图中的一条回路。 对于一个带

    2023年04月09日
    浏览(49)
  • 【算法与数据结构】3 知行合一,线性查找的自定义类测试

    欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于算法与数据结构体系专栏, 本专栏 对于0基础者极为友好,欢迎与我一起完成算法与数据结构的从0到1的跨越 ☑️首篇详细讲述线性查找法并且对其进行了 初步的优化 :👉传送门

    2023年04月27日
    浏览(48)
  • 【数据结构】线性表(一)线性表的定义及其基本操作(顺序表插入、删除、查找、修改)

    目录 一、线性表 1. 线性表的定义 2. 线性表的要素 二、线性表的基本操作 三、线性表的顺序存储结构 1. 定义 2. 顺序表的操作       a. 插入操作 b. 删除操作 c. 查找操作 d. 修改操作 e. 代码实例          一个线性表是由零个或多个 具有相同类型的结点 组成的有序集合。

    2024年02月03日
    浏览(70)
  • 数据结构(2)—单链表(带头结点和不带头结点)

            单链表 是通过一组任意的存储单元来存储线性表中的数据元素。每个结点都有 data数据域 (用来存放数据元素)和 next指针域 (用来存放后继节点的地址)。         对于顺序表,单链表可以解决顺序表需要一整个大量的连续的存储单元的缺点,单链表的元素

    2024年02月05日
    浏览(56)
  • 【数据结构】(二叉树)计算结点|叶子结点|高度|第K层结点数

      目录 概念: 特殊的二叉树 二叉树的性质 二叉树的存储结构 二叉树的创建 二叉树遍历  前序: 中序: 后序:  计算结点数 计算叶子结点数 计算树的高度(深度) 计算第K层结点数   一颗二叉树是结点的一个有限集合,该集合: 1.或者为空; 2.由一个根节点加上两棵别称

    2024年02月03日
    浏览(38)
  • 【数据结构】C语言实现单链表(带头结点)

    Single linked list with leading nodes 关于不带头结点的单链表:不带头结点的单链表 结点定义: 接口定义: 初始化需要申请头结点,让头指针指向空的头结点。 将申请结点的代码进行封装: 需要越过头结点 找到尾结点,然后插入到尾结点的后面。 对比不带头结点的单链表的尾插

    2024年02月02日
    浏览(219)
  • 【数据结构】——单链表的基本操作(带头结点)

            单链表解决了顺序表需要大量连续存储单元的缺点,但单链表附加指针域, 存储密度较顺序表低(考点!!) 。由于单链表的元素离散地分布在存储空间中,所以单链表是 非随机存取 的存储结构,即不能直接找到表中某个特定的结点。当查找某个特定结点时,需要

    2024年02月05日
    浏览(52)
  • 【数据结构】C语言实现双向链表(带头结点、循环)

    结点定义: 接口定义: 我们将申请结点的代码封装成函数,方便后续使用 由于是带头结点的双向链表,因此在使用链表前,我们需要对链表进行初始化。 遍历链表,值得说的是,带头结点的双向链表的循环结束条件是 cur != phead 尾插时,需要先找到尾结点,然后将新结点插

    2024年02月03日
    浏览(72)
  • 『初阶数据结构 • C语言』⑨ - 基于结点的数据结构——链表(单链表&&双向循环链表)附完整源码

      本章内容 1.什么是链表 2.链表常见几种形式 3.无头单向非循环链表的实现 3.1结点结构的定义 3.2函数接口的实现 3.2.1尾插 3.2.2尾删 4. 带头双向循环链表的实现 4.1结点结构的定义 4.2函数接口的实现 5.两种链表的差异 ①尾插与尾删的时间复杂度 ②头插与头删的时间复杂度 ③

    2024年02月16日
    浏览(82)
  • 【数据结构OJ题】链表中倒数第k个结点

    原题链接:https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13tqId=11167rp=2ru=/activity/ojqru=/ta/coding-interviews/question-ranking 目录 1. 题目描述 2. 思路分析 3. 代码实现 快慢指针法   (如果有小伙伴不了解快慢指针法,可以看看这篇文章:https://blog.csdn.net/m0_62531913/article/details/

    2024年02月12日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包