树的定义和基本操作

树(一般树)

  • 定义和基本操作
  • 树的术语
  • 树的结构特点
  • 树的基本操作
  • 树的存储结构
  • 树的遍历

定义和基本操作

树是n(n>=0)个结点的有限集合.当n=0时,集合为空,称为空树.在任意一颗非空树中,有且仅有一个特定的结点称为根.当n>1时,除根结点以外的其余结点可分成m(m>=0)个不相交的有限结点集合T1,T2….Tm.其中每个集合本身也是一棵树,称为根的子树.
其中:
(1)有且仅有一个称为根的结点:它没有前继结点,有0个或者多个后继结点.
(2)有若干个称为叶的结点:它们有且仅有一个前几节点,而没有后继结点.
(3)其余称为节的结点:它们有且仅有一个前继结点,至少有一个后继结点.

实际上,树表示了一组结点之间不同于线性表的前继和后继关系的数据结构.一般而言,树种任何一个结点只有一个前继(根结点除外),可以有多个后继(叶结点除外).

树的术语

(1)结点的度:一个结点拥有子树(或后继结点)的个数称为度.度是结点分支树的表示.
(2)树的度:树中所有结点的度的最大值称为树的度.
(3)子结点:一个结点的子树的根节点(或直接后继结点)称为该结点的子结点.
(4)父结点:一棵子树根结点的前继结点称为父结点.除根结点以外的任何结点有且仅有一个父结点.父结点也称双亲结点.
(5)兄弟结点:属于同一个父结点的若干子结点之间互称兄弟结点.
(6)结点的层:树的任何一个结点都处于某一层.因此,树中结点构成一个层次结构.
(7)树的深度:树的最大层次数称为树的深度,也称树的高度.
(8)有序树和无序数:若把结点的子结点都看成从左向右是有序的,则称为有序树;否则称为无序树.有序树的兄弟结点不可交换.
(9)森林:树的集合称为森林.树和森林之间有着密切的关系.删除一个树的根结点,其所有原来的子树都是树,构成森林.用一个结点连接到森林的所有树的根结点就构成树.这个结点称为新的根结点.森林的所有树是该结点的子树.

树的结构特点

(1)层次性:树的每一个结点都处于某一个层次上.除根结点以外的每一层次都有若干结点.所以树结构又称为层次结构.层次结构是表示事物隶属关系常用的方法.如机构的部门人事组织关系,陈品的部件组装关系,程序设计语言中结构类型定义的数据架构关系,层次模型数据库的模型设计,操作系统中文件目录结构等都是树结构的应用.
(2)分支性:从根结点向下,每一个结点都有若干分支(叶结点的分支数视为0).顺着这些分支可以从根结点出发到达树的任何其他结点,其间所经过的结点就构成一条分支路径.因为树的每一个结点都有且仅有一条路径,所以分支特性为树中结点的唯一定位提供了方法.

树的基本操作

  1. 创建树
  2. 判树空
  3. 求树根
  4. 求树高
  5. 求树结点
  6. 求父结点
  7. 求子结点
  8. 插入结点

树的存储结构

父链表表示法

父链表表示法(也称双亲表示法)是用一组地址连续的存储空间存放树的所有结点,以及与父结点关系.每个结点空间存储结点数据本身和父结点号.父结点号是用来表示该结点之父结点在表中的结点号,也成其为指针(但不是指针类型).父结点的位置号用特殊的值”-1”来表示,因为树的根结点是唯一无父结点的结点.
父链表本质上是一种顺序结构,并且也是静态结构,所以可以用一维数组进行存储.数组包含两个域,结点信息域和指针域.指针域存放一个整数.数组元素号即结点位置号.

结构数据类型 TreeFatherCs.c

#include <stdio.h>

//父链表表示法
#define M 100//假设数组内含有100和元素

typedef struct {//定义数组元素结构
    char data;//假设树的结点信息是字符
    int father;//指针类型整型,父结点指针
}FLNODE;

typedef struct {//定义父链表
    FLNODE element[M];//一个M(=100)个元素数组
    int n;//实际元素的个数
};

这里写图片描述
父链表表示法对”求根”和”求父结点”运算十分简单.

求根结点

因为跟结点的指针域的值是”-1”,且只有一个,所以只要找到父结点位置号等于”-1”的结点就是根结点.
TreeFatherControl.h声明

//使用父链表表示法创建一个树
FTTREE createFatherTree();

//打印树
void printFatherTree(FTTREE tree);

//求树的根结点
void getFatherTreeRoot(FTTREE tree);

TreeFatherControl.实现

