二叉树之中序线索
2014-04-29 21:55
399 查看
遍历二叉树是以一定的规则将二叉树中结点排列成一个线性序列,得到二叉树中结点的前序序列或者中序序列或者后序序列。这实际上是对二叉树进行线性化操作,使得每个结点(第一个与最后一个除外)在这些线性序列中有且仅有一个直接前驱和直接后继。
但是,当二叉树以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在任一序列中的前序和后继信息,这种信息只有在遍历的动态过程中才能得到。我们要做的就是保留这种在遍历过程中得到的信息。
一个最简单的方法就是在每个结点 上增加两个指针域分别指示结点在任一次序遍历时得到的前驱和后继信息,但是显然,这种方法将大大降低结构的存储密度。另一方面,在n个结点的二叉链表中必定存在n+1个空链域。所以较为有效的方法是利用好这些空链域来存放结点的前驱和后继的信息。
试做如下规定:
(1)若结点有左子树,则lchild指向其左孩子,否则指示其前驱;
(2)若结点有右子树,则rchild指向其右孩子,否则指示其后继。
为了避免混淆,需要在二叉链表中的结点结构中稍作修改,增加两个 标志域:
lchild
ltag
data
rtag
rchild
其中:
ltag=0时lchild指向结点的左孩子,ltag=1时lchild指向其前驱;
rtag=0时rchild指向结点的右孩子,rtag=1时rchild指向其后继。
以这种结点结构构成的二叉链表作为二叉树的存储结构,我们叫做线索链表,其中指向前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树。下图所示是一个中序二叉树,其中实线是指针,虚线是线索。对二叉树以某种次序遍历使其变为线索二叉树的过程线索化。
可以用C++语言中的结构体类型描述线索链表中的结点:
由于二叉树的遍历次序有4中,故有4种意义下的前驱和后继,相应的有4种线索链表:前序线索链表、中序线索链表、后序线索链表和层序线索链表。这里的讨论以中序线索链表为例。
一、创建二叉树
构造函数的功能是建立一个中序线索链表,实质上就是讲二叉链表中的空指针改为指向前驱或者后继的线索,而前驱和后继的信息只有在遍历的时候才能得到。因此建立线索链表首先要建立二叉链表,然后在遍历的过程中修改指针。建立如下:
在遍历过程中,访问当前结点bt的操作为:
(1)检查结点bt的左右指针域,如果为空,则将相应标志置1。
(2)由于结点bt的前驱结点刚刚被访问过,所以若左指针域为空,则可令其指向它的前驱;但由于bt的后继尚未访问到,所以它的右指针域不能建立线索,而要等到下次访问时才能进行。为了实现这一过程,设指针pre始终指向刚刚访问过的结点。显然pre的初始值为NULL。
(3)令pre=bt,即令pre指向刚刚访问过的结点bt。
中序线索化的算法如下:
1.如果二叉链表bt为空,则空操作返回;
2.对bt的左子树建立线索;
3.对根结点bt建立线索;
3.1 如果bt没有左孩子,那么为bt加上前驱线索;
3.2 如果bt没有右孩子,那么将bt的右标志置1;
3.3 如果结点pre的右标志为1,那么为其加上后继线索;
3.4 令pre指向刚刚访问过的结点;
4.为bt的右子树建立线索。
二、查找其后继结点
对于中序线索链表上的任一结点,其后继结点有一下两种情况:
(1)如果该结点的右标志为1,表明该结点的右指针是线索,则其右指针所指就是后继结点。
(2)如果该结点的右标志为0,表明该结点有右孩子,无法直接找到其后继结点。然而,根据中序遍历的操作定义,它的后继结点应该是遍历其右子树时第一个访问的结点,即右子树的最左下结点。这时只需沿着其右孩子的左指针向下查找,当某结点的左标志为1时,就是它要找的后继结点。
三、查找结点的前驱
与查找后继一个道理,如果该结点的左标志为1,则左指针所指结点就是前驱;如果左标志为0,则遍历左子树时最后访问的一个结点,即左子树最右下结点为前驱。
四、遍历操作
在中序线索链表中进行遍历,只需要在中序遍历序列中的第一个结点,然后依次找每个结点的后继结点,直至某结点无后继为止。
五、查找结点的双亲
如果所查找的结点为左孩子,那么p最右下方的结点的后继就是其双亲;
如果所查找的结点为右孩子,那么p最左下方的结点的前驱就是其双亲。
六、查找结点的存在
在中序遍历的基础上进行查找
下面是所有源码。如有不足,请指教~~
InOrderThreadBiTree.h
InOrderThreadBiTree.cpp
main.cpp
测试结果:
但是,当二叉树以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在任一序列中的前序和后继信息,这种信息只有在遍历的动态过程中才能得到。我们要做的就是保留这种在遍历过程中得到的信息。
一个最简单的方法就是在每个结点 上增加两个指针域分别指示结点在任一次序遍历时得到的前驱和后继信息,但是显然,这种方法将大大降低结构的存储密度。另一方面,在n个结点的二叉链表中必定存在n+1个空链域。所以较为有效的方法是利用好这些空链域来存放结点的前驱和后继的信息。
试做如下规定:
(1)若结点有左子树,则lchild指向其左孩子,否则指示其前驱;
(2)若结点有右子树,则rchild指向其右孩子,否则指示其后继。
为了避免混淆,需要在二叉链表中的结点结构中稍作修改,增加两个 标志域:
lchild
ltag
data
rtag
rchild
其中:
ltag=0时lchild指向结点的左孩子,ltag=1时lchild指向其前驱;
rtag=0时rchild指向结点的右孩子,rtag=1时rchild指向其后继。
以这种结点结构构成的二叉链表作为二叉树的存储结构,我们叫做线索链表,其中指向前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树。下图所示是一个中序二叉树,其中实线是指针,虚线是线索。对二叉树以某种次序遍历使其变为线索二叉树的过程线索化。
可以用C++语言中的结构体类型描述线索链表中的结点:
enum flag{CHILD,THREAD}; //枚举类型,枚举常量CHILD=0,THREAD=1 template<class Type> struct ThreadNode{ //结构体类型描述线索链表的结点 Type data; ThreadNode<Type> *lchild, *rchild; flag ltag, rtag; };
由于二叉树的遍历次序有4中,故有4种意义下的前驱和后继,相应的有4种线索链表:前序线索链表、中序线索链表、后序线索链表和层序线索链表。这里的讨论以中序线索链表为例。
一、创建二叉树
构造函数的功能是建立一个中序线索链表,实质上就是讲二叉链表中的空指针改为指向前驱或者后继的线索,而前驱和后继的信息只有在遍历的时候才能得到。因此建立线索链表首先要建立二叉链表,然后在遍历的过程中修改指针。建立如下:
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::Create(ThreadNode<Type> *bt){ Type ch; cin >> ch; if (ch == '#')bt = NULL; //建立一棵空树 else{ bt = new ThreadNode<Type>; //生成一个结点 bt->data = ch; bt->ltag = CHILD; bt->rtag = CHILD; //左右标志均置为0 bt->lchild = Create(bt->lchild); bt->rchild = Create(bt->rchild); //递归建立左右子树 } return bt; }
在遍历过程中,访问当前结点bt的操作为:
(1)检查结点bt的左右指针域,如果为空,则将相应标志置1。
(2)由于结点bt的前驱结点刚刚被访问过,所以若左指针域为空,则可令其指向它的前驱;但由于bt的后继尚未访问到,所以它的右指针域不能建立线索,而要等到下次访问时才能进行。为了实现这一过程,设指针pre始终指向刚刚访问过的结点。显然pre的初始值为NULL。
(3)令pre=bt,即令pre指向刚刚访问过的结点bt。
中序线索化的算法如下:
1.如果二叉链表bt为空,则空操作返回;
2.对bt的左子树建立线索;
3.对根结点bt建立线索;
3.1 如果bt没有左孩子,那么为bt加上前驱线索;
3.2 如果bt没有右孩子,那么将bt的右标志置1;
3.3 如果结点pre的右标志为1,那么为其加上后继线索;
3.4 令pre指向刚刚访问过的结点;
4.为bt的右子树建立线索。
template<class Type> void InOrderThreadBiTree<Type>::ThreadBiTree(ThreadNode<Type> *bt, ThreadNode<Type>*& pre){ if (bt == NULL)return; ThreadBiTree(bt->lchild, pre); if (bt->lchild == NULL){ bt->ltag = THREAD; bt->lchild = pre; } if (bt->rchild == NULL){ bt->rtag = THREAD; } if (pre != NULL){ if (pre->rtag==THREAD){ pre->rchild = bt; } } pre = bt; ThreadBiTree(bt->rchild, pre); }
二、查找其后继结点
对于中序线索链表上的任一结点,其后继结点有一下两种情况:
(1)如果该结点的右标志为1,表明该结点的右指针是线索,则其右指针所指就是后继结点。
(2)如果该结点的右标志为0,表明该结点有右孩子,无法直接找到其后继结点。然而,根据中序遍历的操作定义,它的后继结点应该是遍历其右子树时第一个访问的结点,即右子树的最左下结点。这时只需沿着其右孩子的左指针向下查找,当某结点的左标志为1时,就是它要找的后继结点。
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::getNext(ThreadNode<Type>* p){ ThreadNode<Type>* q = NULL; if (p->rtag == THREAD) q = p->rchild; else{ q = p->rchild; while (q->ltag == CHILD){ q = q->lchild; //右子树的最左下的结点 } } return q; }
三、查找结点的前驱
与查找后继一个道理,如果该结点的左标志为1,则左指针所指结点就是前驱;如果左标志为0,则遍历左子树时最后访问的一个结点,即左子树最右下结点为前驱。
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::getPre(ThreadNode<Type>* p){ ThreadNode<Type>* q = NULL; if (p->ltag == THREAD) q = p->lchild; else{ q = p->lchild; while (q->rtag == CHILD){ q = q->rchild; //左子树的最后下的结点 } } return q; }
四、遍历操作
在中序线索链表中进行遍历,只需要在中序遍历序列中的第一个结点,然后依次找每个结点的后继结点,直至某结点无后继为止。
template<class Type> void InOrderThreadBiTree<Type>::InOrderTraverse(){ if (root == NULL)return; ThreadNode<Type>* p = root; while (p->ltag == CHILD){ p = p->lchild; } cout << p->data<<" "; while (p->rchild != NULL){ p = getNext(p); cout << p->data << " "; } cout << endl; }
五、查找结点的双亲
如果所查找的结点为左孩子,那么p最右下方的结点的后继就是其双亲;
如果所查找的结点为右孩子,那么p最左下方的结点的前驱就是其双亲。
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::getParent(ThreadNode<Type>* bt, Type x){ ThreadNode<Type>* p = Search(x); if (p == root){ cerr << "根结点没有父节点!" << endl; exit(1); } ThreadNode<Type>* q = NULL; if (p->rtag == THREAD) q = p; else{ q = p->rchild; while (q->rtag == CHILD) q = q->lchild; } if (q->rchild != NULL) return q->rchild; if (p->ltag == THREAD) q = p; else{ q = p->lchild; while (q->ltag == CHILD) q = q->lchild; } if (q->lchild != NULL) return q->lchild; }
六、查找结点的存在
在中序遍历的基础上进行查找
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::Search(Type x){ if (root == NULL)return NULL; ThreadNode<Type>* p = root; while (p->ltag == 0){ p = p->lchild; } while (p != NULL){ if (p->data == x) return p; else p = getNext(p); } return NULL; }
下面是所有源码。如有不足,请指教~~
InOrderThreadBiTree.h
#ifndef INORDERTHREADBITREE_H #define INORDERTHREADBITREE_H enum flag{CHILD,THREAD}; //枚举类型,枚举常量CHILD=0,THREAD=1 template<class Type> struct ThreadNode{ //结构体类型描述线索链表的结点 Type data; ThreadNode<Type> *lchild, *rchild; flag ltag, rtag; //ltag=0时,lchild指向该结点的左孩子,ltag=1时,lchild指向该结点的前驱 //rtag=0时,rchild指向该结点的右孩子,ltag=1时,lchild指向该结点的后继 }; template<class Type> class InOrderThreadBiTree{ private: ThreadNode<Type> *root; //指向线索链表的头指针 ThreadNode<Type>* Create(ThreadNode<Type> *bt); //建立二叉链表 void ThreadBiTree(ThreadNode<Type> *bt, ThreadNode<Type>*& pre);//中序线索化链表 ThreadNode<Type>* getParent(ThreadNode<Type>* bt, Type x); //查找双亲 public: InOrderThreadBiTree(); //构造函数 ~InOrderThreadBiTree(); //析构函数 ThreadNode<Type>* getNext(ThreadNode<Type> *p); //查找结点的后继 ThreadNode<Type>* getPre(ThreadNode<Type> *p); //查找结点的前驱 void InOrderTraverse(); //中序遍历线索链表 ThreadNode<Type>* getParent(Type x); ThreadNode<Type>* Search(Type x); }; #endif
InOrderThreadBiTree.cpp
#include "InOrderThreadBiTree.h"
#include <iostream>
using namespace std;
//建立二叉链表
template<class Type>
ThreadNode<Type>* InOrderThreadBiTree<Type>::Create(ThreadNode<Type> *bt){
Type ch;
cin >> ch;
if (ch == '#')bt = NULL; //建立一棵空树
else{
bt = new ThreadNode<Type>; //生成一个结点
bt->data = ch;
bt->ltag = CHILD;
bt->rtag = CHILD; //左右标志均置为0
bt->lchild = Create(bt->lchild);
bt->rchild = Create(bt->rchild); //递归建立左右子树
}
return bt;
}
//中序线索化链表
template<class Type> void InOrderThreadBiTree<Type>::ThreadBiTree(ThreadNode<Type> *bt, ThreadNode<Type>*& pre){ if (bt == NULL)return; ThreadBiTree(bt->lchild, pre); if (bt->lchild == NULL){ bt->ltag = THREAD; bt->lchild = pre; } if (bt->rchild == NULL){ bt->rtag = THREAD; } if (pre != NULL){ if (pre->rtag==THREAD){ pre->rchild = bt; } } pre = bt; ThreadBiTree(bt->rchild, pre); }
//构造函数
template<class Type>
InOrderThreadBiTree<Type>::InOrderThreadBiTree(){
root = Create(root);
ThreadNode<Type>* pre = NULL;
ThreadBiTree(root, pre);
}
//查找后继结点
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::getNext(ThreadNode<Type>* p){ ThreadNode<Type>* q = NULL; if (p->rtag == THREAD) q = p->rchild; else{ q = p->rchild; while (q->ltag == CHILD){ q = q->lchild; //右子树的最左下的结点 } } return q; }
//查找前驱结点
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::getPre(ThreadNode<Type>* p){ ThreadNode<Type>* q = NULL; if (p->ltag == THREAD) q = p->lchild; else{ q = p->lchild; while (q->rtag == CHILD){ q = q->rchild; //左子树的最后下的结点 } } return q; }
//遍历操作
template<class Type> void InOrderThreadBiTree<Type>::InOrderTraverse(){ if (root == NULL)return; ThreadNode<Type>* p = root; while (p->ltag == CHILD){ p = p->lchild; } cout << p->data<<" "; while (p->rchild != NULL){ p = getNext(p); cout << p->data << " "; } cout << endl; }
//析构函数
template<class Type>
InOrderThreadBiTree<Type>::~InOrderThreadBiTree(){
if (root == NULL)return;
ThreadNode<Type>* p = root;
while (p->ltag == 0){
p = p->lchild;
}
while (p!= NULL){
ThreadNode<Type>* q = p;
p = getNext(p);
delete q;
}
}
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::getParent(ThreadNode<Type>* bt, Type x){ ThreadNode<Type>* p = Search(x); if (p == root){ cerr << "根结点没有父节点!" << endl; exit(1); } ThreadNode<Type>* q = NULL; if (p->rtag == THREAD) q = p; else{ q = p->rchild; while (q->rtag == CHILD) q = q->lchild; } if (q->rchild != NULL) return q->rchild; if (p->ltag == THREAD) q = p; else{ q = p->lchild; while (q->ltag == CHILD) q = q->lchild; } if (q->lchild != NULL) return q->lchild; }
template<class Type>
ThreadNode<Type>* InOrderThreadBiTree<Type>::getParent(Type x){
return getParent(root, x);
}
template<class Type> ThreadNode<Type>* InOrderThreadBiTree<Type>::Search(Type x){ if (root == NULL)return NULL; ThreadNode<Type>* p = root; while (p->ltag == 0){ p = p->lchild; } while (p != NULL){ if (p->data == x) return p; else p = getNext(p); } return NULL; }
main.cpp
#include "InOrderThreadBiTree.h" #include "InOrderThreadBiTree.cpp" int main(){ cout << "请输入二叉树的前序扩展序列:"; InOrderThreadBiTree<char> tb; cout << "打印二叉树的中序序列:"; tb.InOrderTraverse(); cout << "输入x,查找它的前驱:"; char x; cin >> x; if (tb.Search(x)){ if (tb.getPre(tb.Search(x))){ cout << "前驱是:" << tb.getPre(tb.Search(x))->data << endl; } else{ cout << "没有前驱!" << endl; } } else{ cout << "不存在该结点!" << endl; } cout << "输入y,查找它的双亲:"; char y; cin >> y; if (tb.getParent(y)){ cout << "y的双亲是:" << tb.getParent(y)->data << endl; } return 0; }
测试结果:
相关文章推荐
- 第十周【项目1-中序线索化二叉树的算法验证】(3)
- 中序线索化二叉树
- 2015年大二上-数据结构-树和二叉树-2-(4)中序线索化二叉树
- 第10周项目1(3)中序线索化二叉树的算法验证
- 第十一周项目1--(3)中序线索化二叉树的算法验证
- 第十一周 项目一 -二叉树算法验证(3)中序线索化二叉树的算法验证
- 【第11周 项目1 - 二叉树算法验证(3)中序线索化二叉树的算法验证】
- 第十一周项目1-验证算法(3)中序线索化二叉树的算法验证
- 第十一周 项目1-验证算法(3)中序线索化二叉树的算法验证
- 第十一周项目1(3) 中序线索化二叉树的算法验证
- 第11周上机实践项目1—二叉树算法验证(3)中序线索化二叉树的算法验证
- 【数据结构】中序线索化二叉树后实现一个迭代器来遍历二叉树
- 数据结构——中序线索化二叉树
- 第十一周项目1(3)-中序线索化二叉树的算法验证
- 第十一周 项目1:二叉树算法验证(3) 中序线索化二叉树的算法验证
- 第11周项目1-验证算法(3)中序线索化二叉树的算法验证
- [Java算法分析与设计]中序线索化二叉树
- YTU 3026: 中序线索化二叉树
- 二叉排序树的创建,删除结点;树的前序,中序,后序非递归遍;二叉树的线索化
- 第11周 项目1-验证算法(3)线索化二叉树(中序)