数据结构之树

树的基本概念

数的定义
树(Tree)是n(n>=0)个结点的有限集。
当n=0时称为空树
在任意一颗非空树中:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集 T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)

注意:n>0时根结点是唯一的
m>0时,子树的个数没有限制,但他们一定是互不相交的

结点分类

  1. 树的结点包含一个数据元素及若干指向其子树的分支。
  2. 结点拥有的分支结点称为结点的度,树的度就是树内各结点的度的最大值
  3. 度为0的结点数称为叶结点(Leaf)或终端结点
  4. 度不为0的结点称为非终端结点或分支结点(除根结点之外,分支结点也称为内部结点)
    在这里插入图片描述

结点之间的关系

  1. 结点的子树的根称为该结点的孩子(Child) ,相应的该结点称为孩子的双亲(Parent)
  2. 同一个双亲的孩子之间称为兄弟(Sibling)
  3. 结点的祖先是从根到该结点所经分支的所有结点
  4. 以某结点为根的子树的任一结点都称为该结点的子孙

树的其他相关概念
结点的层次从根开始定义,根为第一层,根的孩子为第二层
双亲在同一层的结点互为堂兄弟
树中结点的最大层次称为树的深度(Depth)或高度

如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树

森林是m(m>=0)棵互不相交的树的集合。

树的性质

  1. 结点数=总度数+1
  2. 树的度与m叉树的区别

这里是引用

  1. 度为m的树第i层至多有M为底 i-1次方个结点(i>=1)

  2. 第三方公司对方

  3. 高度为h的m叉树至少有h个结点

  4. 高度为h、度为m的树至少有h+m+1个结点

  5. 在这里插入图片描述

树的存储结构

使用顺序存储和链式存储结构对树的存储结构进行表示的方法:
1、双亲表示法
2、孩子表示法
3、孩子兄弟表示法

双亲表示法
在每个结点中,附设一个指示器指示其双亲结点在数组中的位置
在这里插入图片描述
data是数据域,存储结点的数据信息。而parent是指针域,存储结点的双亲的下标。

这种存储结构,我们可以根据结点的parent指针找到它的双亲结点,所以时间复杂度为O(1),但是无法知道结点的孩子

孩子表示法
每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法
在这里插入图片描述
data是数据域,而child1至childd则为指向孩子结点的指针域

孩子兄弟表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
在这里插入图片描述
这个表示法的最大好处就是它将一个复杂的树变成一棵二叉树。

注意:存储结构的设计是一个非常灵活的过程。一个存储结构设计的是否合理,取决于基于该存储结构的运算是否合适,是否方便,时间复杂度好不好等。
以上的方法都可以根据实际情况进行拓展以适应不同的情况。

实例代码

  public class Node<T>
    {
       public T data;
       public Node<T> sibling;//右兄弟
       public Node<T> child;//长子
    }

    class MyTree<T>
    {
  
     private  Node<T> root = null;
     public   Node<T> CreateRoot(T data)
     {
            root = new Node<T>();
            root.data = data;
            return root;

     }
     public Node<T> InsertChild(Node<T> parent ,T data)
     {
            if (parent.child != null)
                return parent.child;


            var LChild = new Node<T>();
            LChild.data = data;
            parent.child = LChild;

            return LChild;
     }
     public Node<T> insertSibling(Node<T> leftbling, T data)
        {
            if (leftbling.sibling != null)
                return leftbling.sibling;
            var Sibling = new Node<T>();
            Sibling.data = data;
           leftbling.sibling = Sibling;

            return Sibling;
        }

        public static void Test()
        {
            MyTree<string> tree = new MyTree<string>();
            Node<string> nodeA = tree.CreateRoot("A");
            Node<string> nodeB = tree.InsertChild(nodeA, "B");
            Node<string> nodeC = tree.insertSibling(nodeB, "C");
            Node<string> nodeD = tree.insertSibling(nodeC, "D");
            Node<string> nodeE = tree.InsertChild(nodeB, "E");
            Node<string> nodeF = tree.InsertChild(nodeC, "F");
            Node<string> nodeG = tree.insertSibling(nodeF, "G");
            Node<string> nodeH = tree.InsertChild(nodeF, "H");
            tree.Traverse();
        }

        public void _TraverByDepth(Node<T> current)//深度遍历
        {
            Console.WriteLine(current.data);
            if (current.child != null)
            {
                _TraverByDepth(current.child);
            }
            if (current.sibling != null)
            {
                _TraverByDepth(current.sibling);
            }

            #region 简易写法
    //if (current == null)
            //    return;

            //_TraverByDepth(current.sibling);
            //_TraverByDepth(current.child);

            #endregion
        
        }

        public void _TraverseByLevel(Node<T> current)//广度遍历
        {
            Console.WriteLine(current.data);
            if (current.sibling != null)
            {
                _TraverseByLevel(current.sibling);//递归
            }
            if (current.child != null)
            {
                _TraverseByLevel(current.child);
            }
        }
        public void Traverse()
        {
            //_TraverseByLevel(root);
            _TraverByDepth(root);
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            MyTree<object>.Test();
            Console.ReadLine();
        }
    }

普通树的删除与查找

