树和二叉树

第四章 树和二叉树

树的基本概念

树形结构是一类重要的非线性结构。树形结构是结点之间有分支,并且具有层次关系的结构。

树的定义

树:是n(n>=0)个结点的有限集,满足:

  1. 当 n=0 时,称为 空树
  2. 当 n>0 时,有且仅有一个特定的称为 的结点;其余的结点可分为m(m>=0)个 互不相交 的子集,其中每个子集又是一颗树,并称其为 子树

树的逻辑表示

树的相关术语

结点:有一个数据元素及若干指向其它结点的分支所组成。

结点的度:所拥有的子树的数目;树的度:树中所有结点的度的最大值。

叶子(终端结点):度为 0 的结点。

非终端结点:度不会 0 的结点。

孩子(子结点):结点的子树的根称为该结点的孩子。

双亲(父结点):一个结点称为该结点所有子树根的双亲。

祖先:结点祖先指根到此节点的一条路径上的所有结点。

子孙:从某节点到叶节点的分支上的所有结点称为该结点的子孙。

兄弟:同一双亲的孩子之间互称兄弟。(父结点相同的点)

结点的层次:从根开始算起,根的层次为1,其余结点的层次为双亲的层次加1。

堂兄弟:其双亲在同一层的结点。

树的深度(高度):一个树中所有结点层次数的最大值。

有序树:若树中各结点的子树从左到右是有次序的,不能互换,称为有序树。

无序树:若树中各结点的子树是无次序的,可以互换,称为无序树。

森林:是 m(m>=0) 棵树的集合。

二叉树

二叉树的基本概念

二叉树是 n(n>=0) 各结点的有限集合,它或为空(n=0),或是由一个 两棵 互不相交的 左子树右子树 组成,其左子树和右子树也是二叉树。

二叉树的 特点

  1. 二叉树可以是空的,称为 空二叉树
  2. 每个结点 最多 只能有两个孩子;
  3. 子树 有左、右之分次序不能颠倒

二叉树和树的比较:

二叉树
结点 n >= 0 n >= 0
子树 <= 2 不定(有限)
结点顺序 有(左、右)

完全二叉树:深度为 k 的二叉树中,k-1 层结点数是满的 ,k 层结点是左连续的(即结点编号是连续的)。

满二叉树:深度为 k(k>=1) 且有 个结点的二叉树。满二叉树是完全二叉树的特例。

二叉树的性质

在二叉树的第 i(i>=1) 上至多有 个结点;

深度为 k(k>=1) 的二叉树至多有 个结点;

对任何一棵二叉树,如果其叶结点数为 ,度为2的结点数为 ,有:;

含有 n 个结点的 完全二叉树 的深度为 ;

「x」:代表向下取整x

如果将一棵有 n 个结点的 完全二叉树 按层编号(从上到下,从左到右进行编号),则对任一编号为 i(1 <= i <= n) 的结点 A 有:

  1. 若 i = 1,则结点 A 是根;
  2. 若 i > 1,则结点 A 的双亲的编号为「 i / 2 」;
  3. 若 2 * i > n,则结点 A 即无左子结点,也无右子节点;
  4. 若 2 * i <= n,则结点 A 的左子节点编号为 2 * i;
  5. 若 2 * i + 1 > n,则结点 A 无右子节点;
  6. 若 2 * i + 1 <= n,则结点 A 的右子节点编号为 2 * i + 1;

二叉树的存储结构

二叉树的顺序存储结构

它是用一组连续的存储单元存储二叉树的数据元素。因此,必须把二叉树的所有结点安排成为一个恰当的序列,结点在这个序列中的相互位置能反映出结点之间的逻辑关系,可用编号的方法。

二叉树的顺序存储结构 -- 即对二叉树按完全二叉树进行编号,然后用一维数组存储,其中 编号为i 的结点存储在数组中 下标为i 的分量中。

该方法称为 “以编号为地址” 策略。

从树根起,从上层至下层,每层从左至右的给所有结点编号,缺点 是:

  1. 有可能对存储空间造成极大的浪费,在最坏的情况下,一个深度为 H 且只有 H 个结点的右单支树却需要 个结点存储空间;
  2. 若经常需要插入与删除树中结点时,顺序存储方法不是很好;