//使用父链表表示法创建一个树
FTTREE createFatherTree(){
    FTTREE tree;
    tree.n=10;

    FLNODE  node;
    //A
    node.data='A';
    node.father=-1;
    tree.element[0]=node;
    //B
    node.data='B';
    node.father=0;
    tree.element[1]=node;
    //C
    node.data='C';
    node.father=0;
    tree.element[2]=node;
    //D
    node.data='D';
    node.father=0;
    tree.element[3]=node;
    //E
    node.data='E';
    node.father=1;
    tree.element[4]=node;
    //F
    node.data='F';
    node.father=1;
    tree.element[5]=node;
    //H
    node.data='H';
    node.father=3;
    tree.element[6]=node;
    //I
    node.data='I';
    node.father=3;
    tree.element[7]=node;
    //G
    node.data='G';
    node.father=6;
    tree.element[8]=node;
    //K
    node.data='K';
    node.father=7;
    tree.element[9]=node;

    return tree;
}

//打印树
void printFatherTree(FTTREE tree){
    printf("\n");
    printf("tree={");
    printf("{");

    int flag=0;
    for(int i=0;i<tree.n && i<M;i++){
        if(flag==0){
            printf("%c",tree.element[i].data);
            flag=1;
        }else{
            printf(",%c",tree.element[i].data);
        }
    }

    printf("}");
    printf("%d",tree.n);
    printf("}\n");
    printf("\n");
}

void printFlNode(int position,FLNODE node){
    printf("(位置号:%d,数据域:%c,父结点号:%d)",position,node.data,node.father);
}

//求树的根结点
void getFatherTreeRoot(FTTREE tree){
    printf("\n");
    //1.遍历数组,找到指针域是-1的结点,就是根结点
    for(int i=0;i<tree.n;i++){
        FLNODE  node=tree.element[i];
        if(node.father==-1){
            printf("tree的根结点--->");
            printFlNode(i,node);
            break;
        }
    }
     printf("\n");

}

main.c方法中调用

#include "TreeFatherControl.h"
int main(int argc, const char * argv[]) {


    FTTREE tree=createFatherTree();
    printFatherTree(tree);


    //求树的根结点
    getFatherTreeRoot(tree);

       return 0;
}

打印结果:

tree={{A,B,C,D,E,F,H,I,G,K}10}


tree的根结点--->(位置号:0,数据域:A,父结点号:-1)
求父结点

只要找到已知结点所在的数组元素,那么其指针域就是其父结点的位置号.

TreeFatherControl.h

//求指定位置号结点结点的父结点
void getFatherTreeFatherNode(FTTREE tree,int position);

TreeFatherControl.c


//求指定位置号结点结点的父结点
void getFatherTreeFatherNode(FTTREE tree,int position){
     printf("\n");
    //1.判断此位置号是否在数组中
    if(position>=tree.n){
        printf("树中不存在位置号为%d的结点",position);
    }else{
        //2.找到指定位置号的数组元素
        FLNODE  node= tree.element[position];
        //3.获取其父结点的位置号(此数组元素的指针域就是其父结点位置号)
        FLNODE  father=tree.element[node.father];
        printFlNode(position,node);
        printf("的父结点-->");
        printFlNode(node.father,father);
    }
     printf("\n");
}


main.c方法中调用

#include "TreeFatherControl.h"
int main(int argc, const char * argv[]) {

      //求父结点
    getFatherTreeFatherNode(tree, 2);

  return 0;
}

打印结果:

(位置号:2,数据域:C,父结点号:0)的父结点-->(位置号:0,数据域:A,父结点号:-1)
求子结点

求子结点那么就比较麻烦了,必须遍历整个数组表,找到其指针域是其指定结点的位置号,才能找到其子结点.因为结点的子结点数大于等于0,是不定的,所以必须要遍历整个数组,才能找到全部子结点.

TreeFatherControl.h


//求指定位置号结点的子结点
void getFatherTreeNodeChilds(FTTREE tree,int position);

TreeFatherControl.c

//求指定位置号结点的子结点
void getFatherTreeNodeChilds(FTTREE tree,int position){
    printf("\n");
    //1.判断数组中是否存在此位置的元素或者不为空
    if(tree.n==0 ||  position>=tree.n){
        printf("树中不存在位置号为%d的结点",position);
    }else{
         //2.指定位置的结点--为了打印此位置的元素
        FLNODE  node= tree.element[position];
        printFlNode(position,node);
        printf("的子结点是:\n");
        //3.遍历数组,找到指针域为position的结点,因为一个结点的子结点是大于等于0的,所以必须遍历整个数组
        for(int i=0;i<tree.n;i++){
            FLNODE  child=tree.element[i];
            //指针域是父结点的位置号
            if(child.father==position){
                printFlNode(i, child);
                printf("\n");
            }
        }
    }


    printf("\n");
}

