引言
在用二叉树编程时,总希望看到二叉树的内部结构。使用各种遍历方法虽能将二叉树按某种顺序转换成一个字符串,但不太直观。如要绘成图形,又将增加不少资源开销。如能将二叉树用文本方法显示其树状结构,则既节约开销,又不失直观。本文在VS2010以WPF框架实现
基本思路
要构建二叉树,首先得建立一个二叉树节点的类,如BinNode,该类具有二叉树的基本属性:Text(节点文本)、Father(父节点)、Laft(左节点)、Right(右节点)等。为了确定一节点在树中的位置,我们为该类添加了Level(节点所在层,根节点的Level=0)、Index(节点在完全二叉树中按层次遍历法的顺序)。
有了BinNode,即可构造一二叉树,记住其根节点(设为Root)
第三步,将所构造的二叉树使用层次遍历法,构造出文本形式的树状结构。
构造节点类
在拟建项目中添加类 BinNode,代码如下:
public class BinNode
{
#region 属性
public string Text { get; set; }
public int Value { get; set; }
public int Level { get; set; }
public int Index { get; set; }
public bool? IsLeft { get; set; }
public BinNode Left { get; set; }
public BinNode Right { get; set; }
public BinNode Father { get; set; }
public int Count
{
get
{
return countNode(this);
}
}
public int Height
{
get
{
return getHeight(this);
}
}
public int Priority
{
get
{
if ("*/".Contains(this.Text)) return 2;
else if ("+-".Contains(this.Text)) return 1;
else return 0;
}
}
public bool IsRight
{
get
{
return this.Father != null && IsLeft == false;
}
}
public bool IsOperator
{
get
{
return "+-*/".Contains(this.Text);
}
}
public bool IsNumber
{
get
{
return char.IsDigit(this.Text[0]);
}
}
#endregion
public BinNodei(string text)
{
this.Text = text;
this.Level = 0;
this.Index = 0;
}
public BinNode(string text, BinNodei father, bool? isLeft)
{
this.Text = text;
this.Father = father;
this.IsLeft = isLeft;
this.Level = father.Level + 1;
if (father == null) this.Index = 0;
else
{
if (isLeft == true) this.Index = 2 * father.Index + 1;
else this.Index = 2 * father.Index + 2;
}
}
private int countNode(BinNode node)
{
if (node == null) return 0;
int nl = countNode(node.Left);
int nr = countNode(node.Right);
return 1 + nl + nr;
}
private int getHeight(BinNode node)
{
if (node == null) return 0;
if (node.Left == null && node.Right == null) return 1;
int lh = 0, rh = 0;
lh = getHeight(node.Left);
rh = getHeight(node.Right);
return (lh > rh ? lh : rh) + 1;
}
}
添加测试窗口
在项目中添加窗口WinTest,窗口有三行,依次是工具栏,文本框(供输出树状图),状态栏,工具栏中有一文本框用于输入要创建二叉树的节点数,和三个按钮“构造”,“显示”,“清空”。效果如图:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ToolBar>
<Label>节点数:</Label>
<TextBox Name="tbCount" Text="5"/>
<Separator />
<Button Name="btBuild" Click="btBuild_Click">构造</Button>
<Button Name="btShow" Click="btShow_Click">显示</Button>
<Button Name="btCls" Click="btCls_Click">清空</Button>
</ToolBar>
<TextBox Name="tbOut" Grid.Row="1" TextWrapping="NoWrap"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" FontFamily="NSimSun" />
<StatusBar Grid.Row="2">
<TextBlock Name="tbStatus"/>
</StatusBar>
</Grid>
需要说明的是,为了使节点显示在正确的文本位置,中间的文本框的字体需设置等宽字体。
编写后台代码
转到测试窗口的代码页,开始编写实现代码。
1. 构建二叉树
这里我们以“根据给定节点数构建二叉树”为例,节点文本为随机的大写字母。新建二叉树函数如下:
/// <summary>
/// 根据节点数递归构造二叉树
/// </summary>
/// <param name="node">当前节点</param>
/// <param name="n">剩余节点数</param>
void newTree(BinNode node, int n)
{
if (n < 1) return;
string txt = rndChar();
if (n == 1)
{
int t = rand.Next(0, 10);
if (t < 5) node.Left = new BinNode(txt, node, true);
else node.Right = new BinNode(txt, node, false);
return;
}
node.Left = new BinNode(txt, node, true);
txt = rndChar();
node.Right = new BinNode(txt, node, false);
int n1 = (n - 2) / 2;
newTree(node.Left, n1);
newTree(node.Right, n - n1 - 2);
}
代码采用递归算法,先构造当前节点的左、右子节点,再以刚建立的左、右子节点为参数递归调用新建函数。其中用到的rndChar()函数,返回一个随机的大写英文字母,函数体内仅一个语句:
return new string((char)rand.Next(65, 91), 1);
2. 构造文本形式的树状图
用层次遍历法遍历二叉树,遍历到新层时,计算当前节点与前一节点的空格数,确定层中每一节点的位置,构建每一层的文本,再添加到总的文本中。代码为:
//层次遍历二叉树,构造文本形式的树状图
private string lev(BinNode node)
{
if (node == null) return "";
int lev = 0;
int h = node.Height;
int nn = (int)Math.Pow(2, h - 1); //最后一行节点数的最大值
int ns = 2 * nn - 1; //最后一行间隔数的最大值
double space = (nn + ns); //间隔大小
StringBuilder sb = new StringBuilder();//总字符串
StringBuilder line = new StringBuilder();//当前行字符串
Queue<BinNodei> q = new Queue<BinNodei>();
q.Enqueue(node);
while (q.Count > 0)
{
BinNodei n = q.Dequeue();
if (n.Level > lev)
{
sb.AppendLine(line.ToString());
line = new StringBuilder();
lev = n.Level;
space /= 2;
}
int sp = (int)Math.Ceiling(space);
if (n.Level > 0)
{
int first = ((int)Math.Pow(2, n.Level) - 1);
int diff = n.Index - first;
sp = (int)Math.Ceiling(space * (2 * diff + 1) - line.Length);
}
line.AppendFormat("{0," + sp + "}", n.Text);
if (n.Left != null) q.Enqueue(n.Left);
if (n.Right != null) q.Enqueue(n.Right);
}
sb.AppendLine(line.ToString());
return sb.ToString();
}
3. 按钮单击事件处理函数
在“构造”、“显示”按钮单击事件处理函数中分别调用newTree和lev函数即可。在“构造”按钮单击事件处理函数中的代码是:
//“构造”按钮单击事件处理函数
private void btBuild_Click(object sender, RoutedEventArgs e)
{
int n = Convert.ToInt16(tbCount.Text);
string text = new string((char)rand.Next(65, 91), 1);
root = new BinNode(text);
newTree(root, n - 1);
tbStatus.Text = string.Format("构造成功!二叉树有{0}层,{1}个节点", root.Height, root.Count);
}
//“显示”按钮单击事件处理函数
private void btShow_Click(object sender, RoutedEventArgs e)
{
string tree = lev(root);
tbOut.AppendText(tree+"\n");
}