数据结构入门——树的相关算法(一)

  • 写在前面
  • 工具
  • 二叉树的遍历
    • 二叉链表的定义
    • 递归实现
    • 非递归实现
  • 参考书籍

写在前面

本系列是记录与总结性质的文章,原创的内容少,记录的内容大都与考研有关。在考研的范畴里,与树相关的算法很多,程序设计题中属于必考题。我准备用三篇博客来总结与树有关的算法。

因为与考研相关,数据结构的定义和规范参考严书1 和王道2。前一本是计算机考研的指定参考书,后一本是考研辅导书。

最后,由于写博客的时间仓促,文中若有错误之处,恳请朋友们批评指正。


工具

在考试中,实现树的相关算法时很可能会用到栈或队列,可以直接把他们作为一个工具来解决问题。即,把栈或队列的声明和操作写得很简单,不必分函数写出。以顺序栈的操作为例:
(1) 声明一个栈并初始化:

//下面两句连声明带初始化都有了
ElemType stack[maxSize];
int top = -1;

(2)元素进栈

stack[++top] = x;       //仅一句话即实现进栈操作

(3)元素x出栈

x = stack[top--];

更简单的,可以直接使用栈和队列的基本操作的函数来实现操作。栈的基本操作有:

InitStack(S);   //初始化

StackEmpty(S);  //判空

Push(S,x);  //元素x进栈

Pop(S,x);   //栈顶元素出栈并赋值给x

GetTop(S,x);    //读栈顶元素并赋值给x

相应的,队列的基本操作如下:

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

QueueEmpty(Q);  //判空

EnQueue(Q,x);   //元素x入队

DeQueue(Q,x);   //出队,赋值给x

GetHead(Q,x);   //读队头元素,赋值给x

二叉树的遍历

二叉链表的定义

typedef struct BiTNode{
    ElemType data;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

二叉树的遍历,递归算法写起来非常简单。重点在与如何用非递归算法来实现遍历。

递归实现

1.先序遍历(PreOrder)

void PreOrder(BiTree T){
    if(T != NULL){
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

2.中序遍历(InOrder)

void InOrder(BiTree T){
    if(T != NULL){
        InOrder(T->lchild);
        visit(T);
        InOrder(T->rchild);
    }
}

3.后序遍历(PostOrder)

void PostOrder(BiTree T){
    if(T != NULL){
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        visit(T);
    }
}

非递归实现

1.先序遍历

void PreOrder2(BiTree T){
    BiTree p = T;

    InitStack(S);   //初始化一个空栈

    if(p != NULL){
        Push(S,p);  //结点进栈
        while(!StackEmpty(S)){
            Pop(S,p);  //出栈,赋值给p
            visit(p);

            if(p->rchild){  //一定是右孩子先入栈
                Push(S,p-rchild);
            }

            if(p->lchild){
                Push(S,p->lchild);
            }
        }
    }
}

2.中序遍历

void InOrder2(BiTree T){
    BiTree = T;

    InitStack(S);
    while(p || !StackEmpty(S)){
        if (p != NULL){
            Push(S,p);
            p = p->lchild;
        }else{
            Pop(S,p);
            visit(p);
            p = p->rchild;
        }

    }
}

3.后序遍历(重点难点)

void PostOrder2(BiTree T){
    BiTree p;

    InitStack(S);

    while(T || !StackEmpty(S)){

        while(T){
            Push(S,T);
            T = T->lchild;
        }

        flag = 1;   //flag = 1代表左孩子为空或者该节点已经被访问
        p = NULL;   //p指向该节点的前驱

        while(!StackEmpty(S) && flag == 1){
            GetTop(S,T);    //获取栈顶元素,非出栈
            if(T->rchild == p){
                Pop(S,T);
                visit(T);
                p = T;
            }else{
                T = T->rchild;
                flag = 0;
            }
        }
    }
}

注: 上面的后续遍历没有直接copy王道数据结构P175页的代码,但二者思路是一致的。

之所以说后序遍历的非递归算法是重点难点,是因为每当访问一个结点x时,此时栈里存储的元素正好是x的所有祖先。后面的题目里求叶子结点的所有祖先,或者输出根到叶子的路径,都和这个非递归的后序遍历紧密相关。

4.层次遍历

void LevelOrder(BiTree T){
    BiTree p;

    InitQueue(Q);
    EnQueue(Q,T);  //根节点入队

    while(!QueueEmpty(Q)){
        DeQueue(Q,p);  //出队,赋值给p
        visit(p);
        if(p->lchild){
            EnQueue(Q,p->lchild);
        }
        if(p->rchild){
            EnQueue(Q,p->rchild);
        }
    }
}

参考书籍


  1. 严蔚敏,吴伟民.数据结构:C语言版[M].北京:清华大学出版社.2007. ↩
  2. 王道论坛.2019年数据结构考研复习指导[M].北京:电子工业出版社.2018. ↩

你可能感兴趣的:(数据结构)