main.c方法中调用

#include "TreeFatherControl.h"
int main(int argc, const char * argv[]) {

  //求子结点
  getFatherTreeNodeChilds(tree, 3);

  return 0;
}

打印结果:

(位置号:3,数据域:D,父结点号:0)的子结点是:
(位置号:6,数据域:H,父结点号:3)
(位置号:7,数据域:I,父结点号:3)

子链表表示法

子链表表示法(也称孩子表示法),一个结点的子结点个数不确定.
如果使用顺序表的存储结构的话,那么必须就需要使用树的度(最大子结点个数)为每个结点设定相等个数的子结点号域,这样会使大量的存储空间闲置而造成空间效率不高.设有n个结点,树的度为m,其指向子结点的分支数为n-1.即只有n-1个子结点号域存储了子结点的位置号,其余的n(m-1)+1个子结点号域闲置而不被利用.
这里写图片描述
使用链式存储结构存储可以提高空间的利用率.对树的每一个结点建立一个带头结点的单链表.头结点由结点数据域和链头指针域构成.链头指针存放指向该结点的第一个子结点的指针.链结点由结点数据域和链头指针域构成.子结点号域存放子结点号,链指针域存放下一个子结点的链结点指针.最后一个链结点的链指针域置空,这样就存储成某一个结点的子结点链.
子链表实际上又是顺序存储结构和链式存储结构的结合使用.

这里写图片描述
数据结构类型:
TreeChildCs.c


#define M 100

typedef struct cnode{//定义链结点
    int  cno; //子结点号
    struct cnode *next;//下一结点指针
}CLINK;

typedef struct {//定义头结点
    char data;//树结点信息域
    CLINK *heaer;//链头指针
}CTNODE;

typedef struct {//定义子链表
    CTNODE node[M];//头结点数组
    int n,root;//树的结点个数,根结点位置号
}CLTREE;

子弟链表表示法

子弟链表表示法(也称孩子兄弟表示法)克服了父链表只存储与父结点的关系和子链表值存储与子结点的关系的缺点,同时也解决了子链表的子结点个数不确定的问题.基本思想是吧结点的父子关系和兄弟关系一起表示在一个结构中.使存储结点的构造形式相同,便于使用顺序存储结构.
子弟链表的每个存储结点由3个域组成:结点数据域,子结点号域,兄弟结点号域.
子结点号域:存放本结点的第一个(即最左)子结点的位置号.叶结点无子结点可存储一个特殊值,如”-1”.
兄弟结点号域:存放本结点(从左到右)下一个兄弟结点的位置号.如果无下一个兄弟结点,则存储”-1”.
采用这种存储结构,易于实现求子结点的运算.方式是根据已知结点选取得该结点的第1个结点,然后沿着它的兄弟结点号域走下去,就能找到它的所有子结点.实际上,兄弟结点号把属于同一个结点的所有子结点连城一条链.

在TreeBrotherCs.c

#define M 100
typedef struct {//定义数组元素结构
    char data;//树的结点属于假设为char类型
    int child,nextsib;//指针是整数型
}CSNODE;


typedef struct {//定义兄弟链表
    CSNODE element[M];
    int n;//实际元素个数
};

这里写图片描述

这种结构不便于求父结点运算,但是如果增加一个父结点指针域,那么就简单了.
这里写图片描述

树的遍历

先根遍历

先访问根结点,遍历的次序是:先访问树的跟结点,然后自左至右依次遍历根结点的所有子树.因为子树也是树,所以在遍历每一棵子树时还是按照上述步骤执行.
这里写图片描述
算法描述:
若树非空
(1)访问根结点
(2)自左至右依次先根遍历根的各子树.

后根遍历

最后访问根结点.遍历次序是先自左至右依次遍历根的子树.然后再访问树的根结点.在遍历每一棵子树时还是按照上述步骤执行.
这里写图片描述
算法描述:
如果非空树
(1)自左至右依次后根遍历各个子树.
(2)访问根结点.

层次遍历

从根结点开始从上到下逐层访问结点.遍历次序是:若树非空树,则先访问根结点(第1层,只有一个根结点);依次遍历第2层,第3层…..的结点,直到访问全部结点.遍历某一层时从左向右次序逐个访问结点.

这里写图片描述
算法描述
若树为非空,则:
(1)先访问根结点.
(2)若已访问完第i层,则第i+1层还有未访问的结点,则自左至右依次访问第i+1层上结点.


这是一般树的基本操作,请大家指点,有好的建议请大家指出,有好的书籍,还往大家推荐!

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值