目录
树
是n(n>0)个节点的有限集,树是一种递归的数据结构。
概念
空树 子树
结点 子孙 双亲 孩子 兄弟 度 分支结点和叶子结点
结点的深度,高度和层次
有序树和无序树
路径和路径长度
森林
树的性质
树中的结点树等于所有结点的的度数加1
度为m的树中第i层上至多有m^(i-1)个结点
高度为h的m叉树至多有(m^h-1)/(m-1)个结点
具有n个结点的m叉树的最小高度为 logm(n(m-1)+1)向上取整
树的存储结构
双亲表示法
该存储结构利用了每个结点(除根结点外)只有唯一双亲的性质,可以很快得到每个结点的双亲结点,但求结点的孩子时需要遍历整个结构
#define MAXSIZE 100;//树中最多结点数
typedef struct{//树的结点定义
ElemType data;//数据域
int parent;//双亲位置域
}PTNode;
typedef struct{//树的类型定义
PTNode nodes[MAXSIZE];//双亲表示
int n;//结点数
}PTree;
孩子表示法
孩子表示法是将每个结点的孩子结点都用单链表连接起来形成一个线性结构,此时n个结点就有n个孩子链表(叶子结点的孩子链表为空表)
这种存储方式寻找子女的操作非常直接,而寻找双亲的操作则需要遍历n个结点中孩子链表指针域所指向的n个孩子链表
孩子兄弟表示法 (也称二叉树表示法)
以二叉链表作为树的存储结构。孩子兄弟法是每个结点包含三部分内容:结点值,指向结点第一个孩子的指针,及指向结点下一个兄弟结点的指针(沿此域可以找到结点的所有兄弟结点)
这种存储方式比较灵活,其最大的优点是可以方便的实现树转换为二叉树的操作,易于查找结点的孩子等,但缺点是从当前结点查找其双亲结点比较麻烦。若为每一个结点增设一个parent域指向其父结点,则查找结点的父结点也很方便
typedef struct{
ElemType data;//数据域
struct CSNode *fistchild,nextsibling;//第一个孩子和右兄弟指针
}CSNode,*CSTree;
二叉树
是另一种树形结构,其特点是每个结点最多只有两颗子树(即二叉树中不存在度大于2的结点),并且二叉树子树有左右之分,其次序不能任意颠倒。二叉树是有序树。
几个特殊的二叉树
满二叉树:一颗高度为h,且含有2^h-1个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点
完全二叉树:高度为h,有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树编号为1~n的结点一一对应时,称为完全二叉树。其叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。
二叉排序树:左子树上所有结点的关键字军小于根结点的关键字;右子树上所有结点的关键字均大于根结点的关键字;左子树和右子树又各是一颗二叉排序树
平衡二叉树:树上任一结点的左子树和右子树的深度只差不超过1
二叉树的性质
非空二叉树上的叶子结点数等于度为2的结点数加1
非空二叉树上第k层至多有2^(k-1) ,k>=1
高度为h的二叉树至多有2^h-1个结点,h>=1
具有n个结点的完全二叉树的高度为 log2(n+1)向上取整,或log2 n 向下取整 +1
如果对一棵有n个结点的完全二叉树的结点按层序编号, 则对任一结点i (1≤i≤n) 有:
如果i=1, 则结点i是二叉树的根, 无双亲;如果i>1, 则其双亲parent (i) 是结点i/2
如果2i>n, 则结点i无左孩子, 否则其左孩子lchild (i) 是结点2i
如果2i+1>n, 则结点i无右孩子, 否则其右孩子rchild (i) 是结点2i+1
二叉树的存储结构
顺序存储结构
用一组地址连续的存储单元依次自上而下,从左往右存储完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元素存储在一维数组下标为i-1的分量中
依据二叉树性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一的反映结点之间的逻辑关系
对于一般的二叉树,为了让数组下标能够反映二叉树中结点之间的逻辑关系,只能添加一些并不存在的空结点(用0表示),让其每个结点与完全二叉树上的结点相对照,再存储到一维数组的响应分量中
链式存储结构
由于顺序存储的空间利用率较低,因此二叉树一般采用链式存储结构,用链表结点来存储二叉树中的每个结点。在二叉树中,结点结构通常包括若干数据域和若干指针域,二叉链表至少包含三个域:数据域data,左指针域lchild和右指针域rchild
typedef struct BiTNode{ ElemType data;//数据域 struct BiTNode *lchild,*rchild;//左右孩子指针 }BiTNode,*BiTree;
二叉树的遍历
不管是哪种遍历,每个结点都只访问一次
先序遍历
//二叉树的先序遍历
void PreOrder(BiTree T){
if(T!=NULL){//若二叉树为空则什么都不做,若不为空
visit(T);//访问根结点
PreOrder(T->lchild);//递归遍历左子树
PreOrder(T->rchild);//递归遍历右子树
}
}
中序遍历
//二叉树的中序遍历
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);//递归遍历左子树
visit(T);// 访问根节点
InOrder(T->rchild);//递归遍历右子树
}
}
后序遍历
//二叉树的后序遍历
void PostOrder(BiTree T){
if(T!=NULLL){
PostOrder(T->lchild);//递归遍历左子树
PostOrder(T->rchild);//递归遍历右子树
visit(T);//访问根结点
}
}
层次遍历
需要借助一个队列
//二叉树的层次遍历
void LevelOrder(BiTree T){
InitQueue(Q);//初始化辅助队列
BiTree p;
EnQueue(Q,T);//将根结点入队
while(!IsEmpty){//二叉树不为空时
Dequeue(Q,p);//队头结点出队
visit(p);//访问出队结点
if(p->lchild!=NULL){//左子树不为空时
EnQueue(Q,p->lchild);//则左子树根结点入队
}
if(p->rchild!=NULL){//右子树不为空时
EnQueue(Q,p->rchild);//则右子树根结点入队
}
}
}
例
如下图
先序遍历:A B D H I E J C F K G
中序遍历:H D I B E J A F K C G
后序遍历:H I D J E B K F G C A
层次遍历:A B C D E F G H I J K
思考一下:由该二叉树推出四个遍历顺序;由任意两个(先,中,后)遍历顺序推出 二叉树
线索二叉树
以一定规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点(第一个和最后一个除外)都有一个直接前驱和直接后继
引入线索二叉树正是为了加快查找结点的前驱和后继的速度
线索二叉树的存储结构
线索二叉树的结点结构
//二叉树的存储结构
typedef struct ThreadNode{
ElemType data;//数据域
struct ThreadNode *lchild,*rchild;//左右孩子指针
int ltag,rtag;//左右线索标志
}ThreadNode,*ThreadTree;
以这种结点结构构成的二叉链表作为二叉树的存储结构,称为线索链表,其中指向结点的前驱后继的指针称为线索。加上线索的二叉树称为线索二叉树。
线索二叉树的构造
//中序线索二叉树的构造 void InThread(ThreadTree &p,ThreadTree &pre){ if(p!=NULL){ InTree(p->lchild,pre);//递归,线索化左子树 if(p->lchild==NULL){//左子树为空,建立前驱线索 p->lchild=pre; p->ltag=1; } if(pre!=NULL&&pre->rchild==NULL){ pre->rchild=p;//建立前驱结点的后继线索 pre->rtag=1; } pre=p;//标记当前结点成为刚刚访问过的结点 InThread(p->rchild,pre);//递归,线索化右子树 if(p->rchild==NULL){//右子树为空,建立前驱线索 p->rchild=pre; p->rtag=1; } if(pre!=NULL&&pre->lchild==NULL){ pre->lchild=p;//建立前驱结点的后继线索 pre->ltag=1; } pre=p;//标记当前结点成为刚刚访问过的结点 } }
线索二叉树的遍历
//求中序线索二叉树中中序序列下的第一个结点 ThreadNode *FistNode(ThreadNode *p){ while(p->ltag==0) p=p->ltag;//ltage==0时,得到最左下结点 return p; } //求中序线索二叉树结点p在中序序列下的后继 ThreadNode *NextNode(ThreadNode *p){ if(p->rtag==0) return FirstNode(p->rchild);//rtag==0时,无右孩子,返回左孩子 else return p->rchild;//rtag==1,有右孩子,直接返回后继线索 } //利用上面两个算法,可以写出中序线索二叉树的中序遍历算法 void InOrder(ThreadNode *T){ for(ThreadNode *p=FirstNode(T);p!=NULL;p=NextNode(p)) visit(p); }
树,森林与二叉树的转换
由于二叉树和树都可以以二叉链表作为存储结构,因此以二叉链表为媒介可以导出树与二叉树的一个对应关系,即给定一棵树,可以找到唯一的一颗二叉树与之对应。从物理结构上来看,他们的二叉链表是相同的,只是解释不同而已
树转换成二叉树的规则:每个结点左指针指向他的第一个孩子,右指针指向他在树中的相邻右兄弟,这个规则又称“左孩子右兄弟”。由于根结点没有兄弟,所以对应的二叉树没有右子树
树转换成二叉树的画法:
1.在兄弟结点之间加连一条线
2.对每个结点,只保留他与第一个孩子的连线,而与其他孩子的连线全部抹掉
3.以树根为轴心,顺时针旋转45°
将森林转换为二叉树的规则与树类似,若把森林中的每棵树转换为二叉树,由于任何一颗和树对应的二叉树的右子树必空,若把森林中的第二颗树根视为第一颗树根的兄弟,即将第二棵树对应的二叉树当做第一棵二叉树跟的右子树,将第三棵树对应的二叉树当做第二颗二叉树根的右子树...以此类推,最终就可以将森林转换为二叉树
可以参考树的二叉树表示法,一个结点包含三个部分:结点值,指向结点第一个孩子的指针,及指向结点下一个兄弟结点的指针
树和森林的遍历
这章内容主要了解了森林,树,二叉树,主要重点在二叉树上,存储结构与结点的表示是我们需要深入了解的