之前我们介绍了中序线索二叉树的构造、遍历、寻找前趋和寻找后继。中序线索二叉树既可以寻找前趋结点也可以寻找后继结点。
今天我们再来看一下先序线索二叉树的构造:
首先是结构声明:
我们将左线索和右线索初始化为0。
/*
结构声明
*/
typedef struct node{
struct node* left;
int ltag = 0;
char val;
int rtag = 0;
struct node* right;
}TreeNode,*Tree;
先序构造线索二叉树的递归代码:
这里和中序线索二叉树做对比,不仅仅是把对线索的处理移到了遍历左右子树之前,并且在遍历左右子树的时候加上了tag==0的判断,这是因为当先序遍历时处理完孩子结点之后又会返回到根节点,如果不判断它是否具有线索的话会再次回到孩子结点。
void PreThread(Tree& t,TreeNode* &pre){
//pre指针指向t的先序前驱,在主函数中预设为NULL
if(t != NULL){
if(t->left == NULL){ //建立当前结点的前驱线索
t->left = pre;
t->ltag = 1;
}
if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索
pre->right = t;
pre->rtag = 1;
}
pre = t;
if(t->ltag == 0)
PreThread(t->left,pre); //左子树线索化
if(t->rtag == 0)
PreThread(t->right,pre); //右子树线索化
}
}
继续包装一下递归函数,因为最后一个pre结点也需要收尾(与中序线索二叉树的收尾相同)。
/*
通过先序遍历建立先序线索二叉树
*/
void CreateThread(Tree& t){
TreeNode* pre = NULL; //前驱指针
if(t != NULL){
PreThread(t,pre);
pre->rtag = 1; //最后一个结点右标志改为1
}
}
先序线索二叉树访问后继:
/*
返回结点p在线索二叉树中的先序序列下的后继结点
如果有左孩子,则后继是左孩子,如果没有左孩子,则后继是右孩子。
*/
TreeNode* successor(TreeNode* p){
if(p->ltag == 0) return p->left;
else return p->right;
}
先序线索二叉树不能很好的解决寻找前驱,例如不能通过b找到a。
建立好先序线索二叉树之后,我们就可以根据线索和左右孩子指针以线性的时间来先序遍历先序线索二叉树了。
/*
先序线索二叉树的先序遍历
先找到第一个结点,再一直寻找它的后继结点。
*/
void PreOrder(Tree& t){
TreeNode* p = t;
for(p;p != NULL;p = successor(p))
cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<" ";
}
我们可以层次遍历一下,看看树打上标记后的样子。
/*
层次遍历
因为线索二叉树的叶子节点几乎没有了空指针,所以进队的条件应该做修改,用 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 PreThread(Tree& t,TreeNode* &pre){
//pre指针指向t的中序前驱,在主函数中预设为NULL
if(t != NULL){
if(t->left == NULL){ //建立当前结点的前驱线索
t->left = pre;
t->ltag = 1;
}
if(pre && pre->right == NULL){ //建立前驱结点pre的后继线索
pre->right = t;
pre->rtag = 1;
}
pre = t;
if(t->ltag == 0)
PreThread(t->left,pre); //左子树线索化
if(t->rtag == 0)
PreThread(t->right,pre); //右子树线索化
}
}
/*
通过先序遍历建立先序线索二叉树
*/
void CreateThread(Tree& t){
TreeNode* pre = NULL; //前驱指针
if(t != NULL){
PreThread(t,pre);
pre->rtag = 1; //最后一个结点右标志改为1
}
}
/*
返回结点p在线索二叉树中的先序序列下的后继结点
如果有左孩子,则后继是左孩子,如果没有左孩子,则后继是右孩子。
*/
TreeNode* successor(TreeNode* p){
if(p->ltag == 0) return p->left;
else return p->right;
}
/*
先序线索二叉树不能很好的解决寻找前驱,例如不能通过b找到a。
*/
/*
先序线索二叉树的先序遍历
先找到第一个结点,再一直寻找它的后继结点。
*/
void PreOrder(Tree& t){
TreeNode* p = t;
for(p;p != NULL;p = successor(p))
cout<<p->ltag<<" "<<p->val<<" "<<p->rtag<<" ";
}
/*
层次遍历
因为线索二叉树的叶子节点几乎没有了空指针,所以进队的条件应该做修改,用 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;
}
}
/*
先序构造二叉树
*/
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;
PreOrder(t);
cout<<endl;
cout<<"层次遍历:"<<endl;
levelOrderTraverse(t);
}
程序运行结果:
更多代码请参考:手撕考研数据结构(代码汇总)