树和二叉树的了解掌握

目录

一. 树和二叉树的定义

二. 树的基本术语

三. 二叉树的性质

四. 二叉树的存储结构

五. 二叉树的遍历

六. 二叉树遍历算法的应用

七. 线索二叉树


一. 树和二叉树的定义

        树就是具有n个结点的有限集合。有且只有一个根节点,其余结点又可以组合成一棵新的树,所以树的定义也是一个递归的定义。当它不包括结点的时候,也就是n等于0的时候,就为空树。

        二叉树也是具有n个结点的有限集合。与树最大的区别就在于,不管二叉树中的某个结点有没有子树,它都包括左子树和右子树。也就是说当一个结点没有分支的时候,它也相当于拥有两个为空的左子树和右子树。并且有左右之分,次序不能颠倒。而树就没有这些要求和特点,当树的结点只有一个分支的时候,就不需要区分它是左还是右的次序。

二. 树的基本术语

一些重要的常用术语如下所示:

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

        结点的度:结点拥有的子树数,也就是该结点的后继个数或者称为分支数。

        树的度:整个树里面结点度的最大值。

        叶子:度为0的结点。

        孩子:一个结点的子树的根

        双亲:该结点的上一个结点。

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

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

        树的深度:树中结点的最大层次。也就是最多出现了多少层。

二叉树的术语也跟上面树的术语相同。

三. 二叉树的性质

        由于树的结构比较复杂,不方便我们的计算,我们后面也会学习如何将树转换为二叉树来分析,所以我们这里主要学习二叉树的性质和存储结构。

        性质一:在二叉树的第i层上至多有2的i-1次方个结点。

        性质二:深度为k的二叉树至多有2的k次方减1个结点。

        性质三:对任何一棵二叉树T,如果其叶子数为n,度为2的结点数为m,则n=m+1。

由于二叉树的性质四和性质五涉及到满二叉树和完全二叉树。所以我们来看下满二叉树和完全二叉树的定义:

满二叉树:一棵深度为k且有2的k次方减1个结点的二叉树。

它的特点就是每一层上的结点数都是最大结点数(也就是每一层都是满的)。并且叶子结点全都在最底层。

完全二叉树:深度为k的具有n个结点的二叉树,并且仅当其每一个结点都与深度为k的满二叉树中编号为1~n的结点一一对应的时候,称之为完全二叉树。

它的特点就是叶子只可能分布在层次最大的1两层上。并且对任一结点,如果其右子树的最大层次为i,则其左子树的最大层次为i或i+1。

所以满二叉树一定是完全二叉树,因为它的编号完全对应。

下面我们接着看剩下的性质:

      

         性质五:如果对一棵有n个结点的完全二叉树,让它的结点按层序编号,则对任一结点i,有:

性质五其实就是表明完全二叉树中双亲结点编号和孩子结点编号之间的关系。

 

四. 二叉树的存储结构

        二叉树的顺序存储:按满二叉树的结点层次编号,依次存放二叉树中的数据元素。

//二叉树顺序存储的结构定义
#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;

 

五. 二叉树的遍历

        如果规定只能遍历完左子树再遍历右子树,那么就有三种遍历情况。如下所示:

1)先序遍历二叉树:若二叉树为空,则空操作;否则先访问根节点,再先序遍历左子树,先序遍历右子树。

2)  中序遍历二叉树:若二叉树为空,则空操作;否则先中序遍历左子树;再访问根结点;最后中序遍历右子树。

3)后序遍历二叉树:若二叉树为空,则空操作;否则先后序遍历左子树,再后序遍历右子树,最后访问根节点。

上面的遍历很明显是一个递归遍历,例如先序遍历,先访问完根结点之后,再把剩下的部分看成一个新的二叉树,再对它进行先序遍历,直到遍历完成。中序遍历和后序遍历也是如此。

用代码定义结构类型为:

//二叉树先序遍历算法
int PreOrderTraverse(BiTree T){
    if(T==NULL) return 1; //空二叉树,不进行操作
    else{
        printf("%d\t",T->data); //访问根结点
        PreOrderTraverse(T->lchild); //递归遍历左子树,也是先访问根结点,再递归遍历这里的左子树
        PreOrderTraverse(T->rchild); //递归遍历右子树
    }
}
//中序递归遍历算法
int InOrderTraverse(BiTree T){
    if(T==NULL) return 1;
    else{
        InOrderTraverse(T->lchild);
        printf("%d\t",T->data);
        InOrderTraverse(T->rchild);
     }
}
//后序递归遍历算法
int PostOrderTraverse(BiTree T){
    if(T=NULL) return 1;
    else{
        PostOrderTraverse(T->lchild);
        PostOrderTraverse(T->rchild);
        printf("%d\t",T->data);
    }
}

