二叉树

二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个结点最多只能有两棵子树,且有左右之分 [1] 。
二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点 [1] 。
在这里插入图片描述

完全二叉树

概念:——若设⼆叉树的⾼度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最⼤个
数,第h层有叶⼦结点,并且叶⼦结点都是从左到右依次排布,这就是完全⼆叉树。

判断一棵树是否是完全二叉树的思路 [3]
<1>如果树为空,则直接返回错
  <2>如果树不为空:层序遍历二叉树
  <2.1>如果一个结点左右孩子都不为空,则pop该节点,将其左右孩子入队列;
  <2.1>如果遇到一个结点,左孩子为空,右孩子不为空,则该树一定不是完全二叉树;
  <2.2>如果遇到一个结点,左孩子不为空,右孩子为空;或者左右孩子都为空;则该节点之后的队列中的结点都为叶子节点;该树才是完全二叉树,否则就不是完全二叉树;

满二叉树

)满⼆叉树——除了叶结点外每⼀个结点都有左右⼦叶且叶⼦结点都处在最底层的⼆叉树。

深度为k,则其节点数为 (2^k) -1。
第i层有2^(i-1) 个节点

平衡二叉树

概念—平衡⼆叉树⼜被称为AVL树(区别于AVL算法),它是⼀棵⼆叉排序树,且具有以下性
质:它是⼀棵空树或它的左右两个⼦树的⾼度差的绝对值不超过1,并且左右两个⼦树都是⼀棵平衡⼆
叉树。

堆是一种基本的数据结构。在这里我用数组来形容,在一个二叉堆的数组中,每一个元素都要保证大于等于另外两个特定位置的元素。同时相应的,这些元素又要大于等于另外两个相应位置的元素,整个数据结构以此类推。如果我们将整个数据结构画成树状结构,就能够清晰地看出整个结构的样子。
在这里插入图片描述
图片中是我们的最大堆与最小堆。

下面我们堆排序代码举例:(视频讲解推荐:b站搜堆排序)

package com.cx.Set_;

public class Heap {
    public static void main(String[] args) {
        int arr[]={1,4,45,6,33,32,29};
        heap_Sort(arr,7);
        for(int i=0;i<arr.length;i++){
            System.out.println(arr[i]);
        }

    }
    public static  void heap_Sort(int arr[],int size){
        //先形成最大堆
        build_Heap(arr,size);
        //交换位置将最后一个节点和第一个节点,最后砍断最后一个节点
        for(int i=size-1;i>=0;i--){
            //交换最后一个和第一个
            swpe(arr,i,0);
            //再进行hepify交换位置
            hepify(arr,i,0);
        }
    }
    /*
    最大堆实现,从后面往前
     */
    public static void build_Heap(int arr[],int size){
        int last_Node=size-1;
        if((size-1)%2==0){
            int parent=(last_Node-2)/2;
        }else{
            int parent=(last_Node-1)/2;
        }
        for(int i=size-1;i>=0;i--){
            hepify(arr,size,i);
        }

    }
    /*
    最大堆实现,从前往往后。
   */
    public static void hepify(int arr[], int size, int n){
        //创建一个出口
        if(n>=size){
            return;
        }
        //左孩子节点位置
        int left=2*n+1;
        //右孩子节点位置
        int right=2*n+2;
        //假设n为最大值坐标
        int max=n;
        if(left<size&&arr[max]<arr[left]){
            max=left;
        }
        if(right<size&&arr[max]<arr[right]){
            max=right;
        }
        if(max!=n){
            swpe(arr,max,n);
            hepify(arr,size,max);//现在值变成了max继续hepify
        }
    }
    //交换位置的方法
    public static  void swpe(int arr[],int max,int n){
        int temp=0;
        temp=arr[n];//temp指向被hepify的值
        arr[n]=arr[max];//将被hepify位置的值指向较大值
        arr[max]=temp;//让较小值指向hepify的值
    }
}

二叉查找树(BST):

二叉查找树(Binary Search Tree),又被称为二叉搜索树。
它是特殊的二叉树:对于二叉树,假设x为二叉树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。那么,这棵树就是二叉查找树。如下图所示:

在这里插入图片描述
⼆叉查找树的特点:

  1. 若任意节点的左⼦树不空,则左⼦树上所有结点的 值均⼩于它的根结点的值;
  2. 若任意节点的右⼦树不空,则右⼦树上所有结点的值均⼤于它的根结点的值;
  3. 任意节点的左、右⼦树也分别为⼆叉查找树;
  4. 没有键值相等的节点(no duplicate nodes)。

