六、树和森林

一、概念:树(Tree)是n(n>=0)个结点的有限集。n= 0时称为空树。

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

二、结点

树的结点包含一个数据元素及若干指向其子树的分支。
  结点拥有的子树的个数称为结点的度(Degree)。度为零的结点称为叶结点(Leaf)或终端结点;度不为零的结点称为非终端结点或分支结点。除根结点外,分支结点也称内部结点,树的度是树内个节点的度的最大值。
  40a9b8b7bf6df21c69e34c57cb726d72.png

三、结点间关系:

结点的子树的根称为该结点的孩子(child),相应的,该结点称为孩子结点的双亲(parent)。
  同一个双亲的孩子之间互称为兄弟(Sibling);
  结点的祖先是从根到该结点所经分支上的所有结点。反之,某结点为根的子树中的任意结点都称为该结点的子孙。
  4bcafa70b8182c43aa998b4256c3a470.png

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。某结点在第i层,其孩子结点在第i+1层;
  其双亲在同一层的结点互为堂兄弟
  树中结点的最大层次称为树的深度(Depth)或这高度.
    393f13f3520fc350e8ff3d6ecc57da0c.png

如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树;
  森林(Forest)是m(m>=0)颗互不相交的树的集合。对树中的每个结点而言,其子树的集合即为森林。
  线性结构与树结构对比:

线性结构树结构
第一个数据元素无前驱根结点,无双亲,唯一
最后一个数据元素,无后继叶结点,无孩子,可以多个
中间元素,一个前驱一个后继中间结点,一个双亲多个孩子

四:树的存储结构(双亲表示法、孩子表示法、双亲孩子表示法)

1、双亲表示法:
  假设以一组连续空间存储树的结点,同时每个结点中,附设一个指示器,指向其双亲系结点到链表中的位置。,
          e470191273c0785c1051afc535f40788.png

data是数据域,存储结点的数据信息,而parent是指针域,存储该结点的双亲在数组中的下标。
  2、孩子表示法
    由于树中每个结点可能有多颗子树,可以考虑用 多重链表,即每个结点有多个指针域,其中每个指针指向一颗子树的根结点,这种方法叫做多重链表表示法。
    方案一:
    指针域的个数就等于树的度。
  07ef0c6bc654fed8e0d15d870272f61a.png

其中data是数据域。child1到childd是指针域,用来指向该结点的孩子结点。
    缺点:树中各结点的度各不相同,会浪费空间
c108f77eecfbe538cf55cb4aee555e23.png

方案二、
    每个结点的指针域的个数等于该结点的度。专门取一个位置来存储该结点的指针域的个数。
 0a5935adebad9df763f790fee006ff75.png
    其中data为数据域,degree为度域,也就是存储该结点的孩子结点的个数,child1到childd为指针域,指向该结点的各个孩子的结点。
   61acc180c7886487e20184f484eb952c.png

空间效率高了,但是时间效率降低。
    孩子表示法:把每个结点的孩子结点排列起来,以单链表作为存储结构,则n个结点有n个孩子的链表,如果是叶子结点则此单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
f33723a51d2e1f50dde50c210404afd0.png

3、孩子兄弟表示法:
    任意一颗树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的.因此,设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
          123e22b8c2ac3636cb1e6510bb8cc640.png

其中data为数据域,firstchild为指针域,存储该结点的第一个孩子结点的存储地址,rightsib是指针域,存储该结点的右兄弟结点的存储地址。

五、二叉树的定义:

1、二叉树(Binary Tree):二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树组成。
  2、二叉树的特点:
    ○ 每个结点最多有两颗子树,所以二叉树中不存在度大于二的结点,注意不是只有两颗子树,而是最多有,没有子树或者只有一颗都是可以的。
    ○ 左子树和右子树是有顺序的,次序不能任意颠倒。
    ○ 即使树中某结点只有一个子树。也要区分它是左子树还是右子树。
  3、二叉树的五种基本形态:
    a. 空二叉树
    b. 只有一个根结点
    c. 根结点只有左子树
    d. 根结点只有右子树
    e. 根结点既有左子树又有右子树
  3、特殊二叉树
    a. 斜树:所有的结点都只有左子树的二叉树叫左斜树
      所有结点都是只有右子树的二叉树叫右边斜树
    b. 满二叉树:在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同层上,这样的二叉树称为满二叉树
    c7292929de1b2d22694cca49aaa073e4.png

