Net Core教程

C#算法从入门到跑路 第2章:树之二叉树

本文主要是介绍C#算法从入门到跑路 第2章:树之二叉树,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

定义

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层的叶子节点都靠对齐

image.png
image.png
image.png

表示

顺序存储结构:堆(优先队列)
链式存储结构:二叉链表,三叉链表,线索二叉树

二叉链表

节点类

/// <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表达式可以看图
image.png
简单说就是中序序列以跟为界,分为两段,一段为左子树,一段为右子树。我们可以求出左子树在中序中的长度,从而将前序序列也分为两段。
这样一来,左右子树的开始结束索引都有了

/// <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;
}

使用中序和后续序列创建二叉树

image.png
同上

/// <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));
    }
}
这篇关于C#算法从入门到跑路 第2章:树之二叉树的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!