概念
平衡二叉树,简称 AVL
实现平衡二叉树,需要先了解二叉搜索树
二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树。
二叉搜索树:一棵二叉树,可以为空;如果不为空,满足以下性质:
- 非空左子树的所有键值小于其根结点的键值。
- 非空右子树的所有键值大于其根结点的键值。
- 左、右子树都是二叉搜索树
二叉搜索树的缺点:在数据不合理的情况下,二叉搜索树会向 链条状 退化,因此查找效率就会从O(log n)变为O(n)
针对二叉搜索树的退化现象,将每个结点左右子树的 高度差(绝对值) 控制在 1 以内,即为平衡二叉树
左右子树的高度差,称为该结点的平衡因子(balance)
平衡二叉树的定义: 1、平衡二叉树 必须是 二叉搜索树 2、平衡二叉树内每个结点左右子树的 高度差(平衡因子) ∈ [-1,1]
使用C#实现平衡二叉树
实现泛型平衡二叉树的思路基于泛型二叉搜索树
如果没有泛型二叉搜索树的手搓经验,可以先看这篇文章泛型二叉搜索树(C#)https://blog.youkuaiyun.com/qq_51613678/article/details/134753106
泛型实现思路:将结点的数据类型定义为 T (泛型),满足“字符串、数字、数组” 等数据类型
解决数据比较问题:
在插入值时,需要将插入值与当前结点的值进行比较,如果数据类型为 T ,值之间的比较方法就需要由创建二叉搜索树的类,自定义回调方法
使用 deledage (委托) 实现回调
采用非递归实现所有功能
二叉树的结构
public class TreeNode<T>
{
public int height; // 树高
public T content; // 内容
public TreeNode<T> left; // 左子树
public TreeNode<T> right; // 右子树
public TreeNode<T> parent; // 父节点
}
height为树高,存储该结点的高度值,并提供给父结点计算平衡因子
自定义比较函数的结构
使用 delegate(委托) 制作 比较函数
由于平衡二叉树中需要判断两个结点内的值 ①是否相等 ②谁大谁小
因此比较函数分为两个制作:
1、判断两个值是否相等
2、比较两个值的大小
定义结构如下:
public delegate bool CompareEqual(T a, T b); // 判断相等, false为 a!=b ,true为 a=b
public delegate bool Compare(T a, T b); // 判断大小, false为 a<b ,true为 a>b
泛型二叉树控制器
_treeNode: 二叉树的根节点
_compareEqual: 判断两个值是否相等
_compare: 比较两个值的大小
public class TreeControllNew<T>
{
public delegate bool CompareEqual(T a, T b); // 判断相等, false为 a!=b ,true为 a=b
public delegate bool Compare(T a, T b); // 判断大小, false为 a<b ,true为 a>b
private TreeNode<T> _treeNode;
private CompareEqual _compareEqual;
private Compare _compare;
public TreeControllNew(T[] contents, CompareEqual compareEqual, Compare compare)
{
_compareEqual = compareEqual;
_compare = compare;
for (int i = 0; i < contents.Length; i++)
{
Insert(contents[i]);
}
}
public TreeControllNew(CompareEqual compareEqual, Compare compare)
{
_compareEqual = compareEqual;
_compare = compare;
}
}
创建存储 int 类型的平衡二叉树
// 构造数据
int[] nums = new[] {15, 10, 19, 8, 13, 16, 28, 5, 9, 12, 14, 20, 30, 25};
TreeControll<int> treeControll = new TreeControll<int>(nums,
delegate(int i, int i1)
{
return i == i1;
},
delegate(int i, int i1)
{
return i > i1;
});
以上:基础建设已完成
维持平衡的概念
已知二叉搜索树满足一个基本要求:
1、左子节点一定小于自身
2、右子节点一定大于自身
平衡二叉树自然也满足该条件,所以维持平衡只需要在 插入&删除 完成后,从该结点向上回溯,判断每个父结点的平衡因子是否满足平衡要求:
不满足则进行 旋转
结点旋转分为两种:左旋 和 右旋
左旋:
1、当前结点的右子树会变成新树的根结点
2、当前结点会作为新树根结点的左子树
3、如果新树原先存在左子树,那么该左子树将作为旧根结点的右子树右旋:
1、当前结点的左子树会变成新树的根结点
2、当前结点会作为新树根结点的右子树
3、如果新树原先存在右子树,那么该左子树将作为旧根结点的左子树
单左旋转函数
根据上述概念,写出 单左旋转函数,负责将传入的结点向左旋转
private void leftRoate(TreeNode<T> treeNode)
{
// 1、当前结点的右子树会变成新树的根结点
// 2、当前结点会作为新树根结点的左子树
// 3、如果新树原先存在左子树,那么该左子树将作为旧根结点的右子树
TreeNode<T> newNode = treeNode.right;
TreeNode<T> newNodeLeft = newNode.left;
newNode.left = treeNode;
newNode.parent = treeNode.parent;
treeNode.parent = newNode;
treeNode.right = newNodeLeft;
if (newNodeLeft != null)
newNodeLeft.parent = treeNode;
if (newNode.parent != null)
{
if (_compare(newNode.content, newNode.parent.content))
newNode.parent.right = newNode;
else
newNode.parent.left = newNode;
}
treeNode.height = 1 + Math.Max(getNodeHeight(treeNode.left), getNodeHeight(treeNode.right));
newNode.height = 1 + Math.Max(getNodeHeight(newNode.left), getNodeHeight(newNode.right));
if (_compareEqual(treeNode.content, _treeNode.content))
{
_treeNode = newNode;
}
}
单右旋转函数
写出 单右旋转函数,负责将传入的结点向右旋转
private void rightRoate(TreeNode<T> treeNode)
{
// 1、当前结点的左子树会变成新树的根结点
// 2、当前结点会作为新树根结点的右子树
// 3、如果新树原先存在右子树,那么该左子树将作为旧根结点的左子树
TreeNode<T> newNode = treeNode.left;
TreeNode<T> newNodeRight = newNode.right;
newNode.right = treeNode;
newNode.parent = treeNode.parent;
treeNode.parent = newNode;
treeNode.left = newNodeRight;
if (newNodeRight != null)
newNodeRight.parent = treeNode;
if (newNode.parent != null)
{
if (_compare(newNode.content, newNode.parent.content))
newNode.parent.right = newNode;
else
newNode.parent.left = newNode;
}
treeNode.height = 1 + Math.Max(getNodeHeight(treeNode.left), getNodeHeight(treeNode.right));
newNode.height = 1 + Math.Max(getNodeHeight(newNode.left), getNodeHeight(newNode.right));
if (_compareEqual(treeNode.content, _treeNode.content))
{
_treeNode = newNode;
}
}
写好旋转函数后,再设计 刷新结点平衡状态函数
该函数负责:
1、更新传入结点的树高
2、获取该结点的平衡因子,如果不处于平衡状态,将判断该结点需要使用哪种旋转方式,并进行旋转。
刷新结点平衡状态函数
private void refreshNodeBalance(TreeNode<T> node)
{
// 重新设置结点高度
node.height = 1 + Math.Max(getNodeHeight(node.left), getNodeHeight(node.right));
// 判断平衡因子,如果 平衡因子 > 1,则失衡
int tmpNodeBalance = getNodeBalance(node);
// LL型平衡旋转 (单右旋转)
if (tmpNodeBalance > 1 && getNodeBalance(node.left) > 0)
{
rightRoate(node);
}
// RR型平衡旋转 (单左旋转)
if (tmpNodeBalance < -1 && getNodeBalance(node.right) < 0)
{
leftRoate(node);
}
// LR型平衡旋转 (先左后右双旋转)
if (tmpNodeBalance > 1 && getNodeBalance(node.left) < 0)
{
leftRoate(node.left);
rightRoate(node);
}
// RL型平衡旋转 (先右后左双旋转)
if (tmpNodeBalance < -1 && getNodeBalance(node.right) > 0)
{
rightRoate(node.right);
leftRoate(node);
}
}
至此,最复杂的旋转就解决了
剩下的则是平衡二叉树的 插入,搜索,删除
插入函数
由于使用了非递归的方式,因此需要设计一个栈(Stack)
在插入 A 结点 时,将其经过的结点压入栈内
完成插入后,将栈内的结点一个个取出,刷新结点的平衡状态:refreshNodeBalance(***)
public void Insert(T content) // 用非递归实现
{
if (_treeNode == null)
{
_treeNode = new TreeNode<T>();
_treeNode.height = 1;
_treeNode.content = content;
return;
}
TreeNode<T> curNode = _treeNode;
Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();
stack.Push(_treeNode);
while (true)
{
if (_compareEqual(content, curNode.content))
{
// 特殊情况:插入的值在树中存在
Console.WriteLine("插入失败,该值已存在:" + content);
return;
}
if (_compare(content, curNode.content))
{
if (curNode.right != null)
{
stack.Push(curNode.right);
curNode = curNode.right;
}
else
{
TreeNode<T> node = new TreeNode<T>();
node.content = content;
node.parent = curNode;
node.height = 1;
curNode.right = node;
break;
}
}
else
{
if (curNode.left != null)
{
stack.Push(curNode.left);
curNode = curNode.left;
}
else
{
TreeNode<T> node = new TreeNode<T>();
node.content = content;
node.parent = curNode;
node.height = 1;
curNode.left = node;
break;
}
}
}
// 更新树高
// curNode.height = 1 + Math.Max(getNodeHeight(curNode.left), getNodeHeight(curNode.right));
while (stack.Count > 0)
{
refreshNodeBalance(stack.Pop());
}
}
搜索函数
(1)查找从根结点开始,如果树为空,返回NULL
(2)若搜索树非空,则根结点关键字和X进行比较,并进行不同处理:
① 若X小于根结点键值,只需在左子树中继续搜索;
② 如果X大于根结点的键值,在右子树中进行继续搜索;
③若两者比较结果是相等,搜索完成,返回指向此结点的指针。
public TreeNode<T> Find(T content) // 用非递归实现
{
// 目前该功能只负责返回 是否找到
Console.WriteLine("---------------- 查找 ---------------");
bool isFind = false;
TreeNode<T> curNode = _treeNode;
while (curNode != null)
{
if (_compareEqual(content, curNode.content))
{
Console.WriteLine("已找到节点: " + content);
Console.WriteLine("树高为: " + curNode.height + " 父节点为: " + (curNode.parent != null ? curNode.parent.content.ToString() : "空"));
isFind = true;
break;
}
if (_compare(content, curNode.content))
{
curNode = curNode.right;
}
else
{
curNode = curNode.left;
}
}
if (!isFind)
{
Console.WriteLine("没有找到节点: " + content);
}
Console.WriteLine("----------------查找结束---------------\n");
return isFind ? curNode : null;
}
删除函数
由于使用非递归的方法完成删除,因此也需要设计一个栈
将最后删除的结点压入栈中
从该结点开始向上回溯(获取父结点),并刷新这个结点的平衡状态
直到当前结点的父结点 == NULL
public void Remove(T content, bool repleaseLeft = true) // 用非递归实现
{
/*
* 1、删除叶子节点:直接删
* 2、删除的节点只包含一个孩子节点:将该节点的父节点指向该节点的孩子节点
* 3、删除的节点包含左右节点:找到该节点左子树中的最大元素 或 右子树中的最小元素, 进行替换
* 4、从最后被删除的结点,往父结点回溯,刷新每个父节点的高度和平衡因子,并判断是否需要平衡旋转
*/
// 找到该节点
TreeNode<T> curNode = Find(content);
if (curNode == null)
{
Console.WriteLine("未找到该元素");
return;
}
bool isEnd = false;
Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();
while (!isEnd)
{
if (curNode.left == null && curNode.right == null) // 叶子结点
{
if (curNode.parent != null)
{
if (_compare(curNode.content, curNode.parent.content))
curNode.parent.right = null;
else
curNode.parent.left = null;
stack.Push(curNode.parent);
}
curNode = null;
isEnd = true;
}
else if(curNode.left != null && curNode.right != null) // 存在左右节点
{
TreeNode<T> replaceNode;
if (repleaseLeft) // 找到左节点最大的节点
{
replaceNode = curNode.left;
while (replaceNode.right != null)
{
replaceNode = replaceNode.right;
}
}
else // 找到右节点最小的节点
{
replaceNode = curNode.right;
while (replaceNode.left != null)
{
replaceNode = replaceNode.left;
}
}
curNode.content = replaceNode.content;
curNode = replaceNode;
}
else // 只存在一个子节点
{
TreeNode<T> tmpNode;
if (curNode.left != null)
tmpNode = curNode.left;
else
tmpNode = curNode.right;
tmpNode.parent = curNode.parent;
curNode = null;
if (_compare(tmpNode.content, tmpNode.parent.content))
tmpNode.parent.right = tmpNode;
else
tmpNode.parent.left = tmpNode;
stack.Push(tmpNode.parent);
isEnd = true;
}
}
// 回溯父结点,刷新平衡因子
while (stack.Count > 0)
{
TreeNode<T> node = stack.Pop();
if (node.parent != null)
{
stack.Push(node.parent);
}
refreshNodeBalance(node);
}
}
总结
平衡二叉树的优点:
可以自动更新自身的平衡状态,确保查找的时间复杂度为O(log n)
缺点也很明显,频繁的调整平衡状态、旋转结点也颇耗性能
因此舍弃部分平衡的红黑树则是更优解,得抽空看起来了。。
全部代码
TreeNode.cs
public class TreeNode<T>
{
public int height; // 树高
public T content; // 内容
public TreeNode<T> left; // 左子树
public TreeNode<T> right; // 右子树
public TreeNode<T> parent; // 父节点
}
TreeControll.cs
public class TreeControllNew<T>
{
public delegate bool CompareEqual(T a, T b); // 判断相等, false为 a!=b ,true为 a=b
public delegate bool Compare(T a, T b); // 判断大小, false为 a<b ,true为 a>b
private TreeNode<T> _treeNode;
private CompareEqual _compareEqual;
private Compare _compare;
public TreeControllNew(T[] contents, CompareEqual compareEqual, Compare compare)
{
_compareEqual = compareEqual;
_compare = compare;
for (int i = 0; i < contents.Length; i++)
{
Insert(contents[i]);
}
}
public TreeControllNew(CompareEqual compareEqual, Compare compare)
{
_compareEqual = compareEqual;
_compare = compare;
}
private int getNodeHeight(TreeNode<T> node)
{
return node != null ? node.height : 0;
}
/// <summary>
/// 单左旋转
/// </summary>
/// <param name="treeNode"></param>
/// <returns></returns>
private void leftRoate(TreeNode<T> treeNode)
{
// 1、当前结点的右子树会变成新树的根结点
// 2、当前结点会作为新树根结点的左子树
// 3、如果新树原先存在左子树,那么该左子树将作为旧根结点的右子树
TreeNode<T> newNode = treeNode.right;
TreeNode<T> newNodeLeft = newNode.left;
newNode.left = treeNode;
newNode.parent = treeNode.parent;
treeNode.parent = newNode;
treeNode.right = newNodeLeft;
if (newNodeLeft != null)
newNodeLeft.parent = treeNode;
if (newNode.parent != null)
{
if (_compare(newNode.content, newNode.parent.content))
newNode.parent.right = newNode;
else
newNode.parent.left = newNode;
}
treeNode.height = 1 + Math.Max(getNodeHeight(treeNode.left), getNodeHeight(treeNode.right));
newNode.height = 1 + Math.Max(getNodeHeight(newNode.left), getNodeHeight(newNode.right));
if (_compareEqual(treeNode.content, _treeNode.content))
{
_treeNode = newNode;
}
}
/// <summary>
/// 单右旋转
/// </summary>
/// <param name="treeNode"></param>
/// <returns></returns>
private void rightRoate(TreeNode<T> treeNode)
{
// 1、当前结点的左子树会变成新树的根结点
// 2、当前结点会作为新树根结点的右子树
// 3、如果新树原先存在右子树,那么该左子树将作为旧根结点的左子树
TreeNode<T> newNode = treeNode.left;
TreeNode<T> newNodeRight = newNode.right;
newNode.right = treeNode;
newNode.parent = treeNode.parent;
treeNode.parent = newNode;
treeNode.left = newNodeRight;
if (newNodeRight != null)
newNodeRight.parent = treeNode;
if (newNode.parent != null)
{
if (_compare(newNode.content, newNode.parent.content))
newNode.parent.right = newNode;
else
newNode.parent.left = newNode;
}
treeNode.height = 1 + Math.Max(getNodeHeight(treeNode.left), getNodeHeight(treeNode.right));
newNode.height = 1 + Math.Max(getNodeHeight(newNode.left), getNodeHeight(newNode.right));
if (_compareEqual(treeNode.content, _treeNode.content))
{
_treeNode = newNode;
}
}
/// <summary>
/// 刷新结点平衡状态
/// </summary>
/// <param name="node"></param>
private void refreshNodeBalance(TreeNode<T> node)
{
// 重新设置结点高度
node.height = 1 + Math.Max(getNodeHeight(node.left), getNodeHeight(node.right));
// 判断平衡因子,如果 平衡因子 > 1,则失衡
int tmpNodeBalance = getNodeBalance(node);
// LL型平衡旋转 (单右旋转)
if (tmpNodeBalance > 1 && getNodeBalance(node.left) > 0)
{
rightRoate(node);
}
// RR型平衡旋转 (单左旋转)
if (tmpNodeBalance < -1 && getNodeBalance(node.right) < 0)
{
leftRoate(node);
}
// LR型平衡旋转 (先左后右双旋转)
if (tmpNodeBalance > 1 && getNodeBalance(node.left) < 0)
{
leftRoate(node.left);
rightRoate(node);
}
// RL型平衡旋转 (先右后左双旋转)
if (tmpNodeBalance < -1 && getNodeBalance(node.right) > 0)
{
rightRoate(node.right);
leftRoate(node);
}
}
/// <summary>
/// 获取当前结点的平衡因子
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private int getNodeBalance(TreeNode<T> node)
{
return node != null ? getNodeHeight(node.left) - getNodeHeight(node.right) : 0;
}
/// <summary>
/// 插入值
/// </summary>
/// <param name="content"></param>
public void Insert(T content) // 用非递归实现
{
if (_treeNode == null)
{
_treeNode = new TreeNode<T>();
_treeNode.height = 1;
_treeNode.content = content;
return;
}
TreeNode<T> curNode = _treeNode;
Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();
stack.Push(_treeNode);
while (true)
{
if (_compareEqual(content, curNode.content))
{
// 特殊情况:插入的值在树中存在
Console.WriteLine("插入失败,该值已存在:" + content);
return;
}
if (_compare(content, curNode.content))
{
if (curNode.right != null)
{
stack.Push(curNode.right);
curNode = curNode.right;
}
else
{
TreeNode<T> node = new TreeNode<T>();
node.content = content;
node.parent = curNode;
node.height = 1;
curNode.right = node;
break;
}
}
else
{
if (curNode.left != null)
{
stack.Push(curNode.left);
curNode = curNode.left;
}
else
{
TreeNode<T> node = new TreeNode<T>();
node.content = content;
node.parent = curNode;
node.height = 1;
curNode.left = node;
break;
}
}
}
// 更新树高
// curNode.height = 1 + Math.Max(getNodeHeight(curNode.left), getNodeHeight(curNode.right));
while (stack.Count > 0)
{
refreshNodeBalance(stack.Pop());
}
}
/// <summary>
/// 查找值
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public TreeNode<T> Find(T content) // 用非递归实现
{
// 目前该功能只负责返回 是否找到
Console.WriteLine("---------------- 查找 ---------------");
bool isFind = false;
TreeNode<T> curNode = _treeNode;
while (curNode != null)
{
if (_compareEqual(content, curNode.content))
{
Console.WriteLine("已找到节点: " + content);
Console.WriteLine("树高为: " + curNode.height + " 父节点为: " + (curNode.parent != null ? curNode.parent.content.ToString() : "空"));
isFind = true;
break;
}
if (_compare(content, curNode.content))
{
curNode = curNode.right;
}
else
{
curNode = curNode.left;
}
}
if (!isFind)
{
Console.WriteLine("没有找到节点: " + content);
}
Console.WriteLine("----------------查找结束---------------\n");
return isFind ? curNode : null;
}
/// <summary>
/// 删除值
/// </summary>
/// <param name="content"></param>
/// <param name="repleaseLeft"> 删除值后的替换元素,是否采用左节点内的元素 </param>
public void Remove(T content, bool repleaseLeft = true) // 用非递归实现
{
/*
* 1、删除叶子节点:直接删
* 2、删除的节点只包含一个孩子节点:将该节点的父节点指向该节点的孩子节点
* 3、删除的节点包含左右节点:找到该节点左子树中的最大元素 或 右子树中的最小元素, 进行替换
* 4、从最后被删除的结点,往父结点回溯,刷新每个父节点的高度和平衡因子,并判断是否需要平衡旋转
*/
// 找到该节点
TreeNode<T> curNode = Find(content);
if (curNode == null)
{
Console.WriteLine("未找到该元素");
return;
}
bool isEnd = false;
Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();
while (!isEnd)
{
if (curNode.left == null && curNode.right == null) // 叶子结点
{
if (curNode.parent != null)
{
if (_compare(curNode.content, curNode.parent.content))
curNode.parent.right = null;
else
curNode.parent.left = null;
stack.Push(curNode.parent);
}
curNode = null;
isEnd = true;
}
else if(curNode.left != null && curNode.right != null) // 存在左右节点
{
TreeNode<T> replaceNode;
if (repleaseLeft) // 找到左节点最大的节点
{
replaceNode = curNode.left;
while (replaceNode.right != null)
{
replaceNode = replaceNode.right;
}
}
else // 找到右节点最小的节点
{
replaceNode = curNode.right;
while (replaceNode.left != null)
{
replaceNode = replaceNode.left;
}
}
curNode.content = replaceNode.content;
curNode = replaceNode;
}
else // 只存在一个子节点
{
TreeNode<T> tmpNode;
if (curNode.left != null)
tmpNode = curNode.left;
else
tmpNode = curNode.right;
tmpNode.parent = curNode.parent;
curNode = null;
if (_compare(tmpNode.content, tmpNode.parent.content))
tmpNode.parent.right = tmpNode;
else
tmpNode.parent.left = tmpNode;
stack.Push(tmpNode.parent);
isEnd = true;
}
}
// 回溯父结点,刷新平衡因子
while (stack.Count > 0)
{
TreeNode<T> node = stack.Pop();
if (node.parent != null)
{
stack.Push(node.parent);
}
refreshNodeBalance(node);
}
}
/// <summary>
/// 打印树
/// </summary>
public void PrintOut()
{
// 随便打印的,树的形状不一定合理
Console.WriteLine("---------------------- 打印树 ---------------------");
if (_treeNode == null)
{
Console.WriteLine("树为空");
return;
}
Queue<TreeNode<T>> queue = new Queue<TreeNode<T>>();
queue.Enqueue(_treeNode);
int curHeight = _treeNode.height;
while (queue.Count != 0)
{
TreeNode<T> node = queue.Dequeue();
if (curHeight > node.height)
{
curHeight = node.height;
Console.Write("\n");
}
Console.Write(node.content + "(" + node.height + ")" + " ");
if (node.left != null)
{
queue.Enqueue(node.left);
}
if (node.right != null)
{
queue.Enqueue(node.right);
}
}
Console.WriteLine("\n----------------------打印结束---------------------\n");
}
}
main函数代码.cs
public class T_Main
{
// 平衡二叉树 AVL
/*
* 定义:
* 1、平衡二叉树 必须是 二叉搜索树
* 2、平衡二叉树内每个结点左右子树的 高度差 <= 1
*/
/// <summary>
/// 起始函数
/// </summary>
public static void Main(string[] args)
{
// 构造数据
// int[] nums = new[] {15, 10, 19, 8, 13, 16, 28, 5, 9, 12, 14, 20, 30, 25};
// int[] nums = new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
// int[] nums = new[] {11, 10, 9, 8, 7, 6, 5, 4, ;3, 2, 1, 0};
int[] nums = new[] {5, 3, 15, 1, 10, 18, 19};
// 创建存储 int 类型的二叉搜索树
TreeControllNew<int> treeControll = new TreeControllNew<int>(nums,
delegate(int i, int i1)
{
return i == i1;
},
delegate(int i, int i1)
{
return i > i1;
});
// 打印树结构
treeControll.PrintOut();
// 查找
// treeControll.Find(15);
// treeControll.Find(25);
// treeControll.Find(13);
// treeControll.Find(-1);
// 删除
treeControll.Remove(1);
treeControll.PrintOut();
treeControll.Remove(15);
treeControll.PrintOut();
}
}