对于 完全二叉树 采用此方法,则:

  1. 节省内存;
  2. 结点位置确定方便;

对于 一般二叉树 采用此方法,首先需要用某种方法将其转换成完全二叉树,为此可增设若干个 虚拟结点,则:

  1. 浪费空间;
  2. 用于单分支二叉树则存储空间浪费极大;

二叉树的链式存储结构

// 二叉链表类型定义
typedef struct btnode
{
  DataType data;
  struct btnode *lchild, *rchlid;
} *BinTree;

// 三叉链表类型定义
// parent:指向双亲
typedef struct ttnode
{
  DataType data;
  struct btnode *lchild, *rchlid, *parent;
} *ThreeTree;
二叉树的二叉链表和三叉链表.png

在含 n 个结点的二叉链接表中有 2n 个指针域,其中 n-1 个用来指向结点的左右孩子,其余 n+1 个空链域。

二叉树的遍历

遍历二叉树:是指按 某种次序访问 二叉树上的所有结点,使每个结点被 访问一次 且仅被访问一次。

二叉树遍历的递归实现

先序遍历,DLR:根 -> 左子树 -> 右子树

void preorder(BinTree *bt)
{
  if (bt != NULL)
  {
    visit(bt);
    preorder(bt -> lchild);
    preorder(bt -> rchild);
  }
};

中序遍历,LDR:左子树 -> 根 -> 右子树

void inorder(BinTree *bt)
{
  if (bt != NULL)
  {
    inorder(bt -> lchild);
    visit(bt);
    inorder(bt -> rchild);
  }
};

后序遍历,LRD:左子树 -> 右子树 -> 根

void postorder(BinTree *bt)
{
  if (bt != NULL)
  {
    postorder(bt -> lchild);
    postorder(bt -> rchild);
    visit(bt);
  }
};
二叉树遍历的递归实现例题.png

任意一棵二叉树的前序和后序遍历的结果序列中,各 叶子结点 之间的相对次序关系 都相同

二叉树的层次遍历

二叉树的层次遍历:从二叉树的 根结点 的这一层开始,逐层向下 遍历,在每一层上按 从左到右 的顺序对结点逐个访问。

void levelorder(BinTree *bt)
{
  LkQue Q;
  InitQueue(&Q);

  if (bt != NULL)
  {
    EnQueue(&Q, bt);
    
    while (!EmptyQueue(Q))
    {
      p = GetHead(&Q);
      outQueue(&Q);

      visit(bt);
      if (p -> lchild != NULL) EnQueue(&Q, p -> lchild);
      if (p -> rchild != NULL) EnQueue(&Q, p -> rchild);
    };
  }
}

应用举例

二叉树遍历的应用例题.png

树和森林

树的存储结构

双亲表示法

以一组连续空间存储树的结点,即一个一个数组构成,数组每个分量包含两个域:

  1. 数据域:用于存储树上一个结点的数据元素值;
  2. 双亲域:用于存储本结点的双亲结点在数组中的序号(小标值);

根结点没有双亲,双亲域的值为:-1

双亲链表的类型定义,如下:

#define size 10

typedef struct
{
  DataType data;
  int parent;
} Node;

Node slist[size];

孩子链表表示法

孩子链表:树中每个结点的孩子串成一单链表。

n 个结点 - n 个孩子链表

表头数组:n 个头指针用顺序表存储,数组元素存储:

  1. 结点本身的信息;
  2. 该结点的孩子链表的头指针;

孩子链表表示法的类型定义,如下:

#define MAXND 20

typedef struct bnode
{
  int child;
  struct bnode *next;
} node, *childlink;

typedef struct
{
  DataType data;
  childlink hp;
} headnode;

headnode link[MAXND];

双亲孩子表示法

孩子链表表示法 的基础上,在用一维数组顺序存储树中的各结点,数组元素存储:

  1. 结点本身的信息;
  2. 该结点的孩子链表的头指针;
  3. 双亲结点在数组中的序号;

双亲孩子表示法的类型定义,如下:

#define MAXND 20

typedef struct bnode
{
  int child;
  struct bnode *next;
} node, *childlink;

typedef struct
{
  DataType data;
  int parent;
  childlink hp;
} headnode;

headnode link[MAXND];

孩子兄弟链表表示法

你可能感兴趣的:(树和二叉树)