精华内容
下载资源
问答
  • 二叉树有哪几种存储方式?什么样的二叉树适合数组来存储? 树(Tree) 每个元素就是一个节点,用来连线相邻节点之间的关系就是“父子关系”,没有父节点的节点是根结点,没有子节点的节点是叶节点 高度(Height):...

    二叉树基础(上):什么样的二叉树适合用数组来存储?

    二叉树有哪几种存储方式?什么样的二叉树适合用数组来存储?

    树(Tree)

    每个元素就是一个节点,用来连线相邻节点之间的关系就是“父子关系”,没有父节点的节点是根结点,没有子节点的节点是叶节点

    高度(Height):节点到叶子节点的最长路径(边数)

    深度(Depth):根节点到这个节点所经历的边的个数

    层数(level):节点的深度+1

    树的高度 = 根节点的高度

    即高度从下往上度量,深度是从上往下开始度量,层数的起点是1

    二叉树(Binary Tree)

    满二叉树和完全二叉树

    如何存储一颗二叉树?

    一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法

    一:链式存储法,每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针

    二:基于数组的顺序存储法。把根节点存储在下标i=1的位置,左子节点存储在下标为 2 * i = 2 的位置,右子节点存储在2 * i +1 = 3 的位置,根节点的左子节点的的左子节点存储在 2 * i = 4 的位置,其右子节点存储在2 * i + 1 = 5的位置

    总结是:节点X存储在数组中下标为i的位置,下标为2 * i的位置存储的是左子节点,下标为 2 * i +1存储的是右子节点,反过来下标为i / 2的位置存储的是其父节点,所以我们只要知道根节点存储的位置,可以通过下标计算,把整棵树串起来

    堆和堆排序就是一种完全二叉树,存储方式就是数组

    二叉树的遍历

    前序遍历、中序遍历和后序遍历,前中后表示的是节点与它的左右子树节点遍历打印的先后顺序

    前:对树中任意节点来说,先打印该节点,再打印它左子树,最后打印右子树

    中:先打印其节点的左子树,再打印其节点,再打印其右子树

    后:先打印左子树,再打印右子树,最后打印其节点

    前中后遍历就是递归的过程,递推公式是:

    前序遍历的递推公式:
    preOrder(r) = print r->preOrder(r->left) ->preOrder(r->right)
    
    中序遍历的递推公式:
    inOrder(r) = inOrder(r->left) -> print r->inOder(r->right)
    
    后序遍历的递推公式:
    postOrder(r) = postOrder(r->left) ->postOrder(r->right) -> print r
    

    代码为:

    void preOrder(Node* root){
      if(root == null) return;
      print root;//为伪代码,表示打印root节点
      preOrder(root->left);
      preOrder(root->right);
    }
    
    void inOrder(Node* root){
      if(root == null) return;
      inOrder(root->left);
      print root;//打印root节点
      inOrder(root->right);
    }
    
    void postOrder(Node* root){
      if(root == null) return;
      postOrder(r->left);
      postOrder(r->right);
      print root;
    }
    

    每个节点最多会被访问两次,所以遍历操作的时间复杂度跟节点n成正比,即时间复杂度是O(n)

    1.给定一组数据,1,3,5,6,9,10可以构建出多少种不同的二叉树?

    (1)n个数,即n个节点,能构造出多少种不同形态的树

    (2)n个数,有多少种不同的排列

    (1)*(2)即为最终结果=n! 如果是完全二叉树,可以放在数组中,可以简化为数组中的元素有多少种组合方式

    卡特兰数

    2.如何实现按层遍历?

    层序遍历,借助队列辅助即可,根节点先入队列,然后循环从队列中pop节点,将pop出来的节点的左子节点先入队列,右节点后入队列,依次循环,直到队列为空,遍历结束

    vector<vector<int>> levelOrder(TreeNode* root){
      vector<vector<int>> vecRes;
      if(!root) return vecRes;
      vector<int> vecTemp;
      quene<TreeNode *> queneT1;
      queneT1.push(root);
      int nQueneSize = queneT1.size();
      while(nQueneSize > 0 ){
        for(int i = 0;i < nQueneSize;++i){
          TreeNode * pQuene = queneT1.front();
          vecTemp.push_back(pQuene->val);
          queneT1.pop();
          if(pQuene->left){
            queneT1.push(pQuene->left);
          }
          if(pQuene->right){
            queneT1.push(pQuene->right);
          }
        }
        nQueneSize = queneT1.size();
        vecRes.push_back(vecTemp);
        vecTemp.clear();
      }
      return vecRes;
    }
    
    展开全文
  • 二叉树什么

    2020-10-05 22:41:58
    树中每个节点最多只能两个子节点。 在JS中通常Object来模拟二叉树
    树中每个节点最多只能有两个子节点。
    
    JS中通常用Object来模拟二叉树。
    
    展开全文
  • 最近看二叉树的插入(创建)的是二级指针,一开始有点困惑,再难的东西它也个最简单的原因。 一、理解二级指针 例子1首先看一个简单的 #include <iostream> using namespace std; int change...

    最近看二叉树的插入(创建)用的是二级指针,一开始有点困惑,再难的东西它也有个最简单的原因。

    一、理解二级指针

    例子1 首先看一个简单的

    #include <iostream>
    using namespace std;
    int change(int b)
    {
        b=10;
        return b;
    }
    int main()
    {
        int a=5;
        change(a);
        cout<<a<<endl;//输出5
        int c=0;
        c=change(a);
        cout<<c<<endl;//输出10
    }

    为什么输出a=5呢?C语言中函数参数传递只能是值传递。a这个变量的值,传递给b(函数change的局部变量),局部变量b被赋值成5。然后b这个变量 的内容,用10替换5,b就变成10。函数返回值是10。所以c被赋值为函数的返回值10。函数的返回值和对变量的修改没有关系。函数返回值只是函数的运 算结果。b并没有获得对a的修改权限。b和a是不同的两个变量,a只是把值的内容给了b,之后a和b没有关系。重要的一点是,如果要修改a的值,b必须获得对a的修改权限。

    例子2 如何获得对调用者变量的修改权限?

    #include <iostream>
    using namespace std;
    int change(int *b)
    {
        *b=10;
        return *b;
    }
    int main()
    {
        int a=5;
        int *p=&a;
        change(p);
        cout<<a<<endl;//输出10
       int c=0; c=change(p); cout<<c<<endl;//输出10 }

    这个程序分为以下几步:

    1、为int类型的变量a申请一片地址,初始化为5。(变量a的地址就不再变化了)

    2、创建一个指向int类型的指针p,初始化为指向a(p的值是变量a的地址)。

    3、为函数chang创建一个指向int类型的指针b。

    4、把p的值给b(因为p和b指向的类型都是int,两者兼容,可以直接赋值)。这样b也指向a了(因为变量a的地址唯一,b的内容也是a的地址)

    5、把b指向的内容修改成10。(也就是a被修改成10)

    6、函数返回10,赋值给c。

    由例1和例2总结出:若要通过函数B修改函数A中的某个变量a。需要获得变量a的地址,如果a是普通变量,需要获得一级指针。如果a是指针,需要获得二级指针。重点是看需要修改的变量是什么,再去获得它的指针。

    例3 理解地址

    #include <iostream>
    using namespace std;
    int main()
    {
        int *str= NULL;
        int **p=NULL;
        p=&str;
        cout<<str<<endl;
        cout<<p<<endl;
    }

    输出:

    0

    0x61fe98

    p->str->NULL

    (int **->int*->int)

    NULL是一个宏,#define NULL (void*)0,在C++里面被直接被定义成了整数立即数类型的0。

    任何变量都会被分配内存空间。str指向的是NULL,那么str的值就是NULL的地址(NULL的地址被宏定义为0)。但是str在内存中也是有地址的,p的值是str的地址(这里是0x61fe98)。

    例4 内存分配

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    using namespace std;
    void GetMemory( int *p )
    {
        p = (int *) malloc( 100 );
        cout<<p<<endl;
    }
    int main()
    {
        int *str = NULL;
        GetMemory(str);
        cout<<str;
        return 0;
    }

    输出

    0x12125f8

    0

    p的值最初被初始化为0,进行p = (int *) malloc( 100 ); 之后,p的值是内存随机分配的100个字节地址的首地址。str的值还是0。

    对程序进行修改:

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    using namespace std;
    void GetMemory( int **p )
    {
        *p = (int *) malloc( 100 );
    }
    int main()
    {
        int *str = NULL;
        int **p2=&str; //等价于int **p2; p2=&str;
        GetMemory(p2);
        cout<<str;
        return 0;
    }

    输出

    0xe425f8

    在main中定义一个指向str的指针p2。把str的地址传给p。这样GetMemory函数中的p也指向str(因为变量str的地址是唯一的!),这样操作*p也就是操作str。

    例5 最后看一道牛客网上的一道题http://www.nowcoder.com/profile/826954/myFollowings/detail/1002396

    void GetMemory( char *p )
    {
       p = (char *) malloc( 100 ); //申请100个字节的存放char类型的连续区域
    }
    void Test( void )
    {
       char *str = NULL;
       GetMemory( str );
       strcpy( str, "hello world" );
       printf( str );
    }

    这道题答案是str是NULL,如果在main中调用Test,运行会出错。分析一下:

    1、Test中,创建一个指向char类型的指针str,str被初始化为0。

    2、函数Test中执行GetMemory( str );

    3、创建一个指向char类型的指针p。

    4、把str的值(这里是0)传给p,指针变量p的值也被初始化成0。

    5、把申请到的100个字节的内存强制转换成存放char类型,把所分配内存空间的首地址赋值给p(这时p的值从0变成某地址)。

    但是这时候str还是指向NULL。

    回到例1说的:若要通过函数GetMemory修改函数Test中的变量str,需要获得变量str的地址,而不是str的值。

    PS:

    C语言中的malloc函数(没有C++中new好用啊):

    malloc 函数 void *malloc( unsigned int size)

    在内存的动态存储区中分配一块长度为"size" 字节的连续区域。

    如果分配成功,则返回所分配内存空间的首地址,否则返回NULL,申请的内存不会进行初始化。

    类型说明符表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。例如: pc=(char *) malloc (100); 

     

    二、二叉树的创建为什么用二级指针

    感谢http://lidawn.github.io/pointer-on-pointer/这篇博客。这篇文章,概括一下:

    调用者的变量需要被修改内容,这里是root(指向BTreeNode类型的指针),root需要指向一个新插入的节点,也就是需要修改root的值。所以 应该传入指向root的地址。这样在被调用的函数中,对*BST的操作等价于操作root。否则BST如果是和root类型一样的BTreeNode类型 的指针,BST和root位于两个不同的内存,BST只是被初始化为root的值,之后对BST的操作不会影响root。

    //以下代码来自《大话数据结构》6.9节
    //二叉树的二叉链表结点结构定义
    define char TElemType
    typedef struct BiTNode
    {
        TElemType data;
        struct BiTNode *lchild,*rchild;
    }BiTNode,*BiTree;
    //为BiTNode取别名BiTNode,为BiTNode*取别名BiTree
    
    void CreateBiTree(BiTree *T)//操作*T即可,*T是指向BiTNode的指针
    {
        TElemType ch;
        scanf("%c",&ch);
        if(ch=='#')
            *T=NULL;
        else
        {
            *T=(BiTNode*)malloc(sizeof(BiTNode));
            //这里原来是*T=(BiTree)malloc(sizeof(BiTNode));修改之后便于理解
            if(!=*T)
                exit(OVERFLOW);
            //我理解是如果*T还是0(相当于*T还是指向NULL),表示内存分配失败,就退出
            (*T)->data=ch;//*T指向的节点的data分配内容,即生成根节点
            CreateBiTree(&(*T)->lchild);//创建&(*T)->lchild临时变量,传入CreateBiTree,构造左子树
            CreateBiTree(&(*T)->rchild);//创建&(*T)->rchild临时变量,传入CreateBiTree,构造右子树
            //相当于
            // BiTNode **p1;  
            // p1=&((*T)->lchild);//不能直接p1=&lchild
            // CreateBiTree(p1);
            // BiTNode **p2;  
            // p2=&((*T)->rchild);//不能直接p2=&rchild
            // CreateBiTree(p2);
        }
    }
    操作*T相当于操作双亲节点的lchild或rchild(lchild或rchild的地址作为实参传递给形参T)
    对于树根,没有双亲,最初传给T的就是NULL。
    函数递归本质也是函数调用,之所以CreateBiTree(&(*T)->lchild) ,是因为要传当前节点的lchild(*T)->lchild的地址&(*T)->lchild给被调用者CreateBiTree。 
    这里很重要的是:T是BiTNode**类型。是为了考虑函数的调用者。比如在main中要调用CreateBiTree函数。
     
    如果CreateBiTree用BiTNode*类型的形参
    void CreateBiTree(BiTNode *T){......}
    
    int main(){
    
        struct BiTNode *p=NULL;
    
        CreateBiTree(p);
    
    }
    这样T会指向一棵二叉树,但是p还是指向NULL。
     
    所以应该传入二级指针
    void CreateBiTree(BiTNode **T){......}
    
    int main(){
    
        struct BiTNode **p=NULL;
    
        struct BiTNode *b=NULL;
    
        p=&b;
    
        CreateBiTree(p);
    
    }
    这样操作*T也就是操作main中的b。最后b也会指向一棵二叉树。
     
    或者也可以返回指针
    BiTNode * CreateBiTree(BiTNode *T)
    {
        ......
        return T;
    }
    int main(){
        struct BiTNode *p=NULL;
        p=CreateBiTree(p);
    }
    把T(最终指向创建好的二叉树的树根,树根的类型也是BiTNode ), 返回给BiTNode *类型的p。这样p也指向创建好的二叉树的树根。

    转载于:https://www.cnblogs.com/hslzju/p/5396987.html

    展开全文
  • 思考题:二叉树有哪几种存储方式?什么样的二叉树适合数组来存储? 一、树  树的常用概念及其解释  父节点、子节点、兄弟结点、根节点、叶子结点;结点的高度、深度、层;树的高度。    上图中,A节点...

    思考题:二叉树有哪几种存储方式?什么样的二叉树适合用数组来存储?

    一、树

            树的常用概念及其解释

            父节点、子节点、兄弟结点、根节点、叶子结点;结点的高度、深度、层;树的高度。

        

           上图中,A节点就是B节点的父节点,B节点是A节点的子节点。B、C、D这三个节点的父节点都是同一个节点A,所以它们之间是互称兄弟节点。没有父节点的结点叫做根节点,也就是图中的点E;没有子节点的节点叫做叶子节点或是叶节点,比如图中的G、H、I、J、K、L 都是叶子节点。

          

         举个例子说明一下:

          

     

    二、二叉树

    1. 概念

    • 二叉树:每个节点最多只有2个子节点的树,这两个子节点分别是左子节点和右子节点。
    • 满二叉树:除了叶子节点以外,每个节点都有左右两个子节点,这种二叉树叫做满二叉树。
    • 完全二叉树:叶子节点都在最底下两层,最后一层叶子节点都是靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫做完全二叉树。

    2.二叉树的存储

            存储一棵二叉树有两种方法,一种是基于指针或是引用的二叉链式存储法,一种是基于数组的顺序存储法。

    2.1 链式存储

           从图中可以看到,每个节点有三个字段,一个存储数据,另外两个是指向左右子节点的指针。只要知道了根节点,就可以通过左右子节点的指针把整棵二叉树串起来。这种存储方式比较常用,大部分二叉树代码都是基于这种存储结构实现的。

             

    2.2 顺序存储

          根节点存储在下标为i = 1的位置,那左子节点存储在下标2*i = 2 的位置,右子节点存储在下标为2*i+1 = 3 的位置。以此类推,B节点的左子节点存储在2*i = 2*2 = 4的位置,右节点存储在2*i+1 = 2*2+1=5的位置。

          

            总结:如果节点x存储在下标为i的位置,那么该节点的左节点存储在下标为2*i的位置,右子节点存储在下标为2*i+1的位置。反过来,下标i/2的位置,存储的就是该节点的父节点。通过这种方式,只要知道根节点的存储位置。就可以通过下标计算,把整棵树串起来。

           上面是一棵完全二叉树,仅仅浪费了一个下标为0的存储位置。如果是非完全二叉树,其实会浪费比较多的数组存储空间。例如下面这课非完全二叉树,下标为0,5,7,10,11,12的存储空间,会浪费掉。

           

         所以完全二叉树更适合用数组来存储。

    3.二叉树的遍历

           如何将二叉树的所有节点遍历打印出来?经典的有三种方法:前序遍历、中序遍历、后序遍历。其中,前、中、后序,表示的是节点与它的左右子树节点的遍历打印先后顺序。

    1. 前序遍历:对于树中任意节点,先打印这个节点,再打印它的左子树,再打印它的右子树。
    2. 中序遍历:对于树中任意节点,先打印左子树,再打印它本身,最后打印右子树。
    3. 后序遍历:对于树中任意节点,先打印左子树,再打印右子树,最后打印它本身。

        

            实际上,二叉树的前、中、后序遍历就是一个递归的过程,比如前序遍历,就是先打印根节点,再递归地打印左子树,最后递归地打印右子树。

           二叉树遍历的时间复杂度是O(n)。从前面的遍历图可以看出,每个节点最多会被访问2次,所以遍历操作的时间复杂度跟节点的个数n成正比,也就是说二叉树遍历的时间复杂度是O(n)。

     

         

     

     

    展开全文
  • 我反复强调过,带着问题学习,是最有效的学习方式之一,所以在正式的内容开始之前,我还是给你出一道思考题:二叉树有哪几种存储方式?什么样的二叉树适合数组来存储? 带着这些问题,我们就来学习今天的内容,树...
  • 今天的问题是:二叉树有哪几种存储方式?什么样的二叉树适合数组来存储? 树 首先来看什么是“树"?再完备的定义,都没有图直观。所以通过下图来看看这些树有什么特征? ”树“这种数据结构很像现实中的树,里面每...
  • 出一道思考题:二叉树有哪几种存储方式?什么样的二叉树适合数组来存储? 树(Tree) 什么是“树”?画了几棵“树”。你来看看,这些“树”都有什么特征? “树”这种数据结构真的很像我们现实生活中的“树”...
  • 前序遍历好理解,也可以想象的到在什么地方 但是感觉不到中序遍历和后序遍历的具体用处,请大牛开导
  • 二叉树

    2020-07-28 16:14:19
    二叉树的第n层上,最多2的(n-1)次方个结点,比如根结点在第一层,第一层最多只能1个结点 深度为n的二叉树,最多(2的n次方-1)个结点 在任意一颗二叉树中,度为0的结点总是比度为2的结点多1个 具有n个结点的...
  • 这系列相关博客,参考 数据结构与算法之美 ...我反复强调过,带着问题学习,是最有效的学习方式之一,所以在正式的内容开始之前,我还是给你出一道思考题:二叉树有哪几种存储方式?什么样的二叉树适合
  • 存储一棵二叉树有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。 链式存储法 链表来表示一棵二叉树,即链来指示元素的逻辑关系。二叉链 每个节点有三个字段,其中一个...
  • 这次我们要好好谈一谈递归,为什么很多同学看递归算法都是“一看就会,一写就废”。 主要是对递归不成体系,没有方法论,「每次写递归算法 ,都是靠玄学来写代码」,代码能不能编过都靠运气。 「本篇将介绍前后中序...
  • 带着下面的问题,来学习今天的内容:二叉树有哪几种存储方式?什么样的二叉树适合数组来存储? 树(Tree) 先看下什么是“树”?观察下图,你来看盾树有什么特征? “树”这种数据结构真的很像我们现实生活中的...
  • 二叉树中任意一个节点的左右子树的高度相差不能大于1,完全二叉树和满二叉树都是平衡二叉树,但是非完全二叉树有可能是平衡二叉树 很多平衡二叉查找树其实并没有严格符合定义(树中任意一个节点的左右子树的高度相差...
  • 上一节我们学习了树、二叉树以及二叉树的遍历,...没有哪些地方是散列表做不了,必须要用二叉树来做的呢? 带着这些问题,我们就来学习今天的内容,二叉查找树! 二叉查找树(Binary Search Tree) 二叉查找树是二叉
  • 二叉树之 线索二叉树

    2019-04-15 09:41:49
    什么是线索二叉树二叉树是一种非线性结构,遍历二叉树几乎都是通过递归或者栈辅助实现非递归的遍历。用二叉树作为存储结构时,取到一个节点,只能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历序列的...
  • 什么线索二叉树?当以二叉链表形式来保存二叉树时,只能找到节点的左右子树信息,而不能直接得到节点的前驱和后继信息(只有通过遍历,在动态过程中才能查到前驱和后继的信息) 因此就了线索二叉树,由...
  • 上一篇专栏,我们介绍了二叉树的基本知识,接下来,就来学习一种特殊的二叉树,名为二叉查找树。 二叉查找树最大的特点就是:支持动态数据集合的快速插入、删除、查找操作。 我们知道,散列表也都是支持这些操作的...
  • 做这种理论题之前当然要直到完全二叉树到底是什么玩意儿。 完全二叉树其实就是最后一层没有满的满二叉树。 上图: a为完全二叉树,b为满二叉树。 由于完全二叉树的定义,所以我们只要遍历每层的每个节点判断除了...
  • 二叉搜索树 二叉查找/搜索/排序树 BST (binary search/sort tree) 或者是一棵空树; 或者是具有下列性质的二叉树: ...平衡二叉树(Self-balancing binary search tree) 自平衡二叉查找树 又被称为AVL树(
  • 上一篇我们学习了树、二叉树以及二叉树的遍历,今天我们再来学习一种特殊的的二叉树,二叉查找树。二叉查找树最大的特点就是,支持动态数据集合的...没有哪些地方是散列表做不了,必须要用二叉树来做的呢? 带...
  • 究竟高在什么地方呢,说实话,我也不知道哈哈哈哈哈,上网搜了一圈也没有找到让我信服的答案,有兴趣的小伙伴可以上网查一查,可能是我现在的水平还不足以体会到完全二叉树有什么特殊的效果吧,但是作为数据结构的一...
  • 二叉树基础

    2019-05-10 01:27:47
    首先给出问题:二叉树有几种存储方式?什么样的二叉树适合数组来存储? 树 首先你需要知道什么是树什么不是树。 树的三个概念: 节点的高度 = 节点到叶子节点的最长路径(边数) 节点的深度 = 根节点到节点所经历...
  • 二叉树有什么用? 那用处可就大了,就拿我们前端熟悉的来说,抽象语法树(BST)听过吧,babel在编译的时候构建的BST就是一棵二叉树,vue里面的diff,虚拟dom听过吧,这些都离不开二叉树。二叉树的威力远不止如此,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,250
精华内容 500
关键字:

二叉树有什么用