zoukankan      html  css  js  c++  java
  • 遍历二叉树和线索二叉树

    遍历二叉树

    遍历二叉树就是按某种规则,对二叉树的每个结点均访问一次,而且仅访问一次。这

    实际上就是将非线性的二叉树结构线性化。遍历二叉树的方法有先序、中序、后序和层序
    4 种,访问的顺序各不相同。以图61(a)所示二叉树为例,先序遍历的顺序为1 2 3 4;
    中序遍历的顺序为3 2 4 1;后序遍历的顺序为3 4 2 1;层序遍历的顺序为1 2 3 4。对于这
    棵二叉树,层序遍历和先序遍历的顺序碰巧一致。有关二叉链表存储结构生成二叉树和遍

    历二叉树的算法6.1~6.4 在bo6-2.cpp 中。

    线索二叉树

    为了更方便、快捷地遍历二叉树,最好在二叉树的结点上增加2 个指针,它们分别指
    向遍历二叉树时该结点的前驱和后继结点。这样,从二叉树的任一结点都可以方便地找到
    其它结点。但这样做大大降低了结构的存储密度。另外,根据二叉树的性质3(教科书124
    页),有:n0=n2+1。空链域=2n0+n1(叶子结点有2 个空链域,度为1 的结点有1 个空链
    域)=n0+n1+n2+1=n+1。也就是说,在由n 个结点组成的二叉树中,有n+1 个指针是空指
    针。如果能利用这n+1 个空指针,使它们指向结点的前驱(当左孩子指针空)或后继(当右
    孩子指针空),则既可不降低结构的存储密度,又可更方便、快捷地遍历二叉树。不过,
    这样就无法区别左右孩子指针所指的到底是结点的左右孩子,还是结点的前驱后继了。为
    了有所区别,另增加2 个域LTag 和RTag。当所指的是孩子,其值为0(Link);当所指的
    是前驱后继,其值为1(Thread)。这样做,结构的存储密度也有所降低,但不大。因为
    LTag 和RTag 分别只需要1 个比特(二进制位)即可。c6-3.h 是二叉树的二叉线索存储结

    构。它只是比二叉链表存储结构(在c6-2.h 中)多了LTag 和RTag 个域。

    // c6-3.h 二叉树的二叉线索存储结构(见图6.17)
    enum PointerTag // 枚举
    {Link,Thread}; // Link(0):指针,Thread(1):线索
    struct BiThrNode
    {
    	TElemType data;
    	BiThrNode *lchild,*rchild; // 左右孩子指针
    	PointerTag LTag,RTag; // 左右标志
    };
    typedef BiThrNode *BiThrTree;


    构造线索二叉树的方法和构造以二叉链表存储的二叉树方法相似,都是按先序输入结
    点的值来构造二叉树的。对比bo6-2.cpp 中的CreateBiTree()函数和bo6-3.cpp 中的
    CreateBiThrTree()函数可见,它们的区别有两点:
    (1) 二叉树结点的结构不同;
    (2) 构造线索二叉树时,若有左右孩子结点,还要给左右标志赋值0(Link)。
    图61(a)所示二叉树调用CreateBiThrTree()函数产生的二叉树结构如图618 所
    示。和调用CreateBiTree()函数产生的二叉树结构(见图68)相比,前者只是多了LTag 和
    RTag 两个域。并且当其相应孩子指针不空时,赋值0。
    调用CreateBiThrTree()函数,只是构造了一棵可以线索化的二叉树,还没有完成线
    索化。
    因为对于一棵给定的二叉树,其先序、中序、后序和层序遍历的顺序是不同的。显
    然,其线索化的操作和遍历的操作也是不同的。
    图619 是图61(a)所示二叉树的中序线索二叉树存储结构示例。和二叉链表(见图
    68)存储结构相比,第一,它多了一个头结点。其左孩子指针指向根结点,右孩子指针
    (线索)指向中序遍历所访问的最后一个结点。第二,它每个结点的左右孩子指针都不是空
    指针。在没有孩子的情况下,分别指向该结点中序遍历的前驱或后继。第三,中序遍历的
    第1 个结点(最左边的结点,它没有左孩子,本例是结点3)的左孩子指针(线索)和最后1
    个结点(最右边的结点,它没有右孩子,本例是结点1)的右孩子指针(线索)都指向头结
    点。其目的是标志遍历的起点和终点。



    图620 是空线索二叉树。
    bo6-3.cpp 中的InThreading()函数和InOrderThreading()函数
    共同完成了对二叉树的中序遍历线索化。其算法是:设置全局指
    针变量pre( 之所以设为全局变量, 是因为在递归函数
    InThreading()和InOrderThreading()中都要用到,设为全局变量就
    不必频繁传递变量的值),令pre 总是指向遍历的前驱结点,p 指
    向当前结点;在中序遍历过程中,如果p 所指结点没有左孩子,则结点的左孩子指针指向
    pre 所指结点,结点的LTag 域的值为1(Thread);如果pre 所指结点没有右孩子,则结点的
    右孩子指针指向p,结点的RTag 域的值为1(Thread)。
    对于图619 所示的中序线索二叉树,我们能不能在找到遍历的第1 个结点后,顺着
    右孩子指针一直找到遍历的最后1 个结点呢?这是不一定的。因为结点的右孩子指针并不
    一定指向后继结点,它可能指向右孩子,而右孩子并不一定恰好是后继结点。
    bo6-3.cpp 中的InOrderTraverse_Thr()函数完成了对中序线索二叉树的中序遍历操
    作。其算法是:当树不空时,由树根向左找,一直找到没有左孩子的结点(最左结点)。这
    就是中序遍历的第1 个结点。若该结点没有右孩子,则右孩子指针指向其后继结点;否
    则,以其右孩子为子树的根,向左找,一直找到没有左孩子的结点。这就是后继结点。当
    结点的右孩子指针指向头结点,遍历结束。
    图621 是图61(a)所示二叉树的先序线索二叉树存储结构示例。bo6-3.cpp 中的先
    序线索化的递归函数PreThreading()与中序线索化的递归函数InThreading()很相像,都是
    利用递归进行线索化,只不过顺序不同。但由于PreThreading()是先序线索化,所以判断
    结点是否有左右孩子就不能由其左右孩子指针是否为空决定,而要由结点的LTag 和RTag
    域是否为0(Link)来决定。


    对于先序线索化二叉树的先序遍历算法是这样的:根结点是遍历的第1 个结点;如果
    结点有左孩子,则左孩子是其后继;若结点没有左孩子,则右孩子指针所指的结点是其后
    继(无论该结点有没有右孩子)。相关程序见bo6-3.cpp 中的PreOrderTraverse_Thr()函数。
    bo6-3.cpp 中的后序线索化的递归函数PostThreading()与中序线索化的递归函数
    InThreading()也很相像,也是利用递归进行线索化,也只是顺序不同。图622 是图
    61(a)所示二叉树的后序线索二叉树存储结构示例。对于后序线索化二叉树的后序遍历

    算法较复杂。因为根结点是在最后遍历,所以要采用带有双亲指针的三叉链表结构才行。
    本书没有给出它的算法。


    // bo6-3.cpp 二叉树的二叉线索存储(存储结构由c6-3.h定义)的基本操作,包括算法6.5~6.7
    void CreateBiThrTree(BiThrTree &T) // (见图6.18)
    { // 按先序输入线索二叉树中结点的值,构造线索二叉树T。0(整型)/空格(字符型)表示空结点
    	TElemType ch;
    	scanf(form,&ch);
    	if(ch==Nil)
    		T=NULL;
    	else
    	{
    		T=(BiThrTree)malloc(sizeof(BiThrNode)); // 生成根结点(先序)
    		if(!T)
    			exit(OVERFLOW);
    		T->data=ch; // 给根结点赋植
    		CreateBiThrTree(T->lchild); // 递归构造左子树
    		if(T->lchild) // 有左孩子
    			T->LTag=Link; // 给左标志赋值(指针)
    		CreateBiThrTree(T->rchild); // 递归构造右子树
    		if(T->rchild) // 有右孩子
    			T->RTag=Link; // 给右标志赋值(指针)
    	}
    }
    BiThrTree pre; // 全局变量,始终指向刚刚访问过的结点
    void InThreading(BiThrTree p)
    { // 通过中序遍历进行中序线索化,线索化之后pre指向最后一个结点。算法6.7
    	if(p) // 线索二叉树不空
    	{
    		InThreading(p->lchild); // 递归左子树线索化
    		if(!p->lchild) // 没有左孩子
    		{
    			p->LTag=Thread; // 左标志为线索(前驱)
    			p->lchild=pre; // 左孩子指针指向前驱
    		}
    		if(!pre->rchild) // 前驱没有右孩子
    		{
    			pre->RTag=Thread; // 前驱的右标志为线索(后继)
    			pre->rchild=p; // 前驱右孩子指针指向其后继(当前结点p)
    		}
    		pre=p; // 保持pre指向p的前驱
    		InThreading(p->rchild); // 递归右子树线索化
    	}
    }
    void InOrderThreading(BiThrTree &Thrt,BiThrTree T)
    { // 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点。算法6.6
    	if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点不成功
    		exit(OVERFLOW);
    	Thrt->LTag=Link; // 建头结点,左标志为指针
    	Thrt->RTag=Thread; // 右标志为线索
    	Thrt->rchild=Thrt; // 右指针回指
    	if(!T) // 若二叉树空,则左指针回指(见图6.20)
    		Thrt->lchild=Thrt;
    	else // (见图6.19)
    	{
    		Thrt->lchild=T; // 头结点的左指针指向根结点
    		pre=Thrt; // pre(前驱)的初值指向头结点
    		InThreading(T); // 中序遍历进行中序线索化,pre指向中序遍历的最后一个结点
    		pre->rchild=Thrt; // 最后一个结点的右指针指向头结点
    		pre->RTag=Thread; // 最后一个结点的右标志为线索
    		Thrt->rchild=pre; // 头结点的右指针指向中序遍历的最后一个结点
    	}
    }
    void InOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType))
    { // 中序遍历线索二叉树T(头结点)的非递归算法。算法6.5
    	BiThrTree p;
    	p=T->lchild; // p指向根结点
    	while(p!=T)
    	{ // 空树或遍历结束时,p==T
    		while(p->LTag==Link) // 由根结点一直找到二叉树的最左结点
    			p=p->lchild;
    		Visit(p->data); // 访问此结点
    		while(p->RTag==Thread&&p->rchild!=T) // p->rchild是线索(后继),且不是遍历的最后一个结点
    		{
    			p=p->rchild;
    			Visit(p->data); // 访问后继结点
    		}
    		p=p->rchild; // 若p->rchild不是线索(是右孩子),p指向右孩子,返回循环,
    	} // 找这棵子树中序遍历的第1个结点
    }
    void PreThreading(BiThrTree p)
    { // PreOrderThreading()调用的递归函数
    	if(!pre->rchild) // p的前驱没有右孩子
    	{
    		pre->rchild=p; // p前驱的后继指向p
    		pre->RTag=Thread; // pre的右孩子为线索
    	}
    	if(!p->lchild) // p没有左孩子
    	{
    		p->LTag=Thread; // p的左孩子为线索
    		p->lchild=pre; // p的左孩子指向前驱
    	}
    	pre=p; // 移动前驱
    	if(p->LTag==Link) // p有左孩子
    		PreThreading(p->lchild); // 对p的左孩子递归调用preThreading()
    	if(p->RTag==Link) // p有右孩子
    		PreThreading(p->rchild); // 对p的右孩子递归调用preThreading()
    }
    void PreOrderThreading(BiThrTree &Thrt,BiThrTree T)
    { // 先序线索化二叉树T,头结点的右指针指向先序遍历的最后1个结点
    	if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点
    		exit(OVERFLOW);
    	Thrt->LTag=Link; // 头结点的左指针为孩子
    	Thrt->RTag=Thread; // 头结点的右指针为线索
    	Thrt->rchild=Thrt; // 头结点的右指针指向自身
    	if(!T) // 空树(见图6.20)
    		Thrt->lchild=Thrt; // 头结点的左指针也指向自身
    	else
    	{ // 非空树(见图6.21)
    		Thrt->lchild=T; // 头结点的左指针指向根结点
    		pre=Thrt; // 前驱为头结点
    		PreThreading(T); // 从头结点开始先序递归线索化
    		pre->rchild=Thrt; // 最后一个结点的后继指向头结点
    		pre->RTag=Thread;
    		Thrt->rchild=pre; // 头结点的后继指向最后一个结点
    	}
    }
    void PreOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType))
    { // 先序遍历线索二叉树T(头结点)的非递归算法
    	BiThrTree p=T->lchild; // p指向根结点
    	while(p!=T) // p没指向头结点(遍历的最后1个结点的后继指向头结点)
    	{
    		Visit(p->data); // 访问根结点
    		if(p->LTag==Link) // p有左孩子
    			p=p->lchild; // p指向左孩子(后继)
    		else // p无左孩子
    			p=p->rchild; // p指向右孩子或后继
    	}
    }
    void PostThreading(BiThrTree p)
    { // PostOrderThreading()调用的递归函数
    	if(p) // p不空
    	{
    		PostThreading(p->lchild); // 对p的左孩子递归调用PostThreading()
    		PostThreading(p->rchild); // 对p的右孩子递归调用PostThreading()
    		if(!p->lchild) // p没有左孩子
    		{
    			p->LTag=Thread; // p的左孩子为线索
    			p->lchild=pre; // p的左孩子指向前驱
    		}
    		if(!pre->rchild) // p的前驱没有右孩子
    		{
    			pre->RTag=Thread; // p前驱的右孩子为线索
    			pre->rchild=p; // p前驱的后继指向p
    		}
    		pre=p; // 移动前驱
    	}
    }
    void PostOrderThreading(BiThrTree &Thrt,BiThrTree T)
    { // 后序递归线索化二叉树
    	if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点
    		exit(OVERFLOW);
    	Thrt->LTag=Link; // 头结点的左指针为孩子
    	Thrt->RTag=Thread; // 头结点的右指针为线索
    	if(!T) // 空树(见图6.20)
    		Thrt->lchild=Thrt->rchild=Thrt; // 头结点的左右指针指向自身
    	else
    	{ // 非空树(见图6.22)
    		Thrt->lchild=Thrt->rchild=T; // 头结点的左右指针指向根结点(最后一个结点)
    		pre=Thrt; // 前驱为头结点
    		PostThreading(T); // 从头结点开始后序递归线索化
    		if(pre->RTag!=Link) // 最后一个结点没有右孩子
    		{
    			pre->rchild=Thrt; // 最后一个结点的后继指向头结点
    			pre->RTag=Thread;
    		}
    	}
    }
    void DestroyBiTree(BiThrTree &T)
    { // DestroyBiThrTree调用的递归函数,T指向根结点
    	if(T) // 非空树
    	{
    		if(T->LTag==0) // 有左孩子
    			DestroyBiTree(T->lchild); // 销毁左孩子子树
    		if(T->RTag==0) // 有右孩子
    			DestroyBiTree(T->rchild); // 销毁右孩子子树
    		free(T); // 释放根结点
    		T=NULL; // 空指针赋0
    	}
    }
    void DestroyBiThrTree(BiThrTree &Thrt)
    { // 初始条件:线索二叉树Thrt存在。操作结果:销毁线索二叉树Thrt
    	if(Thrt) // 头结点存在
    	{
    		if(Thrt->lchild) // 根结点存在
    			DestroyBiTree(Thrt->lchild); // 递归销毁头结点lchild所指二叉树
    		free(Thrt); // 释放头结点
    		Thrt=NULL; // 线索二叉树Thrt指针赋0
    	}
    }
    

    // main6-3.cpp 检验bo6-3.cpp的主程序
    #define CHAR 1 // 字符型
    // #define CHAR 0 // 整型(二者选一)
    #if CHAR
    typedef char TElemType;
    TElemType Nil=' '; // 字符型以空格符为空
    #define form "%c" // 输入输出的格式为%c
    #else
    typedef int TElemType;
    TElemType Nil=0; // 整型以0为空
    #define form "%d" // 输入输出的格式为%d
    #endif
    #include"c1.h"
    #include"c6-3.h"
    #include"bo6-3.cpp"
    void vi(TElemType c)
    {
    	printf(form" ",c);
    }
    void main()
    {
    	BiThrTree H,T;
    #if CHAR
    	printf("请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)
    ");
    #else
    	printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)
    ");
    #endif
    	CreateBiThrTree(T); // 按先序产生二叉树
    	InOrderThreading(H,T); // 在中序遍历的过程中,中序线索化二叉树
    	printf("中序遍历(输出)线索二叉树:
    ");
    	InOrderTraverse_Thr(H,vi); // 中序遍历(输出)线索二叉树
    	printf("
    ");
    	DestroyBiThrTree(H); // 销毁线索二叉树
    #if CHAR
    	printf("请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)
    ");
    #else
    	printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)
    ");
    #endif
    	scanf("%*c"); // 吃掉回车符
    	CreateBiThrTree(T); // 按先序产生二叉树T
    	PreOrderThreading(H,T); // 在先序遍历的过程中,先序线索化二叉树
    	printf("先序遍历(输出)线索二叉树:
    ");
    	PreOrderTraverse_Thr(H,vi);
    	DestroyBiThrTree(H); // 销毁线索二叉树
    #if CHAR
    	printf("
    请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)
    ");
    #else
    	printf("
    请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)
    ");
    #endif
    	scanf("%*c"); // 吃掉回车符
    	CreateBiThrTree(T); // 按先序产生二叉树T
    	PostOrderThreading(H,T); // 在后序遍历的过程中,后序线索化二叉树
    	DestroyBiThrTree(H); // 销毁线索二叉树
    }

    代码的运行结果:

    请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)
    abdg e c f (见图623)
    中序遍历(输出)线索二叉树: (见图624)
    g d b e a c f
    请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)
    abdg e c f (见图623)
    先序遍历(输出)线索二叉树: (见图625)
    a b d g e c f
    请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)
    abdg e c f (见图623)


  • 相关阅读:
    政府信息化建设重点——服务、多元化
    随便聊聊水面效果的2D实现(一)
    【Oracel 基础】小结
    漫话Unity(二)
    Codeforces Round #265 (Div. 2) C. No to Palindromes!
    C99中的restrict和C89的volatilekeyword
    开源 java CMS
    JavaScript--基于对象的脚本语言学习笔记(二)
    小试“以图搜图”
    计算几何 《模板》
  • 原文地址:https://www.cnblogs.com/KongkOngL/p/3945945.html
Copyright © 2011-2022 走看看