数据结构——树(5)——树的先序,中序,后序,层次遍历

树的存储结构

之前我们提到过堆是一棵特殊的树,在堆的存储方式中,我们选择了数组的方式去存储。因为只要知道一个节点的位置我们就能找到其他的节点的位置。但是前提是堆是一棵完全二叉树。树的结构多种多样,没人规定说一个节点只能有一个孩子。
我们先看下图,一个最简单的二叉树:
这里写图片描述
怎么表达?看到箭头,我们应该立马反应过来一个基本的工具,没错就是指针,应该立马反应过来一个数据结构,那就是链表!
这里写图片描述
对吧,把value换成我们的A B C,分别定义两个指针,一个指向左边的节点,一个指向右边的节点,其他的节点类似,就能把一棵二叉树表达出来了。很完美对吧。那么我们怎么用c++来描述这一个数据结构呢?代码如下:

struct Tree{
    string value;
    Tree *left;
    Tree *right;
};

代码对应的结构图应该是这样的:
这里写图片描述

但肯定有人问,那么三叉树呢?n叉树呢是不是也可以同样的方式来定义呢?当然!就先说说三叉树,其实三叉树就是在中间多了一个往下的箭头对吧,那么我们的数据结构按上面的结构,多用一个指向中间的指针不就完事了?看下图:
这里写图片描述
那么 n 叉树呢?这个时候我们就发现如果按照上述的定义,太麻烦了,代码都得多几十行,而且不好记忆。那么我们可以把这些指针存进一个vector中,就像这样:

struct Tree {
  string value;
  Vector<Tree *> children;
};

这个vector里面存储着指向他们孩子节点的指针。就像这样:
这里写图片描述

那么树的存储结构在内存中是怎么样的呢?看下图
这里写图片描述

树的四种遍历方式

我们先看一颗树:
这里写图片描述
这是一棵简单的二叉树,现在我们来看看什么叫树的遍历:
遍历树的节点并在每个节点执行一些操作的过程称为遍历或行走树(traversing or walking the tree)。在许多情况下,将需要按照数值的的顺序遍历树 这种处理在左右子树的递归调用之间的节点的方法被称为中序遍历(inorder traversal)。 另外还经常发生另外两种类型的树遍历,它们被称为前序遍历和后序遍历(preorder and postorder traversals)

先序遍历(preOrder)

遍历树的节点并在每个节点执行一些操作的过程称为遍历树,这是我们讲遍历之前提到过的,那么我们就以这个模式来讲解这个部分的内容。操作我们就用最简单的输出数据来实现。这样先序遍历的步骤就是:
- Do something
- Go left
- Go right

结合代码跟上面的树我们来讲解一下具体的步骤

void preOrder(Tree *tree){
    if (tree == NULL) return; //如果树为空,直接返回函数
    cout << tree -> value << endl;
    preOrder(tree -> left);
    preOrder(tree -> right);
}

首先,判断树是否为空,是,则直接返回,否则进行下一步。先执行操作(这里是输出节点的值),为Grumpy,然后继续先序遍历节点的左子树,此时的tree指向的是doc节点,判断这子树是否为空?结果是否,因为doc下面还有子节点(孩子分别为Bashful和Dopey),判断为否后执行操作,输出Doc,继续先序遍历,此时tree指向Bashful,判断这子树是否为空,结果为否!(这是个叶子,没有子节点,但是也是一棵孤立的树),执行输出操作,输出Bashful。继续执行先序遍历操作,此时指针指向Bashful的左孩子,由于它没有孩子,于是判断这子树为空,返回程序(return;)。此时指针往上回退,对右边进行先序遍历,输出Dopey。判断这个节点有无孩子,继续执行上述操作。这样子,我们可以看到先序遍历是先遍历左边的子树,再遍历右边的子树。输出为:
这里写图片描述
这里写图片描述

中序遍历(inOrder)

中序遍历的步骤为:
- Go left
- Do something
- Go right

看一段代码:

void inOrder(Tree *tree){
    if (tree == NULL) return;
    inOrder(tree -> left);
    cout << tree -> value << endl;
    inOrder(tree -> right);
}

