树的基本概念
数的定义
树(Tree)是n(n>=0)个结点的有限集。
当n=0时称为空树
在任意一颗非空树中:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集 T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)
注意:n>0时根结点是唯一的
m>0时,子树的个数没有限制,但他们一定是互不相交的
结点分类
- 树的结点包含一个数据元素及若干指向其子树的分支。
- 结点拥有的分支结点称为结点的度,树的度就是树内各结点的度的最大值
- 度为0的结点数称为叶结点(Leaf)或终端结点
- 度不为0的结点称为非终端结点或分支结点(除根结点之外,分支结点也称为内部结点)
结点之间的关系
- 结点的子树的根称为该结点的孩子(Child) ,相应的该结点称为孩子的双亲(Parent)
- 同一个双亲的孩子之间称为兄弟(Sibling)
- 结点的祖先是从根到该结点所经分支的所有结点
- 以某结点为根的子树的任一结点都称为该结点的子孙
树的其他相关概念
结点的层次从根开始定义,根为第一层,根的孩子为第二层
双亲在同一层的结点互为堂兄弟
树中结点的最大层次称为树的深度(Depth)或高度
如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
森林是m(m>=0)棵互不相交的树的集合。
树的性质
- 结点数=总度数+1
- 树的度与m叉树的区别
-
度为m的树第i层至多有M为底 i-1次方个结点(i>=1)
-
高度为h的m叉树至少有h个结点
-
高度为h、度为m的树至少有h+m+1个结点
树的存储结构
使用顺序存储和链式存储结构对树的存储结构进行表示的方法:
1、双亲表示法
2、孩子表示法
3、孩子兄弟表示法
双亲表示法
在每个结点中,附设一个指示器指示其双亲结点在数组中的位置
data是数据域,存储结点的数据信息。而parent是指针域,存储结点的双亲的下标。
这种存储结构,我们可以根据结点的parent指针找到它的双亲结点,所以时间复杂度为O(1),但是无法知道结点的孩子
孩子表示法
每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法
data是数据域,而child1至childd则为指向孩子结点的指针域
孩子兄弟表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
这个表示法的最大好处就是它将一个复杂的树变成一棵二叉树。
注意:存储结构的设计是一个非常灵活的过程。一个存储结构设计的是否合理,取决于基于该存储结构的运算是否合适,是否方便,时间复杂度好不好等。
以上的方法都可以根据实际情况进行拓展以适应不同的情况。
实例代码
public class Node<T>
{
public T data;
public Node<T> sibling;//右兄弟
public Node<T> child;//长子
}
class MyTree<T>
{
private Node<T> root = null;
public Node<T> CreateRoot(T data)
{
root = new Node<T>();
root.data = data;
return root;
}
public Node<T> InsertChild(Node<T> parent ,T data)
{
if (parent.child != null)
return parent.child;
var LChild = new Node<T>();
LChild.data = data;
parent.child = LChild;
return LChild;
}
public Node<T> insertSibling(Node<T> leftbling, T data)
{
if (leftbling.sibling != null)
return leftbling.sibling;
var Sibling = new Node<T>();
Sibling.data = data;
leftbling.sibling = Sibling;
return Sibling;
}
public static void Test()
{
MyTree<string> tree = new MyTree<string>();
Node<string> nodeA = tree.CreateRoot("A");
Node<string> nodeB = tree.InsertChild(nodeA, "B");
Node<string> nodeC = tree.insertSibling(nodeB, "C");
Node<string> nodeD = tree.insertSibling(nodeC, "D");
Node<string> nodeE = tree.InsertChild(nodeB, "E");
Node<string> nodeF = tree.InsertChild(nodeC, "F");
Node<string> nodeG = tree.insertSibling(nodeF, "G");
Node<string> nodeH = tree.InsertChild(nodeF, "H");
tree.Traverse();
}
public void _TraverByDepth(Node<T> current)//深度遍历
{
Console.WriteLine(current.data);
if (current.child != null)
{
_TraverByDepth(current.child);
}
if (current.sibling != null)
{
_TraverByDepth(current.sibling);
}
#region 简易写法
//if (current == null)
// return;
//_TraverByDepth(current.sibling);
//_TraverByDepth(current.child);
#endregion
}
public void _TraverseByLevel(Node<T> current)//广度遍历
{
Console.WriteLine(current.data);
if (current.sibling != null)
{
_TraverseByLevel(current.sibling);//递归
}
if (current.child != null)
{
_TraverseByLevel(current.child);
}
}
public void Traverse()
{
//_TraverseByLevel(root);
_TraverByDepth(root);
}
}
class Program
{
static void Main(string[] args)
{
MyTree<object>.Test();
Console.ReadLine();
}
}
普通树的删除与查找
//查找
public void Find(Node<T> current, T data)
{
if (current == null)
return;
if (current.data.Equals( data))
{
Console.WriteLine(current.data);
}
Find(current.child,data);
Find(current.sibling,data);
}
//删除
public void Delete(Node<T> current ,T data)
{
if (current == null)
return;
if (current.data.Equals(data))
current.child = null;
Delete(current.child, data);
Delete(current.sibling, data);
}
二叉树
二叉树的定义
二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空而叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
二叉树的特点
- 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。
- 左子树和右子树是有顺序的,次序不能任意颠倒
- 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树
二叉树具有五种基本形态
- 空二叉树
- 只有一个根结点
- 根结点只有左子树
- 根结点只有右子树
- 根结点既有左子树又有右子树
特殊二叉树
1、斜树
所以的结点都只有左子树的二叉树交左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树
2、满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树.
满二叉树的特点
1、叶子只能出现在最下一层
2、非叶子结点的度一定是2
3、在同样深度的二叉树中,满二叉树的结点个数最多,叶子树最多
4、一棵高度为h,且含有2的n次方-1个结点的二叉树
5、按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父结点为[i/2]
3、完全二叉树
对一棵具有n个结点的二叉树按层次编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树.
完全二叉树的特点
1、叶子结点只能出现在最下两层
2、最下层的叶子一定集中在左部连续位置
3、倒数二层,若有叶子结点,一定都在右部连续位置
4、如果结点度为1,则该结点只有左孩子,即使不存在只有右子树的情况
5、同样结点树的二叉树,完全二叉树的深度最小
6、i<=[n/2]为分支结点,i>[n/2]为叶子结点
4、二叉排序树
左子树上所有结点的关键字均小于根结点的关键字
右子树上所有结点的关键字均大于根结点的关键字
左子树与右子树右各是一棵二叉排序树
5、平衡二叉树
树上任一结点的左子树和右子树的深度之差不超过1
二叉树的性质
二叉树的存储结构
二叉树的顺序存储结构
二叉树的顺序存储结构一般只用于完全二叉树
二叉树的链式存储结构
二叉树的链式存储结构也叫二叉链表。二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域。我们称这样的链表为二叉链表
2n个指针
n-1个指针是有指向的
n个结点的二叉链表共有n+1个空链表
根据实际情况可以添加父指针称为三叉指针,以供寻找头结点
二叉树的实现与遍历
遍历二叉树的四种方法
二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
二叉树遍历的方法
- 前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
- 中序遍历:若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树
- 后序遍历:若空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点
- 层序遍历:若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问
实例代码
public class Node<T>
{
public T data;
public Node<T> lchild = null;//左子结点
public Node<T> rchild = null;//右子节点
}
class MyBinaryTree<T>
{
public Node<T> root = null;
public Node<T> CreateRoot(T data)
{
root = new Node<T>();
root.data = data;
return root;
}
public Node<T> InsertIchild(Node<T> current,T data)
{
if (current.lchild != null)
return current.lchild;
var Ichild = new Node<T>();
Ichild.data=data;
current.lchild = Ichild;
return Ichild;
}
public Node<T> InsertRchild(Node<T> current, T data)
{
if (current.rchild != null)
return current.rchild;
var Rchild = new Node<T>();
Rchild.data = data;
current.rchild = Rchild;
return Rchild;
}
public void PreOrder(Node<T> current)//先序遍历
{
Console.WriteLine(current.data);
if (current.lchild != null)
PreOrder(current.lchild);
if (current.rchild != null)
PreOrder(current.rchild);
}
public void MidOrder(Node<T> current)//中序遍历
{
if (current == null)
return;
MidOrder(current.lchild);
Console.WriteLine(current.data);
MidOrder(current.rchild);
}
public void PostOrder(Node<T> current)//后序遍历
{
if (current == null)
return;
PostOrder(current.lchild);
PostOrder(current.rchild);
Console.WriteLine(current.data);
}
}
class Program
{
static void Main(string[] args)
{
MyBinaryTree<string> myBinaryTree = new MyBinaryTree<string>();
Node<string> nodeA = myBinaryTree.CreateRoot("A");
Node<string> nodeB = myBinaryTree.InsertIchild(nodeA,"B");
Node<string> nodeC = myBinaryTree.InsertRchild(nodeA, "C");
Node<string> nodeD = myBinaryTree.InsertIchild(nodeB,"D");
Node<string> nodeE = myBinaryTree.InsertRchild(nodeB,"E");
Node<string> nodeF = myBinaryTree.InsertIchild(nodeC,"F");
Node<string> nodeG = myBinaryTree.InsertRchild(nodeC,"G");
myBinaryTree.MidOrder(myBinaryTree.root);
myBinaryTree.PostOrder(myBinaryTree.root);
myBinaryTree.PreOrder(myBinaryTree.root);
Console.ReadLine();
}
}