树,二叉树,森林

王卓老师的数据结构课程笔记

树和二叉树

定义

  • 结点之间有分支,具有层次关系

  • 是n个结点的有限集。

    • 若n = 0,称为空树;
    • 若n > 0,则它满足如下两个条件:
      • 有且仅有一个特定的称为根的结点;
      • 其余结点可分为m个互不相交的有限集T1,T2,T3,…,Tm,其中每一个集合本身又是一棵树,并称为根的子树。
      • 显然,树的定义是一个递归的定义。
  • 树的其他表示方式:

    • 嵌套集合
    • 广义表
    • 凹入表示

树的基本术语

  • 根结点:非空树中无前驱结点的结点

  • 结点的度:结点拥有的子树数

  • 树的度:树内各结点的度的最大值

  • 度不等于0的结点被称为分支结点,非终端结点,根结点意外的分支结点称为内部结点

  • 度等于0的结点被称为叶子结点,也被称为终端结点

  • 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲

  • 一些结点有共同的双亲,这些结点被称为兄弟结点。

  • 双亲位于同一层的结点,被称为堂兄弟。

  • 结点的祖先:从根到该结点所经分支上的所有结点。

  • 结点的子孙:以某结点为根的子树中的任一结点。

  • 树的深度:树中结点的最大层次。

  • 有序树:树中结点的各子树从左至右有次序(最左边的为第一个孩子)。

  • 无序树:树中结点的各子树无次序。

  • 森林:是m(m>=0)棵互不相交的树的集合。

    • 一棵树可以看成是一个特殊的森林。
    • 给森林中的各子树加上一个双亲结点,森林就变成了树。
    • 树一定是森林,森林不一定是树。

二叉树的定义

  • 一个结点最多只有两个子节点;
  • 二叉树的结构最简单,规律性最强;
  • 所有的树都能转化成唯一对应的二叉树,不失一般性。
  • 普通树(多叉树)若不转化为二叉树,则运算很难实现;
  • 这样可以解决树的存储结构辑器运算中存在的复杂性。
  • 二叉树是n(n>=0)个结点的有限集,它或者是空集(n = 0),或者由一个根结点及两棵不相交的分别称作这个根的左子树和右子树的二叉树组成。
  • 特点:
    • 每个结点最多有两个孩子(二叉树中不存在度大于2的结点)。
    • 子树有左右之分,其次序不能颠倒。
    • 二叉树可以是空集合,根可以有空的左子树或空的右子树。

二叉树不是树的特殊情况,它们是两个概念。

  • 二叉树结点的子树要区分左子树和右子树,即使只有一棵子树也要进行区分,说明它是左子树,还是右子树。
  • 树当结点只有一个孩子时,就无需区分它时左还是右的次序。因此二者是不同的。这是二叉树与树的最主要的差别。
  • 具有两个结点的二叉树有两种状态(双亲结点和左孩子结点以及双亲结点和右孩子结点)
  • 也就是说,二叉树每个结点的位置或者说次序都是固定的,可以是空的,但是不可以说它没有位置,而树的结点位置是相对与别的结点来说的,没有别的结点时,它就无所谓左右了。

案例

  • 数据压缩问题:

    • 将数据文件转换成由0、1组成的二进制串,称之为编码。

      • 等长编码方案
      • 不等长编码方案1
      • 不等长编码方案2
  • 利用二叉树求解表达式的值:
    • 以二叉树表示表达式的递归定义如下:

      • 若表达式为数或简单变量,则相应二叉树中仅有一个根结点,其数据域存放该表达式信息;
      • 若表达式为“第一操作数 运算符 第二操作数”的形式,则相应的二叉树中以左子树表示第一操作数,右子树表示第二操作数,根结点的数据域存放运算符(若为一元运算符,则左子树为空),其中,操作数本身又为表达式。

