二叉树以及其节点的实现
1. 二叉树节点的定义及实现
根据二叉树的特点可知,二叉树的子节点最多有两个子树,分别是左子树和右子树。所以,一个二叉树的节点应有三个部分组成:
² 当前节点数据<T>;
² 左子节点的引用;
² 右子节点的引用。
如下图左部分的Node类定义。
Node类的实现:
Code
/// <summary>
/// 二叉树节点代码
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T>
{
private T _data;
private Node<T> _lChild;
private Node<T> _rChild;
/// <summary>
/// 二叉树当前节点的数据
/// </summary>
public T Data
{
get { return _data; }
set { _data = value; }
}
/// <summary>
/// 二叉树左子节点的引用
/// </summary>
public Node<T> LChild
{
get { return _lChild; }
set { _lChild = value; }
}
/// <summary>
/// 二叉树右子节点的引用
/// </summary>
public Node<T> RChild
{
get { return _rChild; }
set { _rChild = value; }
}
public Node()
{
_data = default(T);
_lChild = null;
_rChild = null;
}
public Node(T _data)
{
this._data = _data;
_lChild = null;
_rChild = null;
}
public Node(T _data, Node<T> _lChild, Node<T> _rChild)
{
this._data = _data;
this._lChild = _lChild;
this._rChild = _rChild;
}
public Node(Node<T> _lChild, Node<T> _rChild)
{
this._lChild = _lChild;
this._rChild = _rChild;
}
}
2 二叉树的实现
二叉树的实现主要有能下几个功能:
Ø 构造一个新的二叉树
Ø 插入指定节点的左子节点;
Ø 插入指定节点的右子节点;
Ø 删除指定节点的左子节点;
Ø 删除指定节点的右子节点;
Ø 判断二叉树是否为空;
Ø 判断节点是否为叶子节点。
代码实现如下:
Code
1 /**//// <summary>
2 /// 二叉树的操作实现
3 /// </summary>
4 /// <typeparam name="T"></typeparam>
5 public class BinaryTree<T>
6 {
7 private Node<T> _header;
8
9
10 ..构造函数..#region ..构造函数..
11
12 public BinaryTree()
13 {
14 _header = null;
15 }
16
17 public BinaryTree(T _data)
18 {
19 Node<T> _headerNode = new Node<T>(_data);
20 this._header = _headerNode;
21 }
22
23 public BinaryTree(T _data, Node<T> _lChild, Node<T> _rChild)
24 {
25 Node<T> _headerNode = new Node<T>(_data, _lChild, _rChild);
26 this._header = _headerNode;
27 }
28
29 #endregion
30
31 /**//// <summary>
32 /// 二叉树根节点(头节点)
33 /// </summary>
34 public Node<T> Header
35 {
36 get { return _header; }
37 set { _header = value; }
38 }
39
40 /**//// <summary>
41 /// 插入左子节点
42 /// </summary>
43 /// <param name="_fNode"></param>
44 /// <param name="_data"></param>
45 public void InsertL(Node<T> _fNode, T _data)
46 {
47 /**//*
48 * 步骤:
49 * 1 New新的节点
50 * 2 新节点引用要原父节点的左子节点
51 * 3 父节点的左子节点引用新节点
52 */
53 Node<T> _node = new Node<T>(_data);
54 _node.LChild = _fNode.LChild;
55 _fNode.LChild = _node;
56 }
57
58 /**//// <summary>
59 /// 插入右子节点
60 /// </summary>
61 /// <param name="_fNode"></param>
62 /// <param name="_data"></param>
63 public void InsertR(Node<T> _fNode, T _data)
64 {
65 //思路同插入左子节点
66 Node<T> _node = new Node<T>(_data);
67 _node.LChild = _fNode.RChild;
68 _fNode.RChild = _node;
69 }
70
71
72 /**//// <summary>
73 /// 删除指定节点的左子节点
74 /// </summary>
75 /// <param name="node"></param>
76 public Node<T> DeleteL(Node<T> node)
77 {
78 if (node == null || node.LChild == null)
79 {
80 return null;
81 }
82 /**//*
83 * 步骤:
84 * 1 获取左子节点的引用
85 * 2 把指定节点的左子节点引用设为NULL
86 * 3 返回左子节点的引用
87 */
88 Node<T> lChild = node.LChild;
89 node.LChild = null;
90 return lChild;
91 }
92
93 /**//// <summary>
94 /// 删除指定节点的右子节点
95 /// </summary>
96 /// <param name="node"></param>
97 /// <returns></returns>
98 public Node<T> DeleteR(Node<T> node)
99 {
100 if (node == null || node.RChild == null)
101 {
102 return null;
103 }
104 //步骤同删除指定节点的左子节点相同,只不过是首先获取的是右子节点的引用
105 Node<T> rChild = node.RChild;
106 node.RChild = null;
107 return rChild;
108 }
109
110 /**//// <summary>
111 /// 判断是否为空
112 /// </summary>
113 /// <returns></returns>
114 public bool IsEmpty()
115 {
116 return _header == null;
117 }
118
119 /**//// <summary>
120 /// 判断指定的节点是否为叶子节点
121 /// </summary>
122 /// <param name="_node"></param>
123 /// <returns></returns>
124 public bool IsLeaf(Node<T> _node)
125 {
126 //叶子结点(Leaf Node):度为0的结点,也叫终端结点
127 return (_node != null && _node.LChild == null && _node.RChild == null);
128 }
129 }
3 二叉树的遍历
二叉树的遍历是按照某种顺序对树中的每个节点访问且只能访问一次的过程,包括查询、修改、计算等。
二叉树的遍历实际是将非线性结构线性化,它是二叉树的各种计算和操作的基础。
由二叉树的定义可知,一棵二叉树由根结点、左子树和右子树三部分组成,若规定D、L、R分别代表遍历根结点、遍历左子树、遍历右子树,则二叉树的遍历方式有6种:DLR、DRL、LDR、LRD、RDL、RLD。由于先遍历左子树和先遍历右子树在算法设计上没有本质区别,所以,只讨论三种方式:DLR(先序遍历)、LDR(中序遍历)和LRD(后序遍历)。
先序遍历:先访问根节点,再访问左子树,再访问右子树。
中序遍历:先访问左子树,再访问根节点,最后访问右子树。
后序遍历:先访问左子以,再访问右子树,最后访问根节点。
3.1 先序遍历(DLR)
先序遍历的基本思想是:首先访问根结点,然后先序遍历其左子树,最后先序遍历其右子树。先序遍历的递归算法实现如下。(这里只是获取节点的值)
Code
/// <summary>
/// 先序遍历之递归
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public string perOrderNode(Node<char> node)
{
if (node == null)
return null;
StringBuilder nodeValues = new StringBuilder();
nodeValues.Append(node.Data);
//递归调用
nodeValues.Append(perOrderNode(node.LChild));
nodeValues.Append(perOrderNode(node.RChild));
return nodeValues.ToString();
}
public string perOrderRecursion(BinaryTree.BinaryTree<char> tree)
{
return perOrderNode(tree.Header);
}
非递归算法实现:
Code
/// <summary>
/// 二叉树先序遍历之非递归算法
/// 利用栈实现二叉树的遍历.
/// </summary>
/// <param name="tree"></param>
/// <returns></returns>
public string perOrderNoRecursion(BinaryTree.BinaryTree<char> tree)
{
StringBuilder result = new StringBuilder();
Stack<Node<char>> rightStack = new Stack<Node<char>>();
Node<char> curNode = tree.Header;
while (curNode != null || rightStack.Count != 0)
{
if (curNode != null)
{
result.Append(curNode.Data);
if (curNode.RChild != null)
{
//如果右子节点不为NULL,则先放入Stack中.
rightStack.Push(curNode.RChild);
}
curNode = curNode.LChild;
}
else
{
curNode = rightStack.Pop();
}
}
return result.ToString();
}
3.2 中序遍历(LDR)
中序遍历的基本思想是:首先中序遍历根结点的左子树,然后访问根结点,最后中序遍历其右子树。中序遍历的递归算法实现如下: (附件)
3.3 后序遍历(LRD)
后序遍历的基本思想是:首先后序遍历根结点的左子树,然后后序遍历根结点的右子树,最后访问根结点。后序遍历的递归算法实现如下: (附件)
代码下载