树 二叉树 先序 中序 后序 层次遍历 找前驱后继结点 构造线索二叉树 C语言实现

  1. 树中一个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度

  2. 度大于0的结点称为分支结点(非终端结点)

  3. 结点的深度是从根结点开始自顶向下累加

    结点的高度是从叶结点开始自底向上累加

总结点数=n0+n1+n2+···+nm

总分支数+1=总结点数

二叉树

定义

  1. 二叉树每个结点至多只有两个子树(子树有左右之分,次序不能任意颠倒)

  2. 满二叉树,一颗高度为h,结点数为
    2 h − 1 2^h-1 2h1

    1. 完全二叉树特征
    • 叶子结点只可能在层次最大的两层上出现
    • 度为1的结点,要么没有,要么只有1个(没有右孩子)
  3. 二叉排序树:左子树所有结点的关键字均小于根结点的关键字,右子树的关键字均大于根结点的关键字

  4. 平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1

性质

度为0,1,2的结点个数分别为n0,n1,n2

  1. n0=n2+1
  2. n=n0+n1+n2
  3. 分支总数 = n1+2n2

存储结构

  1. 顺序存储

    用一组地址连续的存储单元依次自上而下,自左至右存储结点元素(按照完全二叉树的形式,这样就可以利用完全二叉树的性质反映结点之间的关系)

    #define MaxSize 100
    struct TreeNode{
        ElemType value;//数据元素
        bool isEmpty;//结点是否为空
    };
    TreeNode t[MaxSize];
    
  2. 链式存储

    //二叉链表的形式
    typedef struct BiTNode{
        ElemType data;
        struct BiTNode *lchild,*rchild;//左右孩子指针
    }BiTNode,*BiTree;
    
    //三叉链表可以增加一个指向父节点的指针
    
    //初始化
    //定义一个空树
    BiTree root = NULL;
    //插入根结点
    root = (BiTree)malloc(sizeof(BiTNode));
    root->data=1;
    root->lchild=NULL;
    root->rchild=NULL;
    

二叉树的遍历

先序遍历

串的前缀表达式

