二叉树学习

来源:互联网 发布:linux 删除文本第一行 编辑:程序博客网 时间:2024/05/16 08:36

一:树

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

 

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;        }    }}
复制代码

运行图:

 

 

 转自:http://www.cnblogs.com/zcttxs/archive/2012/06/26/2564109.html

 

 

 

 

 

 

 

 

0 0
原创粉丝点击