树和二叉树

树的基本术语:

  • 结点的度(Degree):结点的子树个数;
  • 树的度:树的所有结点中最大的度数;
  • 叶结点(Leaf):度为0的结点;
  • 父结点(Parent):有子树的结点是其子树的根节点的父结点;
  • 子结点/孩子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;
  • 兄弟结点(Sibling):具有同一个父结点的各结点彼此是兄弟结点;
  • 路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2,…,nk。ni是ni+1的父结点路径所包含边的个数为路径的长度;
  • 祖先结点(Ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点;
  • 子孙结点(Descendant):某一结点的子树中的所有结点是这个结点的子孙;
  • 结点的层次(Level):规定根结点在1层,其他任一结点的层数是父结点的层数加1;
  • 树的深度(Depth):树中所有结点中的最大层次是这棵树的深度;

树的存储结构

树像线性表一样也有两种存储方式 ,一种是顺序存储,另一种就是链式存储;

1、双亲存储结构(顺序存储)

用一组连续的空间存储所有结点,同时在每个结点上面设一个伪指针指示其双亲的位置,根结点的双亲结点位置为特殊值-1。
在这里插入图片描述

typedef struct {
    ElemType data;      //结点的值
    int parent;         //存放双亲的值
}PTree[MaxSize];        //PTree为双亲存储结构类型

2、孩子链式存储(链式存储)

每个结点不仅包括DATA域还包括了指向所有孩子结点的指针域,注意了这里的指针域可以存在多个,如果按照各个结点的度来设计变长结构,就会因为结点的的孩子结点的指针域个数不同而导致算法实现非常麻烦。孩子链存储结构可按照树的度设计结点孩子的结点的指针域个数。

这个简单来说就是
给你一颗树,你得知道对他的度数,知道他的度数树的度:树的所有结点中最大的度数你就按照他的度数事先分配好每个结点最多可能需要几个指针域,到时候,我不管你有的是儿子还是兄弟,有就指向他们,没有就拉鸡儿倒(乖乖的置空NULL)!
在这里插入图片描述

typedef struct {
    ELemType data;                 //结点的值
    struct node *sons[MaxSons];   //指向孩子的结点
}TSonNode;                        //孩子链存储结构的结点类型

孩子兄弟链式存储

每个结点有三个域。一个数据元素域,一个指向该结点左边第一个孩子结点的指针域,一个指向该结点的下一个兄弟结点的指针域。
简单来说就是:
中间放数据,左指针永远只能指向孩子,右指针永远只能指向兄弟姐妹,两个指针域没有孩子或者兄弟姐妹的话就置空(NULL)。

在这里插入图片描述

typedef struct {
    struct tnode *vp;      //指向孩子   
    ElemType data;         //存放数据
    struct tnode *hp;      //指向兄弟
}TSBNode;                  //孩子兄弟链式存储类型
    

二叉树

二叉树是一个有限的结点集合,这个集合或者为空,或者由一个根结点和两棵互不相交的称为左子树和右子树的二叉树组成。

二叉树的结构简单,存储效率高,其运算算法也相对简单,而且任何m次树都可以转换为二叉树结构,因此二叉树具有很高的地位。

二叉树和度为2的树(2次树)是不相同的,对于非空树,有以下两点差异:

  • 度为2的树至少有一个节点的度为2,而二叉树没有这种要求;
  • 度为2的树不区分左、右子树,儿二叉树严格区分左、右子树;

所有二叉树都有以下集中形态:
在这里插入图片描述

满二叉树和完全二叉树

在这里插入图片描述
满二叉树:
在一棵二叉树中,所有分支节点都有左孩子节点和右孩子节点,并且所有叶子节点都在二叉树的最下层,这样的二叉树我们称为满二叉树。

完全二叉树:

  • 叶子节点只可能在最下面两层出现;
  • 最大层次的叶子结点都从左至右依次排列;
  • 有可能且只有一个度数为1的结点,如果由它也只有一个左孩子;
  • 按层编号的话,一旦出现编号为i的结点是叶结点或者只有左孩子的结点,那么标号大于i的结点均是叶子节点;
  • 当结点总数n为奇数时,n 1 _1 1=0,当结点总数为偶数时,n 1 _1 1=1(n1为单分支结点数)

二叉树具有的性质

  1. 非空二叉树的叶子结点数等于双分支结点数加1;
  2. 非空二叉树的第i层最多有 2 i 2^i 2i − ^- 1 ^1 1个结点;
  3. 高度为h的二叉树最多有 2 h 2^h 2h − ^- 1 ^1 1个结点;
  4. 具有n(n>0)个结点的完全二叉树的高度为[ l o g log log 2 _2 2 ( n + 1 ) (n+1) (n+1)]或[ l o g log log 2 _2 2 n n n]。

二叉树与树、森林之间的转换

1、森林、树转换为二叉树

单棵树转换为二叉树
  1. 树中所有相邻兄弟之间加一条线;
  2. 对树中的每个节点只保留他与长子之间的连线,删除它与其他孩子之间的连线;
  3. 以树的根结点为轴心,将整棵树顺时针旋转45°;

过程详解:
在这里插入图片描述

森林转换为二叉树
  1. 将森林中的每棵树转换为二叉树;
  2. 第一棵二叉树不动,依次把后一棵二叉树的根结点作为前一棵二叉树的右孩子节点;

过程详解:
在这里插入图片描述

单棵树转换成的二叉树还原为森林
  1. 若结点是其双亲的左孩子,则把该结点的右孩子、右孩子的孩子等都与该结点的双亲结点用线连起来;
  2. 删除原来二叉树所有双亲结点与右孩子之间的连线;
  3. 根结点为轴,旋转45°。

过程演示:
在这里插入图片描述

多棵树转换成的二叉树还原为森林

若一棵二叉树是由m颗树构成的森林转换而来的,该二叉树的根节点一定有m-1个右下孩子,还原过程:

  1. 抹掉二叉树根节点右链上所有结点之间的”双亲—右孩子关系“,将其分成若干个右链上的结点为根结点的二叉树;
  2. 将上面分解得到的二叉树还原为森林
    在这里插入图片描述

二叉树的存储结构

和线性表一样,二叉树也有顺序存储结构和链式存储结构。

二叉树的顺寻存储结构

对于完全二叉树和满二叉树,树中的结点的层次序编号可以唯一的反映出节点中的逻辑关系,所以可以用一维数组从上到下,从左到右的顺序存储树中的所有结点值。
在这里插入图片描述
对于一般的二叉树,我们可以改进一下二叉树,增加一些不存在的空结点,使他成为一棵完全二叉树。
在这里插入图片描述
在这里插入图片描述
这样一来,一般的二叉树就和等高的完全二叉树标号相同,当然也就看可以用一维数组存储结点的值啦!
在这里插入图片描述

二叉树顺序存储结构的类型声明:

typedef ElemType SqBinTree[MAxSize];

二叉树的链式存储(二叉链)

不用说都明白,上面的一般二叉树相当程度上是不合适用数组存储的,为啥?因为当需要添加的空结点一多对于计算机来说就是得不偿失的对吧,所以链式存储不就出来了吗?

二叉树链式存储结构:
在这里插入图片描述

  • data表示数据域,存储对应的数据元素;
  • lchild存储左孩子地址(没儿子老规矩置空NULL);
  • rchild存储右孩子地址(没儿子老规矩置空NULL);
    在这里插入图片描述
typedef struct node {
    ElemType data;          //数据元素
    struct node *lchild;    //指向左孩子
    struct node *rchild;    //指向右孩子
}BTNode;                    //二叉链结点类型

上面我记得我好像认识了一个叫做“孩子兄弟链式存储”的玩意儿吧,我觉得那个和这个看上去好像没啥效率上的区别啊,就人眼直观感受上来说,孩子兄弟那个一般树的链式存储不像二叉链这么容易一眼就看出来谁是哪些孩子的双亲。

二叉树的基本运算算法实现

以下都用二叉链存储 结构讨论我们的算法

btree.h头文件:

typedef struct node {
    ElemType data;          //数据元素
    struct node *lchild;    //指向左孩子
    struct node *rchild;    //指向右孩子
}BTNode;                    //二叉链结点类型

创建二叉树CreatBTree(*b,*str)

算法描述:

比如说我们现在要把括号表示的字符串“ A ( B ( D ( , G ) ) , C ( E , F ) ) A(B(D(,G)),C(E,F)) A(B(D(,G)),C(E,F))”存储到二叉链中去,思路如下:
这里我们用到了顺序栈,怎么做呢?我们来开始从头扫描我们的这个字符串,顺序扫描到的无非就字母,左括号,右括号和逗号。最先扫描得到的一定是根结点吧,这时候我们就把它塞到栈的最下面去,并且用一个结点先装着他,并且左右指针都置空。
接下来肯定左括号吧,只要扫描到左括号,只需要把栈顶指针向上移动一个位置,并且设置一个k值设为1,表示接下来我们扫描到的肯定是前者的儿子对吧,这不,B进来的时候,不久被塞到A的上面了吗?
只要扫描到逗号,不用多说,那肯定是遇到栈顶结点的右孩子了,只需要把k设为2即可;
扫描到右括号的时候,说明前面扫描的那个时某个栈顶结点的最后一个孩子了,是时候关栈了。

***代码:

#include "btree.h"
void CreatBTree ( BTNode * &b, char *str) {
    BTNode * St[MaxSize],*p;                              //St数组作为顺序栈
    int top = -1,k,j = 0;                                 //top为顶指针
    char ch;
    b = NULL;                                             //初始时二叉链为空
    ch = str[j];
    while (ch != '\0' ) {
        switch (ch) {
            case '(' : top++;St[top] = p; k = 1; break;   //开始处理左孩子节点
            case ')' :top--;break;                        //栈顶结点的子树处理完毕
            case ',' ;k = 2;break;                        //开始处理右孩子结点
            default.p = (BTNode *)malloc (sizeof(BTNode));//创建一个随机结点,用p指向它
            p ->data = ch;                                //存放结点值
            p ->lchild = p ->rchild = NULL;               //左、右结点置空
            if (b ==NULL)                                 //如果尚未建立根结点
                b = p;                                    //p就指向根结点
                else {                                    //已经有根结点
                    switch (k) { 
                    case 1 : St[top] ->lchild = p;break;  //新建结点作为栈顶结点的左孩子
                    case 2 : St[top] ->rchild = p;break;  //新建结点作为栈顶结点的右孩子
                    }
                }
            }
            j++;                                          //继续扫描str
            ch = str[j];
        }
}
                    

销毁二叉链DestorBTree(&b)

算法模型:
f(b) = NULL; 不做任何事情
f(b)=f(b ->lchild);f(b ->rchild);释放b结点
显然这是一个递归的模型把。

代码

void DestoryBTree(BTree &b) {
    if ( b!= NULL) {
        DestoryBTree(b ->lchild);
        DestoryBTree(b ->rchild);
        free(b);
        }
}

查找二叉链结点FindNode(b,x)

设置f(b,x)的功能是在二叉树b中找到值为x的结点。找到后返回其地址,没找到就返回NULL。

代码:

BTNode * FindNode (BTNode *b ,ElemType x) {
    BTNode *p ;
    if ( b == NULL){
        return NULL;
    else if (b ->data == x)
        return b;
    else {
        p = FindNode (b ->lchild,x);
        if (p!=NULL)
            return p;
        else
            return FindNode(b ->rchild ,x);
        }
}        
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值