二叉树的遍历 | 数据结构

73 阅读4分钟

1 - 次序遍历

1.1 先序遍历

递归算法

若二叉树为空,则什么也不做;否则,

  1. 访问根节点;
  2. 先序遍历左子树;
  3. 先序遍历右子树。
void PreOrder(BiTree T)
{
    if (T != NULL)
    {
        visit(T);            //访问根节点
        PreOrder(T->lchild); //递归遍历左子树
        PreOrder(T->rchild); //递归遍历右子树
    }
}

时间复杂度:O(N)O(N) ,每个节点都访问且仅访问一次;

空间复杂度:O(N)O(N) ,递归遍历中递归工作栈的栈深为树的深度。

非递归算法

  1. 沿着根的左孩子,依次访问并入栈,直到左孩子为空;
  2. 栈顶元素出栈:
    • 若其右孩子为空,继续执行 2 ;
    • 若其右孩子非空,将右子树转执行 1 。
void PreOrder2(BiTree T)
{
    InitStack(S); BiTree p = T;   //初始化栈S;p是遍历指针
    while (p || !IsEmpty(S))      //栈不空或p不空时循环
    {
        if (p)                    //一路向左
        {
            visit(p); Push(S, p); //访问当前结点,并入栈
            p = p->lchild;        //左孩子不空,一直向左走
        }
        else                      //出栈,并转向出栈结点的右子树
        {
            Pop(S, p);            //栈顶元素出栈
            p = p->rchild;        //向右子树走,p赋值为当前结点的右孩子
        }                         //返回while循环继续进入if-else语句
    }
}

1.2 中序遍历

递归算法

若二叉树为空,则什么也不做;否则,

  1. 中序遍历左子树;
  2. 访问根节点;
  3. 中序遍历右子树。
void InOrder(BiTree T)
{
    if (T != NULL)
    {
        InOrder(T->lchild); //递归遍历左子树
        visit(T);           //访问根节点
        InOrder(T->rchild); //递归遍历右子树
    }
}

时间复杂度:O(N)O(N) ,每个节点都访问且仅访问一次;

空间复杂度:O(N)O(N) ,递归遍历中递归工作栈的栈深为树的深度。

非递归算法

  1. 沿着根的左孩子,依次入栈,直到左孩子为空,说明已经找到可以输出的结点;
  2. 栈顶元素出栈并访问:
    • 若其右孩子为空,继续执行 2 ;
    • 若其右孩子非空,将右子树转执行 1 。
void InOrder2(BiTree T)
{
    InitStack(S); BiTree p = T;  //初始化栈S;p是遍历指针
    while (p || !IsEmpty(S))     //栈不空或p不空时循环
    {
        if (p)                   //一路向左
        {
            Push(S, p);          //当前结点入栈
            p = p->lchild;       //左孩子不空,一直向左走
        }
        else                     //出栈,并转向出栈结点的右子树
        {
            Pop(S, p); visit(p); //栈顶元素出栈,访问出栈结点
            p = p->rchild;       //向右子树走,p赋值为当前结点的右孩子
        }                        //返回while循环继续进入if-else语句
    }
}

1.3 后序遍历

递归算法

若二叉树为空,则什么也不做;否则,

  1. 后序遍历左子树;
  2. 后序遍历右子树。
  3. 访问根节点;
void PostOrder(BiTree T)
{
    if (T != NULL)
    {
        PostOrder(T->lchild); //递归遍历左子树
        PostOrder(T->rchild); //递归遍历右子树
        visit(T);             //访问根节点
    }
}

时间复杂度:O(N)O(N) ,每个节点都访问且仅访问一次;

空间复杂度:O(N)O(N) ,递归遍历中递归工作栈的栈深为树的深度。

非递归算法

  1. 沿着根的左孩子,依次入栈,直到左孩子为空
  2. 读栈顶元素:
    • 若其右孩子不空未被访问过,将右子树转执行 1 ;
    • 否则,栈顶元素出栈并访问

上述思想的第 2 步中,必须分清返回时是从左子树返回的还是从右子树返回,因此设定一个辅助指针 r ,指向最近访问过的节点。(也可以在节点中增加一个标志域,记录是否已被访问)

void PostOrder2(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指针
            }
        }
    }
}

注意:每次出栈访问完一个根节点就相当于遍历完以该根节点为根的子树,需将 p 置为 NULL

2 - 层序遍历

先将二叉树根节点入队,然后出队,访问出队节点,若它有左子树,则将左子树根节点入队;若它有右子树。则将右子树根节点入队。然后出队,访问出队节点……如此反复,直至队列为空。

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); //右子树不空,则右子树根节点入队
    }
}