Chapter 4 树与二叉树

文章目录

  • 1. 树的基本概念
    • 1.1 树的定义
    • 1.2 术语
  • 2. 二叉树
    • 2.1 二叉树的定义
    • 2.2 二叉树的存储结构
  • 3. 二叉树的遍历
    • 3.1 二叉树遍历的递归算法
      • 3.1.1 前序遍历
      • 3.1.2 中序遍历
      • 3.1.3 后序遍历
    • 3.2 二叉树遍历的非递归算法
      • 3.2.1 利用栈的非递归算法
      • 3.2.2 层次遍历
    • 3.3 由遍历序列构造二叉树
  • 4. 线索二叉树
  • 5. 树与森林
    • 5.1 树的存储结构
    • 5.2 森林与二叉树的转换
    • 5.3 树与森林的遍历

1. 树的基本概念


1.1 树的定义

自由树
有关自由树的研究是图论讨论的主要内容之一,这里不做讨论

有根树
简称树,它是 n ( n ≥ 0 ) n(n\ge 0) n(n0)个结点的有限集合。当 n = 0 n=0 n=0时,称为空树。
在任意一棵非空树中应满足:

  1. 有且仅有一个特定的称为根的结点
  2. n > 1 n>1 n>1时,其余结点可分为 m ( m > 0 ) m(m>0) m(m>0)个互不相交的有限集合 T 1 , T 2 , ⋯   , T m T_1,T_2,\cdots ,T_m T1,T2,,Tm,其中每个集合本身又是一棵树,并且称为根结点的子树。

树的定义是递归的,是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下特点:

  • 树的根结点没有前驱结点,除根结点以外的所有结点有且仅有一个前驱结点。
  • 树中所有结点可以有零个或多个后继结点。

n n n个结点的树中有 n − 1 n-1 n1条边

树的性质

  1. 树中的结点数=所有结点的度数(相当于所有边的条数)+1
  2. 度为 m m m的树中第 i i i层上至多有 m i − 1 m^{i-1} mi1个结点 ( i ≥ 1 ) (i\ge1) (i1)
  3. 高度为 h h h m m m叉树至多有 m h − 1 m − 1 \frac{m^h-1}{m-1} m1mh1个结点
  4. 具有 n n n个结点的 m m m叉树的最小高度为 ⌈ l o g m ( n ( m − 1 ) + 1 ) ⌉ \lceil log_m(n(m-1)+1) \rceil logm(n(m1)+1)

1.2 术语

结点——它包含数据项及指向其他结点的分支
结点的度——是结点拥有的子树棵树
叶结点——即度为0的结点,又称终端结点
分支结点——除叶结点以外的其他结点,又称非终端结点
子女结点
父结点
兄弟结点
祖先结点
子孙结点
结点所处层次
树的深度
树的高度
树的度
有序树
无序树
森林
路径和路径长度

2. 二叉树


2.1 二叉树的定义

与树相似,二叉树也以递归的形式定义。二叉树是 n ( n ≥ 0 ) n(n\ge0) n(n0)个结点的有限集合:

  1. 或者为空树,即 n = 0 n=0 n=0
  2. 或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。

几种特殊的二叉树

  • 满二叉树
  • 完全二叉树
  • 二叉排序树
  • 平衡二叉树

二叉树的性质 王P109

  1. 非空二叉树上的叶子结点数=度为2的结点数+1
  2. 非空二叉树第 k k k层上至多有 2 k − 1 2^{k-1} 2k1个结点 ( k ≥ 1 ) (k\ge1) (k1)
  3. 高度为 h h h的二叉树至多有 2 h − 1 2^h-1 2h1个结点 ( h ≥ 1 ) (h\ge1) (h1)

2.2 二叉树的存储结构

顺序存储结构
完全二叉树可采用一维数组存储。
将完全二叉树自顶向下,从左往右编号,该编号即数组的下标。
这种方式是完全二叉树最简单最省存储的存储方式。

链式存储结构
由于顺序结构空间利用率低,因此二叉树一般都采用链式存储结构。
链表可采用二叉链表(只存左子结点和右子结点)和三叉链表(多存了父结点)

3. 二叉树的遍历

3.1 二叉树遍历的递归算法

3.1.1 前序遍历

操作步骤
若二叉树为空,什么也不做

  1. 访问根结点
  2. 前序遍历左子树
  3. 前序遍历右子树

代码

