(注:本文参考MSDN:An Extensive Examination of Data Structures Using C# 2.0)
二叉树是以一种非线性的方式存储数据的,是树的一种(但不是树的特殊情形),所有节点最多只能有两个子节点,左孩子结点和右孩子结点。二叉搜索树是二叉树的另外一种形式,它规定了排列树的每个元素项的一些规则,这些规则保证了二叉搜索树能够以一种低于线形搜索时间的性能来搜索数据。下面的图表示了两棵二叉树。
图一:二叉树的两个图示
一. 创建结点基类
二叉树结点可以继承自这个结点,当然这个结点也可以作为图,SkipList等结点的基类.作为基类,这个结点类要反应出结点的当前值,以及他的邻居,在二叉树中,邻居则为左右子结点.
public class Node<T>
{
私有成员#region 私有成员
private T m_data;
private NodeList<T> m_Neighbors = null;
#endregion
属性#region 属性
public T Value
{
get
{
return m_Data;
}
set
{
m_Data = value;
}
}
protected NodeList<T> Neighbors
{
get
{
return m_Neighbors;
}
set
{
m_Neighbors = value;
}
}
#endregion
构造函数#region 构造函数
public Node() { }
public Node(T data) : this(data, null) { }
public Node(T data, NodeList<T> neighbors)
{
this.m_Data = data;
this.m_Neighbors = neighbors;
}
#endregion
}
注意这里使用了泛型,可以在构造结点的时候传入类型,这个Node类有两个私有成员变量:
m_data,类型是T,这个值是用来存储在这个结点的值.
m_neighbors,NodeList<T> 类型,这个成员变量存储了这个Node的邻居结点.
其中NodeList<T>继承自Collection<Node<T>>,Collection来自命名空间System.Collections.ObjectModel,Collection<T>类提供了如:Add(T),Remove(T),Clear(),以及Count属性.让NodeList<T>继承自Collection<T>类,可以使用Collection<T>类本身一些很好的方法,可以在NodeList<T>类中存储一定数量的Node<T>类,以及可以找到并返回结点的方法.当然我们也可以使用其他的方法去存储结点数据集.
public class NodeList<T>: Collection<Node<T>>
{
构造函数#region 构造函数
public NodeList() : base() { }
public NodeList(int initialSize)
{
for (int i = 0; i < initialSize; i++)
{
base.Items.Add(default(Node<T>));
}
}
#endregion
类方法#region 类方法
public Node<T> FindByValue(T value)
{
//遍历NodeList
foreach (Node<T> node in Items)
{
if (node.Value.Equals(value))
{
return node;
}
}
return null;
}
#endregion
}
到此为止,我们的结点基类就已经创建完成了,我们可以继承他们得到我们所需要的类.
二. 创建二叉树结点
下面我们就来构造一查已知的二叉树,其二叉树的结点继承自Node<T>.其neighbors为二叉树的左右子结点.
public class BinaryTreeNode<T> : Node<T>
{
属性#region 属性
public BinaryTreeNode<T> Left
{
get
{
if (base.Neighbors == null)
{
return null;
}
else
{
return (BinaryTreeNode<T>)base.Neighbors[0];
}
}
set
{
if (base.Neighbors == null)
{
base.Neighbors = new NodeList<T>(2);
}
base.Neighbors[0] = value;
}
}
public BinaryTreeNode<T> Right
{
get
{
if (base.Neighbors == null)
{
return null;
}
else
{
return (BinaryTreeNode<T>)base.Neighbors[1];
}
}
set
{
if (base.Neighbors == null)
{
base.Neighbors = new NodeList<T>(2);
}
base.Neighbors[1] = value;
}
}
#endregion
构造函数#region 构造函数
public BinaryTreeNode() : base() { }
public BinaryTreeNode(T data) : base(data, null) { }
public BinaryTreeNode(T data, BinaryTreeNode<T> left, BinaryTreeNode<T> right)
{
base.Value = data;
NodeList<T> children = new NodeList<T>(2);
children[0] = left;
children[1] = right;
base.Neighbors = children;
}
#endregion
}
从上面的二叉树结点类,可以从构造函数给其左右结点的值也可以通过属性对其进行赋值。这在这里基类的neighbors为其的左右子结点.
三. 创建二叉树类
二叉树的结点类我们已经构造好了,下面我们会创建树的类,二叉树会有一个根结点,除了叶子,每个结点都会有一到两个子结点,叶结点的子结点则为null.
public class BinaryTree<T>
{
私有成员变量#region 私有成员变量
private BinaryTreeNode<T> m_Root = null;
#endregion
属性#region 属性
public BinaryTreeNode<T> Root
{
get
{
return m_Root;
}
set
{
m_Root = value;
}
}
#endregion
公共方法#region 公共方法
public void Clear()
{
//这里只是简单的将根结点置为null,可以改进
m_Root = null;
}
#endregion
}
这里这棵二叉树的类很简单,其Clear()方法也很简单,只是给根结点置为null.
BinaryTree<int> btree = new BinaryTree<int>();
btree.Root = new BinaryTreeNode<int>(1);
btree.Root.Left = new BinaryTreeNode<int>(2);
btree.Root.Right = new BinaryTreeNode<int>(3);
btree.Root.Left.Left = new BinaryTreeNode<int>(4);
btree.Root.Right.Right = new BinaryTreeNode<int>(5);
btree.Root.Left.Left.Right = new BinaryTreeNode<int>(6);
btree.Root.Right.Right.Right = new BinaryTreeNode<int>(7);
btree.Root.Right.Right.Right.Right = new BinaryTreeNode<int>(8);
这样一棵最简单的树就生成了,可以通过左右子结点的方式进行调用,我们创建BinaryTree类的实例后,要创建根节点(root)。我们必须人工地为相应的左、右孩子添加新节点类Node的实例。例如,添加节点4,它是根节点的左节点的左节点,我们的代码是:
btree.Root.Left.Left = new Node(4);
如果我们要访问二叉树中的特定节点,我们需要搜索二叉树的每个节点。它不能象数组那样根据指定的节点直接访问。搜索二叉树要耗费线性时间,最坏情况是查询所有的节点。也就是说,当二叉树节点个数增加时,查找任意节点的步骤数也将相应地增加。
因此,如果二叉树的定位时间为线性,查询时间也为线性,一般的二叉树确实不能提供比数组更好的性能。然而当我们有技巧地排列二叉树中的元素时,我们就能很大程度改善查询时间(当然,定位时间也会得到改善)。