二叉树
- 每个节点最多有两颗子数;
- 左右子树有序,不能颠倒
- 即使某个节点中只有一颗子树,也要区分是左右;如上图所示:节点C是节点A的右子树,节点F是节点C的右子树。
- 特殊的二叉树:
1、斜树:所有节点都只有左子节点或都只有右子节点
2、满二叉树:在一颗二叉树中,所有的分支节点都存在左子树和右子树,并且所有叶子节点都在同一层,这样的二叉树称为满二叉树。
3、完全二叉树:对一颗具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的节点同样深度的满二叉树中编号为i的节点在二叉树中的位置完全相同,则这颗二叉树称为完全二叉树。如下:如果10号节点存在则是一颗完全二叉树,如果不存在则不是
二叉树的遍历
-
前序遍历:
若二叉树为空,则空操作返回,否则先访问跟节点,然后前序遍历左子树,在遍历右子树(根左右)。上图完全二叉树序号的前序遍历顺序为:1->2->4->8->9->5->10->11->3->6->7 -
中序遍历:
若二叉树为空,则空操作返回,否则先遍历左子树,然后访问根节点,最后访问右子树(左根右)。上图完全二叉树序号的中序遍历顺序为:8->4->9->2->10->5->11->1->6->3->7 -
后序遍历:
若二叉树为空,则空操作返回,否则先遍历左子树,然后遍历右子树,最后遍历根节点(左右根)。上图完全二叉树序号的后序遍历顺序为:8->9->4->10->11->5->2->6->7->3->1 -
层序遍历:
若二叉树为空,则空操作返回,否则从树的第一层(根节点)开始访问,从上而下,逐层遍历,在同一层中,按从左到右的顺序对节点逐个访问。上图完全二叉树的层序遍历为:1->2->3->4->5->6->7->8->9->10->11
void PreOrder(BitTree tree)
{
if (tree == null)
return;
Console.WriteLine(tree.data);//先显示根节点
PreOrder(tree.left);//再遍历左子树
PreOrder(tree.right);//最后遍历右子树
}
以上述前序代码为例,二叉树的遍历其实很简单,只需要按照规则,利用递归,写好调用的先后顺序即可。
二叉排序树
在实际开发过程中,我们经常遇到这样一种情况,对于一组无序的数据我们需要将其进行排序,并同时这组些数据会频繁的增删改查。我们设计时既要考虑查找的效率,也要考虑增、删排序的效率问题。利用二叉树,我们可以构件而插排序树,创建时就已经排好序了。
对于集合{68,52,30,57,72,78,80,75}做存储时,就可以考虑采用二叉树这种数据结构,并且是已经排好序的二叉树。
68放入根节点,之后的每个数据通过判断与根节点的大小一次放在合适的位置,这样我们就得到了一颗二叉排序树,当我们对它进行中序遍历时,我们就能得到一个有序的序列。
//二叉树结构定义
class BitTree
{
public int data;
public BitTree left;
public BitTree right;
public BitTree parent;
public BitTree(int num)
{
data = num;
}
}
static void Main(string[] args)
{
int[] num = { 68, 52, 30, 57,20,31, 72, 78,54,60, 80, 75 };
BitTree root = new BitTree(num[0]);
//数据创建
for (int i = 1; i < num.Length; i++)
{
InsertBitTree(root, num[i]);
PrintBiteTree(root);
Console.WriteLine("第" + i + "个插入数据");
}
RemoveBitTree(root, 52);
Console.WriteLine("删除52后");
PrintBiteTree(root);
}
static bool SearchBitTree(BitTree T, int key, BitTree parent, out BitTree target)
{
target = null;
if (T == null)
{
target = parent;
return false;
}
else if (T.data == key)
{
target = T;
return true;
}
else if (key < T.data)
{
return SearchBitTree(T.left, key, T, out target);
}
else
{
return SearchBitTree(T.right, key, T, out target);
}
}
/// <summary>
/// 插入操作
/// </summary>
/// <param name="root"></param>
/// <param name="key"></param>
static void InsertBitTree(BitTree root,int key)
{
BitTree target;
if (!SearchBitTree(root, key, null,out target))
{
BitTree node = new BitTree(key);
node.left = null;
node.right = null;
node.parent = target;
if (key < target.data)
{
target.left = node;
}
else
target.right = node;
}
}
/// <summary>
/// 移除操作
/// </summary>
/// <param name="target"></param>
/// <param name="key"></param>
static void RemoveBitTree(BitTree target, int key)
{
//删除操作
if (target == null)
{
return;
}
else
{
if (key == target.data)
{
DeleteBitTree(target);
}
else if (key < target.data)
{
RemoveBitTree(target.left, key);
}
else
RemoveBitTree(target.right, key);
}
}
static bool DeleteBitTree(BitTree target)
{
if (target.right == null)
{
//右子树为空,从接左子树
target = target.left;
}
else if (target.left == null)
{
//左子树为空,重接右子树
target = target.right;
}
else
{
BitTree temp = target;
BitTree s = target.left;
while (s.right != null)
{
temp = s;
//找到最右端
s = s.right;
}
target.data = s.data;
if (temp != s)
{
temp.right = s.left;
}
else
{
temp.left = s.right;
}
}
return false;
}
/// <summary>
/// 中序遍历
/// </summary>
/// <param name="tree"></param>
static void PrintBiteTree(BitTree tree)
{
if (tree == null)
return;
PrintBiteTree(tree.left);
Console.Write(tree.data + " ");
PrintBiteTree(tree.right);
}
运行结果:
52 68 第1个插入数据
30 52 68 第2个插入数据
30 52 57 68 第3个插入数据
20 30 52 57 68 第4个插入数据
20 30 31 52 57 68 第5个插入数据
20 30 31 52 57 68 72 第6个插入数据
20 30 31 52 57 68 72 78 第7个插入数据
20 30 31 52 54 57 68 72 78 第8个插入数据
20 30 31 52 54 57 60 68 72 78 第9个插入数据
20 30 31 52 54 57 60 68 72 78 80 第10个插入数据
20 30 31 52 54 57 60 68 72 75 78 80 第11个插入数据
删除52后
20 30 31 54 57 60 68 72 75 78 80
平衡二叉树
如果待插入的序列本身就是有序的,那么我们按照上述二叉排序树构建二叉树很容易构建成局部甚至是完全的斜二叉树,这样构建显然不是我们愿意看到的,因此就得引入平衡二叉树。在二叉排序树插入时动态的调整节点,使二叉树保持每一个节点的左右子树的高度差最多等于1。我们需要在BitTree结构中添加一个平衡因子bf(Balance Factor),每次添加节点后自行平衡二叉树的结构。
例如 int[] num = { 15,25,35,45,55,65,75,85,95};
按照平衡二叉树的构建方法,我们会得到一个斜二叉树(实际会更长,这里部分截屏以做说明):
这样会导致二叉树会比较深,儿采用平衡二叉树后:
每次插入后检查当前二叉树的是否打破平衡,根据平衡因子进行相应的选择。
- 定义常量因子:
由于C#没有指针,对象之间是引用关系,因此需要treeRoot一直指向二叉树的根节点
public const int LH = 1;//左高
public const int EH = 0;//等高
public const int RH = -1;//右高
static BitTree treeRoot;//
- 左旋转:
/// <summary>
/// 对以root为根的二叉树排序作左旋处理
/// </summary>
/// <param name="root"></param>
static BitTree L_Rotate(BitTree root)
{
int pos = 0;
BitTree parent = root.parent;
///记录当前节点在父节点下是左子树还是右子树
if(parent!=null)
{
if (parent.left == root)
{
pos = -1;
}
else if (parent.right == root)
{
pos = 1;
}
}
BitTree R = root.right;
//将原来跟下右节点的左子树赋给跟节点的右子树
root.right = R.left;
if (R.left != null)
{
R.left.parent = root;
}
//将原来跟的左节点赋给原来跟的右节点左子树
//这样原来的根的右子树变成了新的根节点
R.left = root;
root.parent = R;
//将新的根替换原来跟的位置
if (parent != null)
{
if (pos == -1)
parent.left = R;
else if (pos == 1)
parent.right = R;
R.parent = parent;
}
else
{
R.parent = null;
}
if (R.parent == null)
{
treeRoot = R;//将treeRoot引用指向新的根节点
}
//treeRoot = R;
return R.left;//此时的R.left则为原来根节点root
}
- 左平衡:根据新增节点时设置的平衡因子,将二叉树平衡:
static void LeftBalance(BitTree root)
{
BitTree L, Lr;
L = root.left;
switch (L.bf)
{
case LH://新插入的节点在左孩子的左子树上,要做右旋处理
root.bf = L.bf = EH;
root = R_Rotate(root);
break;
case RH://新插入的节点在左孩子的右子树上双旋
Lr = L.right;//左孩子的右子跟
switch (Lr.bf)
{
case LH:
root.bf = RH;
L.bf = EH;
break;
case EH:
root.bf = L.bf = EH;
break;
case RH:
root.bf = EH;
L.bf = LH;
break;
}
Lr.bf = EH;
L_Rotate(root.left);
R_Rotate(treeRoot);
break;
}
if (root.parent == null)
{
treeRoot = root;
}
}
同样的,右旋转,和右平衡的代码类似:
/// <summary>
/// 对以root为根的二叉树排序作右旋处理
/// </summary>
/// <param name="root"></param>
static BitTree R_Rotate(BitTree root)
{
int pos = 0;
BitTree parent = root.parent;
BitTree L = root.left;
if (parent != null)
{
if (parent.left == root)
{
pos = -1;
}
else if (parent.right == root)
{
pos = 1;
}
}
root.left = L.right;
if (L.right != null)
{
L.right.parent = root;
}
L.right = root;
root.parent = L;
if (parent != null)
{
if (pos==-1)
parent.left = L;
else if(pos==1)
{
parent.right = L;
}
L.parent = parent;
}
else
{
L.parent = null;
}
if (L.parent == null)
{
treeRoot = L;
}
//treeRoot = L;
return L.right;
}
static void RightBalance(BitTree root)
{
BitTree R, Rl;
R = root.right;
switch (R.bf)
{
case RH://新插入的节点在右孩子的右子树上,要做左旋处理
root.bf = R.bf = EH;
root = L_Rotate(root);
break;
case LH://新插入的节点在右孩子的左子树上双旋
Rl = R.left;//左孩子的右子跟
switch (Rl.bf)
{
case LH:
root.bf = EH;
R.bf = RH;
break;
case EH:
root.bf = R.bf = EH;
break;
case RH:
root.bf = LH;
R.bf = EH;
break;
}
Rl.bf = EH;
R_Rotate(root.right);
L_Rotate(treeRoot);
break;
}
if (root.parent == null)
{
treeRoot = root;
}
}
- 二叉平衡树插入:
static bool InsertAVL(BitTree root, int key, out bool taller)
{
taller = true;
if (root == null)
{
root = new BitTree(key);
root.left = root.right = null;
root.bf = EH;
taller = true;
}
else
{
if (root.data == key)
{
taller = false;
return false;
}
if (key < root.data)
{
if (root.left == null)
{
root.left = new BitTree(key);
root.left.parent = root;
root.left.left = root.left.right = null;
root.left.bf = EH;
taller = true;
}
else
{
if (!InsertAVL(root.left, key, out taller))
{
//未插入
return false;
}
}
if (taller)
{
switch (root.bf)
{
case LH://原本左子树比右子树高,需要做平衡处理
LeftBalance(root);
taller = false;
break;
case EH://原本左右子树等高,因为左子树而增高
root.bf = LH;
taller = true;
break;
case RH://原本右子树高,现在左右等高
root.bf = EH;
taller = false;
break;
}
}
//应该在左子树中搜索
}
else
{
if (root.right == null)
{
root.right = new BitTree(key);
root.right.parent = root;
root.right.left = root.right.right = null;
root.right.bf = EH;
taller = true;
}
else
{
if (!InsertAVL(root.right, key, out taller))
{
//未插入
return false;
}
}
if (taller)
{
switch (root.bf)
{
case LH://原本左子树高,现在左右等高
root.bf = EH;
taller = false;
break;
case EH:
root.bf = RH;
taller = true;
break;
case RH://原本右子树比左子树高,需要做平衡处理
RightBalance(root);
taller = false;
break;
}
}
}
}
return true;
}
- 为了方便查看每一步执行的结果以及二叉树的实际构造,将二叉树按照层序输出:
static void LevelOrder(BitTree tree)
{
List<List<string>> orders = new List<List<string>>();
Queue<BitTree> cureent_level = new Queue<BitTree>();
cureent_level.Enqueue(tree);
int index = 0;
while (cureent_level.Count > 0)
{
int count = cureent_level.Count;
orders.Add(new List<string>());
//注意C#是引用,需要每次new新的Queue断开原来的引用关系
Queue<BitTree> next_level = new Queue<BitTree>();
while (count > 0)
{
BitTree info = cureent_level.Dequeue();
if (info != null)
{
orders[index].Add(info.data.ToString());
next_level.Enqueue(info.left);
next_level.Enqueue(info.right);
}
else
{
//节点不存在用null填充战位
orders[index].Add("NULL");
next_level.Enqueue(null);
next_level.Enqueue(null);
}
count = cureent_level.Count;
}
bool goNext = false;
foreach (var info in next_level)
{
if (info != null)
{
goNext = true;
}
}
if (goNext)
cureent_level = next_level;
else
{
break;
}
index++;
}
for (int i = 0; i < orders.Count; i++)
{
int enmpty_space = (int)MathF.Pow(2, orders.Count - i) - 1;
for (int j = 0; j < orders[i].Count; j++)
{
for (int k = 0; k < enmpty_space; k++)
{
Console.Write(" ");
}
if (orders[i][j] != "NULL")
Console.Write(orders[i][j]);
else
Console.Write(" ");
for (int k = 0; k < enmpty_space; k++)
{
Console.Write(" ");
}
}
Console.WriteLine("\n");
}
}
- 测试调用:
static void Main(string[] args)
{
int[] num = { 15,25,35,45,55,65,75,85,95};
bool taller;
treeRoot = new BitTree(num[0]);
treeRoot.parent = null;
treeRoot.left = treeRoot.right = null;
treeRoot.bf = EH;
//数据创建
for (int i = 1; i < num.Length; i++)
{
InsertAVL(treeRoot, num[i], out taller);
//InsertBitTree(treeRoot, num[i]);
LevelOrder(treeRoot);
Console.WriteLine("***************");
}
// LevelOrder(treeRoot);
}
很直观的就能看出程序的平衡过程。