精华内容
下载资源
问答
  • 若将关键字1,2,3,4,5,6,7 依次插入到初始为的平衡二叉树 T 中,则 T 中平衡因子为 0 的分支结点的个数是( )。 图中可以知道,平衡因子为0的结点有:4,2,6 共三个。 这个题目其实考的就是如何...

    题目:

    若将关键字1,2,3,4,5,6,7 依次插入到初始为空的平衡二叉树 T 中,则 T 中平衡因子为 0 的分支结点的个数是( )。 

     

     

     

    从图中可以知道,平衡因子为0的结点有:4,2,6 共三个。

    这个题目其实考的就是如何根据关键字构造平衡二叉树,只要把平衡二叉树正确地画出来了,就能够很清楚地知道各个平衡因子的结点个数。

     

    展开全文
  • 1.二叉树 定义:(1)空树;(2)只有一个根节点;(3)有左右两个子树,并且子树也是一颗二叉树(如图)。性质:第 i 层上最多有个节点.2.深度为k的树最多有个节点,我们称之为满二叉树,满二叉树在底层右向左减少n个节点,...

    8aec9e88b1a6d3ee1c55f53a4362b877.png

    f49e2f716377b2a4abdec1d51403e288.png

    树是数据结构内很重要的一种结构。不过我们此处不深究,仅讨论二叉树,线索二叉树,哈夫曼树(最优树)。

    1.二叉树

    定义:

    (1)空树;(2)只有一个根节点;(3)有左右两个子树,并且子树也是一颗二叉树(如图)。

    2a70e5439e48ecee40292995adafa8e9.png

    性质:

    1. 第 i 层上最多有

    966d73398602e486eef65201d5c62a6b.png

    个节点.

    2.深度为k的树最多有

    18f92b0de8b7c3eaee95678ac5751378.png

    个节点,我们称之为满二叉树,满二叉树在底层从右向左减少n个节点,此时称为完全二叉树。

    3.度为0的结点的个数为度为2的节点个数加一,

    73783a592c4329a1a43152fe446deacf.png
    1. n个节点的完全二叉树的深度为

    f7ffe6a169703537ec5521468acbd037.png

    。注释:[x]表示不超过x的最大整数,也就是向下取整。

    节点序号为 i ,在子节点存在的情况下,节点的序号为2i和2i+1

    遍历方式

    1. 先序遍历:先访问根节点,再访问左子树然后是右子树。
    2. 中序遍历:先访问左子树,再访问根节点然后再右子树。
    3. 后序遍历:先访问左子树,再访问右子树然后是根节点。

    先序中序,后序中序能唯一的确定唯一一个二叉树

    还有其他三种遍历方式 : 只需将以上三种方式的左和右换位置即可

    存储结构

    1. 顺序存储结构(由于太浪费内存,所以说一般不用)
    2. 链式存储结构:利用二叉链表来组成的二叉树(有n个节点的二叉树,含有n+1个空链域)。

    #include<stdio.h>
    ​
    typedef struct Lnode{
      char data;
      struct Lnode *left,*right;
    }Lnode,*linkLnode;
    ​
    //创建树
    void setleaf(linkLnode &L){
      char data;
      scanf("%c",&data);
    ​
      if(data=='#'){
        L=NULL;
        return;
      }
    ​
      Lnode *w=new Lnode;
      L=w;
      L->data=data;
      setleaf(L->left);
    ​
      setleaf(L->right);
    }
    ​
    //先序遍历
    void xian(linkLnode L){
    ​
    ​
      if(L==NULL)
        return;
      else
        printf("%ct",L->data);
    ​
      xian(L->left);
      xian(L->right);
    ​
    }
    //中序遍历
    void zhong(linkLnode L){
    ​
      if(L==NULL)
        return;
    ​
      zhong(L->left);
      printf("%ct",L->data);
      zhong(L->right);
    ​
    }
    //后序遍历
    void hou(linkLnode L){
    ​
      if(L==NULL)
        return;
      hou(L->left);
    ​
      hou(L->right);
      printf("%ct",L->data);
    }
    ​
    //度为0的节点个数
    int ling(linkLnode L){
      int i;
      if(L!=NULL)
        i=ling(L->left)+ling(L->right);
      else
        return 0;
    ​
      if(L->left==NULL && L->right==NULL)
        i=i+1;
      return i;
    }
    ​
    //度为1的节点个数
    int yi(linkLnode L){
      int i;
      if(L!=NULL)
        i=yi(L->left)+yi(L->right);
      else
        return 0;
    ​
      if((L->left==NULL && L->right!=NULL) ||(L->left!=NULL && L->right==NULL))
        i=i+1;
      return i;
    }
    ​
    //从数的最左边,最底层,开始往高处,往右侧检测
    //度为2的节点个数
    int er(linkLnode L){
      int i;
      if(L!=NULL)
        i=er(L->left)+er(L->right);
      else
        return 0;
    ​
      if(L->left!=NULL && L->right!=NULL)
        i=i+1;
      return i;
    }
    ​
    ​
    ​
    ​
    //计算树的深度
    int deep(linkLnode L){
    ​
      if(L==NULL)
        return 0;
    ​
      if( deep(L->left) > deep(L->right) )
        return deep(L->left)+1;
      else
        return deep(L->right)+1;
    ​
    }
    ​
    ​
    void main(){
      linkLnode L;
      L=new Lnode;
      L->left=L->right=NULL;
    ​
    ​
      printf("请以先序遍历的方式输入(#代表此位置无值)n");
      setleaf(L);
      printf("创建成功n");
    ​
    ​
      printf("先序遍历:n");
      xian(L);
    ​
      printf("n中序遍历:n");
    ​
      zhong(L);
    ​
      printf("n后序遍历:n");
      hou(L);
    ​
    ​
      printf("n度为0的节点个数为:%dn",ling(L));
      printf("n度为1的节点个数为:%dn",yi(L));
      printf("n度为2的节点个数为:%dn",er(L));
    ​
      printf("n树的深度为:%dn",deep(L));
    }
    ​

    2 .线索二叉树

    线索二叉树与二叉树大致一样,只不过是将空链域充分利用起来,便于查找每个节点的前驱和后继。

    代码仅是中序线索二叉树

    #include<stdio.h>
    ​
    typedef struct Lnode{
      char data;
      struct Lnode *left,*right;
      int zuo,you;
    }Lnode,*linkLnode;
    ​
    ​
    //创建树
    void setleaf(linkLnode &L){
      char data;
      scanf("%c",&data);
    ​
      if(data=='#'){
        L=NULL;
        return;
      }
    ​
      Lnode *w=new Lnode;
      L=w;
      L->data=data;
      setleaf(L->left);
    ​
      setleaf(L->right);
    }
    ​
    //中序遍历
    void zhong(linkLnode &L){
    ​
      if(L==NULL)
        return;
    ​
      zhong(L->left);
      zhong(L->right);
    ​
      if(L->left==NULL)
        L->zuo=0;
      else
        L->zuo=1;
    ​
      if(L->right==NULL)
        L->you=0;
      else
        L->you=1;
    }
    //设置线索
    void guo(linkLnode &L){
    ​
      Lnode *m,*n;
      m=L->left;n=L->right;
      printf("此时进入到了%cn",L->data);    //输出一些过程,便于观察
      if(L->zuo!=0){
        while(m->you==1)
          m=m->right;
    ​
        m->right=L;
        printf("m的指向值为%c,m的右侧为%cn",m->data,m->right->data);
        guo(L->left);
      }
      printf("此时返回到了%cn",L->data);
    ​
      if(L->you!=0){
        while(n->zuo==1)
          n=n->left;
    ​
        n->left=L;
        printf("n的指向值为%c,n的左侧为%cn",n->data,n->left->data);
        guo(L->right);
      }
    ​
      printf("准备返回上一层n");
      return;
    ​
    }
    ​
    ​
    ​
    void zhongappear(linkLnode L){
      Lnode *m;
      m=L;
    ​
      while(1){
    ​
        while(m->zuo==1)
          m=m->left;
        if(m==NULL)
          break;
        printf("%ct",m->data);
    ​
        if(m->you==0){
          printf("%ct",m->right->data);
          m=m->right->right;}
        else
          m=m->right;
        if(m==NULL){
          printf("圆满结束n");
          break;}
    ​
      }
    }
    ​
    void main(){
    ​
      linkLnode L,S;
      L=new Lnode;
      S=new Lnode;
      S->you=0;S->zuo=1;
      S->left=L;
      L->left=L->right=NULL;
    ​
      printf("请以先序遍历的方式输入(#代表此位置无值)n");
      setleaf(L);
      printf("创建成功n");
    ​
      zhong(L);
      guo(L);
    ​
      printf("n中序遍历结果:n");
      zhongappear(L);
    ​
    ​
    }
    ​

    3 .哈夫曼树

    又名最优树,就是指带权路径最小的数,也是二叉树的一种,仅包含度为0或者2的节点。

    7742179ad09aeff59cd373745e6b834e.png

    构造方法

    取权值最小的两个节点,作为额外一个空节点的左右两个孩子,然后取第三小的权值节点,与这颗树组成新树,以此类推,构成一颗哈夫曼树。权值越大的节点离根节点越近。


    森林

    一棵树为树,两棵树及以上则称为森林

    1——树

    前面介绍的仅是二叉树,一个节点只能有两个孩子,对于树的话则是一个节点可以有无数个孩子

    存储结构

    1. 双亲表示法:节点存储在线性表中,每个节点除了存数data外,还存储父亲的下标,这种方法查找节点的父亲很容易,查找孩子的话则不简便。
    2. 孩子表示法:用的是一个顺序表,然后链接许多链表构成分,连表内包含的是其孩子。
    3. 孩子兄弟表示法:此方法等于说是将树化为一颗二叉树,孩子在左,兄弟在右,处理方法将于二叉树类似了,颇为常用(根节点的右孩子必为空)。

    遍历

    • 先根遍历:等于二叉树的前序遍历,应为根节点的右孩子为空。
    • 后根遍历:等于二叉树的中序遍历

    2——森林

    两颗及以上的树构成森林,森林也可以转化为一棵树,规则也是孩子在左,兄弟在右

    遍历

    • 先序遍历:等于二叉树的先序
    • 中序遍历:等于二叉树的中序
    展开全文
  • 为什么要有红黑树想必大家对二叉树搜索树都不陌生,首先看一下二叉搜索树的定义:二叉搜索树(Binary Search Tree),或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均...

    为什么要有红黑树

    想必大家对二叉树搜索树都不陌生,首先看一下二叉搜索树的定义:二叉搜索树(Binary Search Tree),或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
    从理论上来说,二叉搜索树的查询、插入和删除一个节点的时间复杂度均为O(log(n)),已经完全可以满足我们的要求了,那么为什么还要有红黑树呢?
    我们来看一个例子,向二叉搜索树中依次插入(1,2,3,4,5,6),插入之后是这样的

    65fcbf2619ae4ad3d88e99a6e69bf9bd.png

    可以看到,在这种情况下,二叉搜索树退化成了链表!!!这时候查询、插入和删除一个元素的时候,时间复杂度变成了O(n),显然这是不能接受的。出现这种情况情况的原因是二叉搜索树没有自平衡的机制,所以就有了平衡二叉树的概念。

    平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

    还是刚刚的例子,假如我们用平衡二叉树来实现的话,插入完元素后应该是下面这样的(不唯一)

    9d64796b641f7e911ca99dfe2b751e61.png

    平衡二叉树保证了在最差的情况下,二叉树依然能够保持绝对的平衡,即左右两个子树的高度差的绝对值不超过1。但是这又会带来一个问题,那就是平衡二叉树的定义过于严格,导致每次插入或者删除一个元素之后,都要去维护二叉树整体的平衡,这样产生额外的代价又太大了。二叉搜索树可能退化成链表,而平衡二叉树维护平衡的代价开销又太大了,那怎么办呢?这就要谈到“中庸之道”的智慧了。说白了就是把平衡的定义适当放宽,不那么严格,这样二叉树既不会退化成链表,维护平衡的开销也可以接受。没错,这就是我们要谈的红黑树了。首先看一下红黑树的定义:

    红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须除了满足二叉搜索树的性质外,还要满足下面的性质:

    性质1:每个节点要么是黑色,要么是红色。

    性质2:根节点是黑色。

    性质3:每个叶子节点(NIL)是黑色。

    性质4:每个红色结点的两个子结点一定都是黑色。

    性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

    这就是红黑树的五条性质。我相信很多人都看到过,能背下来的也不在少数,但是真正理解为什么要这样定义的恐怕就不多了。下面就从2-3树的角度来谈谈红黑树的定义。

    从2-3树来看红黑树

    一般我们接触最多的是二叉树,也就是一个父节点最多有两个子节点。2-3树与二叉树的不同之处在于,一个父节点可以有两个子节点,也可以有三个子节点,并且其也满足类似二叉搜索树的性质。还有最重要的,2-3树的所有叶子节点都在同一层,且最后一层不能有空节点,类似于满二叉树。
    我们依次插入10,9,8,7,6,5,4,3,2,1来看一下2-3树是如何进行自平衡的。
    2-3树在插入元素之前首先要进行一次未命中的查找,然后将元素插入叶子节点中,之后再进行平衡操作,下面具体说明。
    首先插入10,如下图

    55aa83df95fcf0c6a75ff1c9301cf54e.png

    然后插入9,9小于10,2-3树在插入时要将9融入10这个叶子节点中(当然也是根节点),融合完成后如下:

    c12c14d45be73db29cb5bc20c046077a.png

    这是一个3节点,不用执行平衡操作。2-3树中把有两个元素,三个子节点的节点称为3节点,把有一个元素,两个子节点的的节点称为2节点。

    接着插入8,插入8的时候同样要先融入叶子节点中,如下图左侧所示

    c7c8f4847a8df3abb1c433c3a8e9f20b.png

    8融入叶子节点后,该结点便拥有了3个元素,不满足2-3树的定义,这时就要把3节点进行分裂,即把左侧和右侧的元素分裂为2节点,而中间的元素抽出,继续融入其父节点,在这里便成为了一个根节点。

    继续插入7,如下

    96c3138cc89b5f346d002ba5cd205e01.png

    插入后,各个节点都满足2-3树的定义,不需要进行平衡操作。

    接着插入6,还是首先找到叶子节点,然后将其融入,如下图左侧所示

    60d2f4015781aeb039475fb9a28eb949.png

    插入后6、7、8三个元素所在的叶子节点不再满足2-3树的定义,需要进行分裂,即抽出元素7融入父节点,6和8分裂为7的左右子节点。

    接着插入5,如下图所示,同样不需要进行平衡操作

    546e940d4588a9c9a9ace1c89f2c1a1c.png

    接着插入4,还是首先找到叶子节点,然后将其融入,如下图左侧所示

    465ba7f3863f302876e568a5bfa52efb.png

    插入后4、5、6三个元素所在的叶子节点不再满足2-3树的定义,需要进行分裂,即抽出元素5融入父节点,4和6分裂为5的左右子节点。5融入父节点后,该结点便有了5、7、9三个元素,因而需要继续分裂,元素7成为新的根节点,5和9成为7的左右子节点。

    接着插入3,3融入4所在的叶子节点中,不需要进行平衡操作

    70ffd0e73b5110c6413567ec14923428.png

    接着插入2,还是首先找到叶子节点,然后将其融入,如下图左侧所示

    ef4acc4996dc9b52dc475fd086fc04af.png

    插入后2、3、4三个元素所在的叶子节点不再满足2-3树的定义,需要进行分裂,即抽出元素3融入父节点,2和4分裂为3的左右子节点,3融入5所在的父节点中。

    最后插入2,同样先找到叶子节点,然后将其融入,如下图所示

    f0b62859700cc4ae7f37a93fd167818d.png

    至此,我们便完成了在2-3树中依次插入10,9,8,7,6,5,4,3,2,1,并且2-3树始终维护着平衡。怎么样,是不是很神奇。

    再看红黑树

    那么红黑树与2-3树有什么关系呢?现在我们对2-3树进行改造,改造成一个二叉树。怎么改造呢?对于2节点,保持不变;对于3节点,我们首先将3节点中左侧的元素标记为红色,如下图2所示。

    f425f08cd3ded5001791a9edddd21167.png

    然后我们将其改造成图3的形式;再将3节点的位于中间的子节点的父节点设置为父节点中那个红色的节点,如图4的所示;最后我们将图4的形式改为二叉树的样子,如图5所示。图5是不是很熟悉,没错,这就是我们常常提到的大名鼎鼎的红黑树了。

    下面我们回过头再看下红黑树的五条性质。

    a331c4944f2ad7dce374cfe674682118.png

    性质1:每个节点要么是黑色,要么是红色。

    2-3树中存在2节点和3节点,3节点中左侧的元素便是红色节点,而其他的节点便是黑色节点。

    性质2:根节点是黑色。

    在2-3树中,根节点只能是2节点或者3节点,2节点与3节点在红黑树中的等价形式,如下图所示

    b6e76d976e09f42462284a54289e8019.png

    显然,无论是哪种情况,根节点都是黑色的。

    性质3:每个叶子节点(NIL)是黑色。

    这里的叶子节点不是指左右子树为空的那个叶子节点,而是指节点不存在子节点或者为空节点被称作叶子节点。在性质2中我们讨论的根节点是黑色的都是讨论根节点不为空的情况,若红黑树是一个空树,那么根节点自然也是空的叶子节点,这时候叶子节点便必然是黑色的。

    性质4:每个红色结点的两个子结点一定都是黑色。

    还是从2-3树的角度来理解,红色节点对应2-3树中3节点左侧的元素,那么它的子节点要么是2节点,要么是3节点。无论是2节点还是3节点对应的节点颜色都是黑色的,这在性质2时已经讨论了。

    性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

    性质5应该是红黑树最重要的一条性质了。2-3树是一颗绝对平衡的树,即2-3树中任意一个节点出发,到达叶子节点后所经过的节点数都是一样的。那么对应到红黑树呢?2-3树中2节点对应到红黑树便是一个黑色的节点,而3节点对应到红黑树是一个红色节点和一个黑色节点。所以,无论是2节点还是3节点,在红黑树中都会对应一个黑色节点。那么2-3树中的绝对平衡,在红黑树中自然就是任意一结点到每个叶子结点的路径都包含数量相同的黑结点了。

    相信大家现在已经对红黑树的五条性质有了更加深刻的体会了。那么我们再看下红黑树维持平衡的三种操作,即变色、左旋、右旋怎么理解呢?

    首先看一下变色,以下图为例,

    0cd4ee666353be7028cfa2d4ea4e5b7a.png

    在2-3树中插入节点3后,便不再满足2-3树的定义,需要进行分解,将元素2抽出作为1和3的父节点,然后2继续向上融合。

    对应到红黑树中就是,首先插入节点3,在红黑树中新插入的节点默认为红色,然后不满足定义,所以需要进行分解,分解后各个节点都为2节点,所以变为黑色。而2节点需要继续向上融合,故要变成红色。

    接着看一下右旋转,以下图为例,

    a4482ee73cfdc9667cd50e10a5d4951f.png

    插入元素1后,进行右旋转操作,首先把2节点与3节点断开连接,同时把2与2的右子树断开连接,然后把2的右子树连接至3的左子树位置,不会违背二分搜索树的性质,然后再把3连接至2的右子树位置。最后还要改变对应节点的颜色,即把2节点的颜色改为原来3节点的黑色,把3节点的颜色改为原来2节点的红色。

    接着看一下左旋转,与右旋转类似,以下图为例,

    1239e0c26f80fc8f6940011ba9616419.png

    插入元素3后,进行左旋转操作,首先把2节点与3节点断开连接,同时把3与3的左子树断开连接,然后把3的左子树连接至2的右子树位置,不会违背二分搜索树的性质,然后再把2连接至3的左子树位置。最后还要改变对应节点的颜色,即把2节点的颜色改为原来3节点的红色,把3节点的颜色改为原来2节点的黑色。

    写在最后

    最后需要说的是,本文中提到的红黑树是一种特殊的红黑树——左倾红黑树,即红色节点都是父节点的左子树,其实按照红黑树的定义不必这样。只要满足红黑树的五条性质,就是红黑树,比如完全可以实现右倾红黑树等等,希望大家不要有误解。
    更多关于红黑树的知识,比如红黑树的插入、删除操作,限于篇幅,本文不再介绍,有兴趣的还是推荐大家阅读《算法4》或者《算法导论》。推荐阅读

    《深入浅出话数据结构》系列之什么是B树、B+树?为什么二叉查找树不行?

    《一文说透数据结构》系列之什么是堆?看这一篇就够了

    为什么会有多线程?什么是线程安全?如何保证线程安全?

    都2020年了,听说你还不会归并排序?手把手教你手写归并排序算法

    觉得文章有用的话,点赞+关注呗,好让更多的人看到这篇文章,也激励博主写出更多的好文章。
    更多关于算法、数据结构和计算机基础知识的内容,欢迎扫码关注我的原创公众号「超悦编程」。

    5cdb119e34022162e3a30c2d6964b1ed.png
    展开全文
  • 为什么要有红黑树想必大家对二叉树搜索树都不陌生,首先看一下二叉搜索树的定义:二叉搜索树(Binary Search Tree),或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于...

    为什么要有红黑树

    想必大家对二叉树搜索树都不陌生,首先看一下二叉搜索树的定义:

    二叉搜索树(Binary Search Tree),或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

    从理论上来说,二叉搜索树的查询、插入和删除一个节点的时间复杂度均为O(log(n)),已经完全可以满足我们的要求了,那么为什么还要有红黑树呢?

    我们来看一个例子,向二叉搜索树中依次插入(1,2,3,4,5,6),插入之后是这样的

    可以看到,在这种情况下,二叉搜索树退化成了链表!!!这时候查询、插入和删除一个元素的时候,时间复杂度变成了O(n),显然这是不能接受的。出现这种情况情况的原因是二叉搜索树没有自平衡的机制,所以就有了平衡二叉树的概念。

    平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

    还是刚刚的例子,假如我们用平衡二叉树来实现的话,插入完元素后应该是下面这样的(不唯一)

    平衡二叉树保证了在最差的情况下,二叉树依然能够保持绝对的平衡,即左右两个子树的高度差的绝对值不超过1。但是这又会带来一个问题,那就是平衡二叉树的定义过于严格,导致每次插入或者删除一个元素之后,都要去维护二叉树整体的平衡,这样产生额外的代价又太大了。二叉搜索树可能退化成链表,而平衡二叉树维护平衡的代价开销又太大了,那怎么办呢?这就要谈到“中庸之道”的智慧了。说白了就是把平衡的定义适当放宽,不那么严格,这样二叉树既不会退化成链表,维护平衡的开销也可以接受。没错,这就是我们要谈的红黑树了。

    首先看一下红黑树的定义:

    红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须除了满足二叉搜索树的性质外,还要满足下面的性质:

    性质1:每个节点要么是黑色,要么是红色。

    性质2:根节点是黑色。

    性质3:每个叶子节点(NIL)是黑色。

    性质4:每个红色结点的两个子结点一定都是黑色。

    性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

    这就是红黑树的五条性质。我相信很多人都看到过,能背下来的也不在少数,但是真正理解为什么要这样定义的恐怕就不多了。下面就从2-3树的角度来谈谈红黑树的定义。

    从2-3树来看红黑树

    一般我们接触最多的是二叉树,也就是一个父节点最多有两个子节点。2-3树与二叉树的不同之处在于,一个父节点可以有两个子节点,也可以有三个子节点,并且其也满足类似二叉搜索树的性质。还有最重要的,2-3树的所有叶子节点都在同一层,且最后一层不能有空节点,类似于满二叉树。

    我们依次插入10,9,8,7,6,5,4,3,2,1来看一下2-3树是如何进行自平衡的。

    2-3树在插入元素之前首先要进行一次未命中的查找,然后将元素插入叶子节点中,之后再进行平衡操作,下面具体说明。

    首先插入10,如下图

    然后插入9,9小于10,2-3树在插入时要将9融入10这个叶子节点中(当然也是根节点),融合完成后如下:

    这是一个3节点,不用执行平衡操作。2-3树中把有两个元素,三个子节点的节点称为3节点,把有一个元素,两个子节点的的节点称为2节点。

    接着插入8,插入8的时候同样要先融入叶子节点中,如下图左侧所示

    8融入叶子节点后,该结点便拥有了3个元素,不满足2-3树的定义,这时就要把3节点进行分裂,即把左侧和右侧的元素分裂为2节点,而中间的元素抽出,继续融入其父节点,在这里便成为了一个根节点。

    继续插入7,如下

    插入后,各个节点都满足2-3树的定义,不需要进行平衡操作。

    接着插入6,还是首先找到叶子节点,然后将其融入,如下图左侧所示

    插入后6、7、8三个元素所在的叶子节点不再满足2-3树的定义,需要进行分裂,即抽出元素7融入父节点,6和8分裂为7的左右子节点。

    接着插入5,如下图所示,同样不需要进行平衡操作

    接着插入4,还是首先找到叶子节点,然后将其融入,如下图左侧所示

    插入后4、5、6三个元素所在的叶子节点不再满足2-3树的定义,需要进行分裂,即抽出元素5融入父节点,4和6分裂为5的左右子节点。5融入父节点后,该结点便有了5、7、9三个元素,因而需要继续分裂,元素7成为新的根节点,5和9成为7的左右子节点。

    接着插入3,3融入4所在的叶子节点中,不需要进行平衡操作

    接着插入2,还是首先找到叶子节点,然后将其融入,如下图左侧所示

    插入后2、3、4三个元素所在的叶子节点不再满足2-3树的定义,需要进行分裂,即抽出元素3融入父节点,2和4分裂为3的左右子节点,3融入5所在的父节点中。

    最后插入2,同样先找到叶子节点,然后将其融入,如下图所示

    至此,我们便完成了在2-3树中依次插入10,9,8,7,6,5,4,3,2,1,并且2-3树始终维护着平衡。怎么样,是不是很神奇。

    再看红黑树

    那么红黑树与2-3树有什么关系呢?现在我们对2-3树进行改造,改造成一个二叉树。怎么改造呢?对于2节点,保持不变;对于3节点,我们首先将3节点中左侧的元素标记为红色,如下图2所示。

    然后我们将其改造成图3的形式;再将3节点的位于中间的子节点的父节点设置为父节点中那个红色的节点,如图4的所示;最后我们将图4的形式改为二叉树的样子,如图5所示。图5是不是很熟悉,没错,这就是我们常常提到的大名鼎鼎的红黑树了。

    下面我们回过头再看下红黑树的五条性质。

    性质1:每个节点要么是黑色,要么是红色。

    2-3树中存在2节点和3节点,3节点中左侧的元素便是红色节点,而其他的节点便是黑色节点。

    性质2:根节点是黑色。

    在2-3树中,根节点只能是2节点或者3节点,2节点与3节点在红黑树中的等价形式,如下图所示

    显然,无论是哪种情况,根节点都是黑色的。

    性质3:每个叶子节点(NIL)是黑色。

    这里的叶子节点不是指左右子树为空的那个叶子节点,而是指节点不存在子节点或者为空节点被称作叶子节点。在性质2中我们讨论的根节点是黑色的都是讨论根节点不为空的情况,若红黑树是一个空树,那么根节点自然也是空的叶子节点,这时候叶子节点便必然是黑色的。

    性质4:每个红色结点的两个子结点一定都是黑色。

    还是从2-3树的角度来理解,红色节点对应2-3树中3节点左侧的元素,那么它的子节点要么是2节点,要么是3节点。无论是2节点还是3节点对应的节点颜色都是黑色的,这在性质2时已经讨论了。

    性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

    性质5应该是红黑树最重要的一条性质了。2-3树是一颗绝对平衡的树,即2-3树中任意一个节点出发,到达叶子节点后所经过的节点数都是一样的。那么对应到红黑树呢?2-3树中2节点对应到红黑树便是一个黑色的节点,而3节点对应到红黑树是一个红色节点和一个黑色节点。所以,无论是2节点还是3节点,在红黑树中都会对应一个黑色节点。那么2-3树中的绝对平衡,在红黑树中自然就是任意一结点到每个叶子结点的路径都包含数量相同的黑结点了。

    相信大家现在已经对红黑树的五条性质有了更加深刻的体会了。那么我们再看下红黑树维持平衡的三种操作,即变色、左旋、右旋怎么理解呢?

    首先看一下变色,以下图为例

    在2-3树中插入节点3后,便不再满足2-3树的定义,需要进行分解,将元素2抽出作为1和3的父节点,然后2继续向上融合。

    对应到红黑树中就是,首先插入节点3,在红黑树中新插入的节点默认为红色,然后不满足定义,所以需要进行分解,分解后各个节点都为2节点,所以变为黑色。而2节点需要继续向上融合,故要变成红色。

    接着看一下右旋转,以下图为例

    插入元素1后,进行右旋转操作,首先把2节点与3节点断开连接,同时把2与2的右子树断开连接,然后把2的右子树连接至3的左子树位置,不会违背二分搜索树的性质,然后再把3连接至2的右子树位置。最后还要改变对应节点的颜色,即把2节点的颜色改为原来3节点的黑色,把3节点的颜色改为原来2节点的红色。

    接着看一下左旋转,与右旋转类似,以下图为例

    插入元素3后,进行左旋转操作,首先把2节点与3节点断开连接,同时把3与3的左子树断开连接,然后把3的左子树连接至2的右子树位置,不会违背二分搜索树的性质,然后再把2连接至3的左子树位置。最后还要改变对应节点的颜色,即把2节点的颜色改为原来3节点的红色,把3节点的颜色改为原来2节点的黑色。

    写在最后

    最后需要说的是,本文中提到的红黑树是一种特殊的红黑树——左倾红黑树,即红色节点都是父节点的左子树,其实按照红黑树的定义不必这样。只要满足红黑树的五条性质,就是红黑树,比如完全可以实现右倾红黑树等等,希望大家不要有误解。

    更多关于红黑树的知识,比如红黑树的插入、删除操作,限于篇幅,本文不再介绍,有兴趣的还是推荐大家阅读《算法4》或者《算法导论》。

    展开全文
  • 我们二叉排序讲起,然后我们聊聊平衡二叉树 二叉排序 首先,对于一颗二叉排序来讲,它满足以下的性质。 1.左子树的所有结点 都 小于 根节点 2.右子的所有结点 都 大于 根节点 3.左右子树本身也是一颗 二叉...
  • 我们求最大深度最大深度的...你先个图,中一个节点没有左子树,只有右子有值。 假设我们让子树为的地方参与比较,那么这个节点的深度值一定会被程序当成1,因为左边节点更浅。 但是宏观来看,这节点的...
  • 但遵行【先上后下,左右至右】的打描方式,组成的层序单个数组,也可出一颗,如下所示(结点用null表示) 如上所示,实际层的是一棵完全二叉树,它的存储一维数组为[10,5,15,3,7,13,18,1,null,6]。 该数组...
  • 把一棵转化为二叉树后,这棵二叉树的形态是() A.唯一的 B.有多种 C.有多种,但根结点都没有左孩子 D.有多种,但根结点都没有右孩子 A 略。 利用二叉链表存储,则根结点的右指针() A.指向最左孩子 B...
  • 把一棵转化为二叉树后,这棵二叉树的形态是() A.唯一的 B.有多种 C.有多种,但根结点都没有左孩子 D.有多种,但根结点都没有右孩子 A 略。 利用二叉链表存储,则根结点的右指针() A.指向最左孩子 B...
  • 数据结构(C++)有关练习题

    热门讨论 2008-01-02 11:27:18
    e. 如有可能,请建立一个存储商品名称和数量的文本文件,并为二叉搜索建立一个成员函数SetupInventory(),用于该文本文件中读取库存商品的数据, 实验报告要求: 1、 按要求记录下二叉搜索的完整实验...
  • pop需要判断Stack1和Stack2中元素的情况,Stack1的话,直接Stack2 pop,Stack1不的话,把Stack1的元素push进入Stack2,然后pop Stack2的值。推广:用两个队列实现栈 面试题8:旋转数组的最小数字:二分查找的...
  • 数据结构题

    2012-12-27 16:58:40
    二叉树T。 3.对关键字序列(72,87,61,23,94,16,05,58)进行堆排序,使之按关键字递减次序排列。请写出排序过程中得到的初始堆和前三趟的序列状态。 初始堆:_94,87,。。。_______ 第1趟:{94},87,72...
  • 数据结构演示软件

    2013-06-02 21:32:36
    图示窗口的上半部分显示图的逻辑结构,初始状态用青色圆圈表示顶点,结点间的黑色连线表示边或弧(连线上有箭头)。演示过程中用红色覆盖已访问的顶点和边(或弧)。窗口下方显示图的邻接表,演示过程中以红色框边...
  • 图示窗口的上半部分显示图的逻辑结构,初始状态用青色圆圈表示顶点,结点间的黑色连线表示边或弧(连线上有箭头)。演示过程中用红色覆盖已访问的顶点和边(或弧)。窗口下方显示图的邻接表,演示过程中以红色框边...
  • 1.4 二叉树 141 1.4.1 获得二叉树的深度和根(顺序结构) 141 范例1-55 获得二叉树的深度和根 141 ∷相关函数:BiTreeDepth函数 Root函数 1.4.2 获得二叉树的深度和根(链表结构) 144 范例1-56 获得二叉树...
  • C语言通用范例开发金典.part2.rar

    热门讨论 2012-08-31 14:18:18
    1.4 二叉树 141 1.4.1 获得二叉树的深度和根(顺序结构) 141 范例1-55 获得二叉树的深度和根 141 ∷相关函数:BiTreeDepth函数 Root函数 1.4.2 获得二叉树的深度和根(链表结构) 144 范例1-56 获得二叉树...
  • C 开发金典

    2013-06-20 16:20:03
    1.4 二叉树 141 1.4.1 获得二叉树的深度和根(顺序结构) 141 范例1-55 获得二叉树的深度和根 141 ∷相关函数:BiTreeDepth函数 Root函数 1.4.2 获得二叉树的深度和根(链表结构) 144 范例1-56 获得二叉树...
  • 符合条件的数据结构就有图、和其它。 嗯~了解一下就行。我们进入正题: 数组 数组是一种线性结构,以十二生肖(鼠、牛、虎、兔、龙、蛇、马、羊、猴、鸡、狗、猪)排序为例:...
  • dk2j_c_赋值语句_复合语句_语句.mp4 KRC0507_多维数组.mp4 lc_流定位.mp4 MCU51_位操作运算符.mp4 MCU51_分支控制.mp4 MCU51_循环控制.mp4 MCU51_编译预处理.mp4 MCU_51_一维数组.mp4 MCU_51_二维数组....
  • 面试题32:怎么把马匹甲村拉到乙村 面试题33:谁打碎了花瓶 面试题34:分机票 面试题35:石头有多重 面试题36:该释放谁 面试题37:谁打碎的玻璃 面试题38:谁是最优秀的医生 面试题39:今天星期几 面试题40:五...
  • 实例074 将数据库数据显示到视图中 88 实例075 用TTreeView控件制作导航界面 90 实例076 TTreeView组件遍历磁盘目录 91 实例077 TTreeView组件在数据库中的应用 92 2.7 TStringGrid控件应用典型实例 94 ...
  • 二叉树、图:深度优先(DFS)、广度优先(BFS) 递归 分治 滑窗 三大牛逼算法:回溯、贪心、动态规划(DP) 怎么学? 刷题 leetcode力扣官网,覆盖简单、中等、困难,刷够300题以上 精选书籍推荐 《漫画算法...

空空如也

空空如也

1 2
收藏数 23
精华内容 9
关键字:

从空树画二叉树