C语言实现二叉树非递归遍历 存储复杂数据类型 详细备注

本文主要实现C语言二叉树的构造,存储复杂数据类型,通过对其进行非递归遍历,有详细备注。

1 二叉树构造

typedef struct Student
{
    int id;
    char name[10];
} Student;
typedef struct Tree
{
    Student *data;//暂存储Student*
    struct Tree *left;
    struct Tree *right;
} Tree;

2 根据数据data创建树节点

Tree *createTreeNode(Student *stu)
{
    Tree *node = (Tree *)malloc(sizeof(Tree *));
    node->left = NULL;
    node->right = NULL;
    node->data = stu;
}

3 中序遍历代码(文末有详细流程)

void inOrderList(Tree *root) //中序遍历,先左、后根、再右
{
    Tree **stk = (Tree **)malloc(sizeof(Tree *) * SIZE); //向堆区申请内存构造栈,栈内存储节点地址Tree*
    int top = 0;
    while (root != NULL || top > 0) //当root==NULL且top==0说明栈内已经没有元素,且没有新元素再入栈,跳出循环
    {
        while (root != NULL) //一直入栈,直到root=NULL,
        {
            stk[top++] = root; //先是根节点入栈//栈顶索引top不断增加
            root = root->left; //然后传入左子节点,左子树入栈//这样出栈时是左子树先出栈,根节点后出栈
        }                      //循环结束时,root及root.left全部入栈//栈顶索引top比实际值大1
        root = stk[--top];     //获取到栈顶的节点
        //上2次陆续入栈的是root和root->left,栈顶获取的是后者
        printf("{id:%d name:%s}  ", root->data->id, root->data->name); //输出栈顶,输出root
        root = root->right;                                            //再将栈顶的右子节点传入循环
    }
}

4 前序遍历代码

void preOrderList(Tree *root) //前序遍历,先根,后左,再右
{
    Tree **stk = (Tree **)malloc(sizeof(Tree *) * SIZE); //向堆区申请内存构造栈,栈内存储节点地址Tree*
    int top = 0;
    while (root != NULL || top > 0) //当root==NULL且top==0说明栈内已经没有元素,且没有新元素再入栈,跳出循环
    {
        while (root != NULL) //一直入栈,直到root=NULL,
        {
            stk[top++] = root;                                             //根节点入栈//栈顶索引top不断增加
            printf("{id:%d name:%s}  ", root->data->id, root->data->name); //输出栈顶root
            root = root->left;                                             //然后传入左子节点,左子树入栈//这样出栈时根节点先出,左子树再出栈
        }
        root = stk[--top];  //获取到栈顶的节点
        root = root->right; //再将栈顶的右子节点传入循环,右子树入栈前相应节点左子树已完成出栈操作
    }
}

5 后序遍历代码

  • 中序遍历中,从栈中弹出的节点,其左子树是访问完了,可以直接访问该节点,然后接下来访问右子树。
  • 后序遍历中,从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。
  • 因此,我们在后序遍历中,引入了一个preNode来记录历史访问记录。
  • 当访问完一棵子树的时候,我们用preNode指向该节点。
  • 这样,在回溯到父节点的时候,我们可以依据preNode是指向左子节点,还是右子节点,来判断父节点的访问情况。
  • 主要思想如下:
  • 由于在某颗子树访问完成以后,接着就要回溯到其父节点去
  • 因此可以用prev来记录访问历史,在回溯到父节点时,可以由此来判断,上一个访问的节点是否为右子树
void postOrderList(Tree *root) //后序遍历,先左,后右,再根
{
    Tree **stk = (Tree **)malloc(sizeof(Tree *) * SIZE); //向堆区申请内存构造栈,栈内存储节点地址Tree*
    int top = 0;
    Tree *preNode = NULL;
    while (root != NULL || top > 0) //当root==NULL且top==0说明栈内已经没有元素,且没有新元素再入栈,跳出循环
    {
        while (root != NULL) //一直入栈,直到root=NULL,
        {
            stk[top++] = root; //根节点入栈//栈顶索引top不断增加
            root = root->left; //然后传入左子节点,左子树入栈//这样出栈时是左子树先出栈,根节点后出栈
        }
        //从栈中弹出的元素,左子树一定是访问完了的
        root = stk[--top]; //获取到栈顶的节点
        //现在需要确定的是是否有右子树,或者右子树是否访问过
        //如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时
        //说明可以访问当前节点
        if (root->right == NULL || root->right == preNode)
        {
            printf("{id:%d name:%s}  ", root->data->id, root->data->name); //输出栈顶root
            preNode = root;                                                //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
            root = NULL;
        }
        else //如果右子树没有被访问,那么将当前节点压栈,访问右子树
        {
            stk[top++] = root;
            root = root->right;
        }
    }
}

6 总体测试代码

