数据结构学习4
13、二叉树基础
①二叉树的定义
定义 是n(n>=0)个结点的有限集合,该集合或者为空集(称 为空二叉树),或者由一个根结点和两颗互不相交的、分别称 为根结点的左子树和右子树的二叉树组成。
特征: 1.每个结点最多两颗子树 2.左子树和右子树是有顺序的 3.即使树中某结点只有一颗子树也要区分是左子树还是右子树
五种基本形态 1.空二叉树 2.只有一个根结点 3.根结点只有左树 4.根结点只有右树 5.根结点既有左树又有右树
具有3个结点的二叉树有几种不同的形态? 具有下面5种:
几种特殊的二叉树: 1.斜树: 所有结点都只有左子树的二叉树交左斜树 所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
2.满二叉树: 在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树
特点: 1.叶子只能出现在最下层 2.非叶子结点的度一定为2 3.同样深度的的二叉树中,满二叉树的结点个数最多,叶子树最多。
3.完全二叉树: 对一个具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
特点: 1.叶子只能出现在最下两层 2.最下层的叶子一定集中在左部连续位置 3·倒数第二层,若有叶子结点,一定都在右部连续位置。 4·如果结点度为1,则该结点只有左孩子,即不存在只有右树的情况 5·同样结点数的二叉树,完全二叉树的深度最小
几种特殊的二叉树: 满二叉树与完全二叉树的区别 满二叉树是叶子一个也不少的树,而完全二叉树虽然前k-1层是满的,但最底层却允许在右边缺少连续若干个结点。 满二叉树是完全二叉树的一个特例 为什么要研究这两种特殊的二叉树呢? 满二叉树与完全二叉树在顺序存储方式下可以复原
性质1:在二叉树的第i层上至多有2^i-1
个结点(i>=1) 性质2:在深度为k的二叉树至多有2^k-1个结点(k>=1) 性质3:对任何一棵二叉树T,如果其终端结点树为n0,度为2的的结点数为n2,则n0=n2+1 性质4: 具有n个结点的完全二叉树的深度为Log2n+1(取不大于log2n的整数) 性质5:对完全二叉树,若从上至下,从左至右编号,则编号为i的结点 1.其左孩子编号必为2i 2.其右孩子的编号必为2i+1 3.其双亲的编号必为i/2(i=1时为根,除外)
②二叉树的存储结构
顺序存储结构 按二叉树的结点“自上而下,从左至右”编号,用一组连续的存储单元存储。
不是完全二叉树,一律转换成完全二叉树。 方法:将各层空缺处补上“虚结点” ,其内容为空。
链式存储结构 顺序存储适应性不强,一般考虑用链式存储
typedef struct BiTNode /* 结点结构 */
{
TElemType data; /* 结点数据*/
struct BiTNode *Ichild,*rchild; /* 左右孩子指针*/
}BiTNode,*BiTree;
14、二叉树遍历
①遍历二叉树
二叉树遍历原理 是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。 二叉树遍历的用途 二叉树的遍历是二叉树进行插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
二叉树遍历方法 前序遍历 中序遍历 后序遍历 层序遍历
前序遍历 访问顺序:根结点->左树->右树
/* 初始条件:二叉树T存在 */
/* 操作结果:前序递归遍历T*/
void PreOrderTraverse(BiTree T)
{
if(T==NULL)
return;
printf("%c",T->data);/*显示结点数据,可以更改为其它对结点操作*/
PreOrderTraverse(T->Ichild); /*再先序遍历左子树 */
PreOrderTraverse(T->rchild); /*最后先序遍历右子树*/
}
中序遍历 访问顺序:左树->根结点->右树
/* 初始条件: 二叉树T存在*/
/* 操作结果:中序递归遍历T*/
void InOrderTraverse(BiTree T)
{
if(T==NULL)
return;
InOrderTraverse(T->Ichild); /*中序遍历左子树*/
printf("%c",T->data);/*显示结点数据,可以更改为其它对结点操作*/
InOrderTraverse(T->rchild); /*最后中序遍历右子树*/
}
后序遍历 访问顺序:左树->右树->根结点
/* 初始条件:二叉树T存在 */
/*操作结果:后序递归遍历T*/
void PostOrderTraverse(BiTree T)
{
if(T==NULL)
return;
PostOrderTraverse(T->Ichild); /*先后序遍历左子树 */
PostOrderTraverse(T->rchild); /* 再后序遍历右子树 */
printf("%c",T->data);/*显示结点数据,可以更改为其它对结点操作*/
}
层序遍历 访问顺序:1层->2层->......->k层
总结 先、中、后的意思指访问的结点是先于子树还是后于子树,以根结点为参照系。
15、线索二叉树
线索二叉树的原理 前面学的二叉树的链式存储结构的方法叫做二叉链表法 用二叉链表法存储包含n个结点的二叉树,结点的指针区域中会有n+1个空指针。 可见,用二叉链表存储二叉树的方法空间利用率很低,能否利用这些空闲区域存放有用的信息或线索呢? 我们可以把二叉链表里空闲的指针用来存放当前结点的直接前驱和后继等线索,以加快查找速度。这样的二叉树就是线索二叉树。
如何定义直接前驱和直接后继 对二叉树进行某种遍历后,将得到一个线性有序的序列。例如对一个二叉树进行中序遍历后的结果是HDIBJEAFCG,意味着已将该树转为线性排列,显然其中结点具有唯一前驱和唯一后继。 意义: 二叉树中容易找到结点的左右孩子信息,但该结点的直接前驱和直接后继只能在某种遍历过程中动态获得。 先依照遍历规则把每个结点对应的前驱和后继线索预存起来,这叫做“线索化” 意义:从任意结点出发都能快速找到其前驱和后继
如何预存直接前驱和后继信息 充分利用那n+1个空链域。 规定: 1,若结点有左子树,则Ichild指向其左孩子,否则,Ichild指向其直接前驱(线索指针)。 2.若结点有右子树,则rchild指向其右孩子,否则,rchild指向其直接后继(线索指针)。
如何区分孩子指针和线索指针 为了区分孩子指针和线索指针,增加两个标志域
约定: 当Tag=0时,表示正常情况(孩子指针) 当Tag=1时,表示线索情况(线索指针)
线索二叉树的生成------线索化 线索化的过程就是在遍历过程中修改空指针的过程 为了记下遍历过程中访问结点的先后次序,需要设置两个指针: p指针:当前结点指针 pre指针:当前结点前驱结点指针
设计技巧: 依某种顺序遍历二叉树,对每个结点p,判断其左指针是否为空,以及其前驱结点右指针是否为空。 每次只修改前驱结点的右指针(后继)和本结点的左指针(前驱) 若p->IchildNULL,则p->LTag=1;p->Ichild=pre; 若pre->rchildNULL,则pre->RTag=1;pre->rchild=p;
线索二叉树的遍历 有了线索二叉树后,我们对它进行遍历的时候就像操作双向链表一样,不用递归。 为了方便,和双向链表一样,在二叉线索链表中添加一个头结点,其Ichild指向二叉树的根结点, rchild指向二叉树中序遍历时访问的最后结点
/*中序遍历二叉线索树T头结点)的非递归算法*/
Status InOrderTravetse_Thr(BiThrTree T)
{
BiThrTree p;
p=T->Ichild;/*p指向根结点*/
while(p!=T)
{/*空树或遍历结束时,p==T*/
while(p->LTag==Link)
p=p->Ichild;
if(!visit(p->data))/"访问其在子树为空的结点"
return ERROR;
while(p->RTag==Thread&&p->rchild!=T)
{
p=p->rchild;
visit(p->data):/*访问后继结点*/
}
p=p->rchild;
}
return OK;
}
算法流程:
②树、森林与二叉树的转换 树转换成二叉树
二叉树转换成树
森林转换成二叉树
法1: 1.每个树先各自转为二叉树 2.依次连到前一个二叉树的右树上 法2: 森林直接变为兄弟,再转为二叉树
法1和法2得到的二叉树是完全相同的,唯一的。
二叉树转换成森林
③树和森林的遍历
树的遍历 1.深度优先遍历(先根、后根) [树没有中序遍历,因子树不分左右] 2.广度优先遍历(层次)
森林的遍历 1.深度优先遍历(先根、后根) 2.广度优先遍历(层次)
16、图
①图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为: G(VE),其中, G表示一个图, V是图G中顶点的集合,E是图G中边的集合。 注意: 图中的数据元素我们称为顶点 图的结构中不允许没有顶点 图中任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以为空。
②图的基本术语 基本术语 无向边:若顶点vi到y之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(vi,vj)来表示。 无向图:图中两个顶点之间的边都是无向的 有向边:若从顶点vi到vj的边是有方向的,则称这条边为有向边,也称为弧。用有序偶表示<vi,vj>,vi表示弧尾,vj表示弧头。 有向图:图中两个顶点间的边都是有向的 简单图:不存在顶点到自身的边,且同一条边不重复出现 无向完全图:任意两个顶点之间都存在边 有向完全图:任意两个顶点之间都存在方向互为相反的两条边 含有n个顶点的无向完全图有n(n-1)/2条边 含有n个顶点的有向图有n(n-1)条边
证明: 1.有向完全图有n(n-1)条边 若是完全有向图,则n个顶点中的每个顶点都有一条弧指向其他n-1个顶点,因此总边数为:n(n-1) 2.无向完全图有n(n-1)/2条边 从1中可以直接推论出无向完全图的边数,因为没有方向,两弧合并为一边,所有边数减半,总边数为:n(n-1)/2
稀疏图:边较少的图 稠密图:边较多的图 子 图: 设有两个图G=(V,E)和G'=(V",E')。若V'sV且E'CE,则称 图G’是 图G 的子图。
带权图:边上带权的图。其中权是指每条边可以表上具有某种含义的数值(即与边相关的数) 网:带权有向图 连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。 强连通图:在有向图中,若对每一对顶点vi和vj都存在一条从vi到vj 的路径
连通图
强连通图
连通分量:无向图中的极大连通子图
强连通分量:有向图中的极大强连通子图
生成树:连通图的生成树是一个极小连通子图,它包含图中全部n个顶点和n-1条不构成回路的边。 1·如果在生成树上添加1条边,必构成一个环 2·若图中有n个顶点,却少于n-1条边,必为非连通图
生成森林:若干棵生成树的集合,含全部顶点,但构成这些树的边或弧是最少的 非连通图的生成树则组成一个生成森林。
下面是一个有向图及其生成森林