线索化二叉树
我们知道一棵二叉树实际上可以由多个结点组成,每个结点都有一个左右指针,指向其左右孩子。我们在最后也讲解了二叉树的遍历,包括前序、中序、后序以及层序遍历。只不过在遍历时实在是太麻烦了,我们需要借助栈来帮助我们完成这项遍历操作。
一棵二叉树的某些结点会存在NULL的情况,我们可以利用这些为NULL的指针,将其线索化为某一种顺序遍历的指向下一个按顺序的结点的指针,这样我们在进行遍历的时候,就会很方便了。
线索化的规则为:
- 结点的左指针,指向其当前遍历顺序的前驱结点。
- 结点的右指针,指向其当前遍历顺序的后继结点。
在线索化之后,G的指向情况如下:

这样,G原本两个为NULL的指针就被我们利用起来了,但是现在有一个问题,我们怎么知道,某个结点的指针到底是指向的其左右孩子,还是说某种遍历顺序下的前驱或是后继结点呢?所以,我们还需要分别为左右添加一个标志位,来表示左右指针到底指向的是孩子还是遍历线索:
1 | typedef char E; |
接着是H结点,同样的,因为H结点的左右指针都是NULL,那么我们也可以将其线索化:

接着我们来看结点E,这个结点只有一个右孩子,没有左孩子,左孩子指针为NULL,我们也可以将其线索化:

最后,整棵二叉树完成线索化之后,除了遍历顺序的最后一个结点没有后续之外,其他为NULL的指针都被利用起来了:

在利用上那些为NULL的指针之后,当我们再次进行前序遍历时,我们不需要再借助栈了,而是可以一路向前。
线索化
前序遍历
这里我们弄一个简单一点的线索化二叉树,来尝试对其进行遍历:

首先我们要对这棵二叉树进行线索化,将其变成一棵线索化二叉树:
1 | Node createNode(E element){ //单独写了个函数来创建结点 |
进行线索化,我们只需要正常按照对应的遍历顺序进行即可,不过在遍历过程中需要留意那些存在空指针的结点,我们需要修改其指针的指向:
1 | void preOrderThreaded(Node root){ //前序遍历线索化函数 |
首先还是老规矩,先把前序遍历写出来,然后我们需要进行判断,如果存在指针指向为NULL,那么就将其线索化:
1 | Node pre = NULL; //这里我们需要一个pre来保存后续结点的指向 |
现在我们成功得到了一棵线索化之后的二叉树,那么怎么对其进行遍历呢?我们只需要一个简单的循环就可以了:
1 | void preOrder(Node root){ //前序遍历一棵线索化二叉树非常简单 |
中序遍历
中序遍历需要重新线索化,线索化后长这样:

我们接着来看看中序遍历的线索化二叉树,整个线索化过程我们只需要稍微调整位置就行了:
1 | Node pre = NULL; //这里我们需要一个pre来保存后续结点的指向 |
那么像这样的一棵树,我们怎么对其进行遍历呢?中序遍历要稍微麻烦一些:
1 | void inOrder(Node root){ |
后序遍历
最后我们来看看后序遍历的线索化,同样的,我们只需要在线索化时修改为后序就行了:
1 | Node pre = NULL; //这里我们需要一个pre来保存后续结点的指向 |
线索化完成之后,变成一棵后续线索化二叉树:

后序遍历的结果看起来有点怪怪的,但是这就是后序,那么怎么对这棵线索化二叉树进行后续遍历呢?这就比较复杂了。因此要完成后续遍历,我们只能对结点进行改造:
1 | typedef struct TreeNode { |
现在每个结点都保存其父结点,这样就可以顺利地找上去了。现在我们来编写一下吧:
1 | Node pre = NULL; //这里我们需要一个pre来保存后续结点的指向 |
后序遍历代码如下:
1 | void postOrder(Node root){ |
这里记得在上面的createNode函数中将父节点初始化为NULL,否则可能会报错。
说些什么吧!