数据结构学习笔记【第6章 树和二叉树】
文章目录
6.1 树的定义和基本术语
- 树(Tree)是n(n≥0)个结点的有限集,有且只有一个根结点,其余结点可划分为不同的根的子树
- 树的结点包含一个数据元素及若干指向其子树的分支
- 结点拥有的子树数称为结点的度(Degree)
- 度数为0的结点称为叶子(Leaf)或终端结点
- 度数不为0的结点称为非终端结点或分支结点
- 树的度是树内各结点的度的最大值
- 结点的子树的跟称为该结点的孩子,该结点称为孩子的双亲,同一个双亲的孩子互称兄弟
- 结点的祖先是从根到该结点所经分支上的所有结点,反之,以某结点为根的子树的任意结点都称为该结点的子孙
- 结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层
- 其双亲在同一层的结点互称为堂兄弟
- 树中结点的最大层次称为树的深度
- 如果树中结点的各子树看成从左到右是有次序的,则称该树为有序树,否则称为无序树
- 森林(Forest)是m(m≥0)棵互不相交的树的集合
6.2 二叉树
6.2.1 二叉树的定义
- 二叉树(Binary Tree)是另一种树型结构,它的特点是每个结点至多只有两棵子树(度小于等于2),并且二叉树的子树有左右之分
- 满二叉树一棵深度为 且有 个结点的二叉树
- 完全二叉树深度为 的,有n个结点的二叉树,当且仅当其中每一个结点都与深度为 的满二叉树中编号1至n的结点一一对应
6.2.2 二叉树的性质
- 性质1 在二叉树的第 层上至多有 个结点
- 性质2 深度为 的二叉树至多有 个结点
- 性质3 对任何一棵二叉树 ,如果其终端结点数为 0,度为2的结点数为 2,则 0 2
- 性质4 具有
个结点的完全二叉树的深度为
( 表示不大于 的最大整数, 表示不小于 的最小整数) - 性质5 如果对一棵有
个结点的完全二叉树(其深度为
)的结点按层序编号(从第1层到第
层,每层从左到右),则对任一结点
,有
(1) 如果 ,则结点 ,是二叉树的根,无双亲;如果 ,则其双亲PARENT 是结点
(2) 如果 ,则结点 无左孩子(结点 为叶子节点);否则其左孩子LCHILD 是结点
(3) 如果 ,则结点 无右孩子;否则其右孩子RCHILD 是结点
6.2.3 二叉树的存储结构
- 顺序存储结构
用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素;对于一般二叉树,则应将其每个结点于完全二叉树上的结点相对照,存储在一维数组的相应分量中 - 二叉树的顺序存储表示
#define MAX_TREE_SIZE 100
typedef TElemType SqBiTree[MAX_TREE_SIZE];
SqBiTree bt;
- 链式存储结构
二叉树的链表中的结点至少包含3个域:数据域和左、右指针域,称之为二叉链表,如若还包含指向双亲结点的指针域则称之为三叉链表
容易证得,在含有 个结点的二叉链表中有 个空链域 - 二叉树的二叉链表表示
typedef struct BiTNode {
TElemType data;
struct BiTNode* lchild, * rchild;//左右孩子指针
}BiTNode,*BiTree;
6.3 遍历二叉树和线索二叉树
6.3.1 遍历二叉树
- 遍历二叉树即如何按照某条搜索路径巡访树中每个结点,使得每个结点均被访问一次,而且仅被访问一次
- 先序遍历二叉树
若二叉树为空,则空操作;否则
(1)访问根节点
(2)先序遍历左子树
(3)先序遍历右子树 - 中序遍历二叉树
若二叉树为空,则空操作;否则
(1)中序遍历左子树
(2)访问根节点
(3)中序遍历右子树 - 后序遍历二叉树
若二叉树为空,则空操作;否则
(1)后序遍历左子树
(2)后序遍历右子树
(3)访问根节点 - 算法6.4 构造二叉链表表示的二叉树T
按先序次序输入二叉树中结点的值(一个字符),空格字符表示空树
Status CreatBiTree(BiTree& T)
{
char ch;
printf("\nPlease input the value of node:");
scanf_s("%c", &ch);
getchar();
if (ch == ' ')T = NULL;
else {
if (!(T = (BiTNode*)malloc(sizeof(BiTNode))))exit(OVERFLOW);
T->data = ch;
CreatBiTree(T->lchild);
CreatBiTree(T->rchild);
}
return OK;
}
- 算法6.1 先序遍历二叉树T的递归算法,对每个数据元素调用函数PrintElement
采用二叉链表存储结构,PrintElement是对数据元素操作的应用函数
Status PreOrderTraverse(BiTree T, Status(*PrintElement)(TElemType e))
{
if (T) {
if (PrintElement(T->data))
{
if (PreOrderTraverse(T->lchild, PrintElement))
{
if (PreOrderTraverse(T->rchild, PrintElement))
{
return OK;
}
}
}
return ERROR;
}
else {
return OK;
}
}
- 算法6.2 中序遍历二叉树T的非递归算法,对每个数据元素调用函数PrintElement
采用二叉链表存储结构,PrintElement是对数据元素操作的应用函数
Status InOrderTraverse(BiTree T, Status(*PrintElement)(TElemType e))
{
SqStack S;
InitStack(S);
Push(S, T); //根指针进栈
BiTree p;
p = (BiTNode*)malloc(sizeof(BiTNode));
while (!StackEmpty(S)) {
while (GetTop(S, p) && p)Push(S, p->lchild);
Pop(S, p); //空指针退栈
if (!StackEmpty(S)) { //访问结点,向右一步
Pop(S, p);
if (!(PrintElement(p->data)))return ERROR;
Push(S, p->rchild);
}
}
return OK;
}
- 算法6.3 中序遍历二叉树T的非递归算法,对每个数据元素调用函数PrintElement
采用二叉链表存储结构,PrintElement是对数据元素操作的应用函数
Status InOrderTraverse2(BiTree T, Status(*PrintElement)(TElemType e))
{
SqStack S;
InitStack(S);
BiTree p;
p = (BiTNode*)malloc(sizeof(BiTNode));
p = T;
while (p || !StackEmpty(S)) {
if (p) { //根指针进栈,遍历左子树
Push(S, p);
p = p->lchild;
}
else { //根指针退栈,访问根结点,遍历右子树
Pop(S, p);
if (!(PrintElement(p->data)))return ERROR;
p = p->rchild;
}
}
return OK;
}
- 完整代码
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef char TElemType;
typedef struct BiTNode {
TElemType data;
struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree, * SElemType;
Status CreatBiTree(BiTree& T);
//按先序次序输入二叉树中结点的值(一个字符),空格字符表示空树
//算法6.4 构造二叉链表表示的二叉树T
Status PreOrderTraverse(BiTree T, Status(*PrintElement)(TElemType e));
//采用二叉链表存储结构,PrintElement是对数据元素操作的应用函数
//算法6.1 先序遍历二叉树T的递归算法,对每个数据元素调用函数PrintElement\
Status InOrderTraverse(BiTree T, Status(*PrintElement)(TElemType e));
//采用二叉链表存储结构,PrintElement是对数据元素操作的应用函数
//算法6.2 中序遍历二叉树T的非递归算法,对每个数据元素调用函数PrintElement
Status InOrderTraverse2(BiTree T, Status(*PrintElement)(TElemType e));
//采用二叉链表存储结构,PrintElement是对数据元素操作的应用函数
//算法6.3 中序遍历二叉树T的非递归算法,对每个数据元素调用函数PrintElement
typedef BiTree SElemType;
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
Status PrintElement(TElemType e);
typedef struct {
SElemType* base;
SElemType* top;
int stacksize;
}SqStack;
Status InitStack(SqStack& S);
Status StackEmpty(SqStack& S);
Status GetTop(SqStack S, SElemType& e);
Status Push(SqStack& S, SElemType e);
Status Pop(SqStack& S, SElemType& e);
int main()
{
BiTree T;
CreatBiTree(T);
PreOrderTraverse(T, PrintElement);
printf("\n");
InOrderTraverse(T, PrintElement);
printf("\n");
InOrderTraverse2(T, PrintElement);
system("pause");
return 0;
}
Status InitStack(SqStack& S)
{
S.base = (SElemType*)malloc(STACK_INIT_SIZE * sizeof(SElemType));
if (!S.base) exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status StackEmpty(SqStack& S)
{
if (S.top == S.base) return TRUE;
return FALSE;
}
Status GetTop(SqStack S, SElemType& e)
{
if (S.top == S.base) return ERROR;
e = *(S.top - 1);
return OK;
}
Status Push(SqStack& S, SElemType e)
{
if (S.top - S.base >= S.stacksize) {
S.base = (SElemType*)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
if (!S.base) exit(OVERFLOW);
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ = e;
}
Status Pop(SqStack& S, SElemType& e)
{
if (S.top == S.base) return ERROR;
e = *--S.top;
return OK;
}
Status PrintElement(TElemType e)
{
printf("%c ", e);
return OK;
}
Status CreatBiTree(BiTree& T)
{
char ch;
printf("\nPlease input the value of node:");
scanf_s("%c", &ch);
getchar();
if (ch == ' ')T = NULL;
else {
if (!(T = (BiTNode*)malloc(sizeof(BiTNode))))exit(OVERFLOW);
T->data = ch;
CreatBiTree(T->lchild);
CreatBiTree(T->rchild);
}
return OK;
}
Status PreOrderTraverse(BiTree T, Status(*PrintElement)(TElemType e))
{
if (T) {
if (PrintElement(T->data))
{
if (PreOrderTraverse(T->lchild, PrintElement))
{
if (PreOrderTraverse(T->rchild, PrintElement))
{
return OK;
}
}
}
return ERROR;
}
else {
return OK;
}
}
Status InOrderTraverse(BiTree T, Status(*PrintElement)(TElemType e))
{
SqStack S;
InitStack(S);
Push(S, T); //根指针进栈
BiTree p;
p = (BiTNode*)malloc(sizeof(BiTNode));
while (!StackEmpty(S)) {
while (GetTop(S, p) && p)Push(S, p->lchild);
Pop(S, p); //空指针退栈
if (!StackEmpty(S)) { //访问结点,向右一步
Pop(S, p);
if (!(PrintElement(p->data)))return ERROR;
Push(S, p->rchild);
}
}
return OK;
}
Status InOrderTraverse2(BiTree T, Status(*PrintElement)(TElemType e))
{
SqStack S;
InitStack(S);
BiTree p;
p = (BiTNode*)malloc(sizeof(BiTNode));
p = T;
while (p || !StackEmpty(S)) {
if (p) { //根指针进栈,遍历左子树
Push(S, p);
p = p->lchild;
}
else { //根指针退栈,访问根结点,遍历右子树
Pop(S, p);
if (!(PrintElement(p->data)))return ERROR;
p = p->rchild;
}
}
return OK;
}
6.3.2 线索二叉树
- 以上述结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针叫做线索。加上线索的二叉树称之为线索二叉树(Threaded Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
- 二叉树的二叉线索存储表示
(error)
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef char TElemType;
typedef enum PointerTag{Link,Thread};
typedef struct BiThrNode {
TElemType data;
struct BiThrNode* lchild, * rchild;
PointerTag LTag, RTag;
}BiThrNode,*BiThrTree;
Status PrintElement(TElemType e);
Status CreatBiTree(BiThrTree& T);
Status InOrderTraverse_Thr(BiThrTree T, Status(*PrintElement)(TElemType e));
//T指向头结点,头结点的左链lchild指向根结点,可参见线索化算法
//算法6.5 中序遍历二叉线索树T的非递归算法,对每个数据元素调用函数visit
Status InOrderThreading(BiThrTree& Thrt, BiThrTree T);
//中序遍历二叉树T,并将其中序线索化,Thrt指向头节点
//算法6.5 中序遍历建立中序线索化链表
void InThreading(BiThrTree p);
//算法6.6 中序遍历建立中序线索化链表
int main()
{
BiThrTree T,Thrt;
CreatBiTree(T);
InOrderThreading(Thrt,T);
InOrderTraverse_Thr(Thrt,PrintElement);
system("pause");
return 0;
}
Status PrintElement(TElemType e)
{
if (e == ' ')return ERROR;
printf("%c ", e);
return OK;
}
Status CreatBiTree(BiThrTree& T)
{
char ch;
printf("\nPlease input the value of node:");
scanf_s("%c", &ch);
getchar();
if (ch == ' ')T = NULL;
else {
if (!(T = (BiThrNode*)malloc(sizeof(BiThrNode))))exit(OVERFLOW);
T->data = ch;
CreatBiTree(T->lchild);
CreatBiTree(T->rchild);
}
return OK;
}
Status InOrderTraverse_Thr(BiThrTree T, Status(*PrintElement)(TElemType e))
{
BiThrNode* p;
p = T->lchild;
while (p != T) {
while (p->LTag == Link)p = p->lchild;
if (!PrintElement(p->data))return ERROR;
while (p->RTag == Thread && p->rchild != T) {
p = p->rchild;
PrintElement(p->data);
}
p = p->rchild;
}
return OK;
}
Status InOrderThreading(BiThrTree& Thrt, BiThrTree T)
{
if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))exit(OVERFLOW);
Thrt->LTag = Link; Thrt->RTag = Thread;
Thrt->rchild = Thrt;
if (!T)Thrt->lchild = Thrt;
else {
Thrt->lchild = T;
BiThrNode* pre;
pre = Thrt;
InThreading(T);
pre->rchild = Thrt;
pre->RTag = Thread;
Thrt->rchild = pre;
}
return OK;
}
void InThreading(BiThrTree p)
{
BiThrNode* pre;
pre = (BiThrNode*)malloc(sizeof(BiThrNode));
if (p) {
InThreading(p->lchild);
if (!p->lchild) {
p->LTag = Thread;
p->lchild = pre;
}
else {
p->LTag = Link;
}
if (!pre->rchild) {
pre->RTag = Thread;
pre->rchild = p;
pre = p;
InThreading(p->rchild);
}
else {
p->RTag = Link;
}
}
}
6.4 树和森林
6.4.1 树的存储结构
- 双亲表示法
#define MAX_TREE_SIZE 100
typedef struct PTNode { //结点结构
TElemType data;
int parent; //双亲位置域
}PTNode;
typedef struct { //树结构
PTNode nodes[MAX_TREE_SIZE];
int r, n; //根的位置和结点数
};
这种存储结构利用了每个结点(除根以外)只有惟一的双亲的性质。这种表示法中,求结点的孩子时需要遍历整个结构。
2. 孩子表示法
#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 n, r; //结点数和根的位置
}CTree;
把每个结点的孩子排列起来,看成一个线性表,且以单链表作存储结构,则
个结点有
个孩子链表(叶子的孩子链表为空表),而
个头指针又组成一个线性表。孩子表示法便于那些涉及孩子的操作的实现。
3. 孩子兄弟表示法
#define MAX_TREE_SIZE 100
typedef struct CSNode {
TElemType data;
struct CSNode* firstchild, * nextsibling;
}CSNode,*CSTree;
链表中结点的两个链域分别指向该结点的第一个孩子结点和下一个兄弟结点。利用这种存储结构便于实现各种树的操作。
6.4.2 森林与二叉树的转换
- 一张图解释
6.4.3 树和森林的遍历
树的两种遍历方法
- 先根(次序)遍历树,即先访问根结点再依次遍历根的每棵子树
- 后根(次序)遍历树,即先依次遍历根的每棵子树再访问根结点
森林的两种遍历方法
- 先序遍历森林
若森林非空,则按下述规则遍历之:
(1)访问森林中第一棵树的根结点;
(2)先序遍历第一棵树中根结点的子树森林;
(3)先序遍历除去第一棵树之后剩余的树构成的森林。 - 中序遍历森林
若森林非空,则按下述规则遍历之:
(1)中序遍历第一棵树中根结点的子树森林;
(2)访问第一棵树的根结点;
(3)中序遍历除去第一棵树之后剩余的树构成的森林。
6.5 树与等价问题
- 未完待续…
6.6 赫夫曼树及其应用
- 未完待续…
6.7 回溯法与树的遍历
- 未完待续…
6.8 树的计数
- 未完待续…