首先判断树是否为空,是直接返回,否则继续。继续中序遍历树的左节点,在这个例子中,一开始的指针指向的是Grumpy,因为树不为空,因此遍历它的左节点,也就是这个时候指针指向Doc,树仍然不为空,那么继续遍历其左节点,此时指针指向Bashful,此时bashful是个孤立的树叶,但仍是一棵子树。继续遍历其左子树,发现此时为空,程序直接返回上一级指针,也就是指向Bashful,并执行输出操作。接下来遍历其右节点,同理发现没有,指针回退到Doc,由于是由inOrder(tree -> left);回退而来,所以接下来输出Doc,然后执行inOrder(tree -> right),指针指向Doc的右节点,也就是Dopey。过程同Bashful。完成后指针就会退至Grumpy。由于是由inOrder(tree -> left);回退而来,所以接下来输出Doc,然后执行inOrder(tree -> right),指针此时指向Sleepy,树为空吗,否,指针继续往下,遍历左节点,也就是指针此时指向Happy,此时Happy是个孤立的树叶,但仍是一棵子树。继续遍历其左子树,发现此时为空,程序直接返回上一级指针,也就是指向Happy,并执行输出操作。然后重复上述的操作,得出结果应该是这样的:
这里写图片描述

Bashful
Doc
Dopey
grumpy
Happy
Sleepy
Sneezy

后序遍历(postOrder)

步骤为:
- Go left
- Go right
- Do something

代码为:

void postOrder(Tree * tree) {
    if(tree == NULL) return;
    postOrder(tree -> left);
    postOrder(tree -> right);
    cout<< tree->value << endl;
}

首先,指针指向Grumpy,判断树是否为空,是则返回,否则继续。后序遍历树的左节点,此时指针指向Doc,树为空吗?否,指针继续指向左子树,也就是Bashful。树仍然不为空,继续往下,发现左右子树都为空,立刻返回,然后输出该节点的值,也就是Bashful。指针往上返回,指向Doc,然后执行postOrder(tree -> right);此时指针指向Dopey,同上操作,输出Dopey。随后指针回退,指向Grumpy,此时树为空吗?否,遍历左边,输出Doc,然后遍历右边,指针指向Sleepy,此时树为空吗?否,继续遍历Happy,为空吗?否,查看树的左节点,为空,立刻返回,输出Happy,随后指针返回Sleep中,查看右节点,为空吗?否,遍历左右子树,为空,立刻返回,然后输出Sneezy,指针回退到Grumpy中,输出Sleepy,最后输出根节点。因此最终的结果为:
这里写图片描述

Bashful
Dopey
Doc
Happy
Sneezy
Sleepy
Grumpy

层次遍历(levelOrder)

考虑这样一个问题,如果我们需要将树的节点按顺序从左往右,依次按顺序输出,我们应该怎么办呢?能否按上述的步骤进行递归实现呢?唔。。。似乎不太容易,但是按顺序依次输出似乎让我们想到了什么?我们前面介绍过一种顺序数据结构,没错,就是我们的栈跟队列,显然这种情况我们选用队列好多了。我们要做的步骤为:
1. 将树装进我们的队列中,如果队列未空执行下列操作
2. 将树根移出队列
3. 对节点进行操作
4. 如果有左节点,进行出队列操作
5. 如果有右队列,进行出队列操作

这样,出队列的顺序就是树的层次顺序。代码为:

void levelOrder(Tree *tree) {
    Queue<Tree *>treeQueue; //建立一个队列存储树,类型为指向树的指针
    treeQueue.enqueue(tree);//将树装进我们的队列中
    while (!treeQueue.isEmpty()) { //判断树是否为空
        Tree *node = treeQueue.dequeue(); //定义一个Tree类型的指针,指向出队的节点
        cout << node->value << " ";  //输出该节点的值

       if (node->left != NULL) {     //如果存在左节点,将左节点移除
            treeQueue.enqueue(node->left);
        }
        if (node->right != NULL) {   //如果存在右节点,将右节点移除
            treeQueue.enqueue(node->right);
        }
    }
}

因此,按上述的例子,输出为:
这里写图片描述
Grumpy
Doc
Sleepy
Bashful
Dopey
Happy
Sneezy

好了,树的四种遍历方式就先讲到这里。不过个人感觉,再去做一下严蔚敏《数据结构(C语言版)》P129的那个表达式,能受益匪浅!!!

猜你喜欢

转载自blog.csdn.net/redrnt/article/details/79167942