定义
0.允许为空
1.每个节点最大度为2
2.左右子树有序
3.每颗子树亦为二叉树
性质
0.第i层有$2^{i-1}$个节点
1.高度为h的二叉树最多$2^{h}-1$个节点
2.
总节点数$N=(n_0+n_1+n_2)$
总边数$T=n_1+2n_2$
关系:$N=T+1$
推论:$N_0-N_2=1$
3.
n个节点的二叉树最低高度为$int(log_2n+1)$,此时为完全二叉树
4.
n个节点从1到n编号:
(1)i>1:则结点i的双亲编号为⌊i/2⌋;否则无双亲。
(2)2i<=n:则结点i的左孩子的编号为2i;否则无左孩子。
(3)2i+1<=n,则结点i的右孩子的编号为2i+1;否则无右孩子。
5.
n个节点构成的二叉树数目为卡塔兰数
特色二叉树
真二叉树 | 度或为0,或为2 |
满二叉树 | 节点数为$2^(i - 1)$ |
完全二叉树 | 叶子节点只会出现最后2 层,最后1 层的叶子节点都靠左 对齐 |
表示
顺序存储结构:堆(优先队列)
链式存储结构:二叉链表,三叉链表,线索二叉树
二叉链表
节点类
/// <summary>
/// 节点类
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
class Node<T>
{
/// <summary>
/// 左子树
/// </summary>
public Node<T> Left { get; set; }
/// <summary>
/// 右子树
/// </summary>
public Node<T> Right { get; set; }
/// <summary>
/// 数据域
/// </summary>
public T Data { get; set; }
/// <summary>
/// 构造器
/// </summary>
/// <param name="data">数据域</param>
/// <param name="left">左子树</param>
/// <param name="right">右子树</param>
public Node(T data,Node<T> left=null,Node<T> right=null)
{
this.Data = data;
this.Left = left;
this.Right = right;
}
}
二叉树
二叉树的创建
从数组创建完全二叉树
/// <summary>
/// 从数组创建完全二叉树
/// </summary>
/// <param name="data">数组</param>
/// <param name="index">索引</param>
/// <returns>返回根节点</returns>
private Node<T> Init1(T[]data,int index=0)
{
Node<T> root=null;
if(index<data.Length)
{
root = new Node<T>(data[index]);
root.Left=Init1(data, index * 2+1);
root.Right = Init1(data, index * 2 + 2);
}
return root;
}
使用前序和中序序列创建二叉树
关于start1,end1,start2,end2表达式可以看图
简单说就是中序序列以跟为界,分为两段,一段为左子树,一段为右子树。我们可以求出左子树在中序中的长度,从而将前序序列也分为两段。
这样一来,左右子树的开始结束索引都有了
/// <summary>
/// 从先序和中序序列构建二叉树
/// </summary>
/// <param name="previous">先序</param>
/// <param name="start1">先序开始</param>
/// <param name="end1">先序结束</param>
/// <param name="center">中序</param>
/// <param name="start2">中序开始</param>
/// <param name="end2">中序结束</param>
/// <returns>返回根</returns>
private Node<T> Init2(T[]previous,int start1,int end1, T[] center, int start2,int end2)
{
//保证数据安全,递归结束点
if (start1 > end1 || start2 > end2)
return null;
//建立节点
Node<T> root = new Node<T>(previous[start1]);
//在中序序列里找到根
int i;
for (i = start2; i < end2; i++)
if (center[i].Equals(previous[start1]))
break;
root.Left = Init2(previous, start1 + 1, i - start2 + start1, center, start2, i - 1);
root.Right = Init2(previous, i - start2 + start1 + 1, end1, center, i + 1, end2);
return root;
}
使用中序和后续序列创建二叉树
同上
/// <summary>
/// 从后序和中序序列构建二叉树
/// </summary>
/// <param name="last">后序</param>
/// <param name="start1">后序开始</param>
/// <param name="end1">后序结束</param>
/// <param name="center">中序</param>
/// <param name="start2">中序开始</param>
/// <param name="end2">中序结束</param>
/// <returns>返回根</returns>
private Node<T> Init3(T[]last,int start1,int end1,T[]center,int start2,int end2)
{
//递归结束点
if (start1 > end1 || start2 > end2)
return null;
//先找到根节点在中序序列中的索引
int i;
for (i = start2; i < end2; i++)
{
if (center[i].Equals(last[end1]))
break;
}
//创建根节点
Node<T> root = new Node<T>(last[end1]);
root.Left=Init3(last, start1, start1 + i - 1 - start2,
center, start2, i - 1);
root.Right=Init3(last, start1 + i - start2 ,end1-1,
center, i + 1, end2);
return root;
}
二叉树的遍历
深度优先遍历
二叉树的深度优先遍历主要有前序,中序,后续三种
为了方便后续利用,此处传入函数指针来实现对节点的访问
前序遍历
/// <summary>
/// 先序遍历
/// </summary>
/// <param name="p">节点</param>
/// <param name="f">访问函数</param>
public void DLR(Node<T> p,Action<Node<T>> f)
{
if(p!=null)
{
f(p.Data);
DLR(p.Left,f);
DLR(p.Right,f);
}
}
中序遍历
/// <summary>
/// 中序遍历
/// </summary>
/// <param name="p">节点</param>
/// <param name="f">访问函数</param>
public void LDR(Node<T> p, Action<Node<T>> f)
{
if (p != null)
{
LDR(p.Left, f);
f(p.Data);
LDR(p.Right, f);
}
}
后续遍历
/// <summary>
/// 后序遍历
/// </summary>
/// <param name="p">节点</param>
/// <param name="f">访问函数</param>
public void LRD(Node<T> p, Action<Node<T>> f)
{
if (p != null)
{
LRD(p.Left, f);
LRD(p.Right, f);
f(p.Data);
}
}
广度优先遍历-层序遍历
我打算把层序遍历当做一个模板来使用,它可以实现很多赛艇的事情
这里的for循环为了分开每一层,这样可以分层打印,求各层宽度等等
/// <summary>
/// 层序遍历
/// </summary>
/// <param name="p">节点</param>
/// <param name="f">访问函数</param>
/// <returns>返回层数</returns>
public int Level(Node<T> p,Action<Node<T>> f)
{
//一个队列
IQueue<Node<T>> q = new LinkQueue<Node<T>>();
//进队一个元素
q.Enter(p);
//记录层数
var cnt = 0;
//队列不空
while(!q.IsEmpty())
{
//获取本层的宽度
int size = q.Count;
//层数增加
cnt++;
//注意:这个循环是为了区分开每一层
for (int i = 0; i < size; i++)
{
//出队并访问
var t = q.Out();
f(t);
if (t.Left != null)
q.Enter(t.Left);
if(t.Right!=null)
q.Enter(t.Right);
}
}
return cnt;
}
关于遍历的测试
class Test
{
public static void Main(string[] args)
{
var arr = new int[] { 1, 2, 3, 4, 5, 6 };
var num1 = "abdec".ToCharArray();
var num2 = "dbeac".ToCharArray();
var num3 = "debca".ToCharArray();
var t = new LinkBTree<char>(num3,num2,false);
t.LRD(t.Root,t=>WriteLine(t.Data));
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。