什么是二叉树的线索化?或者问什么是线索二叉树?
按照某种遍历方式对二叉树进行遍历,可以把二叉树中所有结点排序为一个线性序列。在改序列中,除第一个结点外每个结点有且仅有一个直接前驱结点;除最后一个结点外每一个结点有且仅有一个直接后继结点。这些指向直接前驱结点和指向直接后续结点的指针被称为线索(Thread),加了线索的二叉树称为线索二叉树。
以上是搜狗百科的一段文字,反正我是没看太懂。简单点,以我的理解,线索二叉树就是充分利用了二叉树结点中的空指针,让它们分别指向本结点的前驱或者后继。既充分利用了资源,又可以让我们方便遍历这棵树。
什么是二叉树结点的空指针?
我们先看一棵树。图中的结点3,4,6的左右指针,结点5的右指针等类似指针都为空指针。
\
什么又叫让这些空指针指向本节点的前驱或者后继呢?
这个问题分为3中情况,前序,中序,后序。例如上图中的树前序遍历序列为1,2,3,4,5,6。这样,我们就可以让结点3的左指针指向它的前驱2,结点3的右指针指向它的后继4。让结点4的左指针指向4的前驱3,让结点4的右指针指向4的后继5。结点5的左不为空,所以不操作。结点5的右为空,让结点5的右指向5的后继6。结点6的左右都为空,让结点6的左指针指向6的前驱5,因为6为最后一个元素,6的后继为空,所以让结点6的右指针指向空。
让我们把指针重置后的图画出来:
类似的,后序与中序我就不再细说,这里把图解给大家,一看便知。
我建议大家亲自动手把这3个图画一下,有奇效!!!!!!!
道理大家都懂了,那么代码该如何写呢? 答案:递归。
线索二叉树的结点定义和普通二叉树的结点定义不一样,我们额外需要两个标志_leftTag和_rightTag。
enum Type
{
THREAD,//表示指针被线索化
LINK//表示指针未被线索化
};
template<typename T>
struct BinaryTreeNode
{
T _data;
BinaryTreeNode<T> *_left;
BinaryTreeNode<T> *_right;
Type _leftTag;//标识左指针
Type _rightTag;//标识右指针
BinaryTreeNode(const T& x)//构造函数
:_data(x)
, _left(NULL)
, _right(NULL)
, _leftTag(LINK)
, _rightTag(LINK)
{}
BinaryTreeNode()//默认构造函数
{}
};
我们用Type类型的_leftTag来标识左指针,当_leftTag等于THREAD时,表明这个指针已经被线索化(例图中紫色指针)。当_leftTag等于LINK时,表明这个指针没有被线索化,是普通的二叉树指针(例图中红色指针)。
因为在线索化过程中,我们需要让当前结点的前驱指向当前结点,所以我们需要设立一个prev来保存上一次访问的结点。这个prev要么设置成全局变量,要么设置成局部静态变量,切记!
递归代码:
前序
//前序线索化
void _PrevOrder_Thd(Node* _root)
{
static Node* prev = NULL;
if (_root)
{
if (!_root->_left)
{
_root->_leftTag = THREAD;
_root->_left = prev;
}
if (prev && !prev->_right)
{
prev->_rightTag = THREAD;
prev->_right = _root;
}
prev = _root;
if (_root->_leftTag == LINK)
_PrevOrder_Thd(_root->_left);
if (_root->_rightTag == LINK)
_PrevOrder_Thd(_root->_right);
}
}
中序
void _InOrder_Thd(Node* _root)
{
static Node* prev = NULL;
if (_root)
{
if (_root->_leftTag == LINK)
{
_InOrder_Thd(_root->_left);
}
if (!_root->_left)
{
_root->_leftTag = THREAD;
_root->_left = prev;
}
if (prev && !prev->_right)
{
prev->_rightTag = THREAD;
prev->_right = _root;
}
prev = _root;
if (_root->_rightTag == LINK)
{
_InOrder_Thd(_root->_right);
}
}
}
后序
//后序线索化
void _PostOrder_Thd(Node* _root)
{
if (_root == NULL)
{
return;
}
static Node* prev = NULL;
_PostOrder_Thd(_root->_left);
_PostOrder_Thd(_root->_right);
if (!_root->_left)
{
_root->_leftTag = THREAD;
_root->_left = prev;
}
if (prev && !prev->_right)
{
prev->_rightTag = THREAD;
prev->_right = _root;
}
prev = _root;
}
最后,来说一下今年的一道面试题,如何将一个二叉树转化成一个有序的双向链表?
在你没学线索化之前,这道题可能很麻烦。但是现在不同了,利用中序线索化的思想可以很快将这道题解出来!
//利用中序线索化思想将搜索二叉树转换成有序的双向链表
void _TreeToList(Node* _root)
{
static Node* prev = NULL;//设立prev保存上一次访问的结点
if (_root == NULL)//如果根结点为空,表明树空,直接返回.
{
return;
}
_TreeToList(_root->_left);//递归左子树
_root->_left = prev;//让当前结点的左指针指向上一次访问的结点,即前驱。
if (prev)
{
prev->_right = _root;//让prev指向当前结点,构成双向
}
prev = _root;//更新prev
_TreeToList(_root->_right);//递归右子树
}