数据结构与算法(八)树和二叉树及其遍历算法

逻辑结构:非线性结构(一对多,一个前驱多个后继)、具有层次关系

树(Tree)是n(n≥0)个结点的有限集,n=0时称为“空树”,n>0时满足两个条件:

        1.有且仅有一个(Root)

        2.其余结点可以分为m个互不相交的有限集T1,T2,T3..Tm。每个集合本身又是一颗树,称为子树

        树可以使用嵌套集合、凹入表示、广义表表示

        结点的度:结点拥有的子树数(分支结点),若度为0(没有子树,称为叶子结点

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

        有共同双亲的结点成为兄弟结点(同层),从跟到X结点路径上经过的全部结点称为结点的祖先(仙人)

        有序树:树中结点的各子树从左至右有次序(相反为无序树)

        森林:有m颗不相交的树的集合(树删掉跟结点,树可以视为特殊的森林        <一颗>)

二叉树

        普通树如果不转换为二叉树将难以实现运算

        二叉树结构简单、规律性强

        所有树都可以转换成唯一对应的二叉树,不失一般性

        特点:        1.不存在度大于2的结点(至多两个孩子)

                           2.子树有左右之分,顺序不能颠倒

                           3.可以是空集,根可以有空的左子树或右子树

        若树只有一个孩子,则无需区分左右,否则都需要说明是左子树还是右子树

 案例:使用表达式求解表达式的值

        左子树放第一操作数、右子树放第二操作数,根结点存放运算符,使用后序遍历即可得出结果

         二叉树的抽象数据定义

ADT BinaryTree{
    数据对象D:
    数据关系R:    D=空集    R=Ø
                  Root唯一
                  Dj∩Dk=Ø
    基本操作P:    ...
}ADT BinaryTree

        性质以及存储结构

                性质1:i 层上至多有  2^{i-1}  个结点(i>=1),至少有 1 个节点

                性质2:深度为k的二叉树至多有 2^{k-1} 个结点,至少有 k 个结点

                性质3:叶子数为N0,度为2的结点数为n2,则 n0=n2+1

        满二叉树:

                深度为k且有2^{k-1}个结点的二叉树

                标号:从上到下,从左到右

        完全二叉树:

                 满二叉树中编号为1~n的结点一一对应时,称为完全二叉树。

                性质1:叶子结点只能在最后一层或者倒数第二层

                性质2:对任意结点,如果右子树最大层次为i,左子树最大层次必为i或i+1

                性质3:具有n个结点的完全二叉树的深度为⌊log{_{2}}^{n}⌋+1

                        ⌊ ⌋表示向下取整

                性质4:n个结点的完全二叉树,对其任一节点i(1≤i≤n)有:

                        1.i=1,结点i为root;i>1,则其双亲结点⌊i/2⌋

                        2.2i>n,则i为叶子结点,无左孩子;否则,其左孩子结点为结点2i

                        3.如果2i+1>n,则结点i无右孩子,否则,其右孩子结点2i+1

                顺序存储结构

                        将二叉中的元素按照满二叉树进行编号,一次存放二叉树中的数据元素

                         缺点:深度为k且只有k个结点的单支树可能需要空间为 2^{k}-1 的一维数组。

                 链式存储结构

                        定义

typedef struct BiNode{
    TElemType data;
    struct BiNode *lchild,*rchild;//左右孩子
}BinNode,*BiTree;

                        必有2n个链域,除根结点外,每个结点由且仅有一个双亲结点,所以只会有n-1个结点的链域存放指针,指向非空子女结点

                                空指针数目 = 2n - (n-1) = n+1

                        三叉链表(有一个指针指向双亲,方便控制前驱)

typedef struct TriNode{
    TElemType data;
    struct BiNode *lchild,*rchild,*parent;//左右孩子
}TriNode,*TriTree;

                 遍历二叉树

                        遍历--得到树中所有结点的线性排列

                        遍历方法:(D-根、L-左结点、R-右结点),左子树都在右子树前

                                先(根)序遍历--DLR

                                中(根)序遍历--LDR

                                后(根)序遍历--LRD

先序遍历中序遍历后序遍历

1.访问

2.先序遍历左子树

3.先序遍历右子树

1.中序遍历左子树

2.访问

3.中序遍历右子树

1.后续遍历左子树

2.后续遍历右子树

3.访问

                                先序遍历(根左右)

                                 后序遍历(左右根)

                                 中序遍历(左根右)

                                技巧:从最左边的最下层子树数起,依照对应的顺序,当子树完全数完后再数其双亲结点对应的序列(例如后续遍历)

                                 实例

                 每一层的子树都遵从这个规则。例如中序遍历时:

                        a+完成后应该轮到x,但是x存在左子树和右子树,应当先遍历的左子树b和根+,然后遍历x的右子树 -,而右子树有是一个子树故先遍历左子树c,然后遍历根-,最后遍历d,得到的结果就是 a + [b x (c - d)]

                 可由先序序列中序序列,或后续序列中序序列确定唯一的二叉树

                先序序列的第一个元素、后序序列的最后一个元素必定是

                例1:        先序:ABCDEFGHIJ

                                 中序:CDBFEAIHGJ

1.

2.

3. 

                         例2

                         先序遍历的递归实现(DLR)

Status PreOrderTraverse(BiTreee T){
    if(T == NULL)    return OK;//空二叉树
    else{
        visit(T);//访问根结点
        PreOrderTraverse(T->lchild);//遍历左子树
        PreOrderTraverse(T->rchild);//遍历右子树
    }
}

                         中序遍历递归实现

Status InOrderTraverse(BiTreee T){
    if(T == NULL)    return OK;//空二叉树
    else{
        InOrderTraverse(T->lchild);//遍历左子树
        visit(T);//访问根结点
        InOrderTraverse(T->rchild);//遍历右子树
    }
}

                        后序遍历递归实现   

Status PostOrderTraverse(BiTreee T){
    if(T == NULL)    return OK;//空二叉树
    else{
        PostOrderTraverse(T->lchild);//遍历左子树
        PostOrderTraverse(T->rchild);//遍历右子树
        visit(T);//访问根结点
    }
}

                        三类遍历访问的路径完全一致,只是访问时机有所区别

                         从虚线出发到终点,每个结点经过3次

                         中序遍历非递归实现(栈)

                                思路:        1.建立一个栈

                                                   2.根结点进栈,遍历左子树

                                                   3.根结点出栈,输出根结点,遍历右子树

Status InOrderTraverse(BiTree T){
    BitTree p;    InitStack(S);    p=T;
    while(p || !StatckEmpty(S)){
        if(p) {Push(S,p);    p=p->lchild; }
        else {Pop(S,q);    print(p->data);
                p=q->rchild;}//q是弹栈的元素
    }
    return OK;
}

                                 通用思路:第一次访问到根后不急于输出根,先将其入栈,然后按照先左后右的顺序遍历当前结点的左右子树,当需要输出时再输出,

                        层次遍历(队列实现)

                                从根结点开始,从上到下,从左到右的顺序访问没一个结点

                                思路:        1.将根结点入队

                                                   2.队不为空时循环:从队列中列出一个结点*p,访问它:

                                                        1.若其有左孩子,将左孩子进队

                                                        2.若其有右孩子,将右孩子进队

void LevelOrder(BTNode *b){
    BTNode *p;    SqQueue *qu;
    InitQueue(qu);
    while(!QueueEmpty(qu)){
        deQueue(qu,p);
        print(p->data);
        if(p->lchild!=NULL)    enQueue(qu,p->lchild);//左孩子入队
        if(p->rchild!=NULL)    enQueue(qu,p->rchild);//右孩子入队
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值