1.介绍
在二叉树中,查找某种遍历(如前中后序遍历、层次遍历)的前驱后继,有两种方法可以解决。
1.每次取前驱或者后继时,遍历整棵树,显然这是时间复杂度较大且不可取的。
2.将结点结构变为如下结构。
若结点有左子树,left指向左孩子否则指向某种遍历的前驱。
若结点有右子树,right指向右孩子,否则指向某种遍历的后继。
我们假设遍历的第一个结点前驱和最后一个结点前驱为NULL。
因为有三种遍历,前中后序遍历对应着三种线索二叉树。
分别为前序、中序、后序线索二叉树。中序线索二叉树结构如下:
假设一个普通的二叉树有n个结点,则有2n个指针。因为这n个结点只有根节点没有被指,因此有n+1个指针没有被用上。在线索二叉树这些指针就可以完全被利用。
2.线索化
将一个普通的二叉树构成线索二叉树的过程叫作线索化。
建立一个线索二叉树,我们可以用两个指针pre指向前驱结点和指针current指向当前结点来完成。
我们按照遍历方式对每个结点进行判定,判定其左右指针是否为空,为空就可以连接它的前驱和后继。
(1)中序线索二叉树的做法
做法如下:
template<class T>
void threadTree<T>::inThread()
{
if (root)//当前根节点不为空
{
node* pre = nullptr;
inThreadNode(pre, root);//进行线索化
pre->right = nullptr;//完成线索化最后一步
pre->rTag = true;
}
}
template<class T>
void threadTree<T>::inThreadNode(node* &pre,node* current)
{
if (!current)//当前指针为空就返回
return;
inThreadNode(pre, current->left);
//连接前驱
if (!current->left)//当前左指针为空
{
current->left = pre;
current->lTag = true;
}
//连接后继
if (pre && !pre->right)//pre指针存在且没有右孩子
{
pre->right = current;
pre->rTag = true;
}
pre = current;
inThreadNode(pre, current->right);
}
其实不必把上面的函数看的多复杂,把这两个if看成一个函数,如果把这个函数换成输出当前结点,这个函数就变成了中序遍历函数。
(2)先序线索二叉树的做法
线索化的过程和中序的区别只在于遍历顺序的问题,将其改成前序遍历就好。
另外一个需要注意的是转圈圈问题:
template<class T>
void threadTree<T>::inThreadNode(node* &pre,node* current)
{
if (!current)//当前指针为空就返回
return;
//连接前驱
if (!current->left)//当前左指针为空
{
current->left = pre;
current->lTag = true;
}
//连接后继
if (pre && !pre->right)//pre指针存在且没有右孩子
{
pre->right = current;
pre->rTag = true;
}
pre = current;
if(!current->lTag)//不同的地方
inThreadNode(pre, current->left);
inThreadNode(pre, current->right);
}
我们知道当先序遍历时,我们如果对一最左下个结点进行上面的函数时,它原本空的左指针已经指向了前驱,如果不加条件,就会一直转圈不出来。
(3)后序线索二叉树的做法
改为后续遍历,除了顺序不一样外,几乎和中序线索化函数相同。
template<class T>
void threadTree<T>::inThreadNode(node* &pre,node* current)
{
if (!current)//当前指针为空就返回
return;
inThreadNode(pre, current->left);
//连接前驱
if (!current->left)//当前左指针为空
{
current->left = pre;
current->lTag = true;
}
//连接后继
if (pre && !pre->right)//pre指针存在且没有右孩子
{
pre->right = current;
pre->rTag = true;
}
pre = current;
inThreadNode(pre, current->right);
}
3.查前驱后继
(1)中序前驱后继
考虑一个结点的前驱,两种情况。
1.left指向的不是左孩子,则其left是前驱,直接返回。
2.如果是左孩子,则其前驱是其左孩子的最右下孩子的结点
考虑一个结点的后继,两种情况。
1.right指向的不是右孩子,则其right指向的是后继,直接返回。
2.如果是右孩子,则其前驱是其右孩子的最左下孩子的结点
总的:
#pragma once
#include<iostream>
template<class T>
class threadTree
{
public:
struct node
{
T data;
node* left;
node* right;
bool lTag;
bool rTag;
node(const T& d=0, node* l = nullptr, node* r = nullptr, bool lT = false, bool rT = false)
:data(d),left(l),right(r),lTag(lT),rTag(rT){}
};
node* root;
threadTree() :root(nullptr) {}
~threadTree() { clear(root); }
void inThread();//将二叉树中序线索化
node* last(node* current);//求前驱
node* next(node* current);//求后继
void inOrder(node* p);//遍历
private:
void clear(node* root);//删除root树的所有结点
void inThreadNode(node*& pre, node* current);//判断是否可以连接前驱后继,如果可以则连接
};
template<class T>
void threadTree<T>::clear(node* root)
{
node* pre = root;
node* p = next(pre);
while(p)
{
delete pre;
pre = p;
p = next(pre);
}
}
template<class T>
void threadTree<T>::inThread()
{
if (root)
{
node* pre = nullptr;
inThreadNode(pre, root);
pre->right = nullptr;
pre->rTag = true;
}
}
template<class T>
void threadTree<T>::inThreadNode(node* &pre,node* current)
{
if (!current)
return;
inThreadNode(pre, current->left);
if (!current->left)
{
current->left = pre;
current->lTag = true;
}
if (pre && !pre->right)
{
pre->right = current;
pre->rTag = true;
}
pre = current;
inThreadNode(pre, current->right);
}
template<class T>
typename threadTree<T>::node* threadTree<T>::last(node* current)
{
//前驱两种情况,一种left指针所指对象就是前驱,一种就是左子树的最右下结点
node* p = current->left;
if (!current->lTag)
{
while (!p->rTag)
p = p->right;
}
return p;
}
template<class T>
typename threadTree<T>::node* threadTree<T>::next(node* current)
{
node* p = current->right;
if (!current->rTag)
{
while (!p ->lTag)
p = p->left;
}
return p;
}
template<class T>
void threadTree<T>::inOrder(node* p)
{
if (!p)
return;
if (p->left)
inOrder(p->left);
std::cout << p->data << " ";
if (p->right)
inOrder(p->right);
}