线索化二叉树
线索化:n个结点的二叉链表中含有n+1(2n-(n-1)=n+1)个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为”线索”)(摘自百度百科)
二叉树的遍历本质:将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。现将二叉树的结点结构重新定义如下:
enum PointTag
{
THREAD,
LINK
};
template <class T>
struct BinaryThreadTreeNode
{
T _data;
BinaryThreadTreeNode<T>* _left;
BinaryThreadTreeNode<T>* _right;
BinaryThreadTreeNode<T>* _parent; //为后序遍历特设指针
PointTag _leftTag;
PointTag _rightTag;
BinaryThreadTreeNode(const T& x)
: _data(x)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
, _leftTag(LINK)
, _rightTag(LINK)
{}
};
- 通过枚举常量 LINK,THREAD 来标志当前节点的左右是链式还是线索,如果是链式,则我们只能访问其左右孩子;若是线索化,则我们可以通过先所来访问当前节点的前驱和后继
为了方便我们下边的说明,我们先定义创建一颗二叉树:
BinaryThreadTree()
:_root(NULL)
{}
BinaryThreadTree(const T* arr,size_t n,const T& invalid)
{
size_t index = 0;
//parent节点是为后序线索遍历特设的,先序和中序可以暂时不考虑这个
Node* parent = NULL;
_root = _CreateTree(arr, n, invalid, index, parent);
}
Node* _CreateTree(const T* arr, size_t n, const T& invalid, size_t& index,Node*& parent)
{
Node* root = NULL;
if (index < n && arr[index] != invalid)
{
root = new Node(arr[index]);
root->_parent = parent;
root->_left = _CreateTree(arr, n, invalid, ++index,root);
root->_right = _CreateTree(arr, n, invalid, ++index,root);
}
return root;
}
先序线索化
上述二叉树先序遍历节点的顺序是:1-2-3-4-5-6
我们说过线索化,是利用二叉链表的空指针域来存放该节点的前驱和后继信息的,所以我们首先要找到有空指针域的节点;对于二叉树的线索化来说,我们重点考虑的是如何找到它的前驱节点,因为后继节点可以在遍历二叉树的过程中获得;
- 首先从根一直往左找,找到左孩子为空的节点,将该节点(cur)的左记为 prev ,prev用来记录当前节点的前驱节点
- 该节点的后继节点我们要在遍历到下一个节点时才可以和当前节点进行连接(这点大家好好想一下),此时,我们就要考虑上一个节点prev的情况了,如果 prev 不为空并且它的右孩子为空,那么上一个节点就是有后继节点的,此时我们就可以将上一个节点的有与当前节点进行链接,此时 prev 的左右线索才链接完成
- 接下来处理下一个左孩子为空的节点,此时,我们将 prev = cur,然后重复上面的步骤
- 我们通过代码实现一下上述过程:
void PrevThread()
{
Node* prev = NULL;
_PrevThread(_root, prev);
if (prev)
prev->_rightTag = THREAD;
}
void _PrevThread(Node* cur, Node*& prev)
{
if (cur == NULL)
return;
//prev用来记录前驱节点
if (cur->_left == NULL)
{
cur->_leftTag = THREAD;
cur->_left = prev;
}
if (prev != NULL && prev->_right == NULL)
{
prev->_right = cur;
prev->_rightTag = THREAD;
}
prev = cur;
if (cur->_leftTag == LINK)
_PrevThread(cur->_left, prev);
if (cur->_rightTag == LINK)
_PrevThread(cur->_right, prev);
}
- 我们可以通过监视窗口来查看一下我们先序线索化的结果是否正确:
- 通过监视窗口与先序线索化图相互对比,结果一致
中序线索化
中序遍历顺序为:3-2-4-1-6-5
和先序线索化的思路大致一样,我们也是先考虑前驱节点,后继节点将会在遍历到下一个节点是进行链接
- 中序线索化我们有一个不同于先序的处理,因为中序的遍历顺序为 左-根-右,所以我们还需要对最后一个节点进行线索化处理
- 代码实现:
void InThread()
{
Node* prev = NULL;
_InThread(_root, prev);
//线索化最后一个节点
if (_root)
{
Node* tmp = _root->_right;
while (tmp->_right)
{
tmp = tmp->_right;
}
tmp->_rightTag = THREAD;
}
}
void _InThread(Node* cur, Node*& prev)
{
if (cur == NULL)
return;
_InThread(cur->_left, prev);
if (cur->_left == NULL)
{
cur->_leftTag = THREAD;
cur->_left = prev;
}
if (prev != NULL && prev->_right == NULL)
{
prev->_rightTag = THREAD;
prev->_right = cur;
}
prev = cur;
_InThread(cur->_right, prev);
}
- 通过监视窗口来查看中序线索化是否正确:
- 通过监视窗口与中序线索化图相互对比,结果一致
后序线索化
后序遍历的循序为:3-4-2-6-5-1
线索化思路同上所述
代码实现:
void PostThread()
{
Node* prev = NULL;
_PostThread(_root, prev);
}
void _PostThread(Node* cur, Node*& prev)
{
if (cur == NULL)
return;
_PostThread(cur->_left, prev);
_PostThread(cur->_right, prev);
if (cur->_left == NULL)
{
cur->_left = prev;
cur->_leftTag = THREAD;
}
if (prev != NULL && prev->_right == NULL)
{
prev->_right = cur;
prev->_rightTag = THREAD;
}
prev = cur;
}
通过监视窗口来查看中序线索化是否正确:
- 通过监视窗口与后序线索化图相互对比,结果一致
线索化二叉树的遍历
先序遍历
先序遍历顺序为 左-根-右
- 从根节点依次访问左节点,直到某一个节点的左标志为 THREAD;
- 此时,若该节点的右标志位为 THREAD ,则我们可以通过线索来依次访问到该节点的后继节点
- 否则就一直访问该节点的右节点直到其某一个节点的右标志为 THREAD;
代码实现:
void PrevOrder()
{
Node* cur = _root;
while (cur)
{
while (cur->_leftTag == LINK)
{
cout << cur->_data << " ";
cur = cur->_left;
}//最左节点
cout << cur->_data << " ";
if (cur->_rightTag == LINK)
cur = cur->_right;
else
{
while (cur->_rightTag == THREAD)
{
cur = cur->_right;
if (cur == NULL)
break;
cout << cur->_data << " ";
}
if (cur != NULL)
cur = cur->_right;
}
}
cout << endl;
}
中序遍历
中序遍历的顺序为:左-根-右
- 从根节点依次遍历左节点,直到某一个节点的左标志为 THREAD,访问该节点
- 此时依次遍历该节点的右节点,直到某一个节点的标志位为 THREAD ,则我们可以通过线索来依次访问到该节点的后继节点
代码实现:
void InOrder()
{
Node* cur = _root;
while (cur)
{
while (cur->_leftTag == LINK)
{
cur = cur->_left;
}
cout << cur->_data << " ";
if (cur->_rightTag == LINK)
cur = cur->_right;
else
{
while (cur->_rightTag == THREAD && cur)
{
cur = cur->_right;
if (cur == NULL)
break;
cout << cur->_data << " ";
}
if (cur)
cur = cur->_right;
}
}
cout << endl;
}
后序遍历
后序遍历的顺序为:左-右-根
- 根据后序遍历的顺序,我们从根开始遍历左节点(在遍历的同时,我们需要记住每一个节点的前一个节点prev),找到后序遍历的起点,此时该节点的左标志位为 THREAD
- 若该节点的右标志位为 THREAD,则我们可以通过线索来访问它的后继元素
- 在通过线索访问后继元素的过程中若某一个节点无后继元素(两种情况:1.该树无右子树,则给节点即为根节点;2.有右子树但还没有到达根节点,此时需要找到根节点进行访问),则判断是否已经到达根节点,如果到达根节点则遍历结束
- 若没有到达根节点,则继续寻找该节点的父节点,依次向上寻找直到根节点,继续判断根节点是不是存在右子树(用标志位判断而不是判空)
代码实现:
void PostOrder()
{
Node* cur = _root;
Node* prev = NULL;
while (cur)
{
while (cur->_left != prev && cur->_leftTag == LINK)
{
cur = cur->_left;
}
while (cur != NULL && cur->_rightTag == THREAD)
{
cout << cur->_data << " ";
prev = cur;
cur = cur->_right;
}
if (cur == _root)
{
cout << cur->_data << " ";
cout << endl;
return;
}
while (cur != NULL && cur->_right == prev)
{
cout << cur->_data << " ";
prev = cur;
cur = cur->_parent;
}
if (cur != NULL && cur->_rightTag == LINK)
{
cur = cur->_right;
}
}
}
线索化二叉树迭代器的实现
先序迭代器
template <class T>
struct BinaryThreadTreeIterator
{
typedef BinaryThreadTreeNode<T> Node;
typedef BinaryThreadTreeIterator<T> Self;
Node* _node;
BinaryThreadTreeIterator(Node* root)
:_node(root)
{}
BinaryThreadTreeIterator()
{}
Self& operator++()
{
if (_node->_leftTag == LINK)
_node = _node->_left;
else
_node = _node->_right;
return *this;
}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
Begin(),End():
typedef BinaryThreadTreeIterator<T> Iterator;
Iterator Begin()
{
return Iterator(_root);
}
Iterator End()
{
Node* cur = _root;
while (cur)
cur = cur->_right;
return Iterator(cur);
}
中序迭代器
除了++操作其余同先序,在此不再赘述
Self& operator++()
{
if (_node->_rightTag == THREAD)
_node = _node->_right;
else
{
Node* tmp = _node->_right;
while (tmp->_leftTag == LINK)
{
tmp = tmp->_left;
}
_node = tmp;
}
return *this;
}
Begin(),End():
Iterator Begin()
{
Node* cur = _root;
while (cur->_leftTag)
cur = cur->_left;
return Iterator(cur);
}
Iterator End()
{
Node* cur = _root;
while (cur != NULL)
{
cur = cur->_right;
}
return Iterator(cur);
}
部分测试代码:
void BinaryThreadTreeTest()
{
int arr[] = { 1, 2, 3, '#', '#', 4, '#', '#', 5, 6 };
BinaryThreadTree<int> tree(arr, sizeof(arr) / sizeof(arr[0]), '#');
//tree.PrevThread();
//tree.PrevOrder();
//tree.InThread();
//tree.InOrder();
tree.PostThread();
//tree.PostOrder();
BinaryThreadTree<int>::Iterator it;
it = tree.Begin();
BinaryThreadTree<int>::Iterator* tmp = ⁢
int ret = *it;
for (it = tree.Begin(); it != tree.End(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
完整的代码大家可以访问我的GitHub进行下载查看:https://github.com/l-cong/code