二叉树及其应用 表达式树 后序线索化和遍历

实验三 二叉树及其应用

0: 背景

这是一次数据结构的实验报告。源代码不另附,将文中散乱代码块合在一起即可。

1:二叉树的创建和遍历

需求分析

通过添加虚结点,为二叉树的每一实结点补足其孩子,再对补足虚结点后的二叉树按层次遍历的次序输入。构建二叉树(不包含虚结点),并增加左右标志域,将二叉树后序线索化。完成后序线索化树上的遍历算法,依次输出该二叉树先序遍历、中序遍历和后序遍历的结果。

输入:以#代表虚结点,输入字符,层序创建。如:ABC#DEFG##H######

输出:先中后序遍历结果,以字符串表示。如:

ABDHCEFH, BGDAEHCF, GDBHEFCA

概要设计

程序中使用了二叉树和队列的数据结构,以及字符串数组结构。涉及到出队列,入队列操作,对字符串数组赋值和取值操作。

主程序:接受输入,层序创建二叉树,先序、中序遍历,之后将其后序线索化,并按照后序线索后序遍历。

子函数中后序遍历用到了另一个子函数:求该结点的父结点。

详细设计

数据类型:二叉树和线索

//定义线索
typedef enum{
    link, thread
}ptrtag;

//定义线索二叉树结点
typedef struct node{
    char data;
    ptrtag ltag;
    ptrtag rtag;
    struct node *lchild;
    struct node *rchild;
}node,*Btree;

主程序:

int main(void){
    Btree T;
    T = CreatBtree();
    PreOrder(T);
    puts("\n");
    InOrder(T);
    puts("\n");
    PosTreeLink(T);        //线索化
    PostThreadOrder(T);    //后序遍历
    printf("\n");
    return 0;
}

模块:

  1. 二叉树创建

    /层序创建二叉树
    Btree CreatBtree(){
        Btree T,que[N],new;
        int front = 0, rear = 0;
        char ch = getchar();
        if(ch == '#') T = NULL;
        else{
            T = (Btree)malloc(sizeof(node));
            T->data = ch;
            T->lchild = NULL;
            T->rchild = NULL;
            T->ltag = link;
            T->rtag = link;
            que[rear++] = T;
        }
        while (front != rear) {
    
            ch = getchar();
            if(ch == '\n') break;
            if(ch == '#'){
                que[front]->lchild = NULL;
            }
            else{
                new = (Btree)malloc(sizeof(node));
                new->data = ch;
                new->ltag = link;
                new->rtag = link;
                new->lchild = NULL;
                new->rchild = NULL;
                que[front]->lchild = new;
                que[rear++] = new;
            }
    
            ch = getchar();
            if(ch == '#'){
                que[front]->rchild = NULL;
            }
            else{
                new = (Btree)malloc(sizeof(node));
                new->data = ch;
                new->ltag = link;
                new->rtag = link;
                new->lchild = NULL;
                new->rchild = NULL;
                que[front]->rchild = new;
                que[rear++] = new;
            }
            front++;
        }
        return T;
    }
    
  2. 后序线索化

    //全局变量,指向前一个结点
    Btree Prev = NULL;
    
    //后序线索化
    void PosTreeLink(Btree Root)
    {
        if (Root == NULL)
        {
            return;
        }
        PosTreeLink(Root->lchild);
        PosTreeLink(Root->rchild);
    
        if (Root->lchild == NULL)
        {
            Root->lchild = Prev;
            Root->ltag = thread;
        }
        if (Prev != NULL && Prev->rchild == NULL)
        {
            Prev->rchild = Root;
            Prev->rtag = thread;
        }
        Prev = Root;
    }
    
  3. 先序和中序遍历(递归)

    //先序遍历
    void PreOrder(Btree T){
        if(T){
            printf("%c ", T->data);
            PreOrder(T->lchild);
            PreOrder(T->rchild);
        }
    }
    
    //中序遍历
    void InOrder(Btree T){
        if(T){
            InOrder(T->lchild);
            printf("%c ", T->data);
            InOrder(T->rchild);
        }
    }
    
  4. 寻找双亲

    //找双亲
    void Parent(Btree root,Btree *child)
    {
        Btree temp = *child;
        if (*child == root)
        {
            return;
        }
        if (root)
        {
            if (root->lchild == *child || root->rchild == *child)
            {
                *child = root;
                return;
            }
            if (root->ltag == link)
            {
                Parent(root->lchild, child);
            }
            if (temp == *child && root->rtag == link)
            {
                Parent(root->rchild, child);
            }
        }
    }
    
  5. 后序遍历

    //后序遍历
    void PostThreadOrder(Btree T)
        {
            Btree cur = T;
            Btree lastvisited = NULL;
            while (cur)
            {
                //找最左边结点
                while (cur&&cur->ltag != thread && cur->lchild != lastvisited)
                {
                    cur = cur->lchild;
                }//当cur是通过其左孩子找到的即没有右孩子,不对cur进行重复找最左边的叶子节点
                if (cur && (cur->rtag == thread))//当cur无右孩子时,输出cur
                {
                    printf("%c ", cur->data);
                    lastvisited = cur;
                }
                if (cur == T&&cur->rchild== NULL)//当根节点被访问时,遍历完成
                {
                    printf("%c ", cur->data);
                    return;
                }
                cur = cur->rchild; //前往后继结点
                while (cur && cur->rchild == lastvisited) //cur前驱是自己右孩子
                {
                    printf("%c ", cur->data);
                    lastvisited = cur;
                    Parent(T , &cur); //寻找cur的根节点
                    if (cur == lastvisited)
                    {
                        return;
                    }
                }
            }
        }
    
    

