二叉树是一种重要的数据结构,它由一系列节点组成,每个节点最多有两个子节点,称为左子节点和右子节点。在本文中,将探讨如何在C#中创建二叉树,并使用GDI+技术在位图上绘制它。
二叉树中的每个节点都有一个唯一的值。例如,假设有一个二叉树,其根节点的值为776,那么776就是该节点的唯一值。
向二叉树中添加新节点时,从根节点开始,遵循以下规则:
无论添加到哪个节点,都遵循相同的过程。
从二叉树中移除节点时,遵循以下规则:
当应用程序启动时,它会随机添加一些节点到树中。用户可以通过按下添加按钮(或在文本框中按下回车键),将文本框中的值作为节点添加到二叉树中。通过按下创建按钮,可以创建一个新的二叉树。通过按下移除按钮,可以从树中移除包含文本框中值的节点。
通过按下“随机添加”按钮,可以向树中添加一个随机值作为节点。通过按下保存按钮,可以将当前在PictureBox上的图像保存到磁盘上。
要完全理解如何使用代码及其方法,请参考本文附带的主要源代码中的XML部分。
要创建二叉树并绘制它,请使用以下代码:
private BinaryTree tree;
tree = new BinaryTree();
PaintTree();
要添加一个具有唯一值val的节点,请使用:
tree.Add(val);
添加节点的方法如下:
public void Add(int val)
{
if(val < Value)
{
if(Left == null)
{
Left = new Node(val);
}
else
{
Left.Add(val);
}
IsChanged = true;
}
else if(val > Value)
{
IsChanged = true;
if(Right == null)
{
Right = new Node(val);
}
else
{
Right.Add(val);
}
}
}
要移除一个具有值val的节点,请使用:
tree.Remove(val);
移除节点的方法按照前面描述的方式工作。它移除包含插入值的节点,同时也会移除其子节点。
public bool Remove(int val, out bool containedOnMe)
{
Node nodeToRemove;
containedOnMe = false;
var isLeft = val < Value;
if(val < Value)
{
nodeToRemove = Left;
}
else if(val > Value)
{
nodeToRemove = Right;
}
else
{
if(Left != null)
{
Left.IsChanged = true;
}
if(Right != null)
{
Right.IsChanged = true;
}
containedOnMe = true;
return false;
}
if(nodeToRemove == null)
{
return false;
}
bool containOnChild;
var result = nodeToRemove.Remove(val, out containOnChild);
if(containOnChild)
{
IsChanged = true;
if(nodeToRemove.Left == null && nodeToRemove.Right == null)
{
if(isLeft) Left = null;
else Right = null;
}
else if(nodeToRemove.Right == null)
{
if(isLeft) Left = nodeToRemove.Left;
else Right = nodeToRemove.Left;
}
else
{
if(nodeToRemove.Right.Left == null)
{
nodeToRemove.Right.Left = nodeToRemove.Left;
if(isLeft) Left = nodeToRemove.Right;
else Right = nodeToRemove.Right;
}
else
{
Node nLeft = null;
for(var n = nodeToRemove.Right; n != null; n = n.Left)
{
if(n.Left == null)
{
nLeft = n;
}
}
bool temp;
var v = nLeft.Value;
Remove(nLeft.Value, out temp);
nodeToRemove.Value = v;
}
}
}
return true;
}
return result;
}
绘制操作非常简单:每个节点将绘制自己及其子节点,绘制方法是递归调用每个子节点来绘制自己,然后将结果传递给父节点,以便父节点可以绘制自己,这个过程对所有节点都会发生。
public Image Draw(out int center)
{
center = _lastCenter;
if(!IsChanged) return _lastImage;
var lCenter = 0;
var rCenter = 0;
Image lImg = null, rImg = null;
if(Left != null) lImg = Left.Draw(out lCenter);
if(Right != null) rImg = Right.Draw(out rCenter);
var me = new Bitmap(40, 40);
var g = Graphics.FromImage(me);
g.SmoothingMode = SmoothingMode.HighQuality;
var rcl = new Rectangle(0, 0, me.Width - 1, me.Height - 1);
g.FillRectangle(Brushes.White, rcl);
g.FillEllipse(new LinearGradientBrush(new Point(0, 0), new Point(me.Width, me.Height), Color.Gold, Color.Black), rcl);
var lSize = new Size();
var rSize = new Size();
var under = (lImg != null) || (rImg != null);
if(lImg != null) lSize = lImg.Size;
if(rImg != null) rSize = rImg.Size;
var maxHeight = lSize.Height;
if(maxHeight < rSize.Height) maxHeight = rSize.Height;
var resSize = new Size
{
Width = me.Size.Width + lSize.Width + rSize.Width,
Height = me.Size.Height + (under ? maxHeight + me.Size.Height : 0)
};
var result = new Bitmap(resSize.Width, resSize.Height);
g = Graphics.FromImage(result);
g.SmoothingMode = SmoothingMode.HighQuality;
g.FillRectangle(Brushes.White, new Rectangle(new Point(0, 0), resSize));
g.DrawImage(me, lSize.Width, 0);
g.DrawString(Value.ToString(), new Font("Tahoma", 14), Brushes.White, lSize.Width + 5, me.Height / 2f - 12);
center = lSize.Width + me.Width / 2;
var pen = new Pen(Brushes.Black, 2.5f)
{
EndCap = LineCap.ArrowAnchor,
StartCap = LineCap.Round
};
float x1 = center;
float y1 = me.Height;
float y2 = me.Height * 2;
float x2 = lCenter;
var h = Math.Abs(y2 - y1);
var w = Math.Abs(x2 - x1);
if(lImg != null)
{
g.DrawImage(lImg, 0, me.Size.Height * 2);
var points1 = new List
{
new PointF(x1, y1),
new PointF(x1 - w/6, y1 + h/3.5f),
new PointF(x2 + w/6, y2 - h/3.5f),
new PointF(x2, y2),
};
g.DrawCurve(pen, points1.ToArray(), 0.5f);
}
if(rImg != null)
{
g.DrawImage(rImg, lSize.Width + me.Size.Width, me.Size.Height * 2);
x2 = rCenter + lSize.Width + me.Width;
w = Math.Abs(x2 - x1);
var points = new List
{
new PointF(x1, y1),
new PointF(x1 + w/6, y1 + h/3.5f),
new PointF(x2 - w/6, y2 - h/3.5f),
new PointF(x2, y2)
};
g.DrawCurve(pen, points.ToArray(), 0.5f);
}
IsChanged = false;
_lastImage = result;
_lastCenter = center;
return result;
}
class BinaryTree
{
public Node RootNode { get; private set; }
public BinaryTree() : this(-1) { }
public BinaryTree(int rootValue)
{
RootNode = new Node(-1);
}
public List Items { get; private set; }
public void Add(int value)
{
RootNode.Add(value);
}
public bool Remove(int value)
{
bool isRootNode;
var res = RootNode.Remove(value, out isRootNode);
return !isRootNode && res;
}
public Image Draw()
{
int temp;
return RootNode.Right == null ? null : RootNode.Right.Draw(out temp);
}
}