//递归遍历
void PreOrder(BiTree T){
    if(T!=NULL){
        visit(T);//访问根结点
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

//非递归遍历
void PreOrder(BiTree T){
    InitStack(S);//初始化栈
    BiTree p=T;//p是遍历指针
    while(p||!IsEmpty(S)){
        if(p){
            visit(p);//入栈前访问结点
            Push(S,p);//访问结点之后入栈
            p=p->lchild;//左孩子不空,向左走
        }
        else{
            Pop(S,p);//出栈,并转向出栈结点的的右子树,右孩子为空的话,继续出栈
            p=p->rchild;//向右走,p赋值为出栈元素的右孩子
        }
    }
}

中序遍历

串的中缀表达式

//递归遍历
void Inorder(BiTree T){
    if(T!=NULL){
        PreOrder(T->lchild);
        visit(T);
        PreOrder(T->rchild);
    }
}

//非递归遍历(栈)
/*
1 沿着根的左孩子,依次入栈,直到左孩子为空
2 栈顶元素出栈并访问,如果其没有右孩子,则继续执行2;如果其有右孩子,则执行1
*/
void InOrder(BiTree T){
    InitStack(S);//初始化栈
    BiTree p = T;//遍历树的指针
    while(p||!isEmpty(S)){//两个都为空时退出,已完成遍历
        if(p){
            Push(S,p);
            p=p->lchild;
        }
        else{
            Pop(S,p);//说明栈顶元素是最左边的结点
            visit(p);
            p=p->rchild;
        }
    }
}

后序遍历

串的后缀表达式

算法思想

1 沿着根的左孩子,依次入栈,直到左孩子为空;

2 读取栈顶元素(只是读取,并不是出栈),若其右孩子不为空并且没被访问过,则右孩子执行1,否则栈顶元素出栈并访问

void PostOrder(BiTree T){
    if(T!=NULL){
        PreOrder(T->lchild);
     	PreOrder(T->rchild);
    	visit(T);
    }
}


//非递归遍历(增加一个辅助指针,指向最近访问过的结点)
void PostOrder(BiTree T){
    InitStack(S);
    p=T;
    r=NULL;//辅助指针,指向最近访问过的结点
    while(p||!IsEmpty(S)){
        if(p){//走到最左边
            push(S,p);
            p=p->lchild;
        }
        else{
            GetTop(S,p);//读取栈顶元素
            if(p->rchild&&p->rchild!=r){//如果右子树存在,并且没有被访问过
                p=p->rchild;//访问右子树
            }
            else{//右子树不存在,或者已经访问过
                pop(S,p);//弹出栈顶元素
                visit(p->data);//以此结点为根结点的子树访问完成
                r=p;//记录最近访问过的结点
                p=NULL;//访问结点完后,重置p指针(因为需要弹出栈顶元素)
            }
        }
    }
}


//非递归遍历(增加一个标志域,记录是否被访问)
typedef struct{
    BiTree t;//栈中存放树结点的指针
    int tag;//tag=0表示此结点左子树已被访问,tag=1表示此结点的右子树已被访问
}stack;

void PostOrder(BiTree T){
    stack s[];
    top=0;//栈顶指针索引
    while(bt!=NULL||top>0){
        while(bt!=NULL){
            s[++top].t=bt;
            s[top].tag=0;//标记左子树被访问
            bt=bt->lchild;   
        }
        while(top!=0&&s[top].tag==1){//栈不为空,并且栈顶元素结点的右子树已经被访问了
            top--;//退栈,直到右子树没被访问或者已经遍历完成
        }
        if(top!=0){
            s[top].tag=1;//标记右子树被访问
            bt=s[top].t->rchild;
        }
    }
}

层次遍历

void LevelOrder(BiTree T){
    InitQueue(Q);
    BiTree p;
    EnQueue(Q,T);//根结点入队
    while(!IsEmpty(Q)){
        DeQueue(Q,p);//队头出队
        visit(p);//访问出队结点,查看其左右子树
        if(p->lchild!=NULL){
            EnQueue(Q,p->lchild);//左子树不空,左子树根结点入队
        }
        if(p->rchild!=NULL){
            EnQueue(Q,p->rchild);//右子树不空,右子树根结点入队
        }
    }
}

由遍历序列构造二叉树

  1. 由先序和中序可以构造唯一一棵二叉树
  2. 由后序和中序可以构造唯一一棵二叉树
  3. 由层次和中序可以构造唯一一棵二叉树

先序和中序

//先序序列存放在A数组中,中序序列存放在B数组中
BiTree PreInCreate(ElemType A[],ElemType B[],int l1,int h1,int l2,int h2){
    //l1,h1为先序序列的第一个和最后一个结点,l2,h2为后序序列的第一个和最后一个结点
    //初始调用时,l1=l2=1,h1=h2=n
    int i=0;
    root=(BiTNode*)malloc(sizeof(BiTNode));
    root->data=A[l1];//根结点
    for(i=l2;B[i]!=root->data;i++);//在中序序列中寻找根结点
    llen=i-l2;//左子树长度
    rlen=h2-i;//右子树长度
    if(llen){
        root->lchild=PreInCreate(A,B,l1+1,l1+llen,l2,l2+llen-1);//递归建立左子树
    }else{
        root->lchild=NULL;
    }
    if(rlen){
        root->rchild=PreInCreate(A,B,h1-rlen+1,h1,h2-rlen+1,h2);//递归建立右子树
    }else{
        root->rchild=NULL;
    }
    return root;//返回根结点
}

线索二叉树

一般方法找中序前驱

只能通过一次遍历二叉树来寻找前驱结点

//辅助全局变量,用于查找结点p的前驱
BiTNode *p;//p指向目标结点
BiTNode *pre=NULL;//指向当前访问结点的前驱
BiTNode *final=NULL;

void visit(BiTNode *q){
    if(q==p){//当前访问的结点恰好是结点p
        final=pre;//p的前驱
    }
    else{
        pre=q;//pre移动到当前访问的结点
    }
}

void findPre(BiTree T){
    if(T!=NULL){
        InOrder(T->lchild);//递归遍历左子树
        visit(T);
        InOrder(T->rchild);//递归遍历右子树
    }
}

定义线索结点

通过建立线索二叉树来快速寻找结点的前驱和后继结点

在含n个结点的二叉树中,有n+1个空指针,用这n+1个空指针域来增加线索

规定

若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点;还需增加两个标志域标识指针域是指向其左右孩子还是前驱后继结点

lchildltagdatartagrchild
  1. ltag
    • 0lchild指向其左孩子
    • 1ltag指向其前驱结点
  2. rtag
    • 0rchild指向其右孩子
    • 1rtag指向其后继结点
//线索二叉树
typedef struct ThreadNode{
    ElemType data;
    struct ThreadNode *lchild,*rchild;//左右孩子指针
    int ltag,rtag;//默认为0
}ThreadNode,*ThreadTree;

中序线索化

//全局变量pre指向当前访问结点的前驱
ThreadNode *pre=NULL;//开始的前驱结点指向NULL

//中序遍历二叉树的过程中线索化
void InThread(ThreadTree T){
    if(T!=NULL){
        InThread(T->lchild);//递归遍历左子树
        visit(T);//线索化根结点
        InThread(T->rchild);//递归遍历右子树
    }
}

//线索化一个结点
void visit(ThreadNode *q){
    if(q->lchild==NULL){//左子树为空,建立前驱线索
        q->lchild=pre;
        q->ltag=1;//表示lchild指向前驱结点
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;//建立前驱结点的后继线索
        pre->rtag=1;
    }
    pre=q;
}

//中序线索化二叉树
void CreateInThread(ThreadTree T){
    pre=NULL;//pre初始化NULL
    if(T!=NULL){//非空二叉树才能线索化
        InThread(T);//中序线索化二叉树
        if(pre->rchild==NULL){
            pre->rtag=1;//处理遍历的最后一个结点
        }
    }
}

先序线索化

void PreThread(ThreadTree T){
    if(T!=NULL){
        visit(T);//先处理根结点
        if(T->ltag==0)//lchild不是前驱线索
            PreThread(T->lchild);
        PreThread(T->rchild);
    }
}

void visit(ThreadNode *q){
    if(q->lchild==NULL){//左子树为空,建立前驱线索
        q->lchild=pre;
        q->ltag=1;
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;//建立前驱结点的后继线索
        pre->rtag=1;
    }
    pre=q;
}


//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;
//先序线索化二叉树T
void CreatePreThread(ThreadTree T){
    pre=NULL;//pre初始化为NULL
    if(T!=NULL){//非空二叉树才能线索化
        PreThread(T);//先序线索化二叉树
        if(pre->rchild==NULL)
            pre->rtag=1;//处理遍历的最后一个结点
        
    }
}

后序线索化

//后序遍历二叉树的过程中线索化
void PostThread(ThreadTree T){
    if(T!=NULL){
        InThread(T->lchild);//递归遍历左子树
        InThread(T->rchild);//递归遍历右子树
        visit(T);//线索化根结点
    }
}

//线索化一个结点
void visit(ThreadNode *q){
    if(q->lchild==NULL){//左子树为空,建立前驱线索
        q->lchild=pre;
        q->ltag=1;//表示lchild指向前驱结点
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;//建立前驱结点的后继线索
        pre->rtag=1;
    }
    pre=q;
}


//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre=NULL;
//后序线索化二叉树
void CreateInThread(ThreadTree T){
    pre=NULL;//pre初始化NULL
    if(T!=NULL){//非空二叉树才能线索化
        PostThread(T);//中序线索化二叉树
        if(pre->rchild==NULL){
            pre->rtag=1;//处理遍历的最后一个结点
        }
    }
}

中序线索二叉树找中序后继

//找到以P为根的子树中,第一个被中序遍历的结点
ThreadNode *FirstNode(ThreadNode *p){
    //循环找到最左下(因为中序)的结点(不一定是叶结点)
    while(p->ltag==0){
        p=p->lchild;
    }
    return p;
}

//在中序线索二叉树中找到结点p的后继结点
ThreadNode *NextNode(ThreadNode *p){
    //右子树中最左下结点
    if(p->rtag==0)//有右子树
        return FirstNode(p->rchild);
    else
        return p->rchild;//没有右子树,直接指向的就是后继元素
}

中序遍历

//对中序线索二叉树进行中序遍历(非递归)
//因为知道一个结点的后继结点
void Inorder(ThreadNode *T){
    for(ThreadNode *p=FirstNode(T);p!=NULL;p=NextNode(p))
        visit(p);
}

中序线索二叉树找中序前驱

//找到以P为根的子树中,最后一个被中序遍历的结点
ThreadNode *LastNode(ThreadNode *p){
    //循环找到最右下的结点
    while(p->rtag==0)
        p=p->rchild;
    return p;
}

//在中序线索二叉树中找到结点p的前驱结点
ThreadNode *PreNode(ThreadNode *p){
    //左子树中最右下的结点
    if(p->ltag==0)
        return LastNode(p->lchild);
    else
        return p->lchild;//ltag==1 直接返回前驱线索
}

逆向中序遍历

//对中序线索二叉树进行逆向中序遍历
//因为知道一个结点的前驱结点
void RevInorder(ThreadNode *T){
    for(ThreadNode *p=LastNode(T);p!=NULL;p=PreNode(p))
        visit(p);
}
中序线索二叉树先序线索二叉树后序线索二叉树
找前驱10(从头开始遍历或者采用三叉链表的结构)1
找后继110(从头开始遍历或者采用三叉链表的结构)
  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值