平衡二叉树

平衡树(Balance Tree,BT) 指的是,任意节点的子树的高度差都小于等于1。常见的符合平衡树的有,B树(多路平衡搜索树)、AVL树(二叉平衡搜索树)等。平衡树可以完成集合的一系列操作, 时间复杂度和空间复杂度相对于“2-3树”要低,在完成集合的一系列操作中始终保持平衡,为大型数据库的组织、索引提供了一条新的途径。 [1]
设“2-3 树”的每个结点存放一组与应用问题有关的数据, 且有一个关键字 (>0的整数) 作为标识。关键字的存放规则如下:对于结点X, 设左、中、右子树均不空, 则左子树任一结点的关键字小于中子树中任一结点的关键字;中子树中任一结点的关键字小于结点X的关键字;而X的关键字又小于右子树中任一结点的关键字, 称这样的“2-3树”为平衡树, 如概述图所示。 [1]

最好的情况是O(logn),存在于完全二叉树情况下,其访问性能近似于折半查找;
最差的情况是O(n),比如插入的元素所有节点都没有左子树(右子树),这种情况需要将二叉树的全部节点遍历一次。

红黑树

为什么引入红黑树呢?
因为二叉查找树的局限性,因为二叉查找树再插入数据时,会照成形成线性表的情况发生,那么二叉查找树的优势就等同于没有了一样。

在这里插入图片描述
那么什么是红黑树呢?
红黑树即具备了二叉搜索树特性,它有具备自身的一些特性:
1.节点是红色或黑色。
2.根节点是黑色。
3.每个叶子节点都是黑色的空节点(NIL节点)。
4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
下图中这棵树,就是一颗典型的红黑树:
在这里插入图片描述
对于红黑数解决了二叉搜索树的问题,最长路径不会超过最小路径的两倍。
但是对于红黑树也会存在自己的一些问题,那就是它会存在规则破坏的问题、就是插入一个节点时,因为父节点时红色的,插入节点也是红色的,就破坏了其红色节点的两个子节点都必须是黑色的。

如图:在这里插入图片描述
那么如何解决这个问题呢?
1、变色
2、旋转->分左旋、右旋

**变色:

**就是改变原本节点的颜色、但是不改变插入节点的颜色,以让其达到红黑树的要求。

eg:以上图为例:
1、改变22的颜色为黑色,这时发现不符合(每条路径上的黑色节点个数相同的特点)
2、改变27为黑色(这时就符合了红黑树的规则了下面的子树、但是整个子树还没实现)

旋转:

左旋转(逆时针旋转):
大致步骤:让自己的右孩子替换自己、自己在右孩子的位置上。

在这里插入图片描述
在这里插入图片描述

右旋转:(顺时针旋转):
大致步骤:让自己被左孩子替换、自己在右孩子位置上。

在这里插入图片描述
这篇文章讲的很不错:漫画讲解红黑树

红黑树查找的最坏时间复杂度也是O(logN);
推荐网址
https:www.cs.usfcu.edu/~galles/visualization/RedBlack.html

总结归纳什么时候使用变色、什么时候使用旋转。
1、如果添加节点(红)的父节点和父节点的兄弟节点相同颜色,就执行变色操作。
2、如果添加节点(红)的父节点和父节点的兄弟节点颜色不同(这里父节点的兄弟节点为NIL也包括)、就执行旋转操作。
2.1、在2前提下,如果添加节点、父节点、祖父节点三点一线,那么绕父节点执行旋转。
2.2、在2前提下,如果添加节点、父节点、祖父节点三点不共线、那么先旋转一次将三点调整为一线、再进行第二次旋转。

二叉查找树、平衡二叉查找树、红黑树都是动态查找树。
他们的时间复杂度都是与树的深度有关,都是O(log2^n),为了降低树的深度会提高查找效率,于是有了多路的B-tree/B±tree/ B
-tree (B~Tree)。
*

要了解B树我们先了解什么是2-3树、什么是2-3-4树。

2-3树在这里插入图片描述
2-3-4树同理。

B树也叫B-tree----->B-

注意B树也就是B-树,B树的英文是B-tree,很多地方直译成了B-树,其实B-树和B树是同一种树。
B树是为了磁盘或其它存储设备而设计的一种多叉平衡查找树。

(1)B-Tree的接点结构
B-tree中,每个结点包含:
1、本结点所含关键字的个数;
2、指向父结点的指针;
2、关键字;
3、指向子结点的指针数组;

