2020数据结构-树和二叉树

5.1 二叉树

5.1.1二叉树的定义
二叉树或为空树,或是由一个根结点加上两棵分别称为左子树和右子树的‘互不相交的二叉树组成。
特点:每个结点至多有两棵子树,子树有左右之分,其次序不能任意颠倒。
特殊的二叉树
(1)满二叉树
二叉树中每一层的结点数都达到最大,所有叶子结点均在最后一层。
(2)完全二叉树
除最后一层外,其余各层均满。
最后一层或是满的,或者右边缺少连续的若干结点。即叶子结点只能出现在最后一层或者次上一层。
(3)理想平衡树
在一棵二叉树中,除最后一层外,其余层都是满的,称为理想平衡树;满二叉树和完全二叉树都是理想平衡树,但理想平衡树不一定是完全二叉树。
5.1.2二叉树的性质
1.在二叉树的第i层上,至多有2i-1个结点(i>=1)。
推广:m叉树的第i层上至多有mi-1个结点。
2.一个深度为k的二叉树中,至多有ki-1个结点。
推广:深度为k的m叉树中,至多有(mk-1)/(m-1)个结点。
3.对于一个非空二叉树,如果叶子结点数为n0,度数为2的结点数为n2,则有n0 = n2+1。
4.具有n个结点的完全二叉树的深度k为(log2n)+1。(k为整数,表示不大于log2n的整数)
推广:具有n个结点的m叉树的最小深度是log2(n(m-1)+1)。
5.如果对一颗有n个节点的完全二叉树的结点按层序编号(从上到下,从左到右),则对任一结点i(1<=i<=n)有:
(1)如果i=1,i为根。若i>1则其双亲是结点(i/2)取整。
(2)如果2i>n,则结点i为叶子,否则其左孩子是结点2i。
(3)如果2i+1>n,则结点i无右孩子,否则其右孩子是结点2i+1。
5.1.3二叉树的存储结构
1.顺序存储
顺序存储就是用一组连续的存储单元存放二叉树中的结点。通常是按照从上到下,从左到右的顺序存储。这样存储的话前后继关系不一定是按照树中的来,所以要通过一些方法确定前后继关系才可以。不难看出只有完全二叉树和满二叉树采用顺序结构存储才比较合适,这时二叉树中结点的序号可以唯一地反映出结点之间的逻辑关系。这样既能够最大限度地节省空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。
优点:根据二叉树的性质5,直接利用元素在数组中的位置表示其逻辑关系,方便寻找某个节点的双亲结点以及左右孩子结点。
缺点:若不是完全二叉树,会浪费空间,适合完全二叉树或者和完全二叉树相近的二叉树。
顺序结构的定义:

#define MAXNODE 100//二叉树最大结点数
typedef ElemType SqBiTree[MAXNODE+1];//1号单元存放根结点

结论: 顺序存储适合存放完全二叉树,便于寻找双亲和孩子。
2.二叉链表存储结构
链表中每个结点由三个域组成,除了数据域外,还有两个指针域,分别用来存放该结点左孩子和右孩子结点的存储地址。
性质:在含有n个结点的二叉链表中含有n+1个空指针域。

typedef struct BiTree{
	DataType data;//DataType表示结点中存放数据的类型
	struct BiTNode * lchild;//存放左子树根结点的地址
	struct BiTNode * rchild;//存放右子树根结点的地址
}BiTree,*BiTree;//BiTree为二叉链表的结点类型

结论: 二叉链表适合找孩子,不适合找双亲。
3.三叉链表
相较于二叉链表多了一个parent,用来指向该结点双亲结点。好处是可以查找双亲结点,但是缺点是增加了空间开销。

typedef struct TriTNode{
	DataType data;//DataType表示结点中存放数据的类型
	struct TriTNode* lchild;//存放左子树根结点的地址
	struct TriTNode* rchild;//存放右子树根结点的地址
	struct TriTNode *parent;//存放双亲结点的地址
}TriTNode,*TriTNode;//TriTNode为三叉链表的结点类型

结论: 三叉链表即适合找孩子又适合找双亲。
5.1.4二叉树的遍历及其应用
遍历操作可以使非线性结构线性化。
三种访问先序(根左右)、中序(左根右)、后序(左右根)。
先序遍历算法实现;

void preorder(BiTree T){
	if(T){
		printf(T->data);
		preorder(T->lchild);
		preorder(T->rchild);
	}
}

中序遍历算法实现:

void inorder(BiTree T){
	if(T){
		inorder(T->lchild);
		printf(T->data);
		inorder(T->rchild);
	}
}

后续遍历算法的实现;

void postorder(BiTree T){
	if(T){
		inorder(T->lchild);
		inorder(T->rchild);
		printf(T->data);
	}
}

2.二叉树遍历的非递归实现
常用的二叉树遍历非递归实现有两种放法:基于任务分析的方法和基于遍历路径分析的方法。
(1)基于任务分析的方法
在每一种遍历方法中,都会进行三次的操作,即遍历左子树’访问根结点‘遍历右子树,但是在访问左右子树的时候还会继续调用这三个方法,所以需要使用自定义栈保存对根结点布置的子任务。分别按照不同的遍历方法对三种情况的重要程度,规定进栈的顺序。

栈的数据元素类型定义:

typedef struct {
	BiTree ptr;//指向根结点的指针
	int task;//任务性质,1表示遍历,0表示访问
}ElemType;

