1.中序线索二叉树
数据结构:
typedef struct Node{ struct Node* l=NULL; struct Node* r=NULL; int lt=0,rt=0;//left tag, right tag,如果为 1 表示左(右)结点不存在,为前驱(后继) ElemType data; Node(ElemType data=0):data(data){ } }Node;
首先理解如何建立中序线索化二叉树。如果结点的左子树存在,lt为0 。不存在为1;右子树同理。直接对根节点进行中序遍历,在不存在的场合设置标志位和前驱、后继。
把二叉树看成中序遍历序列,序列的第一个结点(最左下结点)的前驱为NULL,最后一个结点(最右下结点)的后继为NULL。通过设置pre的初始化为NULL和最后对pre(指向最后一个结点)的后继(右子树)设置为NULL来实现。
建立中序线索化二叉树代码:(注释为坑点)
void InThread(Node* p,Node* &pre){ if(p) { InThread(p->l,pre); if(p->l==NULL){ //左结点不存在 p->lt=1; //设置 p 的前驱 p->l=pre; } //注意加上pre非空的短路判断条件,避免空指针操作 if(pre!=NULL && pre->r==NULL){ //右结点不存在 pre->r=p; //设置 pre 的后继 pre->rt=1; } pre=p; //前驱结点更新 InThread(p->r,pre); } }
然后我们来看如何遍历。对于一个结点,首先找到他的首结点(最左下结点),然后每次循环后,寻找这个结点的后继。这个过程可以用一个for循环进行抽象:
void InOrderTraversal(Node* root){ //中序线索化遍历 for(Node* p=First(root) ; p!=NULL ; p=Next(p)){ visit(p); } }
对于寻找首结点,我们用First函数来完成:
Node* First(Node* p) {//返回 p 结点的最左下结点 (不一定是叶子节点) while(p->lt==0) {//保证p结点一定存在左子树 p=p->l; } return p; }
注意到p结点是一定要存在左子树的。如果不存在左子树,左子树结点l指向的是p结点的前驱,这样就会乱套。
对于寻找后继结点,我们用Next函数来完成:
Node* Next(Node* p){ if(p->rt==0){ //p 节点存在右子树 return First(p->r); //移动到最左下结点 }else{ //不存在左子树 return p->r; //移动到后继结点 } }
翻译成自然语言就是,如果这个结点的右子树存在,返回右子树的最左下结点(中序遍历首结点),如果不存在,返回这个结点的后继结点。
完整代码:
#include<iostream> #define ElemType char #define MaxSize 100 using namespace std; typedef struct Node{ struct Node* l=NULL; struct Node* r=NULL; int lt=0,rt=0;//left tag, right tag,如果为 1 表示左(右)结点不存在,为前驱(后继) ElemType data; Node(ElemType data=0):data(data){ } }Node; Node* buildSampleTree(){//样例树形结构来自天勤 2019 版 142 页 Node* nd=new Node('A'); nd->l= new Node('B'); nd->r= new Node('G'); nd->l->l= new Node('C'); nd->l->r= new Node('D'); nd->r->l= new Node('H'); nd->l->r->l= new Node('E'); nd->l->r->r= new Node('F'); return nd; } void visit(Node* p){ cout<<p->data<<' '; } void InThread(Node* p,Node* &pre){ if(p) { InThread(p->l,pre); if(p->l==NULL){ //左结点不存在 p->lt=1; //设置 p 的前驱 p->l=pre; } //注意加上pre非空的短路判断条件,避免空指针操作 if(pre!=NULL && pre->r==NULL){ //右结点不存在 pre->r=p; //设置 pre 的后继 pre->rt=1; } pre=p; //前驱结点更新 InThread(p->r,pre); } } void createInThread(Node* root){ //对根节点进行中序线索化 Node * pre=NULL; //第一个结点的前驱为 0 if(root!=NULL){ InThread(root,pre); pre->r=NULL;//最后一个结点的后继为 0 pre->rt=1; } } Node* First(Node* p) {//返回 p 结点的最左下结点 (不一定是叶子节点) while(p->lt==0) {//保证p结点一定存在左子树 p=p->l; } } Node* Next(Node* p){ if(p->rt==0){ //p 节点存在右子树 return First(p->r); //移动到最左下结点 }else{ //不存在左子树 return p->r; //移动到后继结点 } } void InOrderTraversal(Node* root){ //中序线索化遍历 for(Node* p=First(root) ; p!=NULL ; p=Next(p)){ visit(p); } } int main(){ Node *root=buildSampleTree(); createInThread(root); InOrderTraversal(root); }
1.前序线索二叉树
前序线索化代码:
void PreThread(Node* p,Node* &pre){ if(p) { if(p->l==NULL){ //左结点不存在 p->lt=1; //设置 p 的前驱 p->l=pre; } //注意加上pre非空的短路判断条件,避免空指针操作 if(pre!=NULL && pre->r==NULL){ //右结点不存在 pre->r=p; //设置 pre 的后继 pre->rt=1; } pre=p; //前驱结点更新 if(p->lt==0) //左子树存在 PreThread(p->l,pre); if(p->lt==0) //右子树存在 PreThread(p->r,pre); } }
注意到递归语句加上了判断条件。这是因为在中序的时候,对左或右子树不存在的结点进行操作的语句放在中间,对于空结点进行的递归操作会被非空判断退出。而到了前序,则是先对左或右子树不存在的结点进行操作,再运行递归语句。则左右子树即使是空结点,也会被线索化操作赋值(指向前驱或后继),这样就会造成死循环。
前序线索树遍历代码:
void PreOrderTraversal(Node* root){ //中序线索化遍历 if(root==NULL) return; Node* p=root; while(p){ while(p->lt==0){ visit(p); p=p->l; } visit(p); //此时 p 必为前驱结点的后继(线索),但还没有访问到,访问。 p=p->r; //指向 p 的右子树 或 后继 } }
对于p结点不断的往左下角遍历,如果遍历到一个结点,他的左子树不存在了,退出循环。退出循环后,先对当前结点进行遍历(不能漏掉),然后指向其r结点(如果右子树存在,就是右子树。如果右子树不存在,就是后继)。
完整代码:
#include<iostream> #define ElemType char #define MaxSize 100 using namespace std; typedef struct Node{ struct Node* l=NULL; struct Node* r=NULL; int lt=0,rt=0;//left tag, right tag,如果为 1 表示左(右)结点不存在,为前驱(后继) ElemType data; Node(ElemType data=0):data(data){ } }Node; Node* buildSampleTree(){//样例树形结构来自天勤 2019 版 142 页 Node* nd=new Node('A'); nd->l= new Node('B'); nd->r= new Node('G'); nd->l->l= new Node('C'); nd->l->r= new Node('D'); nd->r->l= new Node('H'); nd->l->r->l= new Node('E'); nd->l->r->r= new Node('F'); return nd; } void visit(Node* p){ cout<<p->data<<' '; } void PreThread(Node* p,Node* &pre){ if(p) { if(p->l==NULL){ //左结点不存在 p->lt=1; //设置 p 的前驱 p->l=pre; } //注意加上pre非空的短路判断条件,避免空指针操作 if(pre!=NULL && pre->r==NULL){ //右结点不存在 pre->r=p; //设置 pre 的后继 pre->rt=1; } pre=p; //前驱结点更新 if(p->lt==0) //左子树存在 PreThread(p->l,pre); if(p->lt==0) //右子树存在 PreThread(p->r,pre); } } void createPreThread(Node* root){ //对根节点进行中序线索化 Node * pre=NULL; //第一个结点的前驱为 0 if(root!=NULL){ PreThread(root,pre); pre->r=NULL;//最后一个结点的后继为 0 pre->rt=1; } } void PreOrderTraversal(Node* root){ //中序线索化遍历 if(root==NULL) return; Node* p=root; while(p){ while(p->lt==0){ visit(p); p=p->l; } visit(p); //此时 p 必为前驱结点的后继(线索),但还没有访问到,访问。 p=p->r; //指向 p 的右子树 或 后继 } } int main(){ Node *root=buildSampleTree(); createPreThread(root); PreOrderTraversal(root); }