主程序调用CreateBtree,PostThreadOrder,preorder,inorder,postreelink。

PostThreadOrder调用parent。


调试分析

  1. 编写程序前,思考前和中序遍历,均可以使用简单的递归完成。后序线索化可以在后序遍历基础上修改而成。在得到后序线索化树以后,思考如何处理前驱和后继关系。除了使用三叉链表外,还可以使用寻找父结点的函数来完成。当一个结点两个孩子都存在时,应该找它的父结点。

  2. 调试过程中,试图使用中-右-左的模式来完成倒序后序遍历,但由于各种原因停止,改为找父结点。同时在使用过程中发现结束条件可以改成前驱结点是树根。

  3. 写完程序以后尝试使用各种极端情况来测试,发现在全部为左子树和交替为左子树时出现了bug。原因是没有判断右孩子是否存在和回溯条件不清楚。只输入一个结点时也会出现问题。多次完善以后才完成后序线索化的模块。

  4. 遍历时递归调用自身不能写错,否则会产生不理想的结果。

  5. 本算法创建二叉树时遇到了困难。找到新方法是将前一层结点存入队列,每次往后编入两个结点作为左右孩子,编组完成后将前一层结点出队列,从而完成结点的创建。

  6. 本算法创建二叉树均适用单层结构,没有高次方复杂度的操作。前中序遍历操作也是如此。空间复杂度为o(n)级别。后序遍历使用找父结点的操作,有一个o(n^2/2)的操作,时间复杂度为多项式级别,比较简单。如果可以使用三叉链表,时间复杂度将降低至o(n)级别。

用户使用说明

  1. 本程序为命令行操作,可以使用支持C语言的设备运行。
  2. 进入程序后可以输入字符串,以#作为空结点标志,注意输入的结果字符串必须合法而且正确,否则无法得出正确结果。
  3. 输入后回车,程序将分行输出前、中、后序遍历的结果。行与行之间空一行,输出完成后自动退出。

测试结果

Input:
ABC#DEFG##H######
  
Output:
A B D H C E F H
  
B G D A E H C F
  
G D B H E F C A
Input:
AB#C#D##E##
  
Output:
A B C D E 

D E C B A 

E D C B A 
Input:
A#BC##DE##F##
  
Output:
A B C D E F 

A C E F D B 

F E D C B A 
Input:
ABCD##E##F###
  
Output:
A B D C E F 

D B A C F E 

D B F E C A 

测试结果满足预期,可以认为程序运行正常。

2:表达式树

###需求分析

输入合法的波兰式(仅考虑运算符为双目运算符的情况),构建表达式树,分别输出对应的中缀表达式 (可含有多余的括号)、逆波兰式和表达式的值,输入的运算符与操作数之间会用空格隔开。

输入输出样例:

Input:
-+231 //波兰式 
  
Output:
(2+3)-1 //中缀表达式 23+1- //逆波兰式
4 //求值

选做要求(二选一即可):

  1. 输出的中缀表达式中不含有多余的括号。 例如在上面的样例中,期望的输出结果应该是 2=3-1

  2. 输入逆波兰式,输出波兰式、中缀表达式(可含有多余的括号)和表达式的值。

本人选了第二个要求。


概要设计

本实验中,使用了二叉树和栈的数据结构。涉及到初始化栈,出栈和入栈操作。二叉树则涉及到创建二叉树结点,修改二叉树的指针域,二叉树的三种遍历等。