除了上述三种遍历方法之外,二叉树还有一个层次遍历。层次遍历就是对于一颗二叉树,从根结点开始,按从上到下,从左到右的顺序访问每一个结点。

层次遍历的设计思路就是使用一个队列,首先将根结点入队,如果这时候队列不为空,则从队列里取出一个结点*p,访问它,如果它有左右孩子就将它的左右孩子入队。接着循环这个过程,直到队列为空的时候,表示二叉树的层次遍历已经完成。

六. 二叉树遍历算法的应用

二叉树遍历算法的应用1:建立二叉树算法(按先序遍历序列)。

该应用原理为:首先从键盘输入二叉树的结点信息,建立二叉树的存储结构。接着在建立二叉树的过程中按照二叉树先序方式建立。由于只有先序遍历,所以这样建立的二叉树并不唯一,我们需加入空字符使得它唯一,这里我们可以#号来表示空字符。

代码实现如下所示:

int CreateBiTree(BiTree &T){
    char ch;
    scanf(&ch);
    if(ch=="#") T=NULL;       //如果输入的是#号,则建立的二叉树这个位置为空
    else{
        if(!(T=new BiNode))    //给二叉树结点分配空间,并判断是否分配成功
            return -1;
        T->data=ch;        //生成根结点
        CreateBiTree(T->lchild);  //继续调用建立二叉树的函数,构造左子树
        CreateBiTree(T->rchild);  //构造右子树
    }
    return 1;
}
         

二叉树遍历算法的应用2:复制二叉树.

该应用的原理为:首先判断是否是空树,如果是空树则无需复制。如果不是空树则先申请新结点空间,复制根结点。接着递归复制左子树,递归复制右子树。

代码实现如下所示:

int Copy(BiTree T,BiTree&NewT){
    if(T==NULL)    //如果是空树则返回0
        NewT=NULL; return 0;
    else{
        NewT=new BiNode;
        NewT->data=T->data;        //复制根结点
        Copy(T->lchild,NewT->lchild);    //接着递归复制该结点的左子树
        Copy(T->rchild,NewT->rchild);    //递归复制该结点的右子树
    }
}

二叉树遍历算法的应用3:计算二叉树的深度。

该应用的原理为:首先判断是否是空树,如果是空树,则返回深度为0。否则,递归计算左子树的深度,递归计算右子树的深度,该二叉树的深度就为两者的最大值加1。

代码实现如下所示:

int Depth(BiTree T){
    if(T==NULL) return 0; //如果是空树返回0
    else{
        m=Depth(T->lchild);    //递归调用求左子树的深度
        n=Depth(T->rchild);   
        if(m>n) return (m+1);  //在递归调用求完左右子树之后,得到整个最大的树的深度
        else return (n+1);    //判断哪个子树的深度更大,更大的加1即为整个树的深度
    }
}

二叉树遍历算法的应用4:计算二叉树结点的总数。

该应用的原理为:首先也是判断是否为空树,如果为空树,则返回结点个数为0.否则,左子树的结点个数加上右子树的结点个数再加1就是该数结点的总数。

代码实现如下所示:

int NodeCount(BiTree T){
     if(T==NULL) return 0;
    else
        return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}

 

二叉树遍历算法的应用5:计算二叉树叶子结点的总数。

该应用的原理为:如果是空树,则叶子结点对的个数为0;否则,左子树的叶子结点个数+右子树的叶子结点个数就为该树的叶子结点总数。

代码实现如下所示:

int LeadCount(BiTree T){
    if(T==NULL) return 0;
    if(T->lchild==NULL&&T->rchild==NULL)
        return 1;        
    else
        return LeafCount(T->lchild)+LeafCount(T->rchild);
}

七. 线索二叉树

        利用某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继。这种改变指向的指针称为“线索”。加上了线索的二叉树就称为线索二叉树。 对二叉树按某种遍历次序使其变为线索二叉树的过程称为线索化。

但我们事先不知道它到底有没有左右孩子,所以也就不知道lchild和rchild到底是指向孩子的指针还是指向其前驱或者后继的指针,因此我们对二叉链表中的每个结点增设了两个标志域ltag和rtag,并规定当ltag和rtag为1的时候,表示指向该结点的左右孩子。

代码实现如下所示:

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

 

 

 

 

 

 

        

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值