二叉树的遍历及线索化
先序线索化转圈问题
先序线索化转圈问题先序线索化转圈问题是一种经典的数学问题,它可以帮助我们探索数学中的先序遍历、线索化和环的概念。
让我们来了解一下先序遍历和线索化的含义。
我们将研究如何将这些概念应用到转圈问题上,并通过一些具体的例子来进一步理解。
1. 先序遍历在二叉树中,先序遍历是指先访问根节点,然后遍历左子树,最后遍历右子树。
这个遍历顺序可以用一个节点序列来表示,我们可以把先序遍历的结果存储在一个数组中。
在这个数组中,根节点的位置总是在左子树和右子树之前。
2. 线索化线索化是将二叉树中的空指针调整为指向前驱节点或后继节点的方式。
通过线索化,我们可以用指针在树中前进而不需要使用递归或栈。
对于一个二叉树的节点,如果它没有左子树,我们可以把它的左指针指向它的前驱节点;如果它没有右子树,我们可以把它的右指针指向它的后继节点。
3. 先序线索化转圈问题现在让我们来考虑一个有趣的问题:如何将一棵已经线索化的二叉树转化为一个环形链表呢?我们需要把每一个节点的右指针指向它的后继节点,并使得最后一个节点的后继节点指向第一个节点。
为了解决这个问题,我们可以使用先序遍历的思想。
我们可以从根节点开始,依次遍历每个节点,并将它的右指针指向它的后继节点。
我们还需要记录前一个访问的节点,以便将它的后继节点指向当前节点。
具体的步骤如下:- 如果当前节点为根节点,将它的右指针指向它的后继节点,并记录当前节点为前一个节点。
- 如果当前节点有左子树,递归处理左子树。
- 如果当前节点有右子树,继续遍历它的右子树。
通过这种方式,我们可以将一个已经线索化的二叉树转化为一个环形链表。
这个环形链表可以让我们在任意节点中,通过右指针循环遍历所有节点。
在实际应用中,先序线索化转圈问题可以用于优化二叉树的遍历算法。
由于线索化的特性,我们可以避免使用递归或栈,从而提高遍历的效率。
通过对先序线索化转圈问题的深入研究,我们可以更好地理解先序遍历、线索化和环的概念。
这个问题不仅提供了一个数学的挑战,还具有一定的实际应用价值。
数据结构树的知识点总结
数据结构树的知识点总结一、树的基本概念。
1. 树的定义。
- 树是n(n ≥ 0)个结点的有限集。
当n = 0时,称为空树。
在任意一棵非空树中:- 有且仅有一个特定的称为根(root)的结点。
- 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tm,其中每个集合本身又是一棵树,并且称为根的子树(sub - tree)。
2. 结点的度、树的度。
- 结点的度:结点拥有的子树个数称为结点的度。
- 树的度:树内各结点的度的最大值称为树的度。
3. 叶子结点(终端结点)和分支结点(非终端结点)- 叶子结点:度为0的结点称为叶子结点或终端结点。
- 分支结点:度不为0的结点称为分支结点或非终端结点。
- 除根结点之外,分支结点也称为内部结点。
4. 树的深度(高度)- 树的层次从根开始定义起,根为第1层,根的子结点为第2层,以此类推。
树中结点的最大层次称为树的深度(或高度)。
二、二叉树。
1. 二叉树的定义。
- 二叉树是n(n ≥ 0)个结点的有限集合:- 或者为空二叉树,即n = 0。
- 或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
2. 二叉树的特点。
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,次序不能颠倒。
3. 特殊的二叉树。
- 满二叉树。
- 一棵深度为k且有2^k - 1个结点的二叉树称为满二叉树。
满二叉树的特点是每一层上的结点数都是最大结点数。
- 完全二叉树。
- 深度为k的、有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。
完全二叉树的叶子结点只可能在层次最大的两层上出现;对于最大层次中的叶子结点,都依次排列在该层最左边的位置上;如果有度为1的结点,只可能有一个,且该结点只有左孩子而无右孩子。
三、二叉树的存储结构。
1. 顺序存储结构。
- 二叉树的顺序存储结构就是用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素。
线索二叉树
6.4 线索化二叉树从前面的讨论可知,遍历二叉树就是将非线性结构的二叉树线性化,即按一定规则将二叉树中的结点排列成一个线性序列依次访问。
如图6.20(a)所示的二叉树,经中序遍历得到线性序列:BADEC,经前序遍历得到线性序列:ABCDE,经后序遍历得到线性序列:BEDCA。
在这些线性序列中,二叉树中的每个结点(除第一个和最后一个外)有且仅有唯一的一个前趋和唯一的一个后继,很容易找到各个结点的直接前驱和直接后继。
但当以二叉链表作为二叉树的存储结构时,只能找到结点的左、右孩子,而不能直接找到前驱和后继,只有在遍历的动态过程中得到这些信息。
如果将这些信息在第一次遍历时保存起来,在需要再次对二叉树进行“遍历”时就可以将二叉树视为线性结构进行访问,从而简化遍历操作。
那么,如何存储遍历中得到的结点前驱和后继的信息呢?一个简单的办法是在每个结点上增加两个指针域fwd和bkwd,分别指向存储遍历中得到的结点前驱和后继。
fwd L child data R child bkwd这是采用多重链表来表示二叉树。
这种方法虽简单易行,但这种结构的存储密度将大大降低,浪费存储空间。
另一种方法,是利用原有链域L child 和R child的空链域。
在n个结点的二叉链表中有2n个孩子链域,其中仅有n-1个链域是用来指示结点的左右孩子,而另外n+1个链域是空链域。
现在把这些空链域利用起来,使其指向结点的前驱或后继;对那些原来就不为空的链域,则仍然指向左或右孩子。
如果把指向前驱和后继的指针称为线索(Thread),那么,如何区分指向左、右孩子的指针和指向前驱、后继的线索呢?在原结点结构上增加标志域定义为:0 Lchild为左指针,指向左孩子0 Rchild为右指针,指向右孩子ltag=rtag=1 Lchild为左线索,指向前驱 1 Rchild为右线索,指向后继以这种结点构成的二叉链表作为二叉树的存储结构,叫做线索链表,其C语言类型说明如下:Typedef struct ThreadTNode{enum{0,1} ltag, rtag;Elem Type data;Struct ThreadTNode *Lchild, *Rchild;}ThreadTNode, *ThreadTree;为了节省内存空间,我们用C语言的位段方法将结点中的左标志域和右标志域与数据域合并在一个存储单元中(即各用一位表示左标志和右标志,其余各位表示结点值)。
二叉树遍历(前中后序遍历,三种方式)
⼆叉树遍历(前中后序遍历,三种⽅式)⽬录刷题中碰到⼆叉树的遍历,就查找了⼆叉树遍历的⼏种思路,在此做个总结。
对应的LeetCode题⽬如下:,,,接下来以前序遍历来说明三种解法的思想,后⾯中序和后续直接给出代码。
⾸先定义⼆叉树的数据结构如下://Definition for a binary tree node.struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}};前序遍历,顺序是“根-左-右”。
使⽤递归实现:递归的思想很简单就是我们每次访问根节点后就递归访问其左节点,左节点访问结束后再递归的访问右节点。
代码如下:class Solution {public:vector<int> preorderTraversal(TreeNode* root) {if(root == NULL) return {};vector<int> res;helper(root,res);return res;}void helper(TreeNode *root, vector<int> &res){res.push_back(root->val);if(root->left) helper(root->left, res);if(root->right) helper(root->right, res);}};使⽤辅助栈迭代实现:算法为:先把根节点push到辅助栈中,然后循环检测栈是否为空,若不空,则取出栈顶元素,保存值到vector中,之后由于需要想访问左⼦节点,所以我们在将根节点的⼦节点⼊栈时要先经右节点⼊栈,再将左节点⼊栈,这样出栈时就会先判断左⼦节点。
代码如下:class Solution {public:vector<int> preorderTraversal(TreeNode* root) {if(root == NULL) return {};vector<int> res;stack<TreeNode*> st;st.push(root);while(!st.empty()){//将根节点出栈放⼊结果集中TreeNode *t = st.top();st.pop();res.push_back(t->val);//先⼊栈右节点,后左节点if(t->right) st.push(t->right);if(t->left) st.push(t->left);}return res;}};Morris Traversal⽅法具体的详细解释可以参考如下链接:这种解法可以实现O(N)的时间复杂度和O(1)的空间复杂度。
第五章二叉树
树为空
树为空
根的左右子 树都不空
二、二叉树的性质
第1层(根) 第2层 第3层
第4层
1、若层次从1开始,则第i层最多有2 i-1个结点 2、高度为h的二叉树最多有2h -1个结点 3、任何一棵二叉树,若叶子结点数为n0,度为2的结点数 为n2,则n0 = n2 + 1
5.2.2 二叉树的性质
二叉树具有下列重要性质: 性质1: 在二叉树的第i层上至多有2i-1个结点(i>=1)。
二叉树的二叉链表存储表示
Elem val(){return data;} void setVal(const Elem e){data=e;} inline BinTreeNode<Elem>* left(){return lchild;} inline BinTreeNode<Elem>* right(){return rchild;} void setLeft(BinTreeNode<Elem>* left){lchild=left;} void setRight(BinTreeNode<Elem>* right){rchild=right;} bool isLeaf()
Elem data; BinTreeNode * lchild; BinTreeNode * rchild; public:
BinTreeNode(){lchild=rchild=NULL;} BinTreeNode(Elem e,BinNodePtr*l=NULL,
BinNodePtr*r=NULL) {data=e; lchild=l; rchild=r;} ~BinTreeNode(){}
n0,度为2的结点数为n2,则n0=n2+1。
线索二叉树
遍历 线索 二叉 树非
{
while(p->ltag==0) p=p->left; /*从根往下找到"最左"的结
点,即中序序列的开始结点*/
do
{
递归 算法
printf("%c",p->date);/*访问结点*/
p=succ(p);
}while(p!=NULL); }
返回
}
树
数据结构
在中序遍历线索树过程中,按下述两条 原则即可找到后继结点:
– 1) 如果某结点的右线索标志域为1,说明其 右指针域是线索,这个线索所指的即是该结 点的后继结点;
– 2) 如果某结点的右线索标志为0,则其右指 针域是指向右儿子结点的指针,由此结点的 右儿子结点起按左指针域指针逐结点向左查 找,一直找到左线索标志域为1的结点,即 是该结点的后继结点。
{
中
if(p!=NULL)
序
{ inthread(p->left,pre); /*左子树线索化*/
线
if(p->left==NULL)
索
/*若当前结点的左子树为空,则建立指 向其前趋结点的前趋线索*/
化
{
算
p->ltag=1;
法
p->left=pre; }
else
p->ltag=0树;
if (pre!=NULL && pre->right==NULL)
这种结点类型和相应结点的指针类型定义如 下:
typedef struct tnode {
ElemType data; int ltag,rtag; /*ltag和rtag只能取值为0或1*/ struct tnode *left,*right; }tbtree;
线索二叉树
0 A0 0 B1
0 C0
1 D0
1 E1
1F1
1 G1
(b) root
0
1
ห้องสมุดไป่ตู้
0 A0
0 B1
0 C0
0 A0
0 B1
0 C0
1 D0
1 E1
1F1
1 D0
1 E1
1F1
1 G1
1 G1
(c)
(d)
线索二叉树 b—中序 c—前序 d—后序
一旦建立了某种方式的线索二叉树后,用户程序就可以 像操作双向链表一样操作该线索二叉树。
if(tree->current == tree->root) tree->nextComplete = 1;
}
int EndOfNext(ThreadBiTree *tree) //判断是否已到中序线索二叉树的最后一个结点 { return tree->nextComplete; }
例8-3 编写一个程序,首先建立如图8-10(a)所示的不带头结点的二叉树, 然后中序线索化该二叉树,最后用循环结构输出该中序线索化二叉树各结 点的序列信息。
这种算法设计要求分别设计三个函数: First():定位在第一个结点位置; Next():移动到下一个结点位置; End():是否已经到最后下一个结点位置; 当然,还需要一个根据二叉树构造线索二叉树的函数。
typedef struct { ThreadBiNode *root;
ThreadBiNode *current; int nextComplete; }ThreadBiTree;
规定:当某结点的左指针为空时,令该指针指向按某种方法遍历二叉树时 得到的该结点的前驱结点;当某结点的右指针为空时,令该指针指向按某种 方法遍历二叉树时得到的该结点的后继结点。仅仅这样做会使我们不能区分 左指针指向的结点到底是左孩子结点还是前驱结点,右指针指向的结点到底 是右孩子结点还是后继结点。因此我们再在结点中增加两个线索标志位来区 分这两种情况。
线索二叉树的创建及遍历
实验九线索二叉树的创建及遍历实验目的:掌握二叉树的线索链表存储结构,能够实现二叉树的线索链表的创建、遍历等基本操作实验要求:1、认真阅读和掌握教材上和本实验相关的内容及算法2、上机将线索二叉树的线索链表存储表示的创建和遍历算法实现。
3、进行简单的输入输出验证。
实验内容:编程实现二叉树的线索链表存储表示的基本操作,这些基本操作包括:线索二叉树的创建、线索二叉树的中序遍历算法的实现。
要求对程序中的一些关键语句要有注释,并能够进行简单的输入输出验证。
参考代码#include <stdlib.h>#include <stdio.h>#define OVERFLOW 0//线索二叉树的二叉链表存储定义typedef enum PointerTag {LINK=0,THREAD=1};struct BiThrNode{char data;struct BiThrNode * lchild, * rchild;PointerTag LTag, RTag;};typedef struct BiThrNode BiThrNode;typedef BiThrNode * BiThrTree;/*****************************************************************\** 按先序次序输入二叉树中的结点的值(一个字符)构造二叉链表表示的二叉树,* 字符'#'表示空树。
** 例如,一棵二叉树的三种遍历次序为:* 先序:-+a*b-cd/ef 中序:a+b*c-d-e/f 后序:abcd-*+ef/* 程序中应该输入:-+a##*b##-c##d##/e##f##** 又如,一棵二叉树的三种遍历次序为:* 先序:ABDFGCEH 中序:BFDGACHE 后序:FGDBHECA* 程序中应该输入:AB#DF##G##C#EH###*\******************************************************************/ void CreateBiTree(BiThrTree &T){char ch;ch = getchar();if (ch=='#') T=NULL;else{if (!(T = (BiThrNode *)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);T->data = ch;T->LTag = LINK;T->RTag = LINK;CreateBiTree(T->lchild);CreateBiTree(T->rchild);}return;}//CreateBiTreevoid PrintBiTree(BiThrTree T){//按中序遍历次序输出二叉树T中的结点的值(一个字符),二叉树T用二叉链表存储。
莫里斯算法
莫里斯算法莫里斯算法(Morris Traversal)是一种二叉树遍历的算法,通过只使用一个指针和利用线索二叉树的思想,实现对二叉树的中序遍历,时间复杂度为O(n),空间复杂度为O(1)。
1. 线索二叉树线索二叉树是指将一颗二叉树中所有的空指针域指向该节点的直接前驱节点或直接后继节点的二叉树。
线索化后,原本空闲的指针域存储前驱或后继的指针,可以方便地实现对二叉树的遍历。
在递归遍历的过程中,需要记录每个节点是否已经被遍历。
线索二叉树分为前序线索二叉树、中序线索二叉树、后序线索二叉树。
其中,利用中序线索二叉树可以实现对二叉树的中序遍历。
2. Morris遍历算法Morris遍历算法是一种利用线索二叉树对二叉树进行中序遍历的算法,它的核心思想是在遍历节点的过程中,利用空闲的指针域存储前驱或后继的指针,从而实现对二叉树的遍历。
Morris遍历算法对于空间的要求比较严格,它只需要使用一个指针来实现对二叉树的遍历,因此空间复杂度为O(1),非常适合解决空间限制的问题。
在时间复杂度方面,Morris遍历算法需要在遍历每个节点和寻找其前驱节点时,都需要遍历部分节点,因此时间复杂度为O(n)。
具体而言,Morris遍历算法利用线索化指针判断节点是否被访问过,从而实现对二叉树的遍历。
Morris遍历算法的具体流程如下:1)初始化当前节点为根节点2)重复以下操作,直到当前节点为空a. 如果当前节点没有左孩子节点,则输出当前节点,将其右孩子节点作为下一次要访问的节点,更新当前节点为右孩子节点b. 如果当前节点存在左孩子节点,则寻找当前节点的左子树中的最右侧节点,该节点为当前节点的直接前驱节点i. 如果该节点的右孩子指向当前节点,则说明当前节点是第二次访问该节点,将其右孩子设为null,输出该节点,更新当前节点为右孩子节点ii. 如果该节点的右孩子指向null,则说明当前节点为第一次访问该节点,将其右孩子设为当前节点,更新当前节点为左孩子节点Morris遍历算法的特点是利用空闲的指针域存储前驱或后继的指针,实现对二叉树的遍历。
二叉树遍历讲课教案ppt课件
资金是运动的价值,资金的价值是随 时间变 化而变 化的, 是时间 的函数 ,随时 间的推 移而增 值,其 增值的 这部分 资金就 是原有 资金的 时间价 值
6.5 线索二叉树
§ 何谓线索二叉树? § 线索链表的遍历算法 § 如何建立线索链表?
一、问题的提出
顺着某一条搜索路径巡访二叉树 中的结点,使得每个结点均被访问一 次,而且仅被访问一次。
“访问”的含义可以很是随 时间变 化而变 化的, 是时间 的函数 ,随时 间的推 移而增 值,其 增值的 这部分 资金就 是原有 资金的 时间价 值
if (T) {
visit(T->data);
// 访问结点
Preorder(T->lchild, visit); // 遍历左子树
Preorder(T->rchild, visit);// 遍历右子树 }
}
资金是运动的价值,资金的价值是随 时间变 化而变 化的, 是时间 的函数 ,随时 间的推 移而增 值,其 增值的 这部分 资金就 是原有 资金的 时间价 值
资金是运动的价值,资金的价值是随 时间变 化而变 化的, 是时间 的函数 ,随时 间的推 移而增 值,其 增值的 这部分 资金就 是原有 资金的 时间价 值
二、先左后右的遍历算法
先(根)序的遍历算法 中(根)序的遍历算法 后(根)序的遍历算法
资金是运动的价值,资金的价值是随 时间变 化而变 化的, 是时间 的函数 ,随时 间的推 移而增 值,其 增值的 这部分 资金就 是原有 资金的 时间价 值
先(根)序的遍历算法:
若二叉树为空树,则空操作;否则, (1)访问根结点; (2)先序遍历左子树; (3)先序遍历右子树。
二叉树的遍历PPT-课件
4 、二叉树的创建算法
利用二叉树前序遍历的结果可以非常方便地生成给定的
二叉树,具体做法是:将第一个输入的结点作为二叉树的 根结点,后继输入的结点序列是二叉树左子树前序遍历的 结果,由它们生成二叉树的左子树;再接下来输入的结点 序列为二叉树右子树前序遍历的结果,应该由它们生成二 叉树的右子树;而由二叉树左子树前序遍历的结果生成二 叉树的左子树和由二叉树右子树前序遍历的结果生成二叉 树的右子树的过程均与由整棵二叉树的前序遍历结果生成 该二叉树的过程完全相同,只是所处理的对象范围不同, 于是完全可以使用递归方式加以实现。
void createbintree(bintree *t) { char ch; if ((ch=getchar())==' ') *t=NULL; else { *t=(bintnode *)malloc(sizeof(bintnode)); /*生成二叉树的根结点*/ (*t)->data=ch; createbintree(&(*t)->lchild); /*递归实现左子树的建立*/ createbintree(&(*t)->rchild); /*递归实现右子树的建立*/ }
if (s.top>-1) { t=s.data[s.top]; s.tag[s.top]=1; t=t->rchild; }
else t=NULL; }
}
7.5 二叉树其它运算的实现
由于二叉树本身的定义是递归的,因此关于二叉树的许多 问题或运算采用递归方式实现非常地简单和自然。 1、二叉树的查找locate(t,x)
(1)对一棵二叉树中序遍历时,若我们将二叉树严
格地按左子树的所有结点位于根结点的左侧,右子树的所
6-3线索二叉树
第四节线索二叉树如何快捷地找出结点的孩子?如何快捷地找出结点的前驱、后继?①遍历二叉树,形成一个线性序列,再在序列中查找。
笨!②每个结点增加前驱域、后继域。
③利用结点的空链域存储前驱域和后继域。
思想:若结点有左子树,则其lchild域指示其左子树的位置若结点无左子树,则其lchild域指示其前驱结点的位置若结点有右子树,则其rchild域指示其右子树的位置若结点无右子树,则其rchild域指示其后继结点的位置、二叉树的线索化:以某种次序遍历二叉树,遍历过程中对其线索化。
(即将其空链域填上值)试一试:手工实现“二叉树的线索化”程序实现:一、建立线索树1、算法思路:①利用非递归算法:p遍历各结点,pre指向上一个访问的结点。
②每次指针变化前,线索化*p的前驱,*pre的后继。
2、程序typedef enum {START,LEFT,RIGHT} TravFlag;typedef struct{ //栈中元素的类型BiThrNode *p; //指向树中结点TravFlag flag; //*p结点的遍历状态}StackElem;void BiThrTree_PostOrder(BiThrNode *root) //后序线索树{Stack S; StackElem e;BiThrNode *pre=NULL;Stack_Init(S);e.p=root; e.flag=START; Stack_Push(S,e);while(!Stack_Empty(S)){ e=Stack_Gettop(S);switch (e.flag){case START: //第一次{ e.flag=NextDirect(e.flag); Stack_Settop(S,e);if(e.p->lch){e.p=e.p->lch; e.flag=START; Stack_Push(S,e);}break;}case LEFT: //从左子树回来{ e.flag=NextDirect(e.flag); Stack_Settop(S,e);if(e_p->rch){e.p=e.p->rch; e.flag=START; Stack_Push(S,e);}break;}case RIGHT: //从右子树回来。
计算机二级二叉树知识点
计算机二级二叉树知识点1.二叉树的定义:二叉树是一种常见的树形结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。
二叉树的节点结构通常包括一个数据元素和指向左右子节点的指针。
2.二叉树的性质:(1)二叉树的第i层最多有2^(i-1)个节点。
(2)高度为h的二叉树最多有2^h-1个节点。
(3)对于任意一棵二叉树,如果其叶子节点数为n0,度为2的节点数为n2,则n0=n2+1(4)一棵深度为k且节点总数为n的二叉树,当且仅当其满足2^(k-1)<=n<=2^k-1时,才称为完全二叉树。
3.二叉树的分类:(1)满二叉树:除了叶子节点之外,每个节点都有两个子节点,且所有叶子节点在同一层次上。
(2)完全二叉树:最后一层之前的层都是满的,并且最后一层的节点都靠左排列。
(3)平衡二叉树:左右子树的高度差不超过1的二叉树。
(4)线索二叉树:对于每个节点,除了指向其左右子节点的指针外,还包含指向其在其中一种序列下的前驱节点和后继节点的指针。
4.二叉树的遍历方法:(1)前序遍历:先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。
(2)中序遍历:先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。
(3)后序遍历:先递归地遍历左子树,然后递归地遍历右子树,最后访问根节点。
(4)层次遍历:按照从上到下、从左到右的顺序逐层访问每个节点。
5.二叉树:二叉树(Binary Search Tree,BST)是一种特殊的二叉树,它的每个节点的值都大于其左子树中的所有节点值,小于其右子树中的所有节点值。
因此,对于一个二叉树,可以采用中序遍历的方法得到一个有序序列。
二叉树的插入操作:按照二叉树的定义,从根节点开始,将要插入的值与当前节点的值比较,如果小于当前节点的值,则向左子树递归插入,如果大于当前节点的值,则向右子树递归插入,直至找到一个空节点,然后插入新节点。
二叉树的删除操作:删除一个节点需要考虑三种情况:删除节点没有子节点、只有一个子节点、有两个子节点。
线索化二叉树详解
线索化⼆叉树详解线索化⼆叉树详解说明1. 线索化⼆叉树,由字⾯意思,就是将⼆叉树的节点拿线索连接起来2. 实质上,也就是将⼆叉树的叶⼦节点左右指针域彼此连接⼀个节点3. ⼆叉树的⾮叶⼦节点的左右指针域都各⾃连接了⼀个节点,但是叶⼦节点的左右指针域是空的,因此考虑将叶⼦节点的左右指针域按照某种遍历次序连接起来4. 按照⼆叉树的遍历⽅式,有前序中序后续三种遍历⽅式,因此可以形成三种链式结构5. 每个叶⼦节点前⼀个节点称为前驱节点,后⼀个节点称为后继节点,如果当前节点没有前驱或者后继节点,则直接置为空6. 以中序线索化⼆叉树为例,编写中序线索化⼆叉树的⽅法7. 先判断当前节点是否为空,如果为空,则直接返回8. 否则先向左递归线索化⼆叉树的左⼦树9. 然后再线索化当前节点,定义属性pre保存当前节点的前⼀个节点,因此当前节点的前⼀个节点置为pre10. 注意当前节点的后⼀个节点,需要⽤pre保存当前节点,然后遍历到后⼀个节点,然后⽤pre指向11. 注意第⼀个节点和最后⼀个节点12. 中序线索化如下,前序和后续类似源码及分析节点类//创建节点class HeroNode{//编号private int no;//姓名private String name;//左⼦树private HeroNode left;//右⼦树private HeroNode right;//线索化的前驱节点类型,是节点还是树,假定 0 为树, 1 为节点private int leftType;//线索化的后继节点类型private int rightType;public int getLeftType() {return leftType;}public void setLeftType(int leftType) {this.leftType = leftType;}public int getRightType() {return rightType;}public void setRightType(int rightType) {this.rightType = rightType;}//构造器,左⼦树和右⼦树默认为空public HeroNode(int no, String name) {this.no = no; = name;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public String getName() {return name;}public void setName(String name) { = name;}public HeroNode getLeft() {return left;}public void setLeft(HeroNode left) {this.left = left;}public HeroNode getRight() {return right;}public void setRight(HeroNode right) {this.right = right;}@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +'}';}//删除节点/**** @param no 要删除的节点编号*/public void delNode(int no){//判断当前节点的左⼦树是否为空,如果不为空,再判断是否为要删除的节点if (this.left != null && this.left.no == no){this.left = null;}//判断当前节点的右⼦树是否为空,如果不为空,再判断是否为要删除的节点if (this.right != null && this.right.no == no){this.right = null;}//否则向左向右递归if (this.left != null){this.left.delNode(no);}if (this.right != null){this.right.delNode(no);}}//前序中序后序遍历主要区别在于⽗节点的输出位置不同,/*** 前序遍历先输出⽗节点信息,然后判断左⼦树是否为空,如果不为空,则递归前序遍历 * 然后再判断右⼦树是否为空,如果不为空,则递归遍历前序遍历*///前序遍历public void preOrder(){//先输⼊当前节点信息System.out.println(this);//然后判断当前节点的左⼦树是否为空if (this.left != null){this.left.preOrder();}//再判断当前节点的右⼦树是否为空if (this.right != null){this.right.preOrder();}}//中序遍历public void infixOrder(){//先判断当前节点的左⼦树是否为空if (this.left != null){this.left.infixOrder();}//再输出当前节点的信息System.out.println(this);//然后再判断当前节点的右⼦树是否为空if (this.right != null){this.right.infixOrder();}}//后序遍历public void postOrder(){//先判断当前节点的左⼦树是否为空if (this.left != null){this.left.postOrder();}//再判断当前节点的右⼦树是否为空if (this.right != null){this.right.postOrder();}//最后输出当前节点的信息System.out.println(this);}//前序查找/*** 前序遍历查找* @param no 要查找的节点编号* @return 返回查找的结果*/public HeroNode preOrderSearch(int no){//先判断当前节点是不是要查找的节点if (this.no == no){return this;}//如果当前节点不是要查找的节点,则判断左⼦树是否为空,若不为空,则递归前序查找 HeroNode resNode = null;if (this.left != null){resNode = this.left.preOrderSearch(no);}//如果在左⼦树找到,则直接返回if (resNode != null){return resNode;}//如果左⼦树也没有找到,则判断右⼦树是否为空,并递归if (this.right != null){resNode = this.right.preOrderSearch(no);}return resNode;}//中序查找/*** 中序遍历查找* @param no 要查找的节点编号* @return 返回查找的结果*/public HeroNode infixOrderSearch(int no){//先判断当前节点左⼦树是否为空,如果不为空则递归中序查找//定义变量保存查找的结果HeroNode resNode = null;if (this.left != null){resNode = this.left.preOrderSearch(no);}//如果查找到,则直接返回if (resNode != null){return resNode;}//如果没有找到,判断当前节点是不是要查找的节点if (this.no == no){return this;}//如果还没有找到,则判断右⼦树是否为空,不为空则递归中序查找if (this.right != null){resNode = this.right.infixOrderSearch(no);}return resNode;}//后序查找/*** 后续遍历查找* @param no 要查找的节点编号* @return 返回查找的结果*/public HeroNode postOrderSearch(int no){//判断当前节点的左⼦树是否为空,如果不为空,则递归后续查找HeroNode resNode = null;if (this.left != null){resNode = this.left.postOrderSearch(no);}if (resNode != null){return resNode;}if (this.right != null){resNode = this.right.postOrderSearch(no);}if (resNode != null){return resNode;}if (this.no == no){return this;}return resNode;}}线索化⼆叉树类//创建⼀颗线索化⼆叉树class ThreaderBinaryTree{//⼆叉树必有根节点private HeroNode root;//定义变量指向前驱节点,默认为空private HeroNode pre = null;public void setRoot(HeroNode root) {this.root = root;}//编写中序线索化⼆叉树的⽅法/**** @param node node为当前要中序线索化的节点*/public void infixThreadedBinaryTree(HeroNode node){//先判断当前节点是否为空if (node == null){return;}//如果不为空,先线索化左⼦树infixThreadedBinaryTree(node.getLeft());//再线索化当前节点//当前节点的前驱节点为pre,后继节点需要在下⼀个节点连通,因为是单向的 //设置当前节点的前驱节点,并设置前驱节点类型if (node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}//设置当前节点的后继节点及其类型if (pre != null && pre.getRight() == null){pre.setRight(node);pre.setRightType(1);}//让pre指向当前节点pre = node;//最后再线索化右⼦树infixThreadedBinaryTree(node.getRight());}//重载线索化的⽅法public void infixThreadedBinaryTree(){this.infixThreadedBinaryTree(root);}//删除节点/**** @param no 要删除的节点编号*/public void delNode(int no){//先判断⼆叉树是否为空if (this.root != null){//再判断当前root节点是不是要删除的节点if (this.root.getNo() == no){root = null;}else {this.root.delNode(no);}}else {System.out.println("⼆叉树为空,不能删除...");}}//前序遍历public void preOrder(){if (this.root != null){this.root.preOrder();}else {System.out.println("⼆叉树为空...");}}//中序遍历public void infixOrder(){if (this.root != null){this.root.infixOrder();}else {System.out.println("⼆叉树为空...");}}//后续遍历public void postOrder(){if (this.root != null){this.root.postOrder();}else {System.out.println("⼆叉树为空...");}}//前序查找public HeroNode preOrderSearch(int no){if (this.root != null){return this.root.preOrderSearch(no);}else {return null;}}//中序查找public HeroNode infixOrderSearch(int no){if (this.root != null){return this.root.infixOrderSearch(no);}else {return null;}}//后续查找public HeroNode postOrderSearch(int no){if (this.root != null){return this.root.postOrderSearch(no);}else {return null;}}}测试类public static void main(String[] args) {ThreaderBinaryTree threaderBinaryTree = new ThreaderBinaryTree(); HeroNode root = new HeroNode(1,"tom");HeroNode node2 = new HeroNode(3,"jack");HeroNode node3 = new HeroNode(6,"smith");HeroNode node4 = new HeroNode(8,"king");HeroNode node5 = new HeroNode(10,"mary");HeroNode node6 = new HeroNode(14,"dop");root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);threaderBinaryTree.setRoot(root);//进⾏线索化threaderBinaryTree.infixThreadedBinaryTree();//测试线索化的结果System.out.println("node5的前⼀个节点 = " + node5.getLeft());System.out.println("node5的后⼀个节点 = " + node5.getRight());}。
线索二叉树(图)
线索二叉树:遍历二叉树:实际上是对二叉树(非线性结构)进行的线性化操作,即以一定规则将二叉树中的结点排列成一个线性序列(先序序列、中序序列和后序序列)。
举例:图6.9所示的二叉树中的结点,按中序遍历可得到中序序列:a+b*c-d-e/f,其中‘c’的前驱为‘*’,后继为‘-’。
当以二叉链表作为二叉树的存储结构时,只能找到结点的左右孩子信息,而不能直接得到结点在任一线性序列中的前驱和后继信息,因为这种信息只有在遍历的动态过程中才能得到。
如何保存这种在遍历过程中得到的结点的前驱和后继信息呢?方法一:在二叉链表的每个结点上增加两个指针域fwd和bkwd,分别指向在依任一次序遍历时得到的前驱和后继信息。
(大大影响存储密度)方法二:利用二叉链表中的空链域来存放结点的前驱和后继信息。
(在有n个结点的二叉链表中必定存在n+1个空链域!)(不影响存储密度)为此,可以将二叉链表中的结点结构作如下修改:lchild LTag data RTag rchild其中:Ltag = 0 lchild域指示结点的左孩子1 lchild域指示结点的前驱Rtag = 0 rchild域指示结点的右孩子1 rchild域指示结点的后继我们把如此修改后的二叉链表称为二叉树的线索链表,其中指向结点前驱和后继的指针称为线索。
相应地,把添加线索后的二叉树称为线索二叉树(Threaded Binary Tree)。
对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
举例:图6.11(a)所示为中序线索二叉树,与其对应的中序线索链表如图 6.11(b)所示。
其中实线为指针(指向左、右子树),虚线为线索(指向前驱和后继)。
在线索树上进行遍历,只要找到序列中的第一个结点,然后依次找结点的后继直到其后继为空时而停止。
关键是如何在线索树中找结点的后继?二叉树的二叉线索存储表示:(p133-134)线索二叉树的遍历:(以中序线索二叉树为例,即中序遍历二叉线索树)算法6.5二叉树的线索化:(以中序线索化为例,即通过中序遍历建立中序线索链表)算法6.6,算法6.7。
数据结构——用C语言描述(第3版)教学课件第6章 树与二叉树
6.2 二叉树 6.2.1 二叉树的定义与基本操作 6.2.2 二叉树的性质 6.2.3 二叉树的存储结构
6.2.1 二叉树的定义与基本操作 定义:我们把满足以下两个条件的树型结构叫做二 叉树(Binary Tree): (1)每个结点的度都不大于2; (2)每个结点的孩子结点次序不能任意颠倒。
有序树:在树T中,如果各子树Ti之间是有先后次序的,则称为有序树。 森林:m(m≥0)棵互不相交的树的集合。将一棵非空树的根结点删去,树就变成一 个森林;反之,给森林增加一个统一的根结点,森林就变成一棵树。
同构:对两棵树,通过对结点适当地重命名,就可以使两棵树完全相等(结点对应相 等,对应结点的相关关系也像等),则称这两棵树同构。
二叉树的基本结构由根结点、左子树和右子树组成
如图示
LChild Data RChild
Data
LChild RChild
用L、D、R分别表示遍历左子树、访问根结点、遍 历右子树,那么对二叉树的遍历顺序就可以有:
(1) 访问根,遍历左子树,遍历右子树(记做DLR)。 (2) 访问根,遍历右子树,遍历左子树(记做DRL)。 (3) 遍历左子树,访问根,遍历右子树(记做LDR)。 (4) 遍历左子树,遍历右子树,访问根 (记做LRD)。 (5) 遍历右子树,访问根,遍历左子树 (记做RDL)。 (6) 遍历右子树,遍历左子树,访问根 (记做RLD)。
(8) NextSibling(Tree,x): 树Tree存在,x是Tree中的某个结点。若x不 是其双亲的最后一个孩子结点,则返回x后面的下一个兄弟结点,否则 返回“空”。
基本操作:
(9) InsertChild(Tree,p,Child): 树Tree存在,p指向Tree 中某个结点,非空树Child与Tree不相交。将Child插入Tree中, 做p所指向结点的子树。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
青岛理工大学数据结构课程实验报告
void PreOrderTraverse(BiTree T,Status(*Visit)(TElemType e)){
if(T){
Visit(T->data);//首先访问根结点
PreOrderTraverse(T->lchild,Visit);//然后递归遍历左子树
PreOrderTraverse(T->rchild,Visit);//最后递归遍历右子树}}
//中序遍历时先递归遍历左子树,然后访问根结点,最后递归遍历右子树;后序遍历时先递归遍历左子树,然后递归遍历右子树,最后
访问根结点
3、//先把栈及队列相关操作的头文件包括进来
1)根指针入栈,
2)向左走到左尽头(入栈操作)
3)出栈,访问结点
4)向右走一步,入栈,循环到第二步,直到栈空
//层次遍历时,若树不空,则首先访问根结点,然后,依照其双亲结
点访问的顺序,依次访问它们的左、右孩子结点;
4.首先建立二叉线索存储:包含数据域,左右孩子指针以及左右标志
typedef enum { Link=0,Thread=1 } PointerTag;
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild,*rchild;//左右孩子指针
PointerTag LTag,RTag;//左右标志
}BiThrNode, *BiThrTree;
建立前驱线索和后继线索,并用中序遍历进行中序线索化,然后最
后一个结点线索化
调
试
过
程
及
实
验
结
果
把测试数据放在f:\\file\\data.txt里,测试数据为:1 2 4 0 0 0 3 5 0 0 0
总访问结点是指访问该结点的数据域,弄清楚各个指针所指的类型。