数据结构(c#语言实现)
第8章 树与二叉树
前言
树是一种非线性数据结构,存在一对多的关系,本章重点讨论二叉树的性质、存储结构、遍历算法以及线索二叉树的定义和相关操作的实现
1.1 树的定义与基本术语
1.1.1 树的定义
- 递归形式的定义:树是由n个节点组成的有限集合
1.1.2 基本术语
-
结点——表示树集合中的一个数据结构。
-
子结点与父结点——一个结点的前一个结点称为该结点的父结点,该结点的后一个结点成为子结点。
-
兄弟结点——同一个父结点的子结点间是兄弟关系。
-
祖先结点与后代结点——结点n的所有子结点以及子结点的子结点构成结点n的后代结点;从根结点到结点n经历的所有结点构成n的祖先结点。
-
(1)结点的度——结点所拥有子树的根数
(2)树的度——树中各结点度的最大值 -
叶子结点——度为0的结点,又称终端结点
-
边——若结点M是结点N的父结点,用一条线将这两个连接起来形成的一条分支
-
路径与路径长度——若N1,N2,…,Nk是树中的结点构成的一个序列,且相邻两个结点都是树的边,则称该序列为从N1到Nk的一条路径;路径上边的数目称为路径长度
-
结点层次与根的深度——根结点层次为0,子结点层次依次递增,兄弟结点层次相同,结点的最大层次深度称为根的深度
-
树的五种基本形态
1.2 二叉树
1.2.1 二叉树的定义
- 递归形式的定义:二叉树是由n个结点组成的有限集合,树包括次序且树的度为 2 。
1.2.2 二叉树的性质
- 性质一
- 度为m的树至少有m+1个结点
- 性质二
- 二叉树第i层的结点数目最多为2的i次方
- 性质三
- 在深度为k的二叉树中,最多有(2的k+1次方-1)个结点
- 性质四
- 含有n个结点的m次树,n=n0+n1+…+nm,所有结点度之和=n1+2n2+…+mnm
- 性质五
- 对于含n个结点的树,无论度为多少,其所有结点度之和均为n-1
- 性质六
- 如果一棵完全二叉树有n个结点,则其深度为k=log2n
- 性质七
- (1)满二叉树一定是完全二叉树;完全二叉树只有最下面一层不满;
- (2)完全二叉树最下面一层结点都集中于最左边的若干位置上
- (3)完全二叉树最多只有最下面两层结点的度可以小于2
- 性质八
若将一棵具有n个结点的完全二叉树按顺序表示,编号为i的结点,有如下规律:
- (1)若i=0,则结点i为根结点;若i≠0,则结点i的双亲是编号为(i-1) /2(取整)的结点。
- (2)若2i+1≤n-1,则i的左孩子是编号为2i+1的结点;若2i+1>n-1 ,则i无左孩子。
- (3)若2i+2≤n-1,则i的右孩子是编号为2i+2的结点;若2i+2>n-1,则i无右孩子。
1.2.3 二叉树的链式存储结构
1. 二叉树的结点类
(1) 存储方法
数据域data,表示结点的数据元素;
左链域left,指向该结点的左子结点;
右链域right,指向该结点的右子结点。
(2) 代码实现
using System;
using System.Collections.Generic;
namespace CSharpEight
{
public class BinaryTreeNode<T>
{
private T data;//据域
private BinaryTreeNode<T> left, right;//左链域、右链域
//构造缺省值的函数
public BinaryTreeNode()
{
left = right = null;
}
//构造含一个参数的函数
public BinaryTreeNode(T d)
{
data = d;
left = right = null;
}
//获取或设置据域
public T Data
{
get { return data; }
set { data = value; }
}
//获取或设置左链域
public BinaryTreeNode<T> Left
{
get { return left; }
set { left = value; }
}
//获取或设置右链域
public BinaryTreeNode<T> Right
{
get { return right; }
set { right = value; }
}
}
}
2. 二叉树类
public class BinaryTree<T>
{
protected BinaryTreeNode<T> root;//指向二叉树的根节点
//获取或设置根节点
public BinaryTreeNode<T> Root
{
get { return root; }
set { root = value; }
}
//构造缺省值的函数
public BinaryTree()
{
root = null;
}
}
1.2.4 二叉树的遍历
1. 按递归方法
(1)遍历
先根次序:访问根结点,遍历左子树,遍历右子树。
中根次序:遍历左子树,访问根结点,遍历右子树。
后根次序:遍历左子树,遍历右子树,访问根结点。
(2)代码实现
在Node类添加
//遍历的递归算法
//先根次序
public void ShowPreOrder()
{
Console.Write(this.Data + " ");//输出根
BinaryTreeNode<T> q = this.left;//从左链域开始
if (q != null)
q.ShowPreOrder();//左链域不为空,递归调用
q = this.Right;//左链域为空,从右链域开始
if (q != null)
q.ShowPreOrder();//右链域不为空,递归调用
}
//存放值于表中
public void TraversalPreOrder(IList<T>sql)
{
sql.Add(this.Data);//将根存于表中
BinaryTreeNode<T> q = this.left;//从左链域开始
if (q != null)
q.TraversalPreOrder(sql);//左链域不为空,递归调用
q = this.Right;//左链域为空,从右链域开始
if (q != null)
q.TraversalPreOrder(sql);//右链域不为空,递归调用
}
//中根次序
public void ShowInOrder()
{
BinaryTreeNode<T> q = this.left;//从左链域开始
if (q != null)
q.ShowInOrder();//左链域不为空,递归调用
Console.Write(this.Data + " ");//输出根
q = this.Right;//左链域为空,从右链域开始
if (q != null)
q.ShowInOrder();//右链域不为空,递归调用
}
//存放值于表中
public void TraversalInOrder(IList<T> sql)
{
BinaryTreeNode<T> q = this.left;//从左链域开始
if (q != null)
q.TraversalInOrder(sql);//左链域不为空,递归调用
sql.Add(this.Data);//将根存于表中
q = this.Right;//左链域为空,从右链域开始
if (q != null)
q.TraversalInOrder(sql);//右链域不为空,递归调用
}
//后根次序
public void ShowPostOrder()
{
BinaryTreeNode<T> q = this.left;//从左链域开始
if (q != null)
q.ShowPostOrder();//左链域不为空,递归调用
q = this.Right;//左链域为空,从右链域开始
if (q != null)
q.ShowPostOrder();//右链域不为空,递归调用
Console.Write(this.Data + " ");//输出根
}
//存放值于表中
public void TraversalPostOrder(IList<T> sql)
{
BinaryTreeNode<T> q = this.left;//从左链域开始
if (q != null)
q.TraversalPostOrder(sql);//左链域不为空,递归调用
q = this.Right;//左链域为空,从右链域开始
if (q != null)
q.TraversalPostOrder(sql);//右链域不为空,递归调用
sql.Add(this.Data);//将根存于表中
}
在Tree类添加
/*按先根次序遍历二叉树
(1)访问当前结点;
(2)若当前结点的左子树不空,则沿着left链进入该结点的左子树进行遍历。
(3)若当前结点的右子树不空,则沿着right链进入该结点的右子树进行遍历。
*/
public void ShowPreOrder()
{
Console.Write("先根次序:");
if (root != null)
root.ShowPreOrder();
Console.WriteLine();
}
public List<T> TraversalPreOrder()
{
List<T> sql = new List<T>();
if (root != null)
root.TraversalPreOrder(sql);
return sql;
}
//按中根次序遍历二叉树
public void ShowInOrder()
{
Console.Write("中根次序:");
if (root != null)
root.ShowInOrder();
Console.WriteLine();
}
public List<T> TraversalInOrder()
{
List<T> sql = new List<T>();
if (root != null)
root.TraversalInOrder(sql);
return sql;
}
//按后根次序遍历二叉树
public void ShowPostOrder()
{
Console.Write("后根次序:");
if (root != null)
root.ShowPostOrder();
Console.WriteLine();
}
public List<T> TraversalPostOrder()
{
List<T> sql = new List<T>();
if (root != null)
root.TraversalPostOrder(sql);
return sql;
}
2. 非递归方法
(1)设置一个栈s来记录经过的路径
变量p从根结点开始,如果p不空或栈s不空时,循环执行以下操作,直到扫描完二叉树且栈为空。
- 如果p不空,表示扫描到一个结点,将p结点入栈(s.Push(p)),进入其左子树(p=p.Left)。
- 如果p为空并且栈s不空,表示已走过一条路径,必须返回一步以寻找另一条路径。置p指向出栈的结点(p=s.Pop()),访问p结点,再进入p的右子树(p=p.Right) 。
(2)代码实现
/*非递归中根次序遍历二叉树(利用栈保存路径)
1.中根次序遍历规则: 在每个结点处,先选择遍历左子树,其后必须返回该结点,对其进行访问,然后遍历右子树。
2.设置一个栈s来记录经过的路径。变量p从根结点开始,如果p不空或栈s不空时,循环执行以下操作,直到扫描完二叉树且栈为空。
(1)如果p不空,表示扫描到一个结点,将p结点入栈(s.Push(p)),进入其左子树(p=p.Left)。
(2)如果p为空并且栈s不空,表示已走过一条路径,必须返回一步以寻找另一条路径。
置p指向出栈的结点(p=s.Pop()),访问p结点,再进入p的右子树(p=p.Right)。
*/
public void ShowInOrderNR()
{
Stack<BinaryTreeNode<T>> s = new Stack<BinaryTreeNode<T>>(100);
BinaryTreeNode<T> p = root;
Console.Write("非递归中根次序:");
while (p != null || s.Count != 0)//p非空或栈非空时
{
if (p != null)
{
s.Push(p);//p结点入栈
p = p.Left;//进入左子树
}
else//p为空且栈非空时
{
p = s.Pop();//p指向出栈的结点
Console.Write(p.Data + " ");//访问结点
p = p.Right;//进入右子树
}
}
Console.WriteLine();
}
3. 按层次方法
(1)设置一个队列变量q
结点变量p从根开始,当p不为空时,循环顺序执行以下操作:
- 访问p结点。
- 如果p的left链不空,将p结点的左孩子加入队列q(入队操作q.Enqueue(p.Left))。
- 如果p的right链不空,将p结点的右孩子加入队列q(入队操作q.Enqueue(p.Right))。
- 如果队列q为非空,设置p指向队列q出队的结点(p=Dequeue()),否则置p为null。
(2)代码实现
在Tree类添加
/*按层次遍历二叉树(利用队列辅助输出)
设置一个队列变量q。结点变量p从根开始,当p不为空时,循环顺序执行以下操作:
(1)访问p结点。
(2)如果p的left链不空,将p结点的左孩子加入队列q(入队操作q.Enqueue(p.Left))。
(3)如果p的right链不空,将p结点的右孩子加入队列q(入队操作q.Enqueue(p.Right))。
(4)如果队列q为非空,设置p指向队列q出队的结点(p=Dequeue()),否则置p为null。
*/
public void ShowByLevel()
{
Queue<BinaryTreeNode<T>> q = new Queue<BinaryTreeNode<T>>(100);
BinaryTreeNode<T> p = root;
Console.Write("层次遍历:");
while (p != null)
{
Console.Write(p.Data + " ");
if (p.Left != null)
q.Enqueue(p.Left);//p的左子节点入队
if (p.Right != null)
q.Enqueue(p.Right);//p的右子节点入队
if (q.Count != 0)
p = q.Dequeue();//队列不空,p指向出队的结点
else
p = null;//当队列为空,p置为空
}
Console.WriteLine();
}
1.2.5 二叉树的构建
1. 建立链式结构的二叉树
(1)方法
对于一棵已经顺序存储的完全二叉树,由二叉树的性质五可知,第0个结点为根结点,第i个结点的左孩子为第2i+1个结点,右孩子为第2i+2个结点。
(2)代码实现
在Tree类添加
//构建链式存储的二叉树
public static BinaryTree<T> ByOneList(IList<T> t)
{
int n = t.Count;
BinaryTree<T> bt = new BinaryTree<T>();
if (n == 0)
{
bt.Root = null;
return bt;
}
int i, j;
BinaryTreeNode<T>[] q = new BinaryTreeNode<T>[n];
T v;
for (i = 0; i < n; i++)
{
v = t[i];
q[i] = new BinaryTreeNode<T>(v);
}
for (i = 0; i < n; i++)
{
j = 2 * i + 1;
if (j < n)
q[i].Left = q[j];
else
q[i].Left = null;
j++;
if (j < n)
q[i].Right = q[j];
else
q[i].Right = null;
}
bt.Root = q[0];
return bt;
}
2. 根据广义表表达式建立二叉树
广义表形式有时不能唯一表示一棵二叉树,原因在于无法明确左右子树。例如,广义表A(B)没有表达出结点B是结点A的左子结点还是右子结点。为了唯一表示一棵二叉树,必须重新定义广义表的形式。
在广义表中,除数据元素外还定义四个边界符号:
- 1.空子树符NullSubtreeFlag,如 ‘^’,以标明非叶子结点的空子树。
- 2.左界符LeftDelimitFlag,如‘(’,以标明下一层次的左边界;
- 3.右界符RightDelimitFlag,如‘)’,以标明下一层次的右边界。
- 4.中界符MiddleDelimitFlag,如‘,’,以标明某一层次的左右子树的分界。
依次读取二叉树的广义表表示序列中的每个符号元素,检查其内容,如果
- 遇到有效数据值,则建立一个二叉树结点对象;扫描下一元素,如果
它为LeftDelimitFlag,则LeftDelimitFlag和RightDelimitFlag之间是该结点的左子树与右子树,递归调用,分别建立左、右子树,返回结点对象。
没有遇到LeftDelimitFlag,表示该结点是叶子结点。 - 遇到NullSubtreeFlag,表示空子树,返回null值。
//广义表的分界符
public struct ListFlagsStruc<T>
{
public T NullSubtree;//空子树符
public T LeftDelimit;//左界符
public T RightDelimit;//右界符
public T MiddleDelimit;//中界符
}
public static ListFlagsStruc<T> ListFlags;//记录广义表的分界符
/*根据广义表表达式建立二叉树
依次读取二叉树的广义表表示序列中的每个符号元素,检查其内容,
(1)如果遇到有效数据值,则建立一个二叉树结点对象;扫描下一元素。
(2)如果它为LeftDelimitFlag,则LeftDelimitFlag和RightDelimitFlag之间是该结点的左子树与右子树,递归调用,分别建立左、右子树,返回结点对象。
(3)没有遇到LeftDelimitFlag,表示该结点是叶子结点。
(4)遇到NullSubtreeFlag,表示空子树,返回null值。
*/
public static BinaryTree<T> ByOneList(IList<T> sList, ListFlagsStruc<T> ListFlags)
{
BinaryTree<T>.ListFlags = ListFlags;
BinaryTree<T>.idx = 0;//初始化递归变量
BinaryTree<T> bt = new BinaryTree<T>();
if (sList.Count > 0)
bt.Root = RootByOneList(sList);
else
bt.Root = null;
return bt;
}
//建立树
private static BinaryTreeNode<T> RootByOneList(IList<T> sList)
{
BinaryTreeNode<T> p = null;
T nodeData = sList[idx];
if (IsData(nodeData))
{
p = new BinaryTreeNode<T>(nodeData);//有效数据,建立节点
idx++;
nodeData = sList[idx];
if (nodeData.Equals(ListFlags.LeftDelimit))
{
idx++;//左边界,跳过
p.Left = RootByOneList(sList);//建立左子树,递归
idx++;//中界符,跳过
p.Right = RootByOneList(sList);//建立右子树,递归
idx++;//右边界,跳过
}
}
if (nodeData.Equals(ListFlags.NullSubtree))
idx++;//空子树,跳过
return p;
}
//判断是否为有效数据
private static bool IsData(T nodeValue)
{
if (nodeValue.Equals(ListFlags.NullSubtree))
return false;
if (nodeValue.Equals(ListFlags.LeftDelimit))
return false;
if (nodeValue.Equals(ListFlags.RightDelimit))
return false;
if (nodeValue.Equals(ListFlags.MiddleDelimit))
return false;
else
return true;
}
3. 根据先根和中根序列建立二叉树
设二叉树的先根及中根遍历序列分别为preList和inList 。
- 确定根元素。由先根次序知,二叉树的根为preList[0]。查找它在inList中的位置k = inList.IndexOf(rootData);
- 确定根的左子树的相关序列。由中根次序知, inList[k]之前的结点在根的左子树上, inList[k]之后的结点在根的右子树上。因此根的左子树由k个结点组成:
先根序列——preList[1],…,preList [k]。
中根序列——inList[0] ,…,inList [k-1]。 - 根据左子树的先根序列和中根序列建立左子树,递归。
- 确定根的右子树的相关序列。右子树由n-k-1个结点组成:
先根序列——preList [k+1],…,preList [n-1]。
中根序列——inList [k+1] ,…,inList [n-1]。 - 根据右子树的先根序列和中根序列建立右子树,递归。
/*根据先根和中根次序建立二叉树
(1)确定根元素。由先根次序知,二叉树的根为preList[0]。查找它在inList中的位置k = inList.IndexOf(rootData);
(2)确定根的左子树的相关序列。由中根次序知, inList[k]之前的结点在根的左子树上, inList[k]之后的结点在根的右子树上。因此根的左子树由k个结点组成:
先根序列——preList[1],…,preList [k]。
中根序列——inList[0] ,…,inList [k-1]。
(3)根据左子树的先根序列和中根序列建立左子树,递归。
(4)确定根的右子树的相关序列。右子树由n-k-1个结点组成:
先根序列——preList [k+1],…,preList [n-1]。
中根序列——inList [k+1] ,…,inList [n-1]。
(5)根据右子树的先根序列和中根序列建立右子树,递归。
*/
public static BinaryTree<T> ByTwoList(IList<T> preList, IList<T> inList)
{
BinaryTree<T> bt = new BinaryTree<T>();
bt.Root = RoomByTwoList(preList, inList);
return bt;
}
//建立树
private static BinaryTreeNode<T> RoomByTwoList(IList<T> preList, IList<T> inList)
{
BinaryTreeNode<T> p = null;
T rooData;
int i, k, n;
IList<T> presub = new List<T>();//先根序列
IList<T> insub = new List<T>();//中根序列
n = preList.Count;
if (n > 0)
{
rooData = preList[0];//当前根节点
p = new BinaryTreeNode<T>(rooData);
k = inList.IndexOf(rooData);
Console.WriteLine("\t current root = " + rooData + "\t k = " + k);
for (i = 0; i < k; i++)//准备当前根节点的左子树先根序列
presub.Add(preList[i + 1]);
for (i = 0; i < k; i++)//准备当前根节点的左子树中根序列
insub.Add(inList[i]);
p.Left = RoomByTwoList(presub, insub);//建立当前根节点的左子树,递归
presub.Clear();
for (i = 0; i < n - k - 1; i++)//准备当前根节点的右子树先根序列
presub.Add(preList[k + i + 1]);
insub.Clear();
for (i = 0; i < n - k - 1; i++)//准备当前根节点的右子树中根序列
insub.Add(inList[k + i + 1]);
p.Right = RoomByTwoList(presub, insub);//建立当前根节点的右子树,递归
}
return p;
}
1.2.6 线索二叉树
1. 线索二叉树的实现
- n个结点的二叉树,总共有2n个链,仅需要n-1个链来指明各结点间的关系,其余n+1个链均为空值。利用空链作线索来指明结点在某种遍历次序下的前驱和后继结点,构成线索二叉树。对二叉树以某种次序进行遍历并加上线索的过程称为线索化。
- 线索二叉树中,原先非空的链保持不变,仍然指向该结点的左、右子结点,它记录的是结点间的层次关系。原先空的左链用来指向遍历中该结点的前驱结点,原先空的右链指向后继结点,它记录的是结点间在遍历时的顺序关系。为了区别每条链是否是一个线索,可以在二叉树的结点结构中设置两个状态字段lefttag和righttag,用以标记相应链的状态。
2. 二叉树的中序线索化
设p指向一棵二叉树的某个结点,front指向p的前驱结点,它的初值为null。当p非空时,执行以下操作:
- 中序线索化p结点的左子树。
- 如果p的左子树为空,设置p的lefttag标记为true,它的left链为指向前驱结点front的线索。
- 如果p的右子树为空,设置p的righttag标记为true
- 如果前驱结点front非空并且它的右链为线索,设置front的right链为指向p的线索。
- 移动front,使front指向p。
- 中序线索化p结点的右子树。
- 如果一开始让p指向二叉树的根结点root,则上述过程线索化整个二叉树。
3. 线索二叉树的遍历
(1)中序线索二叉树中查找中根次序的后继结点
设p指向当前结点,执行以下操作:
- 如果p结点的右子树为线索,则p的right链为其后继结点,设置p为该结点。
- 否则说明p的右子树为非空,则p的后继结点是p的右子树上第一个中序访问的结点,即p的右孩子的最左边的子孙结点,设置p为该结点。
- 返回p,作为当前结点在中根次序下的后继结点。
(2) 中序线索二叉树的中根次序遍历
- 寻找第一个访问结点。它是根的左子树上最左边的子孙结点,用p指向该结点。
- 访问p结点。
- 找到p的后继结点,用p指向该结点,跳转到上一步,直至p为null,说明已访问了序列的最后一个结点。
(3)先根次序遍历
- (1)寻找第一个访问结点。它是根结点,用p指向该结点
- (2)访问p结点。
- (3)找到p的后继结点,用p指向该结点,跳转到上一步,直至p为null,说明已访问了序列的最后一个结点。
结点类
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpEight
{
public class ThreadBinaryTreeNode<T>
{
private T data;//数据元素
private ThreadBinaryTreeNode<T> left, right;//指向左右结点的链
private bool lefttag;//左线索标志
private bool righttag;//右线索标志
//构造缺省值的函数
public ThreadBinaryTreeNode()
{
left = right = null;
lefttag = righttag = false;
}
//构造有值结点
public ThreadBinaryTreeNode(T d)
{
data = d;
left = right = null;
lefttag = righttag = false;
}
//设置或获取左链
public ThreadBinaryTreeNode<T> Left
{
get { return left; }
set { left = value; }
}
//设置或获取右链
public ThreadBinaryTreeNode<T> Right
{
get { return right; }
set { right = value; }
}
//设置或获取数据元素
public T Data
{
get { return data; }
set { data = value; }
}
//设置或获取左线索
public bool LeftTag
{
get { return lefttag; }
set { lefttag = value; }
}
//设置或获取右线索
public bool RightTag
{
get { return righttag; }
set { righttag = value; }
}
//查找中根次序的后继节点
/*
设p指向当前结点,执行以下操作:
(1)如果p结点的右子树为线索,则p的right链为其后继结点,设置p为该结点。
(2)否则说明p的右子树为非空,则p的后继结点是p的右子树上第一个中序访问的结点,即p的右孩子的最左边的子孙结点,设置p为该结点。
(3)返回p,作为当前结点在中根次序下的后继结点。
*/
public ThreadBinaryTreeNode<T> NextNodeInOrder()
{
ThreadBinaryTreeNode<T> p = this;
if (p.RightTag) //右子树为空时
p = p.Right; //right指向后继结点
else//右子树非空时
{
p = p.Right; //进入右子树
while (!p.LeftTag)//找到最左边的子孙结点
p = p.Left;
}
return p;
}
//查找先根次序的后继节点
/*
设p指向当前结点,执行以下操作:
(1)如果p结点的左子树为非空,则p的左孩子为其后继结点,设置p为该结点。
(2)否则说明p的左子树为空,如果p的右孩子为非空,则p的后继结点是p的右孩子,设置p为该结点。
(3)如果p结点的左、右子树均为空,p为叶子结点,则p的后继节点为它的中序线索祖先的右孩子,沿着右线索可以找到它,设置p为该结点。
(4)返回p,作为当前结点在中根次序下的后继结点。
*/
public ThreadBinaryTreeNode<T> NextNodePreOrder()
{
ThreadBinaryTreeNode<T> p = this;
if (!p.LeftTag) //左子树为非空时
p = p.Left; //p的左孩子为其后继结点,设置p为该结点。
else//右子树非空时
{
if (!p.RightTag) //右子树为非空时
p = p.Right; //p的右孩子为其后继结点,设置p为该结点。
else
{
while (p.RightTag && p.Right != null)//叶子结点,后继节点为它的中序线索祖先的右孩子。
p = p.Right;//进入右子树
p = p.Right;
}
}
return p;
}
}
}
Tree类
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpEight
{
public class ThreadBinaryTree<T>
{
private ThreadBinaryTreeNode<T> front = null;
private ThreadBinaryTreeNode<T> root;//指向二叉树的根节点
//设置或获取根节点
public ThreadBinaryTreeNode<T> Root
{
get { return root; }
set { root = value; }
}
//构造空二叉树
public ThreadBinaryTree()
{
root = null;
}
//中序线索化以p节点为根的子树
/*
设p指向一棵二叉树的某个结点,front指向p的前驱结点,它的初值为null。
当p非空时,执行以下操作:
(1)中序线索化p结点的左子树。
(2)如果p的左子树为空,设置p的lefttag标记为true,它的left链为指向前驱结点front的线索。
(3)如果p的右子树为空,设置p的righttag标记为true
(4)如果前驱结点front非空并且它的右链为线索,设置front的right链为指向p的线索。
(5)移动front,使front指向p。
(6)中序线索化p结点的右子树。
*/
private void SetThreadInOrder(ThreadBinaryTreeNode<T> p)
{
if (p != null)
{
SetThreadInOrder(p.Left);//中序线索化p的左子树
if (p.Left == null)//p的左子树为空,设置p.left为线索,指向front
{
p.LeftTag = true;
p.Left = front;
}
if (p.Right == null)//p的右子树为空,设置p.right为线索
{
p.RightTag = true;
}
if (front != null && front.RightTag)//前驱结点front非空并且它的右链为线索,设置front的right链为指向p的线索。
front.Right = p;
front = p;//移动front,使front指向p。
SetThreadInOrder(p.Right);//中序线索化p结点的右子树。
}
}
//中序线索化二叉树
public void SetThreadInOrder()
{
front = null;
SetThreadInOrder(root);
}
//中根次序遍历
/*
(1)寻找第一个访问结点。它是根的左子树上最左边的子孙结点,用p指向该结点。
(2)访问p结点。
(3)找到p的后继结点,用p指向该结点,跳转到上一步,直至p为null,说明已访问了序列的最后一个结点。
*/
public void ShowUsingThreadInOrder()
{
ThreadBinaryTreeNode<T> p = root;
if (p != null)
{
Console.Write("中根次序: ");
while (!p.LeftTag)
p = p.Left; //找到根的最左边子孙结点
do
{
Console.Write(p.Data + " ");
p = p.NextNodeInOrder();//返回p的后继结点
}
while (p != null);
Console.WriteLine();
}
}
//先根次序遍历
/*
(1)寻找第一个访问结点。它是根结点,用p指向该结点。
(2)访问p结点。
(3)找到p的后继结点,用p指向该结点,跳转到上一步,直至p为null,说明已访问了序列的最后一个结点。
*/
public void ShowUsingThreadPreOrder()
{
ThreadBinaryTreeNode<T> p = root;
if (p != null)
{
Console.Write("先根次序: ");
do
{
Console.Write(p.Data + " ");
p = p.NextNodePreOrder();//返回p的后继结点
}
while (p != null);
Console.WriteLine();
}
}
//测试函数
public static void Test()
{
ThreadBinaryTree<int> tbt = new ThreadBinaryTree<int>();//建立二叉树
ThreadBinaryTreeNode<int>[] nodes = new ThreadBinaryTreeNode<int>[9];
for (int i = 0; i < 9; i++)
nodes[i] = new ThreadBinaryTreeNode<int>(i);//建立二叉树的节点
tbt.Root = nodes[1];//设置二叉树的节点结构
nodes[1].Left = nodes[2]; nodes[1].Right = nodes[3];
nodes[2].Left = nodes[4];
nodes[3].Left = nodes[5]; nodes[3].Right = nodes[6];
nodes[4].Right = nodes[7]; nodes[6].Left = nodes[8];
tbt.SetThreadInOrder();//中序线索化二叉树
tbt.ShowUsingThreadInOrder();//中根次序遍历中序线索二叉树
tbt.ShowUsingThreadPreOrder();//先根次序遍历中序线索二叉树
}
}
}
总结
以上就是今天要讲的内容,本章介绍树,希望大家有所收获,感谢!