一、树的定义
树(Tree)是 n(n≥0)个相同类型的数据元素的有限集合。树中的数据元素叫结点(Node)。n=0 的树称为空树(Empty Tree);对于 n>0 的任意非空树 T 有:
(1)有且仅有一个特殊的结点称为树的根(Root)结点,根没有前驱结点;
(2)若n>1,则除根结点外,其余结点被分成了m(m>0)个互不相交的集合
T1,T2,…,Tm,其中每一个集合Ti(1≤i≤m)本身又是一棵树。树T1,T2,…,Tm
称为这棵树的子树(Subtree)。
由树的定义可知,树的定义是递归的,用树来定义树。因此,树(以及二叉
树)的许多算法都使用了递归。
树的形式定义为:树(Tree)简记为 T,是一个二元组,
T = (D, R)
其中:D是结点的有限集合;
R是结点之间关系的有限集合。
二、树的相关术语:
1、结点(Node):表示树中的数据元素,由数据项和数据元素之间的关系组
成。在图 5.1 中,共有 10 个结点。
2、结点的度(Degree of Node):结点所拥有的子树的个数,在图 5.1 中,结
点 A的度为3。
3、树的度(Degree of Tree):树中各结点度的最大值。在图 5.1 中,树的度为
3。
4、叶子结点(Leaf Node):度为 0 的结点,也叫终端结点。在图 5.1 中,结
点 E、F、G、H、I、J 都是叶子结点。
5、分支结点(Branch Node):度不为 0 的结点,也叫非终端结点或内部结点。
在图 5.1 中,结点 A、B、C、D 是分支结点。
6、孩子(Child):结点子树的根。在图 5.1 中,结点 B、C、D 是结点 A 的
孩子。
7、双亲(Parent):结点的上层结点叫该结点的双亲。在图 5.1 中,结点 B、C、
D的双亲是结点A。
8、祖先(Ancestor):从根到该结点所经分支上的所有结点。在图 5.1 中,结
点 E的祖先是 A和 B。
9、子孙(Descendant):以某结点为根的子树中的任一结点。在图 5.1 中,除
A之外的所有结点都是A的子孙。
10、兄弟(Brother):同一双亲的孩子。在图 5.1 中,结点 B、C、D互为兄弟。
11、结点的层次(Level of Node):从根结点到树中某结点所经路径上的分支
数称为该结点的层次。根结点的层次规定为1,其余结点的层次等于其双亲结点的层次加 1。
12、堂兄弟(Sibling):同一层的双亲不同的结点。在图 5.1 中,G 和 H 互为
堂兄弟。
13、树的深度(Depth of Tree):树中结点的最大层次数。在图 5.1 中,树的深
度为 3。
14、无序树(Unordered Tree):树中任意一个结点的各孩子结点之间的次序构
成无关紧要的树。通常树指无序树。
15、有序树(Ordered Tree):树中任意一个结点的各孩子结点有严格排列次序
的树。二叉树是有序树,因为二叉树中每个孩子结点都确切定义为是该结点的左
孩子结点还是右孩子结点。
16、森林(Forest):m(m≥0)棵树的集合。自然界中的树和森林的概念差别很
大,但在数据结构中树和森林的概念差别很小。从定义可知,一棵树有根结点和
m 个子树构成,若把树的根结点删除,则树变成了包含 m 棵树的森林。当然,根
据定义,一棵树也可以称为森林。
三、树的根本操作
1、Root():求树的根结点,如果树非空,返回根结点,否则返回空;
2、Parent(t):求结点 t的双亲结点。如果 t 的双亲结点存在,返回双亲结点,
否则返回空;
3、Child(t,i):求结点 t 的第 i 个子结点。如果存在,返回第 i 个子结点,否
则返回空;
4、RightSibling(t):求结点 t 第一个右边兄弟结点。如果存在,返回第一个
右边兄弟结点,否则返回空;
5、Insert(s,t,i):把树 s插入到树中作为结点 t 的第 i 棵子树。成功返回 true,
否则返回 false;
6、Delete(t,i):删除结点t 的第i棵子树。成功返回第i 棵子树的根结点,否
则返回空;
7、Traverse(TraverseType):按某种方式遍历树;
8、Clear():清空树;
9、IsEmpty():判断树是否为空树。如果是空树,返回 true,否则返回false;
10、GetDepth():求树的深度。如果树不为空,返回树的层次,否则返回0。
四、二叉树的定义
称为空二叉树(Empty Binary Tree);对于 n>0 的任意非空二叉树有:
(1)有且仅有一个特殊的结点称为二叉树的根(Root)结点,根没有前驱结
点;
(2)若n>1,则除根结点外,其余结点被分成了 2 个互不相交的集合TL,
TR,而TL、TR本身又是一棵二叉树,分别称为这棵二叉树的左子树(Left Subtree)
和右子树(Right Subtree)。
二叉树的形式定义为:二叉树(Binary Tree)简记为 BT,是一个二元组,
BT = (D, R)
其中:D是结点的有限集合;
R是结点之间关系的有限集合。
由树的定义可知,二叉树是另外一种树形结构,并且是有序树,它的左子树
和右子树有严格的次序,若将其左、右子树颠倒,就成为另外一棵不同的二叉树。
叉树、左子树为空的二叉树和左、右子树非空的二叉树。
的结点,并且度为 0 的结点在同一层上,则这棵二叉树为满二叉树,如图 5.7(a)
所示。 由定义可知,对于深度为k的满二叉树的结点个数为 2k-1。
a) 二叉树的遍历是指按照某种顺序访问二叉树中的每个结点,使每个结点被访
问一次且仅一次。遍历是二叉树中经常要进行的一种操作,因为在实际应用中,
常常要求对二叉树中某个或某些特定的结点进行处理,这需要先查找到这个或这
些结点。
b) 实际上,遍历是将二叉树中的结点信息由非线性排列变为某种意义上的线性
排列。也就是说,遍历操作使非线性结构线性化。
c) 由二叉树的定义可知,一棵二叉树由根结点、左子树和右子树三部分组成,
若规定 D、L、R 分别代表遍历根结点、遍历左子树、遍历右子树,则二叉树的
遍历方式有6 种:DLR、DRL、LDR、LRD、RDL、RLD。由于先遍历左子树和
先遍历右子树在算法设计上没有本质区别,所以,只讨论三种方式:DLR(先序
遍历)、LDR(中序遍历)和 LRD(后序遍历)。
d) 除了这三种遍历方式外,还有一种方式:层序遍历(Level Order)。层序遍历
是从根结点开始,按照从上到下、从左到右的顺序依次访问每个结点一次仅一次。
由于树的定义是递归的,所以遍历算法也采用递归实现。下面分别介绍这四
种算法
e) 先序遍历的基本思想是:首先访问根结点,然后先序遍历其左子树,最后先序遍历其右子树。
f) 中序遍历的基本思想是:首先中序遍历根结点的左子树,然后访问根结点,最后中序遍历其右子树。
g) 后序遍历的基本思想是:首先后序遍历根结点的左子树,然后后序遍历根结点的右子树,最后访问根结点。
h) 层序遍历的基本思想是:由于层序遍历结点的顺序是先遇到的结点先访问,队列操作的顺序相同。所以,在进行层序遍历时,设置一个队列,将根结点引入队,当队列非空时,循环执行以下三步:
(1) 从队列中取出一个结点引用,并访问该结点;
(2) 若该结点的左子树非空,将该结点的左子树引用入队;
(3) 若该结点的右子树非空,将该结点的右子树引用入队;
using System;
using System.Collections.Generic;
using System.Collections;
class Program
{
static void
{
Node<string> H = new Node<string>("H",null,null);
Node<string> I = new Node<string>("I",null,null);
Node<string> J = new Node<string>("J",null,null);
Node<string> D = new Node<string>("D",H,I);
Node<string> E = new Node<string>("E", J,null);
Node<string> B = new Node<string>("B", D, E);
Node<string> F = new Node<string>("F", null, null);
Node<string> G = new Node<string>("G", null, null);
Node<string> C = new Node<string>("C", F, G);
Node<string> A = new Node<string>("A", B, C);
Console.WriteLine("先序遍历结果集:");
Node<string>.PreOrder(A);
Console.WriteLine("中序遍历结果集:");
Node<string>.InOrder(A);
Console.WriteLine("后序遍历结果集:");
Node<string>.PostnOrder(A);
Console.WriteLine("层序遍历结果集:");
Node<string>.LevelOrder(A);
}
}
//建立二叉树的泛型类
public class Node<T>
{
private T data; //数据域
private Node<T> lChild; //左孩子
private Node<T> rChild; //右孩子
//构造器
public Node(T val, Node<T> lp, Node<T> rp)
{
data = val;
lChild = lp;
rChild = rp;
}
//构造器
public Node(Node<T> lp, Node<T> rp)
{
data = default(T);
lChild = lp;
rChild = rp;
} //构造器
public Node(T val)
{
data = val;
lChild = null;
rChild = null;
}
//构造器
public Node()
{
data = default(T);
lChild = null;
rChild = null;
}
//数据属性
public T Data
{
get
{
return data;
}
set
{
value = data;
}
}
//左孩子属性
public Node<T> LChild
{
get
{
return lChild;
}
set
{ lChild = value;
}
}
//右孩子属性
public Node<T> RChild
{
get
{
return rChild;
}
set
{
rChild = value;
}
}
public static void PreOrder(Node<T> root)
{
//根结点为空
if (root == null)
{
return;
}
//处理根结点
Console.WriteLine("{0}", root.Data);
//先序遍历左子树
PreOrder(root.lChild);
//先序遍历右子树
PreOrder(root.rChild);
}
public static void InOrder(Node<T> root)
{
//根结点为空
if (root==null)
{
return;
}
//中序遍历左子树
InOrder(root.LChild);
Console.WriteLine("{0}", root.data);
//中序遍历右子树
InOrder(root.rChild);
}
public static void PostnOrder(Node<T> root)
{
//根结点为空
if (root == null)
{
return;
}
//后序遍历左子树
PostnOrder(root.LChild);
//后序遍历右子树
PostnOrder(root.rChild);
Console.WriteLine("{0}", root.data);
}
public static void LevelOrder(Node<T> root)
{
//根结点为空
if (root == null)
{
return;
}
//设置一个队列保存层序遍历的结点
CSeqQueue<Node<T>> sq = new CSeqQueue<Node<T>>(50);
//根结点入队
sq.In(root);
//队列非空,结点没有处理完
while (!sq.IsEmpty())
{
//结点出队
Node<T> tmp = sq.Out();
//处理当前结点
Console.WriteLine("{0}", tmp.data);
//将当前结点的左孩子结点入队
if (tmp.LChild != null)
{
sq.In(tmp.LChild);
}
//将当前结点的右孩子结点入队
if (tmp.RChild != null)
{
sq.In(tmp.RChild);
}
}
}
}
//Queue接口
public interface IQueue<T>
{
int GetLength(); //求队列的长度
bool IsEmpty(); //判断对列是否为空
void Clear(); //清空队列
void In(T item); //入队
T Out(); //出队
T GetFront(); //取对头元素
}
//Queue泛型实例
public class CSeqQueue<T> : IQueue<T> {
private int maxsize; //循环顺序队列的容量
private T[] data; //数组, 用于存储循环顺序队列中的数据元素
private int front; //指示循环顺序队列的队头
private int rear; //指示循环顺序队列的队尾
//索引器
public T this[int index]
{
get
{
return data[index];
}
set
{
data[index] = value;
}
}
//容量属性
public int Maxsize
{
get
{
return maxsize;
}
set
{
maxsize = value;
}
}
//队头属性
public int Front
{
get
{
return front;
}
set
{
front = value;
}
}
//队尾属性
public int Rear
{
get
{
return rear;
}
set
{
rear = value;
}
}
//构造器
public CSeqQueue(int size)
{
data = new T[size];
maxsize = size;
front = rear = -1;
}
//求循环顺序队列的长度
public int GetLength()
{
return (rear-front+maxsize) % maxsize;
}
//清空循环顺序队列
public void Clear()
{
front = rear = -1;
}
//判断循环顺序队列是否为空
public bool IsEmpty()
{
if (front == rear)
{
return true;
}
else
{
return false;
}
}
//判断循环顺序队列是否为满
public bool IsFull()
{
if ((rear + 1) % maxsize==front)
{
return true;
}
else
{
return false;
}
}
//入队
public void In(T item)
{
if(IsFull())
{
Console.WriteLine("Queue is full");
return;
}
data[++rear] = item;
}
//出队
public T Out()
{
T tmp = default(T);
if (IsEmpty())
{
Console.WriteLine("Queue is empty");
return tmp;
}
tmp = data[++front];
return tmp;
}
//获取队头数据元素
public T GetFront()
{
if (IsEmpty())
{
Console.WriteLine("Queue is empty!");
return default(T);
}
return data[front + 1];
}
由于二叉树是有序的,为了避免混淆,对于无序树,我们约定树中的每个结
点的孩子结点按从左到右的顺序进行编号。如图5.1 所示的树,根结点 A有三个
孩子 B、C、D,规定结点 B 是结点 A 的第一个孩子,结点 C 是结点 A 的第 2
个孩子,结点 D是结点 A的第3个孩子。
将树转换成二叉树的步骤是:
(1)加线。就是在所有兄弟结点之间加一条连线;
(2)抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连
线,删除它与其它孩子结点之间的连线;
(3)旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使
之结构层次分明。
二叉树转换为树是树转换为二叉树的逆过程,其步骤是:
(1)若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来;
(2)删除原二叉树中所有结点与其右孩子结点的连线;
(3)整理(1)和(2)两步得到的树,使之结构层次分明。
图 5.17 是二叉树转换为树的过程示意图。