【2】二叉树基本题型整理

二、树(二叉树)

1、备注:
  • 树的逻辑结构大致可以分为三种:双亲表示法,孩子表示法,孩子兄弟表示法
  • 树以遍历操作为主,一定要弄清楚非递归的先序,中序,后序和层次遍历算法,明白自定义栈或队列如何操作节点元素,方便应对许多题型.main函数如下,栈与队列的节点和函数定义详见【线性表】篇
int main()
{
    TNode *root = (TNode *)malloc(sizeof(TNode));
    root->data = 1;
    TNode *node2 = (TNode *)malloc(sizeof(TNode));
    node2->data = 2;
    TNode *node3 = (TNode *)malloc(sizeof(TNode));
    node3->data = 3;
    TNode *node4 = (TNode *)malloc(sizeof(TNode));
    node4->data = 4;
    TNode *node5 = (TNode *)malloc(sizeof(TNode));
    node5->data = 5;
    TNode *node6 = (TNode *)malloc(sizeof(TNode));
    node6->data = 6;
    TNode *node7 = (TNode *)malloc(sizeof(TNode));
    node7->data = 7;
    TNode *node8 = (TNode *)malloc(sizeof(TNode));
    node8->data = 8;
    TNode *node9 = (TNode *)malloc(sizeof(TNode));
    node9->data = 9;
    TNode *node10 = (TNode *)malloc(sizeof(TNode));
    node10->data = 10;

    root->lchild = node2;
    root->rchild = node3;
    node2->lchild = node4;
    node2->rchild = node5;
    node3->lchild = node6;
    node3->rchild = node7;
    node4->lchild = NULL;
    node4->rchild = node10;
    node5->lchild = node8;
    node5->rchild = NULL;
    node6->lchild = NULL;
    node6->rchild = NULL;
    node7->lchild = NULL;
    node7->rchild = NULL;
    node8->lchild = node9;
    node8->rchild = NULL;
    node9->lchild = NULL;
    node9->rchild = NULL;
    node10->lchild = NULL;
    node10->rchild = NULL;

    //levelOrder
    levelOrder(root);

    //preOrder
    preOrder_No_Recur(root);

    //inOrder
    inOrder_No_Recur1(root);

    //postOrder
    // postOrder_No_Recur(root);

    //postOrder1
    postOrder_No_Recur1(root);
}

2、先序遍历
Ⅰ、Demo 1:非递归先序

算法思想:

  • 先将根结点压入栈,取出根结点。
    undefined把右孩子压入栈,再把左孩子压入栈
void preOrder_No_Recur(TNode *root)
{
    Stack stack;
    InitStack(&stack);
    push(&stack, root);
    printf("preOrder: ");
    while (!isStackEmpty(&stack))
    {
        TNode *node = pop(&stack);

        printf("%d ", node->data);
        //先压入右孩子,再压左孩子
        if (node->rchild != NULL)
        {
            push(&stack, node->rchild);
        }
        if (node->lchild != NULL)
        {
            push(&stack, node->lchild);
        }
    }
    printf("\n");
}
3、中序遍历
Ⅰ、Demo 1:非递归中序

算法思想:

  • 将左孩子一次性压入栈,
  • 取出栈顶结点,先打印,再压入右孩子。
  • 如果右孩子有左孩子,取出栈顶结点,再次把其左孩子一次性压入
  • 如果无,则将指针置为NULL,不用左压
void inOrder_No_Recur1(TNode *root)
{
    Stack stack;
    InitStack(&stack);
    printf("inOrder1: ");
    TNode *p = root;
    while (p != NULL || !isStackEmpty(&stack))
    {

        if (p != NULL)
        {
            push(&stack, p);
            p = p->lchild;
        }
        else
        {
            p = pop(&stack);
            printf("%d ", p->data);
            if(p->rchild != NULL){
                p = p->rchild;
                push(&stack,p);
                p = p->lchild;
            }
            else{
               p = NULL;
            }
        }        
    }
    printf("\n");
}
4、后序遍历
Ⅰ、Demo 1:非递归后序

算法思想(较难,好好理解):

1)双栈法
  • 一个节点栈,一个标记栈,标记子树中的根节点是否被访问两次

  • 先一次性压入左孩子,peek一下栈顶,

  • 如果sign为false,将sign相应位置为True,再访问右孩子
  • 如果sign为true,表示已经访问过右孩子,无需继续访问,直接输出
  • 取出栈顶结点,先打印,再压入右孩子。
  • 如果右孩子有左孩子,取出栈顶结点,再次把其左孩子一次性压入
  • 【注】:如何保证根节点标记为true后,不会连续压入右孩子???
