• 二叉树学习(上)


    一:树

      我们思维中的”树“就是一种枝繁叶茂的形象,那么数据结构中的”树“该是怎么样呢?对的,他是一种现实中倒立的树。

    1:术语

         其实树中有很多术语的,这个是我们学习树形结构必须掌握的。

         <1>  父节点,子节点,兄弟节点

                      这个就比较简单了,B和C的父节点就是A,反过来说就是B和C是A的子节点。B和C就是兄弟节点。

         <2>  结点的度

                     其实”度“就是”分支数“,比如A的分支数有两个“B和C",那么A的度为2。

         <3> 树的度

                    看似比较莫名其妙吧,他和”结点的度“的区别就是,树的度讲究大局观,乃树中最大的结点度,其实也就是2。

         <4> 叶结点,分支结点

                    叶结点就是既没有左孩子也没有右孩子结点,也就是结点度为0。分支节点也就是if的else的条件咯。

        <5> 结点的层数

                   这个很简单,也就是树有几层。

       <6> 有序树,无序树

                   有序树我们先前也用过,比如“堆”和“二叉排序树”,说明这种树是按照一定的规则进行排序的,else条件就是无序树。

       <7>  森林

                   现实中,很多的树形成了森林,那在数据结构中,我们把上图的“A”节点砍掉,那么B,C子树合一起就是森林咯。

    2: 树的表示

         树这个结构的表示其实有很多种,常用的也就是“括号”表示法。

         比如上面的树就可以表示为:(A(B(D),(E)),(C(F),(G)))

    二: 二叉树

             在我们项目开发中,很多地方都会用到树,但是多叉树的处理还是比较纠结的,所以俺们本着“大事化小,小事化了“的原则

          把”多叉树“转化为”二叉树“,那么问题就简化了很多。

    1: ”二叉树“和”树“有什么差异呢?

             第一点:  树的度没有限制,而“二叉树”最多只能有两个,不然也就不叫二叉树了,哈哈。

             第二点:树中的子树没有左右划分,很简单啊,找不到参照点,二叉树就有参照物咯。

    2: 二叉树的类型

           二叉树中有两种比较完美的类型,“完全二叉树”和“满二叉树”。

              <1>  满二叉树    

                           除叶子节点外,所有节点的度都为2,文章开头处的树就是这里的“满二叉树”。

              <2>  完全二叉树

                          必须要满足两个条件就即可:  干掉最后一层,二叉树变为“满二叉树”。

                                                                  最后一层的叶节点必须是“从左到右”依次排开。

                         我们干掉文章开头处的节点“F和”G",此时还是“完全二叉树”,但已经不是“满二叉树”了,你懂的。

    3: 二叉树的性质

             二叉树中有5点性质非常重要,也是俺们必须要记住的。

         <1>  二叉树中,第i层的节点最多有2(i-1)个。

         <2>  深度为k的二叉树最多有2k-1个节点。

         <3>  二叉树中,叶子节点树为N1个,度为2的节点有N2个,那么N1=N2+1。

         <4>  具有N个结点的二叉树深度为(Log2 N)+1层。

         <5>  N个结点的完全二叉树如何用顺序存储,对于其中的一个结点i,存在以下关系,

                  2*i是结点i的父结点。

                  i/2是结点i的左孩子。

                  (i/2)+1是结点i的右孩子。

    4: 二叉树的顺序存储

          同样的存储方式也有两种,“顺序存储”和“链式存储”。

           <1> 顺序存储

                     说实话,树的存储用顺序结构比较少,因为从性质定理中我们都可以看出只限定为“完全二叉树”,那么如果二叉树不是

                  “完全二叉树”,那我们就麻烦了,必须将其转化为“完全二叉树”,将空的节点可以用“#”代替,图中也可看出,为了维护

                  性质定理5的要求,我们牺牲了两个”资源“的空间。

     <2> 链式存储

                   上面也说了,顺序存储会造成资源的浪费,所以嘛,我们开发中用的比较多的还是“链式存储”,同样“链式存储”

                也非常的形象,非常的合理。

                   一个结点存放着一个“左指针”和一个“右指针”,这就是二叉链表。

                   如何方便的查找到该结点的父结点,可以采用三叉链表。

    5: 常用操作

          一般也就是“添加结点“,“查找节点”,“计算深度”,“遍历结点”,“清空结点

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace BinaryTree
    {
    
        ///<summary>
        ///二叉链表存储结构
        ///</summry>
        ///<typeparam name="T"></typeparam>
    
        public class ChinaTree<T>
        {
            //字段
            public T data;
            public ChinaTree<T> left;
            public ChinaTree<T> right;
            public int Length { get; set; }
            //构造函数
            public ChinaTree(T nodevalue)
            {
            this.data = nodevalue;
            this.left = null;
            this.right = null;
            }
    
         /// <summary> 
         /// 将指定节点插入到二叉树中 
         /// </summary> 
         /// <typeparam name="T"></typeparam> 
         /// <param name="tree"></param> 
         /// <param name="node"></param> 
         /// <param name="direction">插入做左是右</param> 
         /// <returns></returns>
            public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction)
            {
                //树为空
                if (tree == null)
                {
                    return null;
                }
    
                if(tree.data.Equals(data))
                {
                   switch(direction)
                   {
                       case Direction.Left:
                           if(tree.left!=null)
                               throw new Exception("树的左节点不为空,不能插入");
                           else
                           tree.left=node;
    
                               break;
                       case Direction.Right:
    
                               if (tree.right != null)
                                   throw new Exception("树的右节点不为空,不能插入");
                               else
                                   tree.right = node;
                               break;
                   }
                }
                BinTreeAddNode(tree.left, node, data, direction);
                BinTreeAddNode(tree.right, node, data, direction);
                return tree;
            }
    
            //查找节点 在二叉树中查找指定的key
            public ChinaTree<T> BinTreeFind<T>(ChinaTree<T> tree, T data)
            {
                if (tree == null)
                    return null;
                if (tree.data.Equals(data))
                    return tree;
                return BinTreeFind(tree, data);
            }
    
            //计算深度 获取二叉树的深度
            public int BinTreeLen<T>(ChinaTree<T> tree)
            {
                int leftLength;
                int rightLength;
    
                if (tree == null)
                    return 0;
                //递归左子树的深度
                leftLength = BinTreeLen(tree.left);
                //递归右子树的深度
                rightLength = BinTreeLen(tree.right);
                if (leftLength > rightLength)
                    return leftLength + 1;
                else
                    return rightLength + 1;
            }
          //遍历节点分为: 
                //先序:先访问根,然后递归访问左子树,最后递归右子树。(DLR模式)
    
                //中序:先递归访问左子树,在访问根,最后递归右子树。(LDR模式)
    
                //后序:先递归访问左子树,然后递归访问右子树,最后访问根。(LRD模式)
    
                //按层:这个比较简单,从上到下,从左到右的遍历节点。
    
            //先序遍历
            public void BinTree_DLR(ChinaTree<T> tree)
            { 
                 if(tree==null)
                {
                   return;
                }
                //先输出根元素
                Console.WriteLine(tree.data+"\t");
    
                //然后遍历左子树
                BinTree_DLR(tree.left);
                //然后遍历右子树
                BinTree_DLR(tree.right);
            }
    
            //中序遍历
            public void BinTree_LDR(ChinaTree<T> tree)
            {
                if (tree == null)
                    return;
                //先遍历左子树
                BinTree_LDR(tree.left);
                //输出节点值
                Console.WriteLine(tree.data + "\t");
                //遍历右子树
                BinTree_LDR(tree.right);
            }
    
            //后序遍历
            public void BinTree_LRD<T>(ChinaTree<T> tree)
            {
                if (tree == null)
                    return;
                //先遍历左子树
                BinTree_LRD(tree.left);
                //再遍历右子树
                BinTree_LRD(tree.right);
                //输出节点值
                Console.WriteLine(tree.data + "\t");
            }
    
            //按层遍历
            public void BinTree_Level<T>(ChinaTree<T> tree)
            {
                if (tree == null)
                    return;
                //申请保存空间
                ChinaTree<T>[] treelist = new ChinaTree<T>[Length];
    
                int head = 0;
                int tail = 0;
                //存放数组
                treelist[tail] = tree;
    
                //循环链中计算tail的位置
                tail = (tail + 1) % Length;
                while (head != tail)
                {
                    var tempNode = treelist[head];
                    head = (head + 1) % Length;
                    //输出节点
                    Console.WriteLine(tempNode.data + "\t");
    
                    //如果左子树不为空 则将左子树存于数组的tail位置
                    if (tempNode.left != null)
                    {
                        treelist[tail] = tempNode.left;
                        tail = (tail + 1) % Length;
                    }
                    //如果右子树不为空,则将右子树存于数组的tail位置
                    if (tempNode.right != null)
                    {
                        treelist[tail] = tempNode.right;
                        tail = (tail + 1) % Length;
                    }
                }
    
            }
    
            //清空二叉树
            public void BinTreeClear<T>(ChinaTree<T> tree)
            {
                //递的结束点,归的起始点
                if (tree == null)
                    return;
                BinTreeClear(tree.left);
                BinTreeClear(tree.right);
                //在归的过程中,释放当前节点的数据空间
                tree = null;
            
            }
    
    
        }
    
        //枚举左节点和右节点
        public enum Direction {Left=1,Right=2}
    
    }

     汇总代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                ChainTreeManager manager = new ChainTreeManager();
                //插入节点操作 
                ChinaTree<string> tree = CreateRoot();
                //插入节点数据 
                AddNode(tree);
                //先序遍历 
                Console.WriteLine("\n先序结果为: \n");
                manager.BinTree_DLR(tree);
                //中序遍历
                Console.WriteLine("\n中序结果为: \n");
                manager.BinTree_LDR(tree);
                //后序遍历 
                Console.WriteLine("\n后序结果为: \n");
                manager.BinTree_LRD(tree);
                //层次遍历 
                Console.WriteLine("\n层次结果为: \n");
                manager.Length = 100;
                manager.BinTree_Level(tree);
                Console.WriteLine("\n树的深度为:" + manager.BinTreeLen(tree) + "\n");
                Console.ReadLine();
            }
            //生成根节点
    
            static ChinaTree<string> CreateRoot()
            {
                ChinaTree<string> tree = new ChinaTree<string>();
                Console.WriteLine("请输入根节点,方便我们生成树\n");
                tree.data = Console.ReadLine();
                Console.WriteLine("根节点已经生成\n");
                return tree;
            }
    
            //插入节点操作
    
            static ChinaTree<string> AddNode(ChinaTree<string> tree)
            {
                ChainTreeManager mananger = new ChainTreeManager();
                while (true)
                {
                    ChinaTree<string> node = new ChinaTree<string>();
                    Console.WriteLine("请输入要插入节点的数据\n");
                    node.data = Console.ReadLine();
                    Console.WriteLine("请输入要查找的父节点的数据\n");
                    var parentData = Console.ReadLine();
                    bool flag = mananger.BinTreeFind(tree, parentData);
                    if (!flag)
                    {
                        Console.WriteLine("未找到你输入的父节点,请重新输入");
                        continue;
                    }
                    Console.WriteLine("你确定要插到父节点的:1 左侧,2右侧");
                    Direction direction = (Direction)Enum.Parse(typeof(Direction), Console.ReadLine());
                    tree = mananger.BinTreeAddNode(tree, node, parentData, direction);
                    Console.WriteLine("是否继续?  1 继续, 2 退出");
                    if (int.Parse(Console.ReadLine()) == 1)
                        continue;
                    else
                        break;
                }
                return tree;
    
            }
    
        }
        public enum Direction { Left = 1, Right = 2 }
        public class ChinaTree<T>
        {
            //字段
            public T data;
            public ChinaTree<T> left;
            public ChinaTree<T> right;
        }
    
        public class ChainTreeManager
        {
            //按层遍历的Length的空间存储
            public int Length { get; set; }
    
            /// <summary> 
            /// 将指定节点插入到二叉树中 
            /// </summary> 
            /// <typeparam name="T"></typeparam> 
            /// <param name="tree"></param> 
            /// <param name="node"></param> 
            /// <param name="direction">插入做左是右</param> 
            /// <returns></returns>
            public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction)
            {
                //树为空
                if (tree == null)
                {
                    return null;
                }
                try
                {
                    if (tree.data.Equals(data))
                    {
                        switch (direction)
                        {
    
                            case Direction.Left:
                                if (tree.left != null)
                                    throw new Exception("树的左节点不为空,不能插入");
                                else
                                    tree.left = node;
    
                                break;
                            case Direction.Right:
    
                                if (tree.right != null)
                                    throw new Exception("树的右节点不为空,不能插入");
                                else
                                    tree.right = node;
                                break;
                        }
                    }
                    BinTreeAddNode(tree.left, node, data, direction);
                    BinTreeAddNode(tree.right, node, data, direction);
                   
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                return tree;
            }
            public ChinaTree<T> BinTreeChild<T>(ChinaTree<T> tree, Direction direction)
            {
                ChinaTree<T> childNode = null;
                if (tree == null)
                    throw new Exception("二叉树为空");
                switch (direction)
                {
                    case Direction.Left:
                        childNode = tree.left;
                        break;
                    case Direction.Right:
                        childNode = tree.right;
                        break;
                }
                return childNode;
            }
    
    
            //查找节点 在二叉树中查找指定的key
            public bool BinTreeFind<T>(ChinaTree<T> tree, T data)
            {
                bool flag = false;
                if (tree == null)
                    return false;
                if (tree.data.Equals(data))
                    return true;
                if (tree.left != null)
                {
                    return BinTreeFind(tree.left, data);
                }
                if (tree.right != null)
                {
                    return BinTreeFind(tree.right, data);
                }
               
                return flag;
    
    
            }
    
            //计算深度 获取二叉树的深度
            public int BinTreeLen<T>(ChinaTree<T> tree)
            {
                int leftLength;
                int rightLength;
    
                if (tree == null)
                    return 0;
                //递归左子树的深度
                leftLength = BinTreeLen(tree.left);
                //递归右子树的深度
                rightLength = BinTreeLen(tree.right);
                if (leftLength > rightLength)
                    return leftLength + 1;
                else
                    return rightLength + 1;
            }
    
            //判断二叉树是否为空
            public bool BinTreeisEmpty<T>(ChinaTree<T> tree)
            {
                return tree == null ? true : false;
            }
            //遍历节点分为: 
            //先序:先访问根,然后递归访问左子树,最后递归右子树。(DLR模式)
    
            //中序:先递归访问左子树,在访问根,最后递归右子树。(LDR模式)
    
            //后序:先递归访问左子树,然后递归访问右子树,最后访问根。(LRD模式)
    
            //按层:这个比较简单,从上到下,从左到右的遍历节点。
    
            //先序遍历
            public void BinTree_DLR<T>(ChinaTree<T> tree)
            {
                if (tree == null)
                {
                    return;
                }
                //先输出根元素
                Console.Write(tree.data + "\t");
    
                //然后遍历左子树
                BinTree_DLR(tree.left);
                //然后遍历右子树
                BinTree_DLR(tree.right);
            }
    
            //中序遍历
            public void BinTree_LDR<T>(ChinaTree<T> tree)
            {
                if (tree == null)
                    return;
                //先遍历左子树
                BinTree_LDR(tree.left);
                //输出节点值
                Console.Write(tree.data + "\t");
                //遍历右子树
                BinTree_LDR(tree.right);
            }
    
            //后序遍历
            public void BinTree_LRD<T>(ChinaTree<T> tree)
            {
                if (tree == null)
                    return;
                //先遍历左子树
                BinTree_LRD(tree.left);
                //再遍历右子树
                BinTree_LRD(tree.right);
                //输出节点值
                Console.Write(tree.data + "\t");
            }
    
            //按层遍历
            public void BinTree_Level<T>(ChinaTree<T> tree)
            {
                if (tree == null)
                    return;
                //申请保存空间
                ChinaTree<T>[] treelist = new ChinaTree<T>[100];
    
                int head = 0;
                int tail = 0;
                //存放数组
                treelist[tail] = tree;
    
                //循环链中计算tail的位置
                tail = (tail + 1) % 100;
                while (head != tail)
                {
                    var tempNode = treelist[head];
                    head = (head + 1) % 100;
                    //输出节点
                    Console.Write(tempNode.data + "\t");
    
                    //如果左子树不为空 则将左子树存于数组的tail位置
                    if (tempNode.left != null)
                    {
                        treelist[tail] = tempNode.left;
                        tail = (tail + 1) % 100;
                    }
                    //如果右子树不为空,则将右子树存于数组的tail位置
                    if (tempNode.right != null)
                    {
                        treelist[tail] = tempNode.right;
                        tail = (tail + 1) % 100;
                    }
                }
    
            }
    
            //清空二叉树
            public void BinTreeClear<T>(ChinaTree<T> tree)
            {
                //递的结束点,归的起始点
                if (tree == null)
                    return;
                BinTreeClear(tree.left);
                BinTreeClear(tree.right);
                //在归的过程中,释放当前节点的数据空间
                tree = null;
    
            }
    
    
    
        }
    }

    运行图:

  • 相关阅读:
    类型"ImportMeta"上不存在属性"env"(Property 'env' does not exist on type 'ImportMeta')
    微信小程序开发常见问题
    js构建沙箱环境sandbox
    js 判断两个对象是否相同
    js判断类型方法
    初学knockoutjs记录4——Computed observables依赖监控(1 Using computed observables使用计算监控属性)
    初学knockoutjs记录3——Observables监控属性(2 Observable Arrays 监控数组)
    初学knockoutjs记录2——Observables监控属性(1 创建带有监控属性的view model)
    初学knockoutjs记录1——入门介绍及下载安装
    C、C++、JAVA编译器是如何处理未经初始化的变量的。
  • 原文地址:https://www.cnblogs.com/zcttxs/p/2564109.html
Copyright © 2020-2023  润新知