抽象数据类型定义

  • 具有相同特性的数据元素的集合

  • 二叉树的建立CreateBiTree(&T,definition)

    • 初始条件:definition给出二叉树T的定义。
    • 操作结果:按照definition构造二叉树T。
  • 先序遍历PreOrderTraverse(T)

    • 初始条件:二叉树T存在。
    • 操作结果:先序遍历T,对每个结点访问一次。
  • 中序遍历InOrderTraverse(T)

    • 初始条件:二叉树T存在。
    • 操作结果:中序遍历T,对每个结点访问一次。
  • 后序遍历PostOrderTraverse(T)

    • 初始条件:二叉树T存在
    • 操作结果:后序遍历T,对每个结点访问一次。

二叉树的性质和存储结构

  • 在二叉树的第i层上至多又2^(i-1)个结点(i>=1).
  • 深度为k的二叉树至多有2^k-1个结点。(k>=1).
  • 对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0 = n2 +1;

完全二叉树和满二叉树的性质

  • 满二叉树:一棵深度为k且有2^k-1个结点的二叉树称为满二叉树。

    • 特点:1.每一层上的结点数都是最大结点数(即每层都满)
    • 叶子节点全部在最底层
  • 对满二叉树节点位置进行编号

    • 编号规则:从根结点开始,自上而下,自左而右
    • 每一个结点位置都有元素
  • 满二叉树在同样深度的二叉树中结点个数最多

  • 满二叉树在同样深度的二叉树中叶子结点个数最多

  • 完全二叉树

    • 深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号为1~n的结点一一对应时,称之为完全二叉树。
    • 在满二叉树中从最后一个结点开始连续去掉任意个结点,即是一棵完全二叉树
  • 完全二叉树的叶子只可能分布在层次最大的两层上。

  • 对于完全二叉树:对任一结点,如果其右子树的最大层次为i,则其左子树的最大层次必为i或i+1。

  • 具有n个结点的完全二叉树的深度为**[log2n]+1**.表明了完全二叉树的结点数n与完全二叉树深度k之间的关系。

  • 如果有一棵n个结点的完全二叉树(双亲结点编号和孩子结点编号之间的关系)

    • 如果i = 1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2].
    • 如果2i>n,则结点i为叶子结点,无左孩子;否则,其左孩子是结点2i。
    • 如果2i + 1 > n,则结点i无右孩子;否则,其右孩子是结点 2i+1。

二叉树的存储结构

  • 顺序存储结构

    按照满二叉树的结点层次编号,依次存放二叉树中的数据元素(从上到下一层一层的记录存储)

    #define MAXTSIZE 100
    Typedef TElemType SqBiTree[MAXTSIZE]
    SqBiTree bt;
    
    • 缺点:数据变动以后不好处理
    • 空间浪费严重。
  • 链式存储结构(二叉链表)