满二叉树的特点:
        (1)叶子只能出现在最下层,
        (2)非叶子结点的度一定是2,
        (3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子树最多
    c. 完全二叉树:
      对一颗具有n个结点的二叉树按层排序,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同,则这颗二叉树成为完全二叉树
      满二叉树一定是一颗完全二叉树,但是完全二叉树不一定是满的。
      02671715c0211e499a620ae46aff9d30.png

完全二叉树的特点:
      § 叶子结点只能出现在最下两层
      § 最下层的叶子一定集中在左部连续位置
      § 倒数二层,若有叶子结点,则一定都在右部连续位置
      § 如果结点度为1,则结点只有左孩子,即不存在只有右子树的情况
      § 同样结点数的二叉树,完全二叉树的深度最小

六、二叉树的性质:

1. 在二叉树的第i层上至多有2^(i-1)个结点(i>=1);
    第一层2^0;
    第二层2^1
    第三层2^2……
  2. 深度为K的二叉树至多有2^k -1个结点;
    一层至多有2^1 -1 = 1;
    二层至多有2^2 -1 = 3;
    三层至多有2^3 -1 = 7……
  3. 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2 +1;
  4. 具有n个结点的完全二叉树的深度为4a17213ff1019606d30c78e7f24f628a.png

整数。
  5. 如果对一棵有n个结点的完全二叉树(其深度为[log2n](向下取整)+1)的结点按层序编号(从第1层到第[log2n](向下取整)+1层,每层从左到右),对任一结点i(1<=i<=n)有:
    a. 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,其双亲是结点[i/2](向下取整)。
    b. 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i;
    c. 如果2i+1 >n,则结点i无右孩子,否则其右孩子是结点2i+1;

七、二叉树的存储结构:

1、顺序结构一般只用于完全二叉树
  2、二叉链表;
    二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域,这样的链表称为二叉链表

LchildDataRChild

其中data是数据域,lchild和rchild都是指针域,分别存放指向左孩子和右孩子的指针。

 namespace TreeNode
    {
     class TreeNode<T>
     {
     private T data; //数据域
     private TreeNode<T> lChild; //左孩子
     private TreeNode<T> rChild;
    
     public TreeNode(T val, TreeNode<T> lp, TreeNode<T> rp)
     {
     data = val;
     lChild = lp;
     rChild = rp;
     }
    
     public TreeNode(TreeNode<T> lp, TreeNode<T> rp)
     {
     data = default(T);
     lChild = lp;
     rChild = rp;
     }
    
     public TreeNode(T val)
     {
     data = val;
     lChild = null;
     rChild = null;
     }
    
     public T Data
     {
     get { return data; }
     set { data = value; }
     }
    
     public TreeNode<T> LChild
     {
     get { return lChild; }
     set { lChild = value; }
     }
    
     public TreeNode<T> RChild
     {
     get { return lChild; }
     set { rChild = value; }
     }
     }
    }

3、二叉树的遍历方法:
    二叉树的遍历(traversing binary tree)是指从结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
    a. 前序遍历
      若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
      ac44895da6402519bdab9680cfa21b1a.png

b. 中序遍历
    若树为空,则空操作返回,否则从跟结开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后访问根结点,最后中序遍历右子树。
    56f6133dfc92d50b7fd17789c6f800c2.png

c. 后序遍历:
    若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问在右子树,最后访问根结点。
    59c0138f8d9887f059c3e660dc82d360.png

d. 层序遍历:
    若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
    f66f6badc66c82f947798c8202f86650.png

namespace TraversingBinaryTree
    {
     [DebuggerDisplay("Value = {value}")]
     public class Tree
     {
     public string Value;
     public Tree Left;
     public Tree Right;
     }
    
     class Program
     {
     static void Main(string[] args)
     {
     Tree newTree = CreatFakeTree();
     PreOrder(newTree);
     PreOrderNoRecursion(newTree);
     }
     /// <summary>
     /// 新建一个二叉树
     /// </summary>
     /// <returns></returns>
     public static Tree CreatFakeTree()
     {
     Tree tree = new Tree() { Value = "A" };
     tree.Left = new Tree()
     {
     Value = "B",
     Left = new Tree() { Value = "D", Left = new Tree() { Value = "G" } },
     Right = new Tree() { Value = "E", Right = new Tree() { Value = "H" } }
     };
     tree.Right = new Tree() { Value = "C", Right = new Tree() { Value = "F" } };
    
     return tree;
     }
    
     #region 前序遍历的递归和非递归实现
     /// <summary>
     /// 递归实现二叉树的前序遍历
     /// </summary>
     /// <param name="tree"></param>
     public static void PreOrder(Tree tree)
     {
     if (tree == null)
     return;
     Console.WriteLine(tree.Value);
     PreOrder(tree.Left);
     PreOrder(tree.Right);
     }
     /// <summary>
     /// 非递归实现二叉树的前序遍历
     /// </summary>
     /// <param name="tree"></param>
     public static void PreOrderNoRecursion(Tree tree)
     {
     if (tree ==null)
     {
     return;
     }
    
     Stack<Tree> stack = new Stack<Tree>();
     Tree node = tree;
    
     while (node!=null || stack.Any())
     {
     if (node!=null)
     {
     stack.Push(node);
     Console.WriteLine(node.Value);
     node = node.Left;
     }
     else
     {
     var item = stack.Pop();
     node = item.Right;
     }
     }
     }
    
     #endregion
    
     #region 中序遍历的递归和非递归实现
     /// <summary>
     /// 递归实现二叉树的中序遍历
     /// </summary>
     /// <param name="tree"></param>
     public static void InOrder(Tree tree)
     {
     if (tree ==null)
     {
     return;
     }
     InOrder(tree);
     Console.WriteLine(tree.Value);
     InOrder(tree);
     }
     /// <summary>
     /// 非递归实现二叉树的中序遍历
     /// </summary>
     /// <param name="tree"></param>
     public static void InOrderNoRecursion(Tree tree)
     {
     if (tree ==null)
     {
     return;
     }
     Stack<Tree> stack = new Stack<Tree>();
     Tree node = tree;
    
     while (node != null || stack.Any())
     {
     if (tree != null)
     {
     stack.Push(node);
     node = node.Left;
     }
     else
     {
     var item = stack.Pop();
     Console.WriteLine(item.Value);
    
     node = item.Right;
     }
     }
     }
     #endregion
    
     #region 后序遍历的递归和非递归实现
     /// <summary>
     /// 后序遍历的递归实现
     /// </summary>
     /// <param name="tree"></param>
     public static void PostOrder(Tree tree)
     {
     if (tree == null)
     {
     return;
     }
     PostOrder(tree.Left);
     PostOrder(tree.Right);
     Console.WriteLine(tree.Value);
     }
     /// <summary>
     /// 后序遍历的非递归实现(一)
     /// </summary>
     /// <param name="tree"></param>
     public static void PostOrderNoRecursion(Tree tree)
     {
     if (tree == null)
     {
     return;
     }
     Stack<Tree> stack = new Stack<Tree>();
     Tree node = tree;
     Tree pre = null;
     stack.Push(node);
    
     while (stack.Any())
     {
     node = stack.Peek();
     if ((node.Left ==null&& node.Right == null)||(pre!=null
     &&(pre == node.Left||pre==node.Right)))
     {
     Console.WriteLine(node.Value);
     pre = node;
    
     stack.Pop();
     }
     else
     {
     if (node.Right != null)
     stack.Push(node.Right);
    
     if (node.Left != null)
     stack.Push(node.Left);
     }
     }
     }
    
     /// <summary>
     /// 后序遍历的非递归实现(二)
     /// </summary>
     /// <param name="tree"></param>
     public static void PostOrderNoRecursion2(Tree tree)
     {
     HashSet<Tree> visited = new HashSet<Tree>();
     Stack<Tree> stack = new Stack<Tree>();
     Tree node = tree;
    
     while (node!=null || stack.Any())
     {
     if (node!=null)
     {
     stack.Push(node);
     node = node.Left;
     }
     else
     {
     var item = stack.Peek();
     if (item.Right!=null&&!visited.Contains(item.Right))
     {
     node = item.Right;
     }
     else
     {
     Console.WriteLine(item.Value);
     visited.Add(item);
     stack.Pop();
     }
     }
     }
     }
     #endregion
    
     #region 层序遍历的实现
     /// <summary>
     /// 层序遍历的实现
     /// </summary>
     /// <param name="tree"></param>
     public static void LevelOrder(Tree tree)
     {
     if (tree == null)
     {
     return;
     }
     Queue<Tree> queue = new Queue<Tree>();
     queue.Enqueue(tree);
    
     while (queue.Any())
     {
     var item = queue.Dequeue();
     Console.WriteLine(item.Value);
    
     if (item.Left!= null)
     {
     queue.Enqueue(item.Left);
     }
     if (item.Right!=null)
     {
     queue.Enqueue(item.Right);
     }
     }
     }
     #endregion
    
     }
    }

八、树、森林与二叉树的转换

1、树转换为二叉树
    a. 加线。在所有兄弟结点之间加一条线
    b. 去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。
    c. 层次调整。以树的跟结点为轴心,将正棵树旋转一定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子就是结点的右孩子。
  7b8e8707bec0f7407b41c5cdaf9f7c0e.png

2、森林转换为二叉树
    森林是由若干树组成的,可以理解为,森林中的每一颗树都是兄弟,可以按照兄弟的处理办法来操作。
    a. 把每个树转换为二叉树。
    b. 第一颗二叉树不动,从第二棵树开始,依次把后一颗二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。当所有的二叉树连接起来后就得到由森林转换而来的二叉树。
    
    f484217080941d5f9566a17b4f819594.png

46d03b0c9909b016ff87c659da1a2c12.png
c8e9d5acbead177bf1c3344d00d3a65a.png

3、二叉树转换为树
      二叉树转换为树是树转换为二叉树的逆过程,
      a、加线。若某结点的左孩子结点存在,则将这个左孩子的右孩子结点,右孩子的右孩子结点,右孩子的右孩子的右孩子结点 ……,左孩子的n右孩子都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来。
      b、去线。删除原二叉树中所有结点与其右孩子结点的连线。
      c、层次调整。使之结构层次分明。
431d243d89c17e9e1ea65430ab225a24.png

4、二叉树转换为森林:
    如果一棵二叉树的根结点有右孩子,则能转换为森林,如果没有,则不能。
    步骤:
    a. 从跟结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除……,直到所有右孩子连线都删除为止,得到分离的二叉树。
    b. 再将没棵分离后的二叉树转换为树即可。
b1ac9ce3fd036ca3f91f67f4852b1ebe.png

九、树和森林的遍历:

1、树的遍历分两种方式:
    a. 先根遍历树:先访问树的根结点,然后依次先根遍历根的没棵子树;
    b. 后根遍历树:即先依次后根遍历每课子树,然后在访问根结点。
    下图先根遍历序列为 ABEFCDG
    下图先根遍历序列为 EFBCGDA
    140ed3705a0d1b02f483723486fca099.png

2、森林的遍历分两种方式:
    a. 前序遍历:先访问森林中的第一颗树的根结点,然后在依次先根遍历每棵子树,再依次用同样的方式遍历除去第一棵树的剩余树构成的森林。
    b. 后序遍历:先访问森林中的第一颗树,后根遍历的方式遍历每棵子树,然后再访问根结点,再依次同样方式遍历除去第一棵树的剩余树构成的森林。
    下图森林的前序遍历的结果是ABCDEFGHJI
        前序遍历的结果是BCDAFEJHIG
    f1d211a8ec1b9808254de90e0706c8b4.png

九、赫夫曼树———赫夫曼编码(压缩编码方法)

路径长度:从树中一个结点到另一个结点之间的分支构成两个节点之间的路径,路径上的分支数目称作路径长度。
  树的路径长度:树的路径长度就是从树根到每一个结点的路径长度之和。
  赫夫曼树:假设有n个权值{w1,w2,……,wn},构造一颗有n个叶子结点的二叉树,每个叶子结点带权wk,每个叶子的路径长度为lk,则wk*lk称为称为叶子结点的带权路径长度,
  树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。其中带权路径WPL长度最小的的e二叉树称为赫夫曼树。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值