【数据结构】 二叉树的遍历

前端之家收集整理的这篇文章主要介绍了【数据结构】 二叉树的遍历前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

“树”是一种重要的数据结构,本文浅谈二叉树的遍历问题,采用C语言描述。

树的销毁应从叶子节点开始逐个向上销毁。如采用非递归的方法,可以使用后序
遍历逐个销毁结点,因后序遍历是先叶子结点后根节点的一种方法
void destroyTree(treeNode *root)
{
if (!root)
{
destroyTree(root‐>left);
destroyTree(root‐>right);
free(root);
}
}

一、二叉树基础

1)定义:有且仅有一个根结点,除根节点外,每个结点只有一个父结点,最多含有两个子节点,子节点有左右之分。
2)存储结构

二叉树的存储结构可以采用顺序存储,也可以采用链式存储,其中链式存储更加灵活。

在链式存储结构中,与线性链表类似,二叉树的每个结点采用结构体表示,结构体包含三个域:数据域、左指针、右指针。

二叉树在C语言中的定义如下:

  1. structBiTreeNode{
  2. intc;
  3. structBiTreeNode*left;
  4. structBiTreeNode*right;
  5. };

二、二叉树的遍历

“遍历”是二叉树各种操作的基础。二叉树是一种非线性结构,其遍历不像线性链表那样容易,无法通过简单的循环实现。

二叉树是一种树形结构,遍历就是要让树中的所有节点被且仅被访问一次,即按一定规律排列成一个线性队列。二叉(子)树是一种递归定义的结构,包含三个部分:根结点(N)、左子树(L)、右子树(R)。根据这三个部分的访问次序对二叉树的遍历进行分类,总共有6种遍历方案:NLR、LNR、LRN、NRL、RNL和LNR。研究二叉树的遍历就是研究这6种具体的遍历方案,显然根据简单的对称性,左子树和右子树的遍历可互换,即NLR与NRL、LNR与RNL、LRN与RLN,分别相类似,因而只需研究NLR、LNR和LRN三种即可,分别称为“先序遍历”、“中序遍历”和“后序遍历”。

二叉树遍历通常借用“栈”这种数据结构实现,有两种方式:递归方式及非递归方式。

在递归方式中,栈是由操作系统维护的,用户不必关心栈的细节操作,用户只需关心“访问顺序”即可。因而,采用递归方式实现二叉树的遍历比较容易理解,算法简单,容易实现。

递归方式实现二叉树遍历的C语言代码如下:

copy
    //先序遍历--递归
  1. inttraverseBiTreePreOrder(BiTreeNode*ptree,int(*visit)(int))
  2. {
  3. if(ptree)
  4. if(visit(ptree->c))
  5. if(traverseBiTreePreOrder(ptree->left,visit))
  6. if(traverseBiTreePreOrder(ptree->right,visit))
  7. return1;//正常返回
  8. return0;//错误返回
  9. }else }
  10. //中序遍历--递归
  11. inttraverseBiTreeInOrder(BiTreeNode*ptree,153); background-color:inherit; font-weight:bold">if(traverseBiTreeInOrder(ptree->left,153); background-color:inherit; font-weight:bold">if(visit(ptree->c))
  12. if(traverseBiTreeInOrder(ptree->right,153); background-color:inherit; font-weight:bold">return1;
  13. return0;
  14. //后序遍历--递归
  15. inttraverseBiTreePostOrder(BiTreeNode*ptree,153); background-color:inherit; font-weight:bold">if(traverseBiTreePostOrder(ptree->left,153); background-color:inherit; font-weight:bold">if(traverseBiTreePostOrder(ptree->right,248); line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> }

以上代码中,visit为一函数指针,用于传递二叉树中对结点的操作方式,其原型为:int (*visit)(char)。

大家知道,函数调用时,会自动进行栈的push,调用返回时,则会自动进行栈的pop。函数递归调用无非是对一个栈进行返回的push与pop,既然递归方式可以实现二叉树的遍历,那么借用“栈”采用非递归方式,也能实现遍历。但是,这时的栈操作(push、pop等)是由用户进行的,因而实现起来会复杂一些,而且也不容易理解,但有助于我们对树结构的遍历有一个深刻、清晰的理解。

在讨论非递归遍历之前,我们先定义栈及各种需要用到的栈操作:

copy
    //栈的定义,栈的数据是“树结点的指针”
  1. structStack{
  2. BiTreeNode**top;
  3. BiTreeNode**base;
  4. intsize;
  5. };
  6. #defineSTACK_INIT_SIZE100
  7. #defineSTACK_INC_SIZE10
  8. //初始化空栈,预分配存储空间
  9. Stack*initStack()
  10. @H_554_301@ Stack*qs=NULL;
  11. qs=(Stack*)malloc(sizeof(Stack));
  12. qs->base=(BiTreeNode**)calloc(STACK_INIT_SIZE,sizeof(BiTreeNode*));
  13. qs->top=qs->base;
  14. qs->size=STACK_INIT_SIZE;
  15. returnqs;
  16. //取栈顶数据
  17. BiTreeNode*getTop(Stack*qs)
  18. @H_554_301@ BiTreeNode*ptree=NULL;
  19. if(qs->top==qs->base)
  20. returnNULL;
  21. ptree=*(qs->top-1);
  22. returnptree;
  23. }
  24. //入栈操作
  25. intpush(Stack*qs,BiTreeNode*ptree)
  26. {
  27. if(qs->top-qs->base>=qs->size)
  28. qs->base=(BiTreeNode**)realloc(qs->base,(qs->size+STACK_INC_SIZE)*sizeof(BiTreeNode*));
  29. qs->top=qs->base+qs->size;
  30. qs->size+=STACK_INC_SIZE;
  31. *qs->top++=ptree;
  32. return1;
  33. //出栈操作
  34. BiTreeNode*pop(Stack*qs)
  35. return*--qs->top;
  36. //判断栈是否为空
  37. intisEmpty(Stack*qs)
  38. returnqs->top==qs->base;
  39. }