void postOrder_No_Recur(TNode *root)
{
    PostStack postStack;
    InitPostStack(&postStack);
    TNode *node = root;
    bool flag = false;
    printf("postOrder: ");
    while (node != NULL || !isPostStackEmpty(&postStack))
    {
        //如果flag为false,为第一次访问该节点,一次性压入左孩子
        while (flag == false && node != NULL)
        {
            pushPostStack(&postStack, node);
            pushPostStackSign(&postStack, false);
            node = node->lchild;
        }

        node = peekPostStack(&postStack);
        flag = peekPostStackSign(&postStack);

        //先将根标记true,表示第二次查询,再访问右孩子
        if (!flag)
        {
            flag = true;
            popPostStackSign(&postStack);
            pushPostStackSign(&postStack, flag);
            if (node->rchild != NULL)
            {
                node = node->rchild;
                //将右孩子标记为未被访问
                flag = false;
            }
        }
        else
        {
            node = popPostStack(&postStack);
            printf("%d ", node->data);
            node = node->rchild;
            popPostStackSign(&postStack);
        }
    }
    printf("\n");
}

postStack代码

//后序遍历用的栈
typedef struct PostStack
{
    ElemType *data[maxsize]; //指针数组
    bool sign[maxsize];
    int top1;
    int top2;
} PostStack;

void pushPostStackSign(PostStack *stack, bool flag)
{
    stack->sign[++stack->top2] = flag;
}

bool popPostStackSign(PostStack *stack)
{
    return stack->sign[stack->top2--];
}

bool peekPostStackSign(PostStack *stack)
{
    return stack->sign[stack->top2];
}
2)单栈法
  • 用r来表示最近访问的节点
  • 主要判断是否有右孩子,并根据r判断右孩子是否访问过
void postOrder_No_Recur1(TNode *root){

    Stack stack;
    InitStack(&stack);
    // push(&stack,root);  //(概念上的)根节点在p非空,依次压入左孩子时压入栈,不在初始时压入根结点
    TNode *r = NULL;  //r的初值为NULL
                      //表示最近一次访问的结点(若r为左结点,则右结点未访问,需访问,然后将r置为右结点)
    TNode *p = root;
    printf("postOrder1: ");
    while(p != NULL || !isStackEmpty(&stack)){//若栈空,遍历指针p也为空,则遍历结束
         if(p != NULL){  //若p非空
             push(&stack,p);
             p = p->lchild;
         }

         else  //p为空,则左孩子压入完毕,先取出当前p的根节点,即栈顶元素
         {
            p = peek(&stack);
            if(p -> rchild != NULL && p -> rchild != r)//若右孩子非空且右孩子未被访问
            {
                p = p -> rchild;
                push(&stack,p);//先将右孩子压入栈
                p = p -> lchild;  //将指针指向左结点,进入下一次循环,一次性压入左结点
            }
            else{   //若右孩子被访问过,则弹出栈顶元素,即为右孩子,并将右孩子标识访问过
                    //若无右孩子,则弹出栈顶元素,即为根结点
                p = pop(&stack);
                printf("%d ",p->data);
                r = p;
                p = NULL;    //由于这里是打印“右孩子”,下一步需要打印其根节点,
                             //即为出栈操作,须将p置null,避免继续压入左孩子。
            }
         }
    }
    printf("\n");
}
Ⅱ、Demo 2:x的祖先

1)描述:

  • 在二叉树中查找值为x的结点,编写算法打印值为x的结点的所有祖先,假设值为x的结点不多于一个

2)算法思想:

  • 采用非递归的后序遍历,当压入的结点为需要打印的x结点时,即栈内元素,即为结点x的所有祖先
void getAncestors(TNode *root,int x){

    Stack stack;
    InitStack(&stack);
   
    TNode *r = NULL;       
    TNode *p = root;
    printf("postOrder1: ");
    while(p != NULL || !isStackEmpty(&stack)){//若栈空,遍历指针p也为空,则遍历结束
         if(p != NULL){  //若p非空
             push(&stack,p);
             p = p->lchild;
         }

         else  //p为空,则左孩子压入完毕,先取出当前p的根节点,即栈顶元素
         {
            p = peek(&stack);
            if(p -> rchild != NULL && p -> rchild != r)//若右孩子非空且右孩子未被访问
            {
                p = p -> rchild;
                push(&stack,p);//先将右孩子压入栈
                p = p -> lchild;  //将指针指向左结点,进入下一次循环,一次性压入左结点
            }
            else{  
                p = pop(&stack);
                if(p -> data == x){
                    break;
                }
                else
                {
                   r = p;
                   p = NULL;       
                }
            }
         }
    }

    while (!isStackEmpty(&stack))
    {
        printf("%d ",pop(&stack) -> data);
    }
    
    printf("\n");
}
5、层次遍历
Ⅰ、Demo 1:非递归层次

算法思想:

  • 先将根结点压入队列,取出根结点。
  • 把左孩子压入队列,再把右孩子压入队列
void levelOrder(TNode *root)
{
    Queue queue;
    InitQueue(&queue);
    Enqueue(&queue, root);
    printf("levelOrder: ");
    while (!isQueueEmpty(&queue))
    {
        TNode *node = Dequeue(&queue);

        printf("%d ", node->data);
        //先压入左孩子,再压右孩子
        if (node->lchild != NULL)
        {
            Enqueue(&queue, node->lchild);
        }
        if (node->rchild != NULL)
        {
            Enqueue(&queue, node->rchild);
        }
    }
    printf("\n");
}

