一、前序、中序、后序遍历的特性:
前序遍历:
1.访问根节点
2.前序遍历左子树
3.前序遍历右子树
中序遍历:
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
后序遍历:
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点
二、二叉树的存储结构
二叉树的存储可分为两种:顺序存储结构和链式存储结构。
1. 顺序存储结构
把一个满二叉树自上而下、从左到右顺序编号,依次存放在数组内,设满二叉树结点在数组中的索引号为i,那么有如下性质。
(1) 如果i = 0,此结点为根结点,无父结点。
(2) 如果i > 0,则其父结点为(i -1) / 2 。(注意,这里的除法是整除,结果中的小数部分会被舍弃。)
(3) 结点i的左孩子为2i + 1,右孩子为2i + 2。
(4) 如果i > 0,当i为奇数时,它是父结点的左孩子,它的兄弟为i + 1;当i为偶数时,它是父结点的右孩子,它的兄弟结点为i – 1。
(5) 深度为k的满二叉树需要长度为2 k-1的数组进行存储。
通过以上性质可知,使用数组存放满二叉树的各结点非常方便,可以根据一个结点的索引号很容易地推算出它的父、孩子、兄弟等结点的编号,从而对这些结点进行访问,这是一种存储二叉满二叉树或完全二叉树的最简单、最省空间的做法。为了用结点在数组中的位置反映出结点之间的逻辑关系,存储一般二叉树时,只需要将数组中空结点所对应的位置设为空即可。
2.链式存储结构
二叉树的链式存储结构可分为二叉链表和三叉链表。二叉链表中,每个结点除了存储本身的数据外,还应该设置两个指针域left和right,它们分别指向左孩子和右孩子。当需要在二叉树中经常寻找某结点的父结点,每个结点还可以加一个指向父结点的指针域parent,这就成了三叉链表。
三、C#代码实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BinaryTreeTest
{
class Program
{
static void Main(string[] args)
{
BinaryTree bTree = new BinaryTree("abcdefg###h##ij");
Console.WriteLine("前序遍历:"+bTree.PreOrder(bTree.Head));
Console.WriteLine("中序遍历:"+bTree.MidOrder(bTree.Head));
Console.WriteLine("后序遍历:"+bTree.AftOrder(bTree.Head));
Console.WriteLine();
Console.WriteLine("前序遍历:" + bTree.PreOrder(bTree.Head));
Console.WriteLine("中序遍历:" + bTree.MidOrder(bTree.Head));
Console.WriteLine("后序遍历:" + bTree.AftOrder(bTree.Head));
Console.WriteLine();
Console.Write("后序遍历:");
BinaryTreeOrder.PreMid("abdehcfgij", "dbehafcigj");
Console.WriteLine();
Console.Write("前序遍历:");
BinaryTreeOrder.AftMid("dhebfijgca", "dbehafcigj");
Console.ReadLine();
}
}
public class Node
{
//成员变量
private string _data;//节点数据
private Node _lChild; //左孩子
private Node _rChild; //右孩子
//get和set方法
public string Data
{
get { return _data; }
set { _data = value; }
}
public Node lChild
{
get { return _lChild; }
set { _lChild = value; }
}
public Node rChild
{
get { return _rChild; }
set { _rChild = value; }
}
//构造方法
public Node() {
}
public Node(string data)
{
this._data = data;
}
}
//是一个二叉树的集合类,它属于二叉链表
public class BinaryTree
{
//成员变量
private Node _head;//头指针
private string cStr;//用于构造二叉树的字符串
//get方法
public Node Head
{
get { return _head; }
}
//构造方法,字符串由满二叉树的方式进行构造,空结点用‘#’号表示
public BinaryTree(string _cStr)
{
this.cStr = _cStr;
_head = new Node(cStr[0].ToString());
Add(_head, 0);
}
private void Add(Node parentNode, int pindex)
{
int lIndex = 2 * pindex + 1;//计算左孩子的索引
if (lIndex < cStr.Length) //如果索引没有超出字符串的长度
{
if (cStr[lIndex] != '#')//‘#’表示空节点
{
parentNode.lChild = new Node(cStr[lIndex].ToString());//将节点赋给当前节点的左节点
Add(parentNode.lChild, lIndex);//递归调用Add方法给左孩子添加孩子
}
}
int rIndex = 2 * pindex + 2; //计算右孩子的索引
if (rIndex < cStr.Length) //若索引没有超出字符串的长度
{
if (cStr[rIndex] != '#') //‘#’表示空节点
{
parentNode.rChild = new Node(cStr[rIndex].ToString());//将当前字符赋给当前节点的右孩子节点
Add(parentNode.rChild, rIndex);//递归调用Add方法给右孩子添加孩子
}
}
}
//先序遍历
private string _PreStr="";
public string PreOrder(Node node)
{
if (node != null)
{
if (node.Equals(Head))
{
_PreStr = "";
}
_PreStr += node.Data;
PreOrder(node.lChild);
PreOrder(node.rChild);
}
return _PreStr;
}
//中序遍历
private string _MidStr="";
public string MidOrder(Node node)
{
if (node != null)
{
if (node.Equals(Head))
{
_MidStr = "";
}
MidOrder(node.lChild);
_MidStr += node.Data;
MidOrder(node.rChild);
}
return _MidStr;
}
//后序遍历
private string _AftStr="";
public string AftOrder(Node node)
{
if (node != null)
{
if (node.Equals(Head))
{
_AftStr = "";
}
AftOrder(node.lChild);
AftOrder(node.rChild);
_AftStr += node.Data;
}
return _AftStr;
}
}
public class BinaryTreeOrder
{
//查找字符串中相应字符的位置
private static int find(string str, char c)
{
for (int i = 0; i < str.Length; i++)
{
if (c == str[i])
return i;
}
return -1;
}
/// <summary>
/// 根据二叉树的前序遍历和中序遍历结果求后序遍历结果
/// </summary>
/// <param name="_pre">前序遍历字符串</param>
/// <param name="_mid">中序遍历字符串</param>
public static void PreMid(string _pre, string _mid)
{
if (_pre.Length == 0)
{
return;
}
else if(_pre.Length == 1)
{
Console.Write(_pre[0]);
}
else
{
int index = find(_mid, _pre[0]);//找前序中第一个元素(第一个元素为根结点)在中序中的位置
string preTempPre = _pre.Substring(1, index);
string midTempPre = _mid.Substring(0, index);
PreMid(preTempPre, midTempPre);
string preTempAft = _pre.Substring(index + 1, _pre.Length - index - 1);
string midTempAft = _mid.Substring(index + 1, _mid.Length - index - 1);
PreMid(preTempAft, midTempAft);
Console.Write(_pre[0]);//变成后序遍历要最后输出结点的值
}
}
/// <summary>
/// 根据后序和中序遍历结果求前序遍历结果
/// </summary>
/// <param name="_aft">后序遍历字符串</param>
/// <param name="_mid">中序遍历字符串</param> public static void AftMid(string _aft,string _mid)
{
if (_aft.Length == 0)
{
return;
}
else if (_aft.Length == 1)
{
Console.Write(_aft[_aft.Length - 1]);
}
else
{
int index = find(_mid, _aft[_aft.Length - 1]);//找后序中最后一个元素(最后一个元素为根结点)在中序中的位置
Console.Write(_aft[_aft.Length - 1]);//变成前序遍历要先输出结点的值
string aftTempPre = _aft.Substring(0, index);
string midTempPre = _mid.Substring(0, index);
AftMid(aftTempPre, midTempPre);
string aftTempAft = _aft.Substring(index, _aft.Length - index - 1);
string midTempAft = _mid.Substring(index + 1, _mid.Length - index - 1);
AftMid(aftTempAft, midTempAft);
}
}
}
}
用前序遍历和中序遍历求后序遍历的注解:
假设前序遍历为 abdehcfgij, 中序遍历为 dbehafcigj
前序遍历是先访问根节点,然后再访问子树的,而中序遍历则先访问左子树再访问根节点
那么把前序的 a 取出来,然后查找 a 在中序遍历中的位置就得到 dbeh a fcigj
那么我们就知道 dbeh 是左子树 fcigj 是右子树,因为数量要吻合
所以前序中相应的 dbeh 是左子树 fcigj 是右子树
然后就变成了一个递归的过程