//查找
   public void Find(Node<T> current, T data)
        {
            if (current == null)
                return;
            if (current.data.Equals( data))
            {
                Console.WriteLine(current.data);
            }
         
            Find(current.child,data);
            Find(current.sibling,data);

        }
//删除
        public void Delete(Node<T> current ,T data)
        {
            if (current == null)
                return;

            if (current.data.Equals(data))
                current.child = null;

            Delete(current.child, data);
            Delete(current.sibling, data);

        }

二叉树

二叉树的定义

二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空而叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

二叉树的特点

  1. 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。
  2. 左子树和右子树是有顺序的,次序不能任意颠倒
  3. 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树

二叉树具有五种基本形态

  1. 空二叉树
  2. 只有一个根结点
  3. 根结点只有左子树
  4. 根结点只有右子树
  5. 根结点既有左子树又有右子树

特殊二叉树
1、斜树
所以的结点都只有左子树的二叉树交左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树

2、满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树.
满二叉树的特点
1、叶子只能出现在最下一层
2、非叶子结点的度一定是2
3、在同样深度的二叉树中,满二叉树的结点个数最多,叶子树最多
4、一棵高度为h,且含有2的n次方-1个结点的二叉树
5、按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父结点为[i/2]

3、完全二叉树
对一棵具有n个结点的二叉树按层次编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树.
完全二叉树的特点
1、叶子结点只能出现在最下两层
2、最下层的叶子一定集中在左部连续位置
3、倒数二层,若有叶子结点,一定都在右部连续位置
4、如果结点度为1,则该结点只有左孩子,即使不存在只有右子树的情况
5、同样结点树的二叉树,完全二叉树的深度最小
6、i<=[n/2]为分支结点,i>[n/2]为叶子结点

4、二叉排序树
左子树上所有结点的关键字均小于根结点的关键字
右子树上所有结点的关键字均大于根结点的关键字
左子树与右子树右各是一棵二叉排序树

5、平衡二叉树
树上任一结点的左子树和右子树的深度之差不超过1

二叉树的性质
在这里插入图片描述

二叉树的存储结构

二叉树的顺序存储结构
二叉树的顺序存储结构一般只用于完全二叉树

二叉树的链式存储结构
二叉树的链式存储结构也叫二叉链表。二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域。我们称这样的链表为二叉链表
在这里插入图片描述
2n个指针
n-1个指针是有指向的
n个结点的二叉链表共有n+1个空链表
根据实际情况可以添加父指针称为三叉指针,以供寻找头结点
二叉树的实现与遍历

遍历二叉树的四种方法
二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

二叉树遍历的方法

  1. 前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
  2. 中序遍历:若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树
  3. 后序遍历:若空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点
  4. 层序遍历:若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问

实例代码

 public class Node<T>
    {
        public T data;
        public Node<T> lchild = null;//左子结点
        public Node<T> rchild = null;//右子节点
    }
    class MyBinaryTree<T>
    {
        public Node<T> root = null;
        public Node<T> CreateRoot(T data)
        {
            root = new Node<T>();
            root.data = data;
            return root;
        }
        public Node<T> InsertIchild(Node<T> current,T data)
        {
            if (current.lchild != null)
                return current.lchild;
            var Ichild = new Node<T>();
            Ichild.data=data;
            current.lchild = Ichild;
            return Ichild;
        }

        public Node<T> InsertRchild(Node<T> current, T data)
        {
            if (current.rchild != null)
                return current.rchild;
            var Rchild = new Node<T>();
            Rchild.data = data;
            current.rchild = Rchild;
            return Rchild;
        }

        public void PreOrder(Node<T> current)//先序遍历
        {
           
            Console.WriteLine(current.data);
            if (current.lchild != null)
                PreOrder(current.lchild);
            if (current.rchild != null)
                PreOrder(current.rchild);
        }

        public void MidOrder(Node<T> current)//中序遍历
        {
            if (current == null)
                return;
            MidOrder(current.lchild);

            Console.WriteLine(current.data);
            MidOrder(current.rchild);
            
        }

        public void PostOrder(Node<T> current)//后序遍历
        {
            if (current == null)
                return;
            PostOrder(current.lchild);
            PostOrder(current.rchild);
            Console.WriteLine(current.data);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyBinaryTree<string> myBinaryTree = new MyBinaryTree<string>();
            Node<string> nodeA = myBinaryTree.CreateRoot("A");
            Node<string> nodeB = myBinaryTree.InsertIchild(nodeA,"B");
            Node<string> nodeC = myBinaryTree.InsertRchild(nodeA, "C"); 
            Node<string> nodeD = myBinaryTree.InsertIchild(nodeB,"D");
            Node<string> nodeE = myBinaryTree.InsertRchild(nodeB,"E");
            Node<string> nodeF = myBinaryTree.InsertIchild(nodeC,"F");
            Node<string> nodeG = myBinaryTree.InsertRchild(nodeC,"G");

            myBinaryTree.MidOrder(myBinaryTree.root);
           myBinaryTree.PostOrder(myBinaryTree.root);
            myBinaryTree.PreOrder(myBinaryTree.root);
            Console.ReadLine();

        }
    }
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值