什么是二叉树?
用一张图直观表示:
二叉树具有层级特效的数据结构。一棵树包含多个节点。
节点的层次属于二叉树的高,二叉树的效率衡量值由二叉树的高决定
排序二叉树(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();
}
}