zoukankan      html  css  js  c++  java
  • 数据结构与算法----树(中)

          hey,我们继续上篇文章学习树。上篇文章,我们主要讲了树的一些基本概念、定义,抽象数据结构。今天,我们要学习它的数据结构,让我们开始学习吧。

    树的存储结构:

          一讲到存储结构,就会想到顺序存储与链式存储,让我们来回顾一下它们的概念。顺序存储,就是用一组连续的内存地址来存储数据元素,我们一般用一维数组来表示连续的内存地址。链式存储,用一块儿空闲的内存区域来存放数据元素。存放数据的地址不需要连续,通过元素中的指针域来表示逻辑关系。像之前我们学习的线性表,因为它是有唯一先驱、唯一后继。所以,顺序存储可以很好的表示出它的逻辑关系,链式存储也可很好得表示出它们的逻辑关系。而树这种数据结构,它有多棵子树,多个孩子结点,单是使用顺序存储、链式存储,是不能很好的显示出它们的逻辑关系的。我们需要通过集合这两种存储结构来存储树。

    一、双亲表示法:

         树虽然很多棵子树,但是子树的根结点是唯一确定的。所以,我们可以采用存储结点双亲的方式来存储树。

    image

       data是数据域,存放结点数据。parent是指针域,存放该结点的双亲结点的位置。我们来定义下树的双亲表示法结点的结构:

         /*树的双亲表示法的结点结构定义*/
         #define MAX_TREE_SIZE 100
        type int TElemType /*树结点的数据类型,目前暂定为int*/ 
        typedef struct PTNode  /*结点结构*/
        {
         TElemType data;  /*结点数据*/
        PTNode   parent;  /*双亲位置*/
       }PTNode;
    
       typedef struct  /*树结构*/
      {
          PTNode nodes[MAX_TREE_SIZE];   /*结点数组*/
           int r,n;  /*根的位置和结点数*/   
     }PTree;

    image,它所对应的双亲表示法应该为像下图这样子:

    image

    因为根结点没有双亲, 所以parent指针域为-1.

    我们要想查询某结点的双亲,可以通过parent指针,快速查询。但是,如果需要查询孩子结点,那么只能整个遍历。

    二、多重链表表示法:

       因为树会有多棵子树,所以我们可以使用双链表来表示。就是说,每个结点有多个指针域,分别指向该结点子树的根结点。我们把这种表示法,称为多重链表表示法。但是,每个结点的度数是不同的,也就是每个结点的指针域个数是不同的。这里,我们有两种思路。

    第一种:

        让每个结点的度数都等于树的度数(树的度数,等于所有结点中度数最大的),其结构如下图:

    image

    其中,data存放结点数据,child1….childd存放该结点子树的根结点地址。

    image

    通过上图,大家可以发现,如果结点的度数相差太大,那么会出现很多没用指针域。

    第二种:

    我们在结点中,加一个计数器,专门记录该结点的度数。

    image

    其中data是数据域,存放结点的数据。degree是计数器,存放结点的度数。child1….childd是指针域,指向该结点子树的根结点。

    image

    这样一来,就不存在浪费指针域的情况了。可是,这种存储结构也是有弊端的。因为各个结点的结构不同,计数器难以维护,会造成时间上的弊端。

    三、孩子表示法:

    我们可以通过改进多重链表表示法,来设计出高效的存储结构。首先,我们把所有结点以顺序存储结构存放入一个一维数组中,这样就构成了一个线性表。我们再把每个结点的孩子结点以链式存储结构存入单链表中。如下图:

    image

    我们设计两种结点结构,一种是孩子链表的存储结构如下图:

    image

    其中child存放某结点的孩子结点在线性表中的数组下标,next指针域,指向下个孩子结点在单链表中的位置。

    另一种结点结构,是线性表中表头结点,如下图:

    image

    data存放结点数据,firstchild指针域,指向第一个孩子结点在单链表中的位置。

    /*树的孩子表示法结构定义*/
    #define MAX_TREE_SIZE 100
    typedef struct CTNode  /*孩子结点*/
    {
         int child;
        struct CTNode  *next;
    
    }*ChildPtr;
    
    typedef struct   /*表头结构*/
    {
       TElemType data;
      ChildPtr firstchild;
    }CTBox;
    
    typedef struct  /*树结构*/
    {
       CTBox nodes[MAX_TREE_SIZE];/*结点数组*/
       int r,n;  /*根的位置和结点数*/
    }CTree;

    以上是树的孩子表示法结构定义。对于孩子表示法,需要查找结点的孩子结点、兄弟结点,我们查看单链表就可以查到。对于,遍历整棵树,只需遍历表头线性表就可以。

    四、孩子兄弟表示法:

    以上三种表示法,都是从双亲、孩子的角度来思考的。我们换一下思路,从兄弟的角度来思考一下。如果树中的结点的左孩子存在,即是唯一的。如果右兄弟结点存在,即是唯一的。我们设置两个指针,分别指向该结点的第一个孩子和该结点的右兄弟。如下图:

    image

    data是数据域,存放结点的数据。firstchild、rightsib是指针域,分别指向结点的左孩子,右兄弟。

    /*树的孩子表示法结构定义*/
    typedef struct CSNode
    {
        TElemType data;
       struct CSNode * firstchild,*rightsib;
    }CSNode,*CSTree;

    image

    兄弟孩子表示法,对查找孩子、兄弟结点提供了方便。我们将上图整理一下:

    image

    上图其实就是一棵二叉树,我们可以通过二叉树的性质来操作树。关于二叉树,我们留在下一章讲。我们来复习一下,这章讲的知识。

    归纳总结:

         这章主要讲了树的几种存储结构,因为树不同与线性表,它的子树个数不确定。单单使用顺序存储、链式存储不能表示树的逻辑结构。所以,需要顺序存储与链式存储配合使用。

    我们讲了双亲表示法:因为树中结点的双亲结点是唯一的,我们可以通过记录双亲的方法来表示树的存储结构。多重链表表示法:我们把结点的所有孩子结点都记录下来,这样的方法就是多重链表表示法。孩子表示法:将所有结点以顺序存储的方式放入一维数组中,再用单链表存储孩子结点。孩子兄弟表示法:记录左孩子、有兄弟的方法,来表示树的结构。image

  • 相关阅读:
    写一个通用的事件侦听器函数
    基于LuckySheet在线表格的Excel下载功能开发
    node项目中npm 第三方包引用规则
    js 本地保存 json/txt 文件
    letcood 算法题 -- 两数相加
    ES6新增语法总结
    关于Promise,你必须知道的几点。
    Promise 基础
    学习网页收藏
    android Listview每一个item添加点击事件
  • 原文地址:https://www.cnblogs.com/VitoCorleone/p/4016133.html
Copyright © 2011-2022 走看看