中序线索二叉树
在 n 个结点的二叉链表中,有(n+1)个空指针域,利用这些空指针域来存放当前结点的直接前驱和后继的线索,可以加快查找速度。
普通二叉树只能找到结点的左右孩子信息,而结点的直接前驱和直接后继只能在遍历中获得,若将遍历后对应的前驱和后继预存起来,从第一个结点开始就可以顺藤摸瓜而遍历整棵树,树实际变成了线性序列。
例如中序遍历的结果实际上就是把二叉树转化成了线性排列。
线索二叉树的结构声明
typedef struct node{
struct node* left;
int ltag = 0;
char val;
int rtag = 0;
struct node* right;
}TreeNode,*Tree;
通过中序遍历建立中序线索二叉树
一个 pre 指针一直指向 t 遍历的前一个结点,每次遍历到一个结点,如果可以做线索的话,刚好做的是 t 的左线索和 pre 的右线索。这样的话当 t 遍历完时候 pre 的右线索还没有处理,此时 pre 指向遍历完之后的最后一个结点,我们给 pre 的右线索置 1 收尾。
void InThread(Tree& t,TreeNode* &pre){
//pre指针指向t的中序前驱,在主函数中预设为NULL
if(t != NULL){
InThread(t->left,pre); //左子树线索化
if(t->left == NULL){ //建立当前结点的前驱线索
t->left = pre;
t->ltag = 1;
}
if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索
pre->right = t;
pre->rtag = 1;
}
pre = t;
InThread(t->right,pre); //右子树线索化
}
}
//通过中序遍历建立中序线索二叉树
void CreateThread(Tree& t){
TreeNode* pre = NULL; //前驱指针
if(t != NULL){
InThread(t,pre);
pre->rtag = 1; //最后一个结点右标志改为1
}
}
中序序列下的后继结点
中序遍历的过程需要不停寻找下一个结点,给定一个结点 p ,如果p的 rtag == 1,那么p的右孩子就是p的后继结点,如果 p 有右孩子,即 rtag == 0,那么p的后继结点就是以 p->right 为根的二叉树的中序序列下的第一个结点,也就是最左边的结点。
//返回结点p在线索二叉树中的中序序列下的后继结点
TreeNode* successor(TreeNode* p){
if(p->rtag == 1) return p->right;
p = p->right;
while(p->ltag == 0) p = p->left;
return p; //最左下结点
}
中序遍历中序线索二叉树
现在我们可以开始中序遍历中序线索二叉树,先找到第一个结点,再一直寻找它的后继结点。
//中序线索二叉树的中序遍历
void Inorder(Tree& t){
TreeNode* p = t;
while(p->ltag == 0) p = p->left;
for(p;p != NULL;p = successor(p))
cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<" ";
}
中序序列下的前驱结点
给定一个结点 p ,如果寻找 p 的前驱结点呢,如果 p->ltag == 1,那么直接返回 p->left ,如果 p 有左孩子,那么 p 的前驱就是p的左子树最右边的结点。
//返回结点p在线索二叉树中的中序序列下的前驱结点
TreeNode* predecessor(TreeNode* p){
if(p->ltag == 1) return p->left;
p = p->left;
while(p->rtag == 0) p = p->right; //找第一个没有右孩子的结点
return p;
}
中序线索二叉树的层次遍历
因为线索二叉树的叶子节点几乎没有了空指针,所以进队的条件应该做修改,用 ltag、rtag 来判断。
//层次遍历
void levelOrderTraverse(Tree& t){
if(t == NULL) return;
queue<TreeNode*> q;
TreeNode* p;
q.push(t);
while(!q.empty()){
int width = q.size();
for(int i = 0;i < width;i ++){
p = q.front();
q.pop();
cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<" ";
if(p->ltag == 0) q.push(p->left);
if(p->rtag == 0) q.push(p->right);
}
cout<<endl;
}
}
树还是那棵老树,完整代码如下:
#include<iostream>
#include<queue>
using namespace std;
typedef struct node{
struct node* left;
int ltag = 0;
char val;
int rtag = 0;
struct node* right;
}TreeNode,*Tree;
void InThread(Tree& t,TreeNode* &pre){
//pre指针指向t的中序前驱,在主函数中预设为NULL
if(t != NULL){
InThread(t->left,pre); //左子树线索化
if(t->left == NULL){ //建立当前结点的前驱线索
t->left = pre;
t->ltag = 1;
}
if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索
pre->right = t;
pre->rtag = 1;
}
pre = t;
InThread(t->right,pre); //右子树线索化
}
}
//通过中序遍历建立中序线索二叉树
void CreateThread(Tree& t){
TreeNode* pre = NULL; //前驱指针
if(t != NULL){
InThread(t,pre);
pre->rtag = 1; //最后一个结点右标志改为1
}
}
//返回结点p在线索二叉树中的中序序列下的后继结点
TreeNode* successor(TreeNode* p){
if(p->rtag == 1) return p->right;
p = p->right;
while(p->ltag == 0) p = p->left;
return p; //最左下结点
}
//中序线索二叉树的中序遍历
void Inorder(Tree& t){
TreeNode* p = t;
while(p->ltag == 0) p = p->left;
for(p;p != NULL;p = successor(p))
cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<" ";
}
//返回结点p在线索二叉树中的中序序列下的前驱结点
TreeNode* predecessor(TreeNode* p){
if(p->ltag == 1) return p->left;
p = p->left;
while(p->rtag == 0) p = p->right; //找第一个没有右孩子的结点
return p;
}
//层次遍历
void levelOrderTraverse(Tree& t){
if(t == NULL) return;
queue<TreeNode*> q;
TreeNode* p;
q.push(t);
while(!q.empty()){
int width = q.size();
for(int i = 0;i < width;i ++){
p = q.front();
q.pop();
cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<" ";
if(p->ltag == 0) q.push(p->left);
if(p->rtag == 0) q.push(p->right);
}
cout<<endl;
}
}
//先序构造二叉树
void CreateTree(Tree& t){
char x;
cin>>x;
if(x == '#') t = NULL;
else{
t = new TreeNode;
t->val = x;
CreateTree(t->left);
CreateTree(t->right);
}
}
int main(){
Tree t;
CreateTree(t);
/*
a b d # # e # # c f # # #
*/
CreateThread(t);
cout<<"层次遍历:"<<endl;
levelOrderTraverse(t);
cout<<"中序遍历:"<<endl;
Inorder(t);
cout<<endl;
TreeNode*p;
cout<<"t的后继结点:"<<endl;
p = successor(t);
cout<<p->val<<endl;
cout<<"t的前驱结点:"<<endl;
p = predecessor(t);
cout<<p->val<<endl;
}
运行结果:
前序线索二叉树和后序线索二叉树
二叉树前序线索化和后序线索化的方式与中序线索化相似,只是区别在线索化的时机。
前序线索化需要对最后一个结点进行收尾处理,标志它为线索,而后序线索二叉树不需要,因为它的最后一个遍历结点是根。
后序线索二叉树不能有效解决寻找后继
在后序线索二叉树中查找结点 *p 的前驱:若结点 *p 无左子树,则 p->left 指向其前驱;否则,若结点 *p 有左子树,当其右子树为空时,其左子树的根(即 p->left )为其后序前驱。当其右子树非空时,其右子树的根(即 p->right )为其后序前驱。
在后序线索二叉树中查找结点 *p 的后继:若结点 *p 为根,则无后继;若结点 *p 为其双亲的右孩子,则其后继为其双亲;若结点*p 为其双亲的左孩子,且双亲无右子女,则其后继为其双亲;若结点 *p 为其双亲的左孩子,且双亲有右子女,则结点 *p 的后继是其双亲的右子树中按后序遍历的第一个结点。所以,求后序线索二叉树中结点的后继要知道其双亲的信息,要使用栈,所以说后序线索二叉树是不完善的。
先序线索二叉树不能有效解决寻找前驱
在先序线索二叉树中查找结点的后继较容易,而查找前驱要知道其双亲的信息,要使用栈,所以说先序线索二叉树也是不完善的。