什么是二叉树?
用一张图直观表示:

二叉树具有层级特效的数据结构。一棵树包含多个节点。
节点的层次属于二叉树的高,二叉树的效率衡量值由二叉树的高决定
排序二叉树(BST)特点:
1、
左
子树上所有结点的值均
小于或等于
它的根结点的值
2、
右
子树上所有结点的值均
大于或等于
它的根结点的值。
3、 左、右子树也分别为二叉排序树。
顶部的节点称之为:
根节点,没有子树的节点称之为:
叶子节点
定义一个二叉树--C#实现:
采用泛型,适应性更广
public
class
Tree
<
TItem
>
where
TItem
:
IComparable
<
TItem
>
{
private
TItem NodeDate;//根节点
private
Tree<
TItem
> Left;
//左子节点
private
Tree<
TItem
> Right;
//右子节点
///
<summary>
///
构造函数对树的对象复制
///
</summary>
///
<param name="
nodeValue
"></param>
public
Tree(
TItem
nodeValue)
{
this
.NodeDate = nodeValue;
this
.Right =
null
;
this
.Left =
null
;
}
}
二叉树的
插入
:
原理:插入值与根节点做比较,若插入值大于根节点,则插入至根节点的右子树,若插入值小于根节点,则插入至左子树
C#代码实现:
///
<summary>
///
树的插入操作,实现二叉排序树
///
</summary>
///
<param name="
newItem
"></param>
public
void
Insert(
TItem
newItem)
{
TItem
currentNodeValue =
this
.NodeDate;
//当currentNodeValue>newItem 即当前节点大于等于当前数值
{
//若当前左子树为空
if
(
this
.Left ==
null
)
{
//则当前左子树作为新的根节点
this
.Left =
new
Tree<
TItem
>(newItem);
}
else
//否则继续判断
{
this
.Left.Insert(newItem);
}
}
else
//根节点小于当前数值
{
if
(
this
.Right ==
null
)
{
this
.Right =
new
Tree<
TItem
>(newItem);
}
else
{
this
.Right.Insert(newItem);
}
}
}
常见的
二叉树遍历
--原理:
1、中序遍历:
访问顺序:首先访问左子树,然后访问根节点,最后访问右子树。
实现逻辑:如果当前左子树不为空,则继续访问左子树,直到左子树的叶子节点,打印当前叶子节点,再打印其根节点,再访问其根节点的右子节点。
适用:将数值从小到大排序
2、前序遍历:首先访问根节点,再访问左子树,最后访问右子树。
适用:当已有一个二叉树的时候,需要将已有二叉树复制一份时,前序遍历效果最好。比重新构建二叉树的效率高了10倍左右
3、后序遍历:首先访问左子树,再访问右子树,最后访问根节点。
适用:举例:操作系统的文件遍历时,若当前根节点存在子树,则代表存在子文件夹,若无子树,则代表当前文件夹为最后一个文件夹,即可访问当前文件夹内的内容
4、层序遍历
5、z-型层序遍历
(4、5在此不做讨论)
一、中序遍历--C#代码实现:
从根节点开始->左子树->右子树
public
void
TraverseTreeInOrder()
{
//如果当前左子树不为空
if
(
this
.Left !=
null
)
{
//则继续查找左子树
this
.Left.TraverseTreeInOrder();
}
//当没有左子树时,打印当前根节点
Console.WriteLine(this.NodeDate);
//如果当前右子树不为空
if
(
this
.Right !=
null
)
{
//继续遍历右子树
this
.Right.TraverseTreeInOrder();
}
}
二、前序遍历--C#代码实现:
从根节点开始->左子树->右子树
public
void
TraverseTreePerOrder()
{
Console.WriteLine(this.NodeDate);
if
(
this
.Left !=
null
)
{
this
.Left.TraverseTreePerOrder();
}
if
(
this
.Right !=
null
)
{
this
.Right.TraverseTreePerOrder();
}
}
三、后续遍历--C#代码实现:
从左子树开始->右子树->根节点
public
void
TraverseTreeLastOrder()
{
if
(
this
.Left !=
null
)
{
this
.Left.TraverseTreeLastOrder();
}
if
(
this
.Right !=
null
)
{
this
.Right.TraverseTreeLastOrder();
}
Console.WriteLine(this.NodeDate);
}
二叉树的
查找
:
原理:先判断当前节点是否存在,若存在,则用根节点与查找值左比较,若大于查找值,则进入左子树查询,若小于查找值,则进入右子树查找。存在则返回true,不存在则返回false
C#代码实现:
public
bool
TreeQuery(Tree<
TItem
> tree,
TItem
item)
{
//如果当前节点为null,则代表不存在该数值
if
(tree ==
null
)
{
return
false
;
}
//如果NodeDate>item 当前节点大于当前数值
{
return
TreeQuery(tree.Left, item);
}//如果当前节点小于当前数值,则进入右子树查找
{
return
TreeQuery(tree.Right, item);
}
else
{
return
true
;
}
}
查找二叉树的最大值:
原理:二叉树的最大值位置在排序二叉树最右侧的节点。
先判断是当前节点是否存在,再判断当前节点是否存在右子树,若存在则进入右子树查询,一直访问至无右子树的节点,并返回此时(无右子树)的节点
C#代码实现:
public
TItem TreeQueryMax(Tree<TItem> tree)
{
if
(tree !=
null
)
{
while
(tree !=
null
&& tree.Right !=
null
)
{
tree = tree.Right;
}
return
tree.NodeDate;
}
return
default
(TItem);
}
查找二叉树的最小值:
原理:二叉树的最小值位于排序二叉树的最左侧的节点。
先判断是当前节点是否存在,再判断当前节点是否存在左子树,若存在则进入左子树查询,一直找到无左子树的节点,并放回此时(无左子树)的节点。
public
TItem TreeQueryMin(Tree<TItem> tree)
{
if
(tree !=
null
)
{
while
(tree !=
null
&& tree.Left !=
null
)
{
tree = tree.Left;
}
return
tree.NodeDate;
}
return
default
(TItem);
}
二叉树节点的删除:
原理:二叉树节点的删除分为三种情况
第一种情况为:删除的节点没有左子树(删除下图的值为3的节点)。
用3与根节点8做比较,3<8,此时则进入当前节点的左子树。此时3==3,那么将此节点删除。

