线索二叉树的引入
二叉树的结构特点会导致有大量的指针域空间的浪费,并且在二叉树的遍历时,每一次要想知道该结点的前驱和后继都需要进行一次遍历,这样会导致空间和时间上的浪费。因此,我们利用浪费的指针域,引入线索二叉树,同时解决上述两个问题。
线索二叉树的原理
初步想法是在二叉树中,我们利用没有指向孩子的指针域,使其指向该结点的前驱或后继。并且规定,原本指向左孩子的指针域负责指向前驱结点,原本指向右孩子的指针域负责指向后继结点。
为了方便记忆,我们可以把二叉树按照某种遍历次序想象成一个线性表,从左到右就是从前到后,方便记忆前驱和后继的指针位置。
但是,这样的方式会导致我们无法得知,在指针域中究竟指向的是该结点的孩子结点还是指向前驱或后继。为此,我们需要稍加修改结点的结构,在其中加入两个域,分别为 ltag 和 rtag,用于分辨上述问题。
数值 | 1 | 0 |
---|---|---|
rtag | 后继结点 | 右孩子结点 |
ltag | 前驱结点 | 左孩子结点 |
线索存储结点的结构定义代码如下
1 | typedef enum {Link,Thread} Pointertag; //Link==0,表示指向左右孩子的指针; |
线索二叉树的线索化
线索化就是对二叉树以某种次序的遍历使其变成线索二叉树,其过程就是在遍历的过程中修改空白指针域的值
线索化的递归函数代码实现如下(以中序为例)
1 | BiThrTree pre; //全局变量,始终指向刚刚访问过的结点 |
通过上述代码,我们不难发现,线索化的递归代码,与之前的中序遍历的递归代码结构完全相同,就只是改变了对结点进行的访问操作。
线索二叉树的遍历
既然提出了线索二叉树是为了解决二叉树的遍历问题,那么我们一起看看实现遍历的代码究竟是怎样的。
对线索二叉树的遍历,其实可以看成是对一个双向链表的遍历,我们使一个头结点指向线索二叉树的根结点。头结点的rchild指向树的根节点,lchild指向
线索二叉树的遍历代码
1 | Status InOrderTraverse_Thr (BiThrTree T) //T即为二叉树的头结点 |
这段代码的就是进行了一个链表的扫描,所以它的时间复杂度是0(n)。
线索二叉树的应用
当一个二叉树需要进行遍历或是查找结点在某种次序遍历中的前驱或后继,那么就可以使用线索二叉树的存储结构。