Ⅱ、Demo 2:逆层次遍历

1)描述:

  • 试给出二叉树的自下而上,从右到左的层次遍历算法

2)算法思想:

  • 采用层次遍历的非递归算法,不同的是右孩子先进入队列,左孩子再进入队列
void levelOrder(TNode *root)
{
    Queue queue;
    InitQueue(&queue);
    Enqueue(&queue, root);
    printf("levelOrder: ");
    while (!isQueueEmpty(&queue))
    {
        TNode *node = Dequeue(&queue);

        printf("%d ", node->data);
        //先压入左孩子,再压右孩子
        if (node->lchild != NULL)
        {
            Enqueue(&queue, node->lchild);
        }
        if (node->rchild != NULL)
        {
            Enqueue(&queue, node->rchild);
        }
    }
    printf("\n");
}
Ⅲ、Demo 3:树的高度

1)描述:

  • 假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度

2)算法思想:

  • 采用层次遍历的算法,设置变量level记录当前结点所在的层数,
  • 设置last指针指向当前层的最右的结点(last指针就好比操作链表的cur,pre指针,只不过赋予了不同的职责)
  • 每次层次遍历出队时与last指针比较,若两者相等,则层数+1(说明当前层及以上已经遍历完,更新一下level),
  • 并让last指向下一层的最右的结点,直到遍历完成。level即为二叉树的高度
  • 【注】:这里可以使用循环队列,但需增加一个getRear(), 也可以不使用循环队列

循环队列

void getLevel1(TNode *root){
    
    Queue queue;
    InitQueue(&queue);
    Enqueue(&queue,root);
    
    TNode *last = root;  //用来记录出队时最右的结点,再root出队后,初始时指向root
    int level = 0;
    while(!isQueueEmpty(&queue)){
        TNode *p  = Dequeue(&queue);
        
        if(p -> lchild != NULL){
            Enqueue(&queue,p -> lchild);
        }
        if(p -> rchild != NULL){//不能用else if
            Enqueue(&queue,p -> rchild);
        }

        if(last == p){
            level++;
            /**
             *     last指向下一层的最右结点,因为last和p相同,last为上一层最右的结点
             * 这时两种情况:p有孩子,p无孩子
             * p有孩子:压入队列的孩子即为下一层最右的结点
             * p无孩子:当前的队列的rear结点即为下一层最右的结点
             */
            last = getRear(&queue);
        }
    }

    printf("level : %d ",level);
}

非循环队列(可能会溢出)

void getLevel2(TNode *root){
 
     int front = -1, rear = -1;
     TNode *queue[maxsize]; //二叉链表的指针数组
     queue[++rear] = root;
     
     TNode *last = root;
     int level = 0;
     while(front != rear){
        TNode *p = queue[++front];

        if(p -> lchild != NULL){
            queue[++rear] = p -> lchild;
        }
        if(p -> rchild != NULL){//不能用else if
            queue[++rear] = p -> rchild;
        }
 
        if(last == p){
            level++;
            last = queue[rear];
        }
     }
    printf("level1 : %d ",level);
}

Ⅳ、Demo 4:判断完全二叉树

1)描述:

  • 二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法

2)算法思想:

  • 采用层次遍历的算法,将所有的结点加入队列(包括空结点),出队遇到空结点时

查看其后是否有非空结点,若有,则二叉树不是完全二叉树。

int isComplete(TNode *root){
    
    Queue queue;
    InitQueue(&queue);
    Enqueue(&queue,root);
    
    while(!isQueueEmpty(&queue)){
        TNode *p  = Dequeue(&queue);
        
        //空结点也入队列
        if(p != NULL){
            Enqueue(&queue,p -> lchild);
            Enqueue(&queue,p -> rchild);
        }
    
        else
        {
            while(!isQueueEmpty(&queue)){
                p = Dequeue(&queue);
                //若空结点后还有非空结点,则非完全二叉树
                if(p != NULL){
  
                    printf("isn't a complete tree\n");
                    return 0;
                }
            }
        }
    }
    printf("is a complete tree\n");
    return 1;
}
Ⅴ、Demo 5:左右子树交换

1)描述:

  • 设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有结点的左右子树进行交换的函数

2)算法思想:

  • 采用层次遍历的算法,设置一个temp指针,用它的右指针指向当前非空结点cur的右结点,

    待cur的右指针指向左孩子后,cur的左指针指向temp的右孩子。

void swap(TNode *root){

    Queue queue;
    InitQueue(&queue);
    Enqueue(&queue,root);

    TNode *temp = (TNode *) malloc(sizeof(TNode));
    while(!isQueueEmpty(&queue))
    {
        TNode *p = Dequeue(&queue);
        
        temp -> rchild = p -> rchild;
        p->rchild = p -> lchild;
        p->lchild = temp -> rchild;

        //不要忘了层次遍历的主体部分
        if(p -> lchild != NULL){
            Enqueue(&queue,p -> lchild);
        }
        if(p -> rchild != NULL){//不能用else if
            Enqueue(&queue,p -> rchild);
        }

        temp -> rchild = NULL;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值