#include <stdio.h>
#include <stdlib.h>
#define SIZE 100
typedef struct Student
{
    int id;
    char name[10];
} Student;
typedef struct Tree
{
    Student *data;
    struct Tree *left;
    struct Tree *right;
} Tree;
Tree *createTreeNode(Student *stu)
{
    Tree *node = (Tree *)malloc(sizeof(Tree *));
    node->left = NULL;
    node->right = NULL;
    node->data = stu;
}
void inOrderList(Tree *root) //中序遍历,先左、后根、再右
{
    Tree **stk = (Tree **)malloc(sizeof(Tree *) * SIZE); //向堆区申请内存构造栈,栈内存储节点地址Tree*
    int top = 0;
    while (root != NULL || top > 0) //当root==NULL且top==0说明栈内已经没有元素,且没有新元素再入栈,跳出循环
    {
        while (root != NULL) //一直入栈,直到root=NULL,
        {
            stk[top++] = root; //先是根节点入栈//栈顶索引top不断增加
            root = root->left; //然后传入左子节点,左子树入栈//这样出栈时是左子树先出栈,根节点后出栈
        }                      //循环结束时,root及root.left全部入栈//栈顶索引top比实际值大1
        root = stk[--top];     //获取到栈顶的节点
        //上2次陆续入栈的是root和root->left,栈顶获取的是后者
        printf("{id:%d name:%s}  ", root->data->id, root->data->name); //输出栈顶,输出root
        root = root->right;                                            //再将栈顶的右子节点传入循环
    }
}
void preOrderList(Tree *root) //前序遍历,先根,后左,再右
{
    Tree **stk = (Tree **)malloc(sizeof(Tree *) * SIZE); //向堆区申请内存构造栈,栈内存储节点地址Tree*
    int top = 0;
    while (root != NULL || top > 0) //当root==NULL且top==0说明栈内已经没有元素,且没有新元素再入栈,跳出循环
    {
        while (root != NULL) //一直入栈,直到root=NULL,
        {
            stk[top++] = root;                                             //根节点入栈//栈顶索引top不断增加
            printf("{id:%d name:%s}  ", root->data->id, root->data->name); //输出栈顶root
            root = root->left;                                             //然后传入左子节点,左子树入栈//这样出栈时根节点先出,左子树再出栈
        }
        root = stk[--top];  //获取到栈顶的节点
        root = root->right; //再将栈顶的右子节点传入循环,右子树入栈前相应节点左子树已完成出栈操作
    }
}
/**
中序遍历中,从栈中弹出的节点,其左子树是访问完了,可以直接访问该节点,然后接下来访问右子树。
后序遍历中,从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。
因此,我们在后序遍历中,引入了一个preNode来记录历史访问记录。
当访问完一棵子树的时候,我们用preNode指向该节点。
这样,在回溯到父节点的时候,我们可以依据preNode是指向左子节点,还是右子节点,来判断父节点的访问情况。
主要思想:
由于在某颗子树访问完成以后,接着就要回溯到其父节点去
因此可以用prev来记录访问历史,在回溯到父节点时,可以由此来判断,上一个访问的节点是否为右子树
 */
void postOrderList(Tree *root) //后序遍历,先左,后右,再根
{
    Tree **stk = (Tree **)malloc(sizeof(Tree *) * SIZE); //向堆区申请内存构造栈,栈内存储节点地址Tree*
    int top = 0;
    Tree *preNode = NULL;
    while (root != NULL || top > 0) //当root==NULL且top==0说明栈内已经没有元素,且没有新元素再入栈,跳出循环
    {
        while (root != NULL) //一直入栈,直到root=NULL,
        {
            stk[top++] = root; //根节点入栈//栈顶索引top不断增加
            root = root->left; //然后传入左子节点,左子树入栈//这样出栈时是左子树先出栈,根节点后出栈
        }
        //从栈中弹出的元素,左子树一定是访问完了的
        root = stk[--top]; //获取到栈顶的节点
        //现在需要确定的是是否有右子树,或者右子树是否访问过
        //如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时
        //说明可以访问当前节点
        if (root->right == NULL || root->right == preNode)
        {
            printf("{id:%d name:%s}  ", root->data->id, root->data->name); //输出栈顶root
            preNode = root;                                                //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
            root = NULL;
        }
        else //如果右子树没有被访问,那么将当前节点压栈,访问右子树
        {
            stk[top++] = root;
            root = root->right;
        }
    }
}
int main()
{
    Student stu[7] = {
        {1, "jakes1"},
        {2, "jakes2"},
        {3, "jakes3"},
        {4, "jakes4"},
        {5, "jakes5"},
        {6, "jakes6"},
        {7, "jakes7"},
    };
    Tree *root = (Tree *)malloc(sizeof(Tree));
    root->data = &stu[0];
    root->left = createTreeNode(&stu[1]);
    root->right = createTreeNode(&stu[2]);
    root->left->left = createTreeNode(&stu[3]);
    root->left->left->right = createTreeNode(&stu[6]);
    root->right->left = createTreeNode(&stu[4]);
    root->right->right = createTreeNode(&stu[5]); //完成一棵树的构建
    printf("中序遍历:\n");
    inOrderList(root);
    puts("");
    printf("前序遍历:\n");
    preOrderList(root);
    puts("");
    printf("后序遍历:\n");
    postOrderList(root);
    return 0;
}

7 以中序遍历为例解析

主函数中构造的二叉树如下:

 前序遍历通过栈实现,执行流程如下:(三种遍历方式均可通过这种方式理解)

 main函数测试输出如下:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jakerc

原创不易,您的奖励将是我最大的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值