主程序:判断用户需求。按照输入逆波兰式或者波兰式来进行创建树,输出表达式和计算结果操作。调用了树的创建,树的遍历,树的求值函数。

子函数:创建树使用了子函数judge,判断字符是否为操作符。遍历树则使用了自身递归。

详细设计

  1. 定义树结点

    typedef struct tree{
        char data;
        tag tag;
        struct tree *lchild;
        struct tree *rchild;
    }treenode,*btree;
    
  2. 定义tag(标记结点储存的数据是什么类型)

    typedef enum tag{
        num,op,mult
    }tag;
    
  3. 定义栈

    typedef struct stac{
        int bottom,top;
        btree data[N];
    }stac;
    
  4. 树的操作:

    1. 创建结点

      btree CreNode(char data){
          btree T = (btree)malloc(sizeof(treenode));
          T->data = data;
          T->rchild = NULL;
          T->lchild = NULL;
          return T;
      }
      
    2. 创建树

      btree CreateBtree() {
          btree root;
          stac s;
          InitStac(s);
      
          char *ch[N], c;
          int i = 0;
          c = ' ';
          while (c == ' ') {
              ch[i] = (char *) malloc(N * sizeof(char));
              scanf("%s", ch[i]);
              i++;
              c = getchar();
          }
          while (i--) {
              int len = (int) strlen(ch[i]) - 1;
              if (len == 0) {
                  char cur = ch[i][0];
                  if (!judge(cur)) {
                      btree t = CreNode(cur);
                      t->tag = num;
                      Push(&s, t);
                  }
                  else {
                      btree lc, rc;
                      Pop(&s, &lc);
                      Pop(&s, &rc);
                      btree t = CreNode(cur);
                      t->tag = op;
                      t->lchild = lc;
                      t->rchild = rc;
                      Push(&s, t);
                  }
              }
              else {
                  char cu[N] = {0};
                  for (int j = 0; j <= len; j++) {
                      cu[j] = ch[i][j];
                  }
                  char cur = (char)atoi(cu);
                  btree t = CreNode(cur);
                  t->tag = mult;
                  Push(&s, t);
              }
          }
          Pop(&s, &root);
          return root;
      }
      
    3. 逆波兰式创建树

      btree ExtraCreate(){
          btree root;
          stac s;
          InitStac(s);
      
          char *ch[N], c;
          int i = 0;
          c = ' ';
          while (c == ' ') {
              ch[i] = (char *) malloc(N * sizeof(char));
              scanf("%s", ch[i]);
              i++;
              c = getchar();
          }
          int n = i;
          for ( i = 0; i < n; i++) {
              int len = (int) strlen(ch[i]) - 1;
              if (len == 0) {
                  char cur = ch[i][0];
                  if (!judge(cur)) {
                      btree t = CreNode(cur);
                      t->tag = num;
                      Push(&s, t);
                  }
                  else {
                      btree lc, rc;
                      Pop(&s, &rc);
                      Pop(&s, &lc);
                      btree t = CreNode(cur);
                      t->tag = op;
                      t->lchild = lc;
                      t->rchild = rc;
                      Push(&s, t);
                  }
              }
              else {
                  char cu[N] = {0};
                  for (int j = 0; j <= len; j++) {
                      cu[j] = ch[i][j];
                  }
                  char cur = (char)atoi(cu);
                  btree t = CreNode(cur);
                  t->tag = mult;
                  Push(&s, t);
              }
          }
          Pop(&s, &root);
          return root;
      }
      
    4. 前缀表达式

      void PrintPreTree(btree T){
          if(T == NULL) return;
          else{
              if ( T->tag == mult )
              {
                  printf("%d ",T->data);
              }
              else printf("%c ", T->data);
              PrintPreTree(T->lchild);
              PrintPreTree(T->rchild);
          }
      }
      
    5. 后缀表达式

      void PrintPosTree(btree T){
          if(T == NULL) return;
          else{
              PrintPosTree(T->lchild);
              PrintPosTree(T->rchild);
              if ( T->tag == mult )
              {
                  printf("%d ",T->data);
              }
              else printf("%c ", T->data);
          }
      }
      
    6. 中缀表达式(没有删除多余括号)

      void PrInTree(btree T){
          if(T == NULL) return;
          else {
              if (T->tag == op) {
                  printf("(");
                  PrInTree(T->lchild);
                  if (T->tag == mult) {
                      printf("%d", T->data);
                  }
                  else {
                      printf("%c", T->data);
                  }
                  PrInTree(T->rchild);
                  printf(")");
              }
              else {
                  if (T->tag == mult) {
                      printf("%d", T->data);
                  }
                  else {
                      printf("%c", T->data);
                  }
              }
          }
      }
      
    7. 计算树的值

      int value(btree t){
          int a,b;
          if (t ->tag == op){
              a = value(t->lchild);
              b = value(t->rchild);
              return calc(a,t->data,b);
          }
          else{
              if(t->tag == num){
                  return atoi(&t->data);
              }
              else {
                  return (int) t->data;
              }
          }
      }
      
  5. 栈的操作

    1. 初始化栈

      stac InitStac(stac S){
          S.bottom = S.top = 0;
          S.data[S.top] = NULL;
          return S;
      }
      
    2. 压栈

      void Push(stac *s, btree T){
          s->top++;
          s->data[s->top] = T;
      }
      
    3. 出栈

      void Pop(stac *s, btree *T){
          if (s->top == s->bottom)
              return;
          *T = s->data[s->top];
          s->top--;
      }
      
  6. 判断操作(判断是否为操作符号)

    int judge(char x){
        if (x == '+'||x == '-'||x == '*' || x == '/')
            return 1;
        else return 0;
    }
    

