线索化:
1. 线索化的目的
对于二叉树的任意一种遍历(前序遍历,中序遍历,后序遍历)都存在反向递归的情况,也就是说在我们查找到子节点的时候,就需要回退到上一个访问过的节点去寻找未被访问的节点,没有就继续反向递归,不能像正常的链表或者数组那样,存在某种规则可以直接询问下一个节点或者上一个节点,所以对于这种情况,我们就需要对二叉树进行线索化,那么线索化后的二叉树,就能像链表或者数组那样,存在某种规则,可以直接访问下一个节点或者上一个节点,便于我们遍历。
2.线索二叉树
任意一棵拥有 N 个节点的单向二叉树,都会右 2N 个指针域(一个节点2个指针域,分别指向左孩子和右孩子),拥有 N - 1 个分支线,每个分支线指向一个使用的指针域,那么空指针域的个数为: 2N - (N - 1) = N + 1 ,那么就意味着每 N 个节点就有 N+1 个空指针域是浪费掉的,所以这些空掉的指针域正好拿来帮助我们对二叉树进行线索化。
线索二叉树
等于是把一棵二叉树变成了一个双向链表,这样的链表对于二叉树节点的插入,删除,查找都带来了方便。
对二叉树以某种次序遍历使其变成线索二叉树的过程称作 线索化
。
备注:二叉树的线索化是针对某种次序来讲的,比如,前序遍历 有前序遍历的线索化,中序遍历 有中序遍历的线索化,二者不能混合使用。
意义:线索化避免了这棵树遍历时的递归回旋问题,使用一个循环一口气就可以将这棵树访问完毕,可以这么理解,树的本身访问到了叶子节点就出现了断点问题,就要开始递归回旋判断,然而我们使用了线索化以后就相当于将断点问题解决,这就是一条可以直达终点的通路。
能够对这颗树进行非递归遍历;能够给这棵树加上迭代器;
3.线索化的理解
可以用两句话概括分析:
A. 如果当前节点没有左孩子,就让左孩子指向父亲,同时让其节点类型变成THREAD;
B. 如果父亲节点没有右孩子,就让其右孩子指向当前节点,同时让其节点类型变成THREAD。
这里以前序遍历为例:
这里以中序遍历为例:
以上两个例子可以看出,二叉树的前驱节点和后继节点一定和固定的某个遍历顺序有关,不同遍历顺序,同一个节点的前驱和后继节点不同。
根据上面分析,再看下面两个例子的图,就明白图中的箭头指向的含义了:
先序线索化如下图:(根左右)
中序线索化如下图:(左根右)
4.节点结构
以下是线索化二叉树的节点数据结构,定义了一个 枚举类型 ThreadType 表示指针域的指向类型。
//线索化指针域类型
enum pointType
{
LINK = 1, //链式类型,表示指向当前节点的(左右)孩子
THREAD = 2, //线索化类型,表示当前节点没有孩子,需要指向自己的前驱或者后继
};
//线索化二叉树节点类型
template <class T>
struct TreeNodeThread
{
T _data; //值类型
//左侧指针域,Link下指向自己的左孩子,Thread下指向自己的前驱节点
TreeNodeThread<T>* _leftChild;
//右侧指针域,Link下指向自己的右孩子,Thread下指向自己的后继节点
TreeNodeThread<T>* _rightChild;
pointType _left; //左侧指针域类型
pointType _right; //右侧指针域类型
TreeNodeThread(const T& data)
:_data(data), _leftChild(NULL), _rightChild(NULL)
, _left(LINK), _right(LINK)
{}
};
5.代码
代码示例:
enum pointType
{
LINK,THREAD
};
template <class T>
struct TreeNodeThread
{
T _data;
TreeNodeThread<T>* _leftChild;
TreeNodeThread<T>* _rightChild;
pointType _left;
pointType _right;
TreeNodeThread(const T& data)
:_data(data),_leftChild(NULL),_rightChild(NULL)
,_left(LINK),_right(LINK)
{}
};
template <class T>
class BinaryTree
{
typedef TreeNodeThread<T> Node;
protected:
Node* _root;
public:
BinaryTree()
:_root(NULL)
{}
BinaryTree(T* arr, size_t n, const T& invalid)
{
int a = 0;
_root = CreateTree(arr, n, invalid, a);
}
BinaryTree(const BinaryTree<T>& t)//拷贝构造函数
{
_root = _copy(t._root);
}
BinaryTree<T>& operator=(const BinaryTree<T>& t)//赋值运算符重载
{
if(&t != this)//两个对象的地址相比较
{
Destory(_root);
_root = _copy(t._root);
}
return *this;
}
Node* _copy(Node* root)
{
if(NULL == root)
return NULL;
Node* cur = new Node(root->_data);
cur->_leftChild = _copy(root->_leftChild);
cur->_rightChild = _copy(root->_rightChild);
return cur;
}
//线索化访问二叉树
//先序线索化-------根左右
//当前节点左孩子没有就指向父亲,父亲的右孩子没有就指向下一个节点(即当前节点)
void PreOrderThread()
{
Node* prev = NULL;
_PreOrderThread(_root,prev);
}
void _PreOrderThread(Node* root, Node*& prev)
{
if(NULL == root)
return;
if(root->_leftChild == NULL)
{
root->_leftChild = prev;
root->_left = THREAD;
}
if(prev && prev->_rightChild == NULL)
{
prev->_rightChild = root;
prev->_right = THREAD;
}
prev = root;
if(root->_left == LINK)
{
_PreOrderThread(root->_leftChild,prev);
}
if(root->_right == LINK)//如果父节点没有右孩子,那么此时不判断是否线索化的话,父节点的右孩子也指向左孩子,这样会再次执行左孩子的过程
{
_PreOrderThread(root->_rightChild,prev);
}
}
void showPreOrderThread()
{
cout<<"先序遍历线索化:"<<endl;
_showPreOrderThread(_root);
cout<<endl;
}
void _showPreOrderThread(Node* root)//先序--根左右
{
if(root == NULL)
return;
Node* cur = root;
while(cur)
{
while(cur->_left == LINK)
{
cout<<cur->_data<<" ";
cur = cur->_leftChild;
}
cout<<cur->_data<<" ";
cur = cur->_rightChild;
}
}
//中序线索化-------左根右
void MidOrderThread()
{
Node* prev = NULL;
_MidOrderThread(_root,prev);
}
void _MidOrderThread(Node* root, Node*& prev)
{
if(NULL == root)
return;
_MidOrderThread(root->_leftChild,prev);
if(root->_leftChild == NULL)
{
root->_leftChild = prev;
root->_left = THREAD;
}
if(prev && prev->_rightChild == NULL)
{
prev->_rightChild = root;
prev->_right = THREAD;
}
prev = root;
_MidOrderThread(root->_rightChild,prev);//为什么这时候不用判断是否是线索化,因为路线是左根右,不会出现回退现象,即将访问的孩子和我的父亲没有直接关系
}
void showMidOrderThread()
{
cout<<"中序遍历线索化:"<<endl;
_showMidOrderThread(_root);
cout<<endl;
}
void _showMidOrderThread(Node* root)//左根右
{
if(NULL == root)
return;
Node* cur = root;
while(cur)
{
while(cur->_left == LINK)
{
cur = cur->_leftChild;
}
cout<<cur->_data<<" ";
while(cur->_right == THREAD)
{
cur = cur->_rightChild;
cout<<cur->_data<<" ";
}
cur = cur->_rightChild;
}
}
//层序遍历
void LevelOrder()
{
cout<<"层序遍历:"<<endl;
_LevelOrder(_root);
cout<<endl;
}
void _LevelOrder(Node* root)
{
assert(root);
queue<Node*> q;//先进先出
q.push(root);
while(!q.empty())
{
Node* cur = q.front();
cout<<cur->_data<<" ";
if(cur->_leftChild != NULL)
{
q.push(cur->_leftChild);
}
if(cur->_rightChild != NULL)
{
q.push(cur->_rightChild);
}
q.pop();
}
}
};
迭代器:我们采用中序遍历的方式来设计迭代器:
//在类BinaryTree中添加以下代码:
typedef TreeIterator<T,T&,T*> Iterator;
//迭代器------中序遍历
Iterator Begin()
{
if(_root == NULL)
return NULL;
Node* cur = _root;
while(cur->_left == LINK)
{
cur = cur->_leftChild;
}
return Iterator(cur);
}
Iterator End()
{
return NULL;
}
//中序遍历迭代器----左根右
template <class T,class Ref,class Ptr>
class TreeIterator
{
typedef TreeNodeThread<T> Node;
typedef TreeIterator<T,Ref,Ptr> Self;
protected:
Node* _node;
public:
TreeIterator(Node* node)
:_node(node)
{}
Self& operator++()//前置++
{
if(_node->_right == THREAD)
{
_node = _node->_rightChild;
}
else
{
Node* cur = _node->_rightChild;
while(cur && cur->_left == LINK)
{
cur = cur->_leftChild;
}
_node = cur;
}
return *this;
}
Self operator++(int)//后置++
{
Self cur(_node);
++*this;
return cur;
}
Self& operator--()//前置--
{
if(_node->_left == THREAD)
{
_node = _node->_leftChild;
}
else
{
Node* cur = _node->_leftChild;
while(cur && cur->_right == LINK)
{
cur = cur->_rightChild;
}
_node = cur;
}
return *this;
}
Self operator--(int)//后置--
{
Self cur(_node);
--*this;
return cur;
}
Ref operator*()
{
return _node->_data;
}
bool operator!=(const Self& t)
{
return _node != t._node;
}
};