void PreOrder (BiTree T){
	if (T != NULL){
		visit(T);
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
}

3.1.2 中序遍历

操作步骤
若二叉树为空,什么也不做

  1. 中序遍历左子树
  2. 访问根结点
  3. 中序遍历右子树

代码

void InOrder(BiTree T){
	if(T != NULL){
		InOrder(T->lchild);
		visit(T);
		InOrder(T->rchild);
	}
}

3.1.3 后序遍历

操作步骤
若二叉树为空,什么也不做

  1. 后序遍历左子树
  2. 后序遍历右子树
  3. 访问根结点

代码

void PostOrder(BiTree T){
	if(T != NULL){
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		visit(T);
	}
}

三种算法的时间复杂度都为 O ( n ) O(n) O(n)。在递归遍历中,递归工作栈的栈深恰好为树的深度,所以在最坏情况下,遍历算法的空间复杂度为 O ( n ) O(n) O(n)

3.2 二叉树遍历的非递归算法

为了把一个递归过程改为非递归过程,一般需要利用一个工作栈,记录遍历时的回退路径。

3.2.1 利用栈的非递归算法

前序遍历的非递归算法

中序遍历的非递归算法

void InOrder2(BiTree T){
	InitStack(S);
	BiTree p = T;
	while(p || !IsEmpty(S)){
		if(p){
			Push(S,p); //每遇到非空二叉树往左走
			p = p->lchild;
		}
		else{
			Pop(S,p);
			visit(p);   //退栈,访问根结点
			p = p->rchild;
		}
	}
}

后序遍历的非递归算法

3.2.2 层次遍历

进行层次遍历,需要借助一个队列。先将二叉树根结点入队,然后出队,访问该结点,若它有左子树,则将左子树根结点入队;若它有右子树,则将右子树根结点入队。然后出队,对出队结点访问,如此反复,直到队列为空。

void LevelOrder(BiTree T){
	InitQueue(Q);
	BiTree p;
	EnQueue(Q,T);
	while(!IsEmpty(Q)){
		DeQueue(Q,p);
		visit(p);
		if(p->lchild != NUll)
			EnQueue(Q,p->lchild);
		if(P->rchild != NULL)
			EnQueue(Q,p->rchild);
	}
}

3.3 由遍历序列构造二叉树

由二叉树的前序序列和中序序列可以唯一地确定一棵二叉树,同理由后序序列和中序序列也可以唯一确定一棵二叉树,而仅通过前序序列和后序序列不能唯一确定一棵二叉树。

由二叉树的层序序列和中序序列也可以唯一确定一棵二叉树。

4. 线索二叉树


引入线索二叉树是为了加快查找结点前驱和结点后继的速度。

王P120

5. 树与森林


5.1 树的存储结构

双亲表示法

孩子表示法
孩子表示法是将每个孩子结点都用单链表链接起来形成一个线性结构,此时n个结点就有n个孩子链表。

孩子兄弟表示法
这种存储表示法比较灵活,其最大的优点是可以方便地实现树转换为二叉树的操作,易于查找结点的孩子等,…

5.2 森林与二叉树的转换

树转换为二叉树的规则
每个结点左指针指向它第一个孩子结点,右指针指向它在树中的相邻兄弟结点,可表示为“左孩子右兄弟”。由于根结点没有兄弟,所以由树转换而得的二叉树没有右子树。

森林转换为二叉树的规则
先将森林中的每棵树转换为二叉树,再将第一棵树的根作为转换后二叉树的根,将第一棵树的左子树作为转换后二叉树根的左子树,将第二棵树作为转换后二叉树的右子树,将第三棵树作为转换后二叉树根的右子树的右子树,以此类推,就可以完成。

二叉树转换为森林的规则
若二叉树非空,则二叉树的根及其左子树为第一棵树的二叉树形式,二叉树根的右子树又可视为一个由除第一棵树外的森林转换后的二叉树,应用同样的方法,直到最后产生一棵没有右子树的二叉树为止,这样就得到了原森林。

树转换成二叉树的画法

  1. 在兄弟结点之间加一条线
  2. 对每个结点,只保留它与第一个子结点的连线,与其他结点的连线全部抹掉
  3. 以树根为中心,顺时针旋转45°

森林转换为二叉树的画法

  1. 将每棵树的根相连
  2. 将森林中的每棵树转换成相应的二叉树
  3. 以第一棵树的根为轴心顺时针旋转45°

5.3 树与森林的遍历

树的遍历

  • 先根遍历
  • 后根遍历

森林的遍历

  • 先序遍历森林
  • 后序遍历森林

你可能感兴趣的:(DataStructure)