调试分析

  1. 实现过程中,最先考虑的是如何输入带空格的字符串。再经过大量尝试和搜索之后,发现可以使用字符串数组,以空格作为分隔符号,从而达到输入字符串的作用。在输入字符串以后,还需要考虑数字的位数。因此在树上加入tag结构域,用于区分是多位数字还是单位数字还是操作符号。否则会因为分不清是否为ascii码还是数值而输出计算错误。
  2. 可以用栈先入后出的特点,实现前后缀表达式的计算。这对计算机来说是非常方便的。而且表达式树是一个非常好的方式,因为可以从表达式树中获取前中后缀表达式,也可以用遍历的方法来创建和计算表达式值。
  3. 由于没有使用一个字符一个字符的储存方式,导致每次都要判断是否为字符。导致程序较长。使用一个字符一个字符的处理方法可能会更好。
  4. 指针在这个过程中起到了非常大的作用,使用指针可以简化程序,对指针进行加减就可以指向不同的结果。相比多维数组更加方便。
  5. 判断递归结束的条件一直是难点,同时加不加括号也比较难想清楚。
  6. 本算法创建只涉及出入栈,遍历等操作也只与输入个数有关。因此时间和空间复杂度为o(n)级别,复杂度为多项式级别,可以接受。

用户使用说明

  1. 本程序为命令行操作,可以使用支持C语言的设备运行。
  2. 进入程序后在提示下输入A和B选择输入模式。A为前缀表达式,B为后缀表达式。选择之后可以输入字符串,以空格作为分隔标志,注意输入的结果字符串必须合法而且正确,否则无法得出正确结果。
  3. 如果选择A,输入后回车,程序将分行输出中缀表达式,后缀表达式和表达式计算的结果。如果选择B,输入后回车,程序将分行输出中缀表达式,前缀表达式和表达式计算的结果。输出完成后自动退出。输出每行间空一行。

测试结果

Input:
A
/ + 15 * 5 + 2 18 5

Output:
((15+(5*(2+18)))/5)

15 5 2 18 + * + 5 / 

23
Input:
A
- / 15 - 2 7 10

Output:
((15/(2-7))-10)

15 2 7 - / 10 - 

-13
Input:
A
- 20 + 11 13

Output:
(20-(11+13))

20 11 13 + - 

-4
Input:
A
/ 32 * 8 2
  
Output:
(32/(8*2))

32 8 2 * / 

2

测试结果与预期相符,可以认为程序设计基本完善。

思考:

  1. 分别给定先序序列和中序序列、中序序列和后序序列、先序序列和后序序列,是否能够唯一确定一颗二叉树?

    答:先序和后序序列是不能确定二叉树的,因为后序和先序缺少根结点信息,无法判断结点之间的关系。中序可以提供根的关系。

  2. **表达式树的先序序列、中序序列和后序序列与波兰式、中缀表达式和逆波兰式之间有什么联系? **

    先序序列其实就是波兰式,后序就是逆波兰式。中序遍历加上括号之后就是中缀表达式。

  3. 给定波兰式、中缀表达式或逆波兰式中的任意一种,是否能够唯一确定一颗表达式树?是什么造成了表达式树与二叉树之间的这种区别?

    都可以确定一颗二叉树。原因是,表达式中的操作符和操作数是有区别的,这可以作为区别根结点和叶结点的关键信息,而普通的二叉树本身并不含有这样的信息。因此给定表达式是可以创建树的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值