首先考虑非递归先序遍历(NLR)。在遍历某一个二叉(子)树时,以一当前指针记录当前要处理的二叉(左子)树,以一个栈保存当前树之后处理的右子树。首先访问当前树的根结点数据,接下来应该依次遍历其左子树和右子树,然而程序的控制流只能处理其一,所以考虑将右子树的根保存在栈里面,当前指针则指向需先处理的左子树,为下次循环做准备;若当前指针指向的树为空,说明当前树为空树,不需要做任何处理,直接弹出栈顶的子树,为下次循环做准备。相应的C语言代码如下:

copy
    //先序遍历--非递归
  1. inttraverseBiTreePreOrder2(BiTreeNode*ptree,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> BiTreeNode*pt=NULL;
  2. qs=initStack();
  3. pt=ptree;
  4. while(pt||!isEmpty(qs))
  5. if(pt)
  6. if(!visit(pt->c)) push(qs,pt->right);
  7. pt=pt->left;
  8. elsept=pop(qs);
  9. //正常返回
  10. 相对于非递归先序遍历,非递归的中序/后序遍历稍复杂一点。

    对于非递归中序遍历,若当前树不为空树,则访问其根结点之前应先访问其左子树,因而先将当前根节点入栈,然后考虑其左子树,不断将非空的根节点入栈,直到左子树为一空树;当左子树为空时,不需要做任何处理,弹出并访问栈顶结点,然后指向其右子树,为下次循环做准备。

    copy
    //中序遍历--非递归
  1. inttraverseBiTreeInOrder2(BiTreeNode*ptree,248); line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> push(qs,pt);
  2. pt=pt->left;
  3. else
  4. pt=pop(qs);
  5. pt=pt->right;
  6. //中序遍历--非递归--另一种实现方式
  7. inttraverseBiTreeInOrder3(BiTreeNode*ptree,87); background-color:inherit; font-weight:bold">int))
  8. Stack*qs=NULL;
  9. BiTreeNode*pt=NULL;
  10. qs=initStack();
  11. while(!isEmpty(qs))
  12. while(pt=getTop(qs))push(qs,pt->left);
  13. pt=pop(qs);
  14. if(!isEmpty(qs))
  15. 最后谈谈非递归后序遍历。由于在访问当前树的根结点时,应先访问其左、右子树,因而先将根结点入栈,接着将右子树也入栈,然后考虑左子树,重复这一过程直到某一左子树为空;如果当前考虑的子树为空,若栈顶不为空,说明第二栈顶对应的树的右子树未处理,则弹出栈顶,下次循环处理,并将一空指针入栈以表示其另一子树已做处理;若栈顶也为空树,说明第二栈顶对应的树的左右子树或者为空,或者均已做处理,直接访问第二栈顶的结点,访问完结点后,若栈仍为非空,说明整棵树尚未遍历完,则弹出栈顶,并入栈一空指针表示第二栈顶的子树之一已被处理。

    copy
    //后序遍历--非递归
  1. inttraverseBiTreePostOrder2(BiTreeNode*ptree,153); background-color:inherit; font-weight:bold">while(1)//循环条件恒“真”
  2. if(!pt)
  3. if(!pt)
  4. if(isEmpty(qs))
    三、二叉树的创建

    谈完二叉树的遍历之后,再来谈谈二叉树的创建,这里所说的创建是指从控制台依次(先/中/后序)输入二叉树的各个结点元素(此处为字符),用“空格”表示空树。

    由于控制台输入是保存在输入缓冲区内,因此遍历的“顺序”就反映在读取输入字符的次序上。

    以下是递归方式实现的先序创建二叉树的C代码

    copy
      //创建二叉树--先序输入--递归
    1. BiTreeNode*createBiTreePreOrder()
    2. charch;
    3. ch=getchar();
    4. if(ch=='')
    5. ptree=NULL;
    6. ptree=(structBiTreeNode*)malloc(sizeof(BiTreeNode));
    7. ptree->c=ch;
    8. ptree->left=createBiTreePreOrder();
    9. ptree->right=createBiTreePreOrder();
    10. 对于空树,函数直接返回即可;对于非空树,先读取字符并赋值给当前根结点,然后创建左子树,最后创建右子树。因此,要先知道当前要创建的树是否为空,才能做相应处理,“先序”遍历方式很好地符合了这一点。但是中序或后序就不一样了,更重要的是,中序或后序方式输入的字符序列无法唯一确定一个二叉树。我还没有找到中序/后序实现二叉树的创建(控制台输入)的类似简单的方法,希望各位同仁网友不吝赐教哈!

      四、运行及结果

      采用如下的二叉树进行测试,首先先序输入创建二叉树,然后依次调用各个遍历函数

      先序输入的格式:ABC ^ ^ D E ^ G ^ ^ F ^ ^ ^ (其中, ^ 表示空格字符)

      遍历操作采用标准I/O库中的putchar函数,其原型为:int putchar(int);

      各种形式遍历输出的结果为:

      先序:ABCDEGF

      中序:CBEGDFA

      后序:CGEFDBA

      测试程序的主函数如下:

      copy
      1. intmain(intargc,87); background-color:inherit; font-weight:bold">char*argv[])
      2. BiTreeNode*proot=NULL;
      3. printf("InOrderinputcharstocreateaBiTree:");
      4. proot=createBiTreePreOrder();//输入(ABCDEGF)
      5. printf("PreOrderOutputtheBiTreerecursively:");
      6. traverseBiTreePreOrder(proot,putchar);
      7. printf("\n");
      8. printf("PreOrderOutputtheBiTreenon-recursively:");
      9. traverseBiTreePreOrder2(proot,putchar);
      10. printf("\n");
      11. printf("InOrderOutputtheBiTreerecursively:");
      12. traverseBiTreeInOrder(proot,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> printf("InOrderOutputtheBiTreenon-recursively(1):");
      13. traverseBiTreeInOrder2(proot,248); line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> printf("InOrderOutputtheBiTreenon-recursively(2):");
      14. traverseBiTreeInOrder3(proot,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> printf("PostOrderOutputtheBiTreenon-recursively:");
      15. traverseBiTreePostOrder(proot,248); line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> printf("PostOrderOutputtheBiTreerecursively:");
      16. traverseBiTreePostOrder2(proot,153); background-color:inherit; font-weight:bold">return0;
      17. }









      定义:
      1、满二叉树:一棵深度为k且有2的k次方减1个结点的二叉树称为满二叉树
      2、完全二叉树:如果有深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。
      性质:
      1、二叉树的第i层上至多有2的i-1次方个结点(i>=1)。
      2、深度为k的二叉树至多有2的k次方减1个结点(k>=1)。
      3、对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
      4、具有n个结点的完全二叉树的深度为以2为底n的对数取下限加1。
      5、如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点i(1=<i=<n)有:
      (1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则双亲PARENT(i)是结点[i/2]
      (2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子LCHILD(i)是结点2i
      (3)如果2i+1>n,则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i+1.
      存储结构:顺序存储结构(数组方式),链式存储结构(二叉链表)
      二叉树链表存储方式:
      struct BiNode
      {
      Type data;
      struct BiNode *lchild,*rchild;//左右孩子指针
      }BiNode,*BiTree
      设计一个算法层序遍历二叉树(同一层从左到右访问)。
      思想:用一个队列保存被访问的当前节点的左右孩子以实现层序遍历。
      void HierarchyBiTree(BiTree Root)
      {
      LinkQueue *Q; // 保存当前节点的左右孩子的队列

      InitQueue(Q); // 初始化队列

      if (Root == NULL) return ; //树为空则返回
      BiNode *p = Root; // 临时保存树根Root到指针p中
      Visit(p->data); // 访问根节点
      if (p->lchild)
      EnQueue(Q,p->lchild); // 若存在左孩子,左孩子进队列
      if (p->rchild)
      EnQueue(Q,p->rchild); // 若存在右孩子,右孩子进队列

      while (!QueueEmpty(Q)) // 若队列不空,则层序遍历
      {
      DeQueue(Q,p); // 出队列
      Visit(p->data);// 访问当前节点

      if (p->lchild)
      EnQueue(Q,p->lchild); // 若存在左孩子,左孩子进队列
      if (p->rchild)
      先找到最左边的叶子并把路上遇到的节点一次进栈,然后弹出栈顶的元素(该元素为最左边的叶子)并判断(1)它有没有右节点;
      (2)右节点是否被访问过;如果有右节点同时没被访问过,则先压入刚才弹出的元素,然后压入它的右子树,否则访问该节点并设置pre为该节点。
      void postOrder2(BinTree *root) //非递归后序遍历

      [cp

        void postOrder2(BinTree *root) //非递归后序遍历
        {
        stack<BinTree*> s;
        BinTree *p=root;
        BinTree *pre=NULL;
        BinTree *top=NULL;
        while(p!=NULL||!s.empty())
        {
        while(p!=NULL)
        s.push(p);
        p=p->lchild;
        }
        if(!s.empty())
        top=s.top();
        if(top->right!=NULL&&top->right!=pre)
        p=p->right;
        else
        cout<<top->data<<" "
        pre=top;
        s.pop()
        }

      猜你在找的数据结构相关文章