typedef struct BiNode
{TElemType data;struct BiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree;
  • 有一个头指针指向根结点。
  • 链式存储结构(三叉链表)

    typedef struct TriTNode
    {TelemType data;struct TriTNode *lchild,*parent,*rchild;
    }TriTNode,*TriTree;
    
    • 包含双亲结点的指针。

遍历二叉树

  • 遍历:顺着某一条路径寻访二叉树中的结点,是的每个结点均被访问依次,而且仅被访问依次(又称为周游)。

    • “访问”的含义很广,可以是对结点作各种处理,如:输出结点的信息、修改结点的数据值等,单要求这种访问不破坏原来的数据结构。
  • 遍历目的:得到树中所有结点的一个线性排列。
  • 用途:是各类二叉树算法的前提
  • 遍历方法:

    • 先序:先根结点,后左节点,再右节点
    • 中序:先左结点,后根结点,再右结点
    • 后序:先左结点,后右节点,再根结点
  • 由二叉树的递归定义可得,遍历左子树和遍历右子树可如同遍历二叉树一样“递归”进行。
  • 遍历的时候递归进行遍历。将大树分为:左,根,右;这样的处理方法。先找到最下面的左子树,再找到其次的右子树,根结点(后序遍历)这样。
  • 由中缀表达式获取前缀和后缀表达式的方法,就是按中序遍历逆向复原二叉树,然后按照先序遍历和后序遍历的方式修改顺序。
  • 若二叉树中各结点的值均不相同,则二叉树结点的先序序列、中序序列和后序序列都是唯一的。
  • 由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一一棵二叉树。
//先序遍历递归算法
Status PreOrderTraverse(BiTree T)
{if(T == NULL) return OK;//空二叉树else{visit(T);//访问根结点,根据需要自行更改PreOrderTraverse(T->lchild);//递归遍历左子树PreOrderTraverse(T->rchild);//递归遍历右子树}
}
//后序
Status PreOrderTraverse(BiTree T)
{if(T == NULL) return OK;else{PostOrderTraverse(T->lchild);PostOrderTraverse(T->rchild);visit(T);}
}
//中序
Status InOrderTraverse(BiTree T)
{if(T == NULL) return OK;else{InOrderTraverse(T->lchild);visit(T);InOrderTraverse(T->rchild);}
}
  • 如果去掉输出语句,从递归的角度上看,三种算法是完全相同的,或说这三种算法的访问路径是相同的,知识访问结点的时机不同。

二叉树遍历的非递归算法

  • 中序遍历非递归算法

    • 二叉树中序遍历的非递归算法的关键:在中序遍历过某结点的整个左子树后,如何找到该结点的根以及右子树。

    • 基本思想:

      • 建立一个栈
      • 根结点进栈,遍历左子树
      • 根结点出栈,输出根结点,遍历右子树。
    • Status InOrderTraverse(BiTree T)
      {BiTree p; InitStack(S);p=T;while(p||!StackEmpty(S)){if(p){Push(S,p);p = p->lchild;}else{Pop(S,q);printf("%c",q->data);p = q ->rchild;}}return OK;
      }

二叉树的层次遍历算法

  • 对于一棵二叉树,从根结点开始,按从上到下、从左到右的顺序访问每一个结点。

  • 每一个结点仅仅访问一次。

  • 算法设计思路(使用一个队列)

    • 将根结点入队
    • 对不空时循环:从队列中列出一个结点*p,访问它:
      • 若他有左孩子结点,将左孩子结点入队;
      • 若它有右孩子结点,将右孩子结点入队;
typedef struct{BTNode data[MaxSize];//存放队中元素int front,rear;//队头和队尾指针
}SqQueue;
  • 二叉树层次遍历算法

    void LevelOrder(BTNode *b)
    {BTNode *p;SqQueue *qu;InitQueue(qu,b);//初始化队列enQueue(qu,b);//根结点指针进入队列while(!QueueEmpty(qu))//队不为空,则循环{deQueue(qu,p);//出队结点pprintf("%c",p->data);//访问结点pif(p->lchile!=NULL){enQueue(qu,p->lchild);//有左孩子时将其入队}if(p->lchild!=NULL){enQueue(qu,p->rchild);//有右孩子时将其入队}}
    }
    

二叉树遍历算法的应用

二叉树的建立

  • 按先序遍历建立二叉树的二叉链表

    • 从键盘输入二叉树的结点信息,建立二叉树的存储结构;
    • 在建立二叉树的过程中按照二叉树先序方式建立;
    • 需要用特定的方式表示空结点的值(如“#”);
Status CreateBiTree(BiTree &T)
{scanf(&ch);if(ch == "#"){T = NULL;}else{if(!(T =             (BiTNode*)malloc(sizeof(BiTNode)))){exit(OVERFLOW);}T->data = ch;//生成根结点CreateBiTree(T->lchild);//构建左子树CreateBiTree(T->rchild);//构建右子树}return OK;
}

二叉树的复制

  • 如果是空树,递归结束;
  • 否则,申请新结点空间,赋值根结点
    • 递归复制左子树
    • 递归复制右子树
int Copy(BiTree T,BiTree &NewT)
{if(T==NULL)//空树返回0{NewT = NULL;return ;//返回空结点}else{NewT = new BiTNode;NewT->data=T->data;Copy(T->lChild,NewT->lchild);//将原来的左子树复制给新树的左子树Copy(T->rChild,NewT->rchild);//将原来的右子树复制给新树的右子树}
}

计算二叉树的深度

  • 如果是空树,则深度为0;
  • 否则,递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加1
int Depth(BiTree T)
{if(T == NULL) return 0;else{m = Depth(T->lChild);n = Depth(T->rChild);if(m>n) return(m+1);else return(n+1);}
}

计算结点的总个数

  • 如果是空树,则结点个数为0;
  • 否则,结点个数为左子树的结点个数+右子树的结点个数再+1;
int NodeCount(Bitree T)
{if(T == NULL){return 0;}else{return NodeCount(T->lchild)+NodeCount(T->rchild)+1;}
}

计算二叉树叶子结点树

  • 如果是空树,则叶子结点个数为0;
  • 否则,为左子树的叶子结点个数+右子树的叶子节点个数。
int LeadCount(BiTree T)
{if(T == NULL) //如果是空树,返回0{return 0;}if(T->lchild == NULL &&T -> rchild == NULL){return 1;//如果是叶子结点返回1}else{return LeafCount(T->lchild)+LeafCount(T->rchild);}
}

线索二叉树

  • 如何寻找特定遍历序列中二叉树结点的前驱和后继

    • 通过遍历查找——费时间
    • 再增设前驱、后继指针域——增加了存储负担
    • 二叉链表中有很多空着的指针域。
  • 利用二叉链表中的空指针域:

    • 如果某个结点的左孩子为空,则将空的左孩子指针改为指向其前驱;
    • 如果某节点的右孩子为空,则将空的右孩子指针域改为指向其后继;
    • 这种改变指向的指针称为“线索”。
  • 为区分lchild和rchild指针到底是指向孩子的指针,还是指向前驱或者后继的指针,对二叉链表中的每个结点增设两个标志域ltag和rtag,并约定:

    • ltag = 0 lchild指向该结点的左孩子
    • ltag = 1 lchild指向该结点的前驱
    • rtag = 0 rchild指向该结点的右孩子
    • rtag = 1 rchild指向该结点的后继

结点格式:

typedef struct BiThrNode{int data;int ltag,rtag;struct BiThrNode *lchild,rchild;
}BiThrNode,*BiThrTree;

树和森林

  • 树,是N个结点的集合。是N个结点的有限集。若n = 0,称为空树;

  • 若n>0

    • 有且仅有一个特定的称为根(Root)的结点;
    • 其余结点可分为m(m>=0)个互不相交的有限集T1,T2,T3…Tm;
  • 树和森林可以相互转化。

树的存储结构

  • 双亲表示法

    • 实现:定义结构数组

      • 存放树的结点
      • 每个结点含两个域:
    • 数据域:存放结点本身信息。
    • 双亲域:指示本结点的双亲结点在数组中的位置。
    • 特点:找双亲容易,找孩子难。
//结点结构,C语言的类型描述
typrdef struct PTNode
{TElemType data;int perent;//双亲位置域
}PTNode;
#define MAX_TREE_SIZE 100
typedef struct
{PTNode nodes[MAX_TREE_SIZE];int r,n;//根结点的位置和结点个数
}PTree;
  • 孩子链表

    • 把每个结点的孩子排列起来,看成是一个线性表,用单链表存储,则n个结点有n个孩子链表(叶子的孩子链表为空表)。而n个头指针又组成一个线性表,用顺序表(含n个元素的结构数组)存储。

    • typedef struct CTNode
      {int child;struct CTNode *next;
      }*ChildPtr;
      
    • typedef struct
      {TElemType data;ChildPtr firstchild;//孩子链表头指针
      }CTBox;
    • typedef struct
      {CTBox nodes[MAX_TREE_SIZE];int n,r;//结点数和根结点的位置
      }CTree;
      

也可以做带双亲的孩子链表

孩子兄弟表示法(二叉树表示法,二叉链表表示法)

  • 实现:用二叉链表作树的存储结构,链表中每个结点发两个指针域分别指向其第一个孩子结点下一个兄弟结点
typedef struct CSNode
{ElemType data;struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;

树和二叉树的转换

  • 将树转化为二叉树进行处理,利用二叉树的算法来实现对树的操作。

  • 由于树和二叉树都可以用二叉链表作为存储结构,则以二叉链表作为媒介可以导出树与二叉树之间的一个对应关系。

  • 用不同的方法解释二叉链表就可以

  • 给定一棵树,可以找到唯一的一棵二叉树与之对应。

转换方法:

  • 加线:在兄弟之间加一连线
  • 抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系。
  • 兄弟相连,留长子

二叉树转换成树

转换方法:

  • 加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子…沿分支找到所有的右孩子,都与p的双亲用线连起来
  • 抹线:抹掉原二叉树中双亲与右孩子之间的连线
  • 调整:将结点按层次排列,形成树状结构
  • 左孩右右连双亲,去掉原来右孩线

森林转换成二叉树(二叉树与多棵树之间的关系)

  • 将各棵树分别转换成二叉树
  • 将每棵树的根结点用线相连
  • 以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构。
  • 森林变二叉树:树变二叉根相连。

树的遍历(三种方式)

  • 先根遍历:

    • 若树不空,则先访问根结点,然后依次先根遍历各棵子树。
  • 后根遍历:
    • 若树不空,则先依次后根遍历各棵子树,然后访问根节点。
  • 按层次遍历:
    • 若树不空,则自上而下自左至右访问书中的每个结点。

森林的遍历

将森林看作由三部分构成

  • 森林中第一棵树的根结点;
  • 森林中第一棵树的子树森林;
  • 森林中其他树构成的森林;

先序遍历

若森林不空

  • 访问森林中的第一棵树的根结点;

  • 先序遍历森林中第一棵树的子树森林;

  • 先序遍历森林中(除第一棵树之外)其余树构成的森林;

  • 即,依次从左至右对森林中的每一棵树进行先根遍历。

中序遍历

若森林不空

  • 中序遍历森林中第一棵树的子树森林
  • 访问森林中第一棵树的根结点
  • 中序遍历森林中(除第一棵树之外)其余树构成的森林
  • 即:依次从左至右对森林中的每一棵树进行后根遍历

数据结构-树,二叉树,森林相关推荐

  1. 数据结构树二叉树计算节点_查找二叉树中叶节点的数量 数据结构

    数据结构树二叉树计算节点 Algorithm: 算法: One of the popular traversal techniques to solve this kind of problems i ...

  2. 数据结构——树、森林和二叉树之间的转换

    摘自大佬博客http://www.cnblogs.com/zhuyf87/archive/2012/11/04/2753950.html 树转换为二叉树 (1)加线.在所有兄弟结点之间加一条连线. ( ...

  3. [数据结构]树、森林与二叉树之间的相互转换方法

    树.二叉树与森林的相互转换 本文只给出树.森林与二叉树之间的转换方法,而详细的证明过程不在本文讨论范围之内. 树 → 二叉树 在所有兄弟结点之间加一连线. 对每个结点,除了保留与其长子的连线外,去掉该 ...

  4. 数据结构——树、森林、二叉树的转换

    树.森林与二叉树的转换 树转换二叉树 规则:左孩子右兄弟,每个结点左指针指向它的第一个孩子结点,右指针指向它在树中相邻兄弟结点. 二叉树转换为树 规则: 逆过程,将指针修改回来,指向其双亲结点. 森林 ...

  5. python链表和树实验报告_数据结构树和森林实验报告

    _ 树和森林应用实验 实验报告 实验目的 ( 1) 掌握树和森林的二叉链表表示方法. (2) 掌握树和二叉树的结构及算法之间的对应关系. (3) 掌握树的两种遍历算法及其应用. 实验运行环境 Visu ...

  6. 树/二叉树/森林之间的相互转换 与遍历

    森林就是多棵树的集合,但是森林也可以只有一棵树,二叉树是一种特殊的树,固定的度为2,这是基本前情提要- 树常见的存储方式有三种: (1)双亲表示法 仅用定义一个结点对象,一个数组,代码定义如下: ty ...

  7. 数据结构——树和森林

    文章目录 定义 树的存储结构 树.森林与二叉树相互转换 树的遍历 Huffman树 二叉编码树 Huffman编码 参考 定义 树是由一个集合以及在该集合上定义的一种关系构成的.集合中元素成为树的结点 ...

  8. c++ 删除二叉树的子树_数据结构—树|二叉树|前序遍历、中序遍历、后序遍历【图解实现】...

    点击蓝字关注我们 AI研习图书馆,发现不一样的精彩世界 数据 结构 二叉树的遍历 一.树 在谈二叉树的知识点之前,我们首先来看一下树和图的基本概念.树:不包含回路的连通无向图,树是一种简单的非线性结构 ...

  9. 数据结构 - 树(二叉树的 前序、中序、后序 遍历)

    二叉树遍历(前序中序后序,主要是看父节点的输出顺序) package tree;public class BinaryTreeDemo {public static void main(String[ ...

  10. 数据结构-树的进阶-串联各科知识

    # 树的专题进阶思考 author : chase time: 2022-4-30 前言 以前实现过了基本的树结构,包括基本二叉树.AVLTree.BSTree.BTree.RBTree.tire T ...

最新文章

  1. PowerShell收发TCP消息包
  2. linux服务器文件后缀名,linux服务器备份网站程序并下载到本地(自动)
  3. 你绝对能懂的“机器学习”(三)
  4. ubuntu下KDvelop中怎么显示行号
  5. Wannafly挑战赛22 D 整数序列 (线段树维护三角函数值)
  6. asp.net core 3.0 中使用 swagger
  7. mysql57服务无法启动_将mysqld.service服务加入到systemctl
  8. Crypto.com宣布将在2021年1月19日下架XRP
  9. 【五级流水线CPU】—— 5. 转移指令(14条)
  10. 宏脉系统显示连接服务器失败,宏脉系统使用手册大全.doc
  11. CBinsight重磅报告 | 如何从谷歌亚马逊苹果微软脸书的9年专利之争,看5大巨头在AI行业的未来10年之争
  12. sql语句 查询表中 每门课都大于80分的学生名称 根据以下三种 学习sql 中其他函数的使用
  13. TravelFlan携小猴问问亮相ITB,看人工智能如何化解旅游痛点
  14. teamviewer Not ready only LAN 无法连接
  15. opencv——图像中关于轮廓的一些操作
  16. 计算机等级考试光敏电阻,光敏电阻的基础知识介绍
  17. 网络线缆连接器和线槽
  18. Win11 22H2使用TranslucentTB让任务栏透明
  19. yolov5 教学入门使用
  20. 修复 office你的许可证不是正版_office你可能是盗版软件的受害者

热门文章

  1. 15电赛b题风力摆小记:陀螺仪计算激光点在大地坐标系下坐标
  2. C语言中goto的用法:
  3. the desired address is beyond limit for this PLC
  4. html 中head显示 在标题栏里面的图片
  5. 【javaScript】探讨 监听input输入框的失去焦点事件与按钮点击事件执行的顺序问题
  6. top2女硕士专业天坑,裸辞公职,现年薪从20w攀升到80w!
  7. 小米Note3 MIUI9可以用的XPosed框架
  8. Bullet3物理引擎碰撞检测相关代码片段
  9. 畅游C++ Boost Serialization 序列化
  10. E+H限位开关FTL31-AA4U3BAXDJZ1