栈的类型定义为:

#define StackMax 20
typedef struct{
	ElemType data[StackMax];
	int top;
}SqStack;

基于任务分析的非递归的中序遍历算法如下:

void inOrder_iter(BiTree T){
	//利用栈实现中序遍历二叉树,BT为指向二叉树的根结点的头指针。
	ElemType e;
	SqStack s;
	InitStack(s);
	e.ptr = T;//布置初始任务
	e.task = 1;
	if(T)//防止栈为空,将遍历左子树放入栈中
		Push(s,e);
	while(!StackEmpty(s)){
		Pop(s,e);//每次处理一项任务
		if(e.task == 0) //e.task == 0处理访问任务
			printf(e.ptr->data);
		else{//处理遍历任务
			p=e.ptr;
			e.ptr = p->rchild;
			if(e.ptr)
				Push(s,e);//遍历右子树
			e.ptr = p;
			e.task = 0;
			Push(s,e);//访问根结点
			e.ptr = p->lchild;
			e.task = 1;
			if(e.ptr)
				Push(s,e);//遍历左子树
		}
	}
}

3.基于搜索路径分析的二叉树遍历算法的非递归实现
路径分析法是根据遍历的路线从根结点开始沿左子树深入下去,当深入到最左端,无法再深入下去时,则返回,逐一进入刚才深入时遇到结点的右子树,在进行如此的深入和返回,直到最后从根结点的右子树返回到根结点为止。
从根结点出发,在沿左子树深入时,深入一个结点入栈一个结点,若为先序遍历,则在入栈之前访问之;当沿左分支深入不下去时,则返回,即从堆栈中弹出前面压入的结点;若为中序遍历,则访问该结点,然后从该结点的右子树继续深入;若为后序遍历,则将该结点再次入栈,然后从该结点的右子树继续深入。与左子树类同,仍为深入一个结点入栈一个结点,深入不下去再返回,直到第二次从栈里弹出该点才访问之。

void NrPostorder(BiTree T){
	SqStack S;
	InitStack(&S);
	char lrtag(STACK_INIT_SIZE)="";//标记数组
	BiTree t;
	t=PriGoFarLeft(T,&S,lrtag);//找T的最左下的结点
	while(t){
		lrtag[S.top]='R';//第二次遇到修改标记
		if(t->rchild)
			t=PriGoFarLeft(t->rchild,&s,lrtag);//找t的右子树最左下的结点
		else
			while(!StackEmpty(S) && lrtag[S.top] == 'R'){//第三次遇到,出栈并输出
				Pop(&S,&t);
				printf(t->data);
			}
		if(!StackEmpty(S))
			GetTop(S,&t);
		else
			t==NULL;
	}
}

BiTree PriGoFarLedt(BiTree T,SqStack *S,charc[]){
	//找T的左下方结点
	if(!T)
		return NULL;
	while(T){
		Push(S,T);  //第一次遇到进栈
		c[S->top]='L';
		if(T->lchild == NULL)
			break;
		T=T->lchild;
	}
	return T;
}

4.层次遍历
从二叉树的根结点出发,从上至下、从左至右依序访问每一个结点。
先访问双亲再访问孩子,符合先进先出的特点,可以使用队列进行保存要访问的每一个点的指针。

void layer(BiTree T){
	InitQueue(Q);//初始化队列
	if(bt)
		EnQueue(Q.bt);//进队列
	while(!QueueEmpty(Q)){
		p=DeQueue(Q);//出队列
		printf(p->data);//访问结点
		if(p->lchild)//左子树跟进队列
			EnQueue(Q,p->lchild);
		if(p->rchild)//右子树跟进队列
			EnQueue(Q,p->rchild);

	}
}

5.创建二叉树的二叉链表存储结构
以字符串“根,左子树,右子树”形式创建二叉链表的算法如下:

void crt_tree(BiTree *T){
	scanf("%c".&ch);
	if(ch=='#')
		*T = NULL:
	else{
		*T = (BiTree)malloc(sizeof(BiTNode));//创建根结点
		(*T)->data = ch;
		crt_tree(&(*T)->lchild);//创建左子树
		crt_tree(&(*T)->lchild);//创建右子树
	}
}

(2)读入边创建二叉链表的非递归算法
算法核心:
1.每读一条边,生成孩子结点,并作为叶子结点;之后将该结点的指针保存在队列中。
2.从队头找该结点的双亲结点指针。如果队头不是,出队列,直至队头是该结点的双亲结点指针。再按lrflag值建立双亲结点的左右孩子关系。

void Create_BiTree(BiTree *T){
	InitQueue(Q);
	*T->Null;
	scanf(fa,ch,lrflag);
	while(ch!='#'){
		p=(BiTree)molloc(sizeof(BiTree));
		p->data = ch;//创建叶子结点
		p->lchild=p->rchild=NULL;//做成叶子结点
		EnQueue(Q,p);//指针入队列
		if(fa=='#')//建立根结点
			*T=p;
		else{
			s=GetHead(Q);//取队列头元素
			while(s->data != fa){
				DeQueue(Q);
				s=GetHead(Q);
			}//在队列中找到双亲结点
			if(lrchild == 0)//链接左孩子结点
				s->lchild = p;
			else//链接右孩子结点
				s->rchild=p;
		}//非根结点的情况
		scanf(fa,ch,lrflag);
	}
}

(3)由二叉树的遍历序列确定二叉树

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

li_jeremy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值