将节点3删除时,用节点8的左孩子,指向被删除节点的左孩子。
即把当前节点(被删除的节点)的左子树,替换为当前节点。这样就不会破坏排序二叉树的平衡性。

第二种情况:删除的节点没有右子树(删除下图的值为10的节点)
原理:用10与根节点8做比较,因为10>8,进入右子树,此时10==10,那么将此节点删除。

将节点10删除时,用节点8的右子树指向被删除节点的右子树。

第三种情况:被删除的节点既包含了左子树,右包含了右子树。也是最复杂的情况,(删除下图值为3的节点)
原理:要删除节点3,从节点3的右子树(被选中的右子树中),找出最小节点,并用找出的最小节点,替换当前被删除的节点,节点3被替换后再删除替换节点4。

删除后,该排序二叉树依然保持平衡性。

第四种情况:是不是以为第三种情况那么复杂,第四种情况要上天了?经过复杂的脑回路思考,咱们休息一下。
第四种情况为当前节点为叶子节点,那么直接删除就好了~~~是不是so easy?~
C#代码实现:
先定义一个查找右子树最小值的函数。(与二叉树查找最小数的方法略有区别,此函数返回的是最小节点的对象,查找二叉树最小值的方法放回的是一个数值)
public
Tree<TItem> FindMinNode(Tree<TItem> tree, TItem item)
{
if
(tree !=
null
)
{
while
(tree !=
null
&& tree.Left !=
null
)
{
tree = tree.Left;
}
}
return
tree;
}
public
Tree<TItem> DeleteTreeNode(Tree<TItem> tree, TItem item)
{
//如果当前节点为Null,则代表不包含此节点,删除失败
if
(tree ==
null
)
{
return
null
;
}
//如果当前值小于当前节点
{
//进入左子树删除
return
DeleteTreeNode(tree.Left, item);
}
//如果值大于当前节点
{
//进入右子树删除
return
DeleteTreeNode(tree.Right, item);
}
//如果值匹配,则用右子树中的最小值替换该节点
else
{
//判断该节点是否为叶子节点,若为叶子节点,则直接删除
if
(tree.Left ==
null
&& tree.Right ==
null
)
{
tree =
null
;
return
tree;
}
//如果当前节点只有右子树
else
if
(tree.Left ==
null
)
{
//则将当前节点的右子树换成当前节点
tree = tree.Right;
//返回被更新过的节点
return
tree;
}
//如果当前节点只有左子树
else
if
(tree.Right ==
null
)
{
//则将当前节点的左子树换成当前节点
tree = tree.Right;
//返回被更新过的节点
return
tree;
}
//如果当前节点左右子树均存在
else
if
(tree.Right !=
null
&& tree.Left !=
null
)
{
//找出右子树的最小节点,替换当前节点
tree.NodeDate = FindMinNode(tree, item).NodeDate;
//删除右子树的最小节点。
tree.Left = DeleteTreeNode(tree.Right, tree.NodeDate);
return
tree;
}
}
return
null
;
}
上述所有方法,均写在
public
class
Tree
<
TItem
>
where
TItem : IComparable<TItem>
中
最后简单说下二叉树的实例化、赋值以及调用。
class Program
{
static void Main(string[] args)
{
Tree<int> t = new Tree<int>(8);//定义初始节点
int[] a = { 3, 10, 1, 6, 4, 7, 14, 13 };
//插入节点
foreach (int item in a)
{
t.Insert(item);
}
Console.WriteLine("二叉树的打印");
t.TraverseTreePerOrder();
t.DeleteTreeNode(t, 3);
Console.WriteLine("删除后,二叉树的打印");
t.TraverseTreePerOrder();
Console.Read();
}
}