#define Max l000 //结点中关键字的最大数目:Max=m-1,m是B-树的
阶(节点中最多孩子数目)
#define Min 500 //非根结点中关键字的最小数目:Min=m/2-1
typedef int KeyType; //KeyType关键字类型由用户定义
typedef struct node{ //结点定义中省略了指向关键字代表的记录的指针
   int keynum; //结点中当前拥有的关键字的个数,keynum<<Max
   KeyType key[Max+1]; //关键字向量为key[1..keynum],key[0]不用。
   struct node *parent; //指向双亲结点
   struct node *son[Max+1];//指向孩子结点的指针数组,孩子指针向量为son[0..keynum]
 }BTreeNode;
typedef BTreeNode *BTree;

B树的插入操作:

1、先通过阶数计算出非叶子节点中最多和最少关键字数。
eg:假设阶数是3,那么最少关键字数是 【3/2】-1=1,最多关键字就是我们的3-1=2,还有一个中间节点的【3/2】=2;然后就进行关键字的添加。

3阶B树,插入下面节点 【20,30,31,33,34,44,50,70】

最低关键字值:【3/2】-1=1
最高关键字值:3-1=2;

当插入20,30,31三个数时超过了关键字最大值,所以这时要进行拆分中间值【3/2】=2;
所以按30拆分,如图
在这里插入图片描述
在这里插入图片描述
计算中间值:向上取整【3/2】=2—>33

接下来继续拆分对31,33,34
在这里插入图片描述
继续插入值:44、50
在这里插入图片描述
继续拆分:
在这里插入图片描述
这时第一层关键字变成了3个所以要进行下一步的拆分:【3/2】=2;所以对33执行拆分:
在这里插入图片描述

节点删除:
1、如果删除的节点是终端节点非(NIL),如果该节点包括的关键字大于最低关键字数,那么直接删除该关键字。
2、如果删除的节点是终端节点非(NIL),如果该节点包括的关键字等于于最低关键字数,那么就得找兄弟节点借节点,这时也分两种情况、一个是兄弟节点大于最低关键字数、二是兄弟节点小于最低关键字数。解决一就直接找兄弟节点借节点、解决二就是找父节点借节点。
3、如果删除的节点是非终端节点非(NIL),不管小于还是大于最低关键字数那么就找到该节点的直接前驱和直接后继节点(光线从上往下照,看影子),交换位置就可以变成终端节点了,那么就是1、2中情况进行。

B+树(B±tree)

B±tree是应文件系统所需而产生的一种B-tree的变形树。
1、根节点和枝节点很简单,分别记录每个叶子节点的最小值,并用一个指针指向叶子节点
2、叶子节点里每个键值都指向真正的数据块(如Oracle里的RowID),每个叶子节点都有前指针和后指针,这是为了做范围查询时,叶子节点间可以直接跳转,从而避免再去回溯至枝和跟节点。

(1)B树和B+树的对比
一棵m阶的B+树和m阶的B树的异同点在于:
1、B+树的关键字树就是它的孩子数,B树中的孩子树是它的关键字树+1。
2、B+树中非根节点的关键字树范围【m/2】<=n<=m(根节点的范围:1<=n<=m);
B树中非根节点的关键字树的范围是【m/2】-1<=n<=m(根节点的范围1<=n<=m-1);
3、B+树种所有叶子节点包含信息、非叶子节点起到索引的效果。而B-树中叶子节点是不包含信息的,B+树中非叶子节点只包含子节点中最大关键字,和指向子节点的指针,而B-树中会包含相应记录的内存地址信息。
4、在B+树中,包含有一个指针指向叶子关键字最小的叶子节点、这些叶子节点串联成一个单链表。
在这里插入图片描述
为什么说B+树比B-树更适合去做数据库或文件索引?
1、B±tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。
如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。
2、支持区间查找、扫库方便。

B*树:

是B+树的一种变体,B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);

**B+树的分裂:**当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
B树的分裂:**当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;
**所以,B
树分配新结点的概率比B+树要低,空间使用率更高;

各种B树的优缺点:

  1. B+ 树的叶⼦节点链表结构相⽐于 B- 树便于扫库,和范围检索。
  2. B+树⽀持range-query(区间查询)⾮常⽅便,⽽B树不⽀持。这是数据库选⽤B+树的最主要原
    因。
  3. B树 是B+树的变体,B树分配新结点的概率⽐B+树要低,空间使⽤率更⾼;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值