06-树(tree)

06-树

基本概念

image-20211028213442226

**结点的度:**结点拥有的分支个数(A1的度为3);

**树的度:**树中各结点度的最大值(上树度为3);

**叶子结点:**度为0的结点(如A3,A4,A5,A6,A7);

**孩子:**A2,A3,A4为A1的孩子;

**双亲:**A2,A3,A4的双亲为A1

**祖先:**从根到某结点的路径上的所有结点(A1,A2为A5的结点);

子孙:以某结点为根的的子树中的所有结点,都是该结点的子孙(A2 ~ A6为A1的子孙);

**兄弟:**同一个双亲的孩子之间互为兄弟(A2,A3,A4互为兄弟);

**堂兄弟:**A6,A7互为堂兄弟;

image-20211028214718157

**树的高度:**树中结点的最大层次;

**结点的深度:**从根结点到该结点路径上的结点个数;

**结点的高度:**从某结点往下走可以达到多个叶子结点,其中最长的那条路径上结点的个数几位该结点在树中的高度;

树的存储结构

顺序存储结构

数组下标012345
结点信息(结构体)A1A2A3A4A5A6
父结点所在数组下标-100011

也可简化成下图

image-20211028220822817

链式存储结构

//链表结点类型,存储孩子结点的所有路径
typedef struct Branch{
	int childIndex;//存储孩子结点的数组下标
	Branch *next;
}Branch;
//树结点
typedef struct{
	int data;
	Branch *first;
}TNode;
image-20211028221424674

二叉树

1)每个结点最多只有两棵子树(即二叉树中的结点度只能为0、1、2);

2)子树有左右顺序之分不能颠倒;

3)满二叉树通过删除结点可以得到完全二叉树

二叉树可能的形态
image-20211028222057306
求完全二叉树的高度(h)

满二叉树列举法求高度

image-20211028222312550

完全二叉树求高度

image-20211028222603920

公式

image-20211028222840122
二叉树的性质

**1.**非空二叉树上叶子结点数等于双分支结点数+1

**2.**二叉树的第i层上最多有2i-1(i大于等于1)个结点

**3.**高度(深度)为k的二叉树最多有2k-1(k大于等于1)个结点,也即满二叉树的前k层的结点个数为2k-1

总分支数=总结点树-1;

叶子结点数为N0,单分支结点数为N1,双分支结点数为N2

总结点数=N0+N1+N2;总分支数=N1+2N2
{ 总 分 支 数 = 总 结 点 数 − 1 总 结 点 数 = N 0 + N 1 + N 2 总 分 支 数 = N 1 + 2 N 2 \begin{cases} 总分支数=总结点数-1 \\ 总结点数=N_0+N_1+N_2\\ 总分支数=N_1+2N_2\\ \end{cases} =1=N0+N1+N2=N1+2N2
解得:
{ N 0 = N 2 + 1 , 也 即 下 行 叶 子 结 点 数 = 双 分 支 结 点 数 + 1 ; \begin{cases} N_0=N_2+1,也即下行 \\ 叶子结点数=双分支结点数+1;\\ \end{cases} {N0=N2+1,=+1;

一般树(度为m)

叶 子 结 点 数 为 N 0 , 单 分 支 结 点 数 为 N 1 , 双 分 支 结 点 数 为 N 2 , 三 分 支 结 点 数 为 N 3 , . . . m 分 支 结 点 数 为 N m ; { 总 分 支 数 = 总 结 点 数 − 1 总 结 点 数 = N 0 + N 1 + N 2 + . . . + N m 总 分 支 数 = N 1 + 2 N 2 + 3 N 3 . . . + m N m 叶子结点数为N_0,单分支结点数为N_1,双分支结点数为N_2,三分支结点数为N_3,...m分支结点数为N_m; \begin{cases} 总分支数=总结点数-1 \\ 总结点数=N_0+N_1+N_2+...+N_m\\ 总分支数=N_1+2N_2+3N_3...+mN_m\\ \end{cases} N0,N1,N2,N3,...mNm;=1=N0+N1+N2+...+Nm=N1+2N2+3N3...+mNm

解得:
{ N 0 = 1 + N 2 + 2 N 3 + . . . + ( m − 1 ) N m ; , 也 即 下 行 叶 子 结 点 数 = 1 + 双 分 支 结 点 数 + 2 倍 三 分 支 结 点 数 + . . + ( m − 1 ) 倍 m 分 支 结 点 数 ; \begin{cases} N_0=1+N_2+2N_3+...+(m-1)N_m;,也即下行 \\ 叶子结点数=1+双分支结点数+2倍三分支结点数+..+(m-1)倍m分支结点数;\\ \end{cases} {N0=1+N2+2N3+...+(m1)Nm;,=1++2+..+(m1)m;

存储结构
顺序存储结构

完全二叉树适应如下存储结构一般二叉树则不适用

image-20211028225938101
二叉链表存储结构(孩子存储结构)
image-20211028230345133
typedef struct BTNode{
	int data;
	struct BTNode *leftChild;
	struct BTNode *rightChild;
}BTNode;

A1->leftChild=A2;
A1->rightChild=A3;
A2->leftChild=A4;
A2->rightChild=NULL;
A3->leftChild=A5;
A3->rightChild=NULL;
树的孩子兄弟存储结构
image-20211028230901465
typedef struct BTNode{
	int data;
	struct BTNode *child;
	struct BTNode *sibling;//放兄弟
}BTNode;
A1->child=A2;
A1->sibling=NULL;
A2->sibling=A3;
A3->sibling=A4;
A4->sibling=NULL;
//取A1孩子结点A3
A1->child->sibling

遍历

二叉树的遍历

广度优先(层次)遍历

从上到下,从左到右

image-20211030172152943
深度优先遍历(先序,中序,后序)

**先序:**第一次来到某个结点时访问,所得序列为先序遍历序列(先访问根节点,然后前先遍历左子树,最后先序遍历右子树)

**中序:**第二次来到某个结点时访问,所得序列为中序遍历序列(先访问左子树,然后访问根结点,最后中序遍历右子树)

**后序:**第三次来到某个结点时访问,所得序列为后序遍历序列(先后序遍历左子树,然后后序遍历右子树,最后访问)

image-20211030172836861

树的遍历

深度优先遍历

树没有中序遍历

在这里插入图片描述

如果一颗二叉树由一颗树转换而来那么:

对二叉树进行先序遍历=对原来的树进行先序遍历,

对二叉树进行中序遍历=对原来的树进行后先序遍历;

森林的遍历

image-20211030174731623 image-20211030182434813

在这里插入图片描述

二叉树遍历-代码(递归)

//下述代码假设Visit()已经定义过,其中包含了对结点p的各种访问操作,例如可以打印出p对应的数值
typedef struct BTNode{
	int data;
	struct BTNode *leftChild;
	struct BTNode *rightChild;
}BTNode;
void r(BTNode *p){
	if(p!=NULL){
		//(1)Visit(p)得到先序
		r(p->leftChild);
		//(2)Visit(p)得到中序
		r(p->rightChild);
		//(3)Visit(p)得到后序
	}
}

在这里插入图片描述

二叉树遍历-先序-递归

在这里插入图片描述

二叉树遍历-中序-递归

在这里插入图片描述

二叉树遍历-后序-递归

在这里插入图片描述

二叉树遍历-代码(非递归)

image-20211030212704793
二叉树遍历-先序-非递归
image-20211030213342650

1.结点1入栈。

2.出栈,输出栈顶结点1,并将1的左右孩子(2、4)入栈;右孩子先入栈,因为对左孩子的访问要先于右孩子,后入栈的会先出栈访问(先右后左)。

3.出栈,输出栈顶结点2,并将2的左右孩子(3、5)入栈。

4.出栈,输出栈顶结点3,3为叶子结点,无孩子,本步无结点入栈。

5.出栈,输出栈顶结点5.

6.出栈,输出栈顶结点4,此时栈空,进入终态。

**遍历序列为:**1,2,3,5,4。

//二叉树先序遍历非递归算法
void preorderNonrecursion(BTNode *bt){
	if(bt!=NULL){
		BTNode *Stack[maxSize];			//定义一个栈
		int top=-1;						//初始化栈
		BTNode *p;
		Stack[++top]=bt;				//根结点入栈
		while(top!=-1){					//栈空循环退出,遍历结束
			p=stack[top--];				//出栈并输出栈顶结点
			Visit(p);					//Visit()为访问p的函数
			if(p->rchild!=NULL){		//栈顶结点的右孩子存在,则右孩子入栈
				Stack[++top]=p->rchild;
			}
			if(p->lchild!=NULL){		//栈顶结点的左孩子存在,则左孩子入栈
				Stack[++top]=p->lchild;
			}
		}
	}
}
二叉树遍历-后序-非递归

先序遍历序列:1 2 3 5 4;

后序遍历序列:3 5 2 4 1;

逆后序遍历序列:1 4 2 5 3;

逆后序遍历序列只不过是先序遍历过程中对左右子树遍历顺序交换所得到的结果。

image-20211030215655352

1.结点1入stack1。

2.stack1元素出栈,并将结点1入Stack2,结点1的左右孩子存在,左孩子结点2入stack1,右孩子结点4入stack1(先左后右)

3.stack1元素出栈,并将出栈结点4入stack2,结点4的左右孩子不存在。

4.stack1元素出栈,并将出栈结点2入stack2,结点2的左右孩子存在,左孩子3入stack1,右孩子5入stack1。

5.stack1元素出栈,并将出栈结点5入stack2。

6.stack1元素出栈,并将出栈结点3入Stack2。

7.此时栈空,stack2中元素自顶向下依次为:3 5 2 4 1,正好为后序遍历序列。

void postorderNonrecursion(BTNode *bt){
	if(bt!=NULL){
		/*定义两个栈*/
		BTNode *Stack1[maxSize]; int top1 = -1;
		BTNode *Stack2[maxSize]; int top2 = -1;
		BTNode *p = NULL;
		Stack1[++top1] = bt;
		while(top1!=-1){
			p = Stack1[top--];
			Stack2[++top2] = p;					//这里和先序遍历的区别是输出改为入Stack2
			/*下边的两个判断语句和先序遍历有区别,左右孩子的入栈顺序相反*/
			if(p->lchild!=NULL){
				Stack1[++top1] = p->lchild;
			}
			if(p->rchild!=NULL){
				Stack1[++top1] = p->rchild;
			}
            while(top2!=-1){
            	/*出栈序列即为后序遍历序列*/
            	p = Stack2[top2--];
            	Visit(p);						//Visit()是访问p的函数,在这里执行打印结点值的操作
            }
		}
	}
}
二叉树遍历-中序-非递归
image-20211030223235920

1.结点1入栈,1左孩子存在。

2.结点2入栈,2左孩子存在。

3.结点3入栈,3左孩子不存在。

4.出栈,输出栈顶结点3,3右孩子不存在。

5.出栈,输出栈顶结点2,2右孩子存在,右孩子5入栈,5左孩子不存在。

6.出栈,输出栈顶结点5,5右孩子不存在。

7.出栈,输出栈顶结点1,1右孩子存在,右孩子4入栈,4左孩子不存在。

8.出栈,输出栈顶结点4,此时栈空,进入终态。

遍历序列为3 2 5 1 4;

由上步骤可以看出,中序非递归遍历过程如下:

1.开始根结点入栈。

2.循环执行如下操作:如果栈顶结点左孩子存在,则左孩子入栈;如果栈顶结点左孩子不存在,则出栈并输出栈顶结点,然后检查右孩子是否存在,如果存在,则右孩子入栈。

3.当栈空时算法结束。

void inorderNorecursion(BTNode *bt){
	if(bt!=NULL){
		BTNode *Stack[maxSize]; int top=-1;
		BTNode *p;
		p = bt;
		while(top!=-1||p!=NULL){
			while(p!=NULL){					//左孩子存在,则左孩子入栈
				Stack[++top] = p;
				p = p->lchild;
			}
			if(top!=-1){					//在栈不空的情况下出栈并输出出栈结点
				p = Stack[top--];
				Visit(p);					
				p = p->rchild;
			}
		}
	}
}

线索二叉树

中序线索二叉树

逻辑结构
image-20211031152759980 image-20211031153407979
存储结构
image-20211031153002070
//线索二叉树存储结构
typedef struct TBTNode{
	int data;
	int lTag;		//lTag=0,表示lChild为指针,指向结点左孩子,lTag=1,则表示lChild为线索,指向结点的直接前驱
	int rTag;		//rTag=0,表示rChild为指针,指向结点右孩子,rTag=1,则表示rChild为线索,指向结点的直接后继
	TBTNode *lChild;
	TBTNode *rChild;
}TBTNode;
二叉树中序线索化
void InThread(TBTNode *p,TBTNode *&pre){
	if(p!=NULL){
		InThread(p->lChild,pre);				//递归,左子树线索化
		if(p->lChild==NULL){					//建立当前结点的前驱线索
			p->lChild=pre;
			p->lTag=1;
		}
		if(pre!=NULL&&pre->rChild==NULL){		//建立前驱结点的后继线索
			pre->rChild=p;
			pre->tag=1;
		}
		pre=p;									//pre指向当前的p,作为p将要指向下一个结点的前驱结点指示指针
		InThread(p->rChild,pre);						//递归,右子树线索化
	}
}

前序线索二叉树

逻辑结构
image-20211031160454371
存储结构

存储结构与中序线索二叉树的存储结构相同

二叉树前序线索化
void preThread(TBTNode *p,TBTNode *&pre){
	if(p!=NULL){
		if(p->lChild==NULL){					//建立当前结点的前驱线索
			p->lChild=pre;
			p->lTag=1;
		}
		if(pre!=NULL&&pre->rChild==NULL){		//建立前驱结点的后继线索
			pre->rChild=p;
			pre->tag=1;
		}
		pre=p;	
		if(p->lTag==0){
			preThread(p->lChild,pre);
		}
		if(p->rTag==0){
			preThread(p->rChild,pre);
		}
	}
}
前序线索二叉树执行遍历
void preorder(TBTNode *root){
	if(root!=NULL){
		TBTNode *p=root;
		while(p!=NULL){
			while(p->lTag==0){			//左指针不是线索,则边访问边左移
				Visit(p);
				p=p->lChild;
			}
			Visit(p);					//此时左指针必定为线索,但还没有被访问,则访问
			p=p->rChild;				//此时p左孩子不存在,则右指针若非空,则不论是否为线索都指向其后继
		}
	}
}

后序线索二叉树

逻辑结构
image-20211031162448838
存储结构

存储结构与中序线索二叉树的存储结构相同

二叉树后序线索化
void postThread(TBTNode *p,TBTNode *&pre){
	if(p!=NULL){
		postThread(p->lChild,pre);				//递归,左子树线索化
		postThread(p->rChild,pre);				//递归,右子树线索化
		if(p->lChild==NULL){					//建立当前结点的前驱线索
			p->lChild=pre;
			p->lTag=1;
		}
		if(pre!=NULL&&pre->rChild==NULL){		//建立前驱结点的后继线索
			pre->rChild=p;
			pre->tag=1;
		}
		pre=p;
	}
}

*若结点X是二叉树的根,则其后继为空;

*若结点X是其双亲的右孩子,或是其双亲的左孩子且其双亲没有右子树,则其后继即为双亲节点;

*若结点X是其双亲的右孩子,且其双亲有右子树,则其后继为双亲右子树上按后序遍历列出的第一个结点;

三种线索二叉树的比较

image-20211031180402172

**前序线索二叉树:**找后继方便,找某些结点的前驱不方便;

**中序线索二叉树:**找后继方便,找前驱方便;

**后序线索二叉树:**找后继不方便,找前驱不方便

树与二叉树的互相转换

树转换为“二叉树”(非严格意义上的二叉树,只是长得像)

1. image-20211030203218397 2. image-20211030203242595 3. image-20211030203251517

二叉树转换为树

1. image-20211030203251517 2. image-20211030204109026 3. image-20211030203556452 4. image-20211030204145322

森林与二叉树的互相转换

森林转换为二叉树

1.image-20211031182915924

2.image-20211031182956019

3.image-20211031183033991

二叉树转换为森林

1.image-20211031183033991

2.image-20211031183138915

3.image-20211031183151139

哈夫曼树

利用编码问题理解哈夫曼树

image-20211101212557241 image-20211101212822157 image-20211101212834427 image-20211101213146099 image-20211101213432557

哈夫曼树中的基本定义

**路径:**路径是指从树中一个结点到另一个结点的分支所构成的路线;

**路径长度:**路径长度是指路径上的分支数目;

**树的路径长度:**树的路径长度是指从根到每个结点的路径长度之和;

**带权路径长度:**结点具有权值,从该结点到根之间的路径长度乘以结点的权值,就是该结点的带权路径长度;

(例如:E的带权路径长度=4*2=8)

**树的带权路径长度(WPL):**树的带权路径长度是指树中所有叶子结点的带权路径长度之和;

(例如:WPL=1X5+3X2+2X3+2X4+1X4=29)

哈夫曼二叉树的特点

*****权值越大的结点,距离根结点越近;

*****树中没有度为1的节点。这类树又叫做正则(严格)二叉树;

*****树的带权路径长度最短;

哈夫曼n(n大于2)叉树

哈夫曼三叉树的构造

image-20211101214443695

当剩余结点不够三个时我们可以补一个权值为0的结点以便构建哈夫曼三叉树

image-20211101214635986

二叉树的确定

已知先序和中序

逻辑推断

先序:A B D E C F G H 中序:D B E A C G F H

1.根结点为A,B D E为左子树 ,C G F H为右子树;

2.左子树根为B,右子树根为C(根据后序序列从左子树范围和右子树范围内找到的最靠前的那个结点);

3.B的左子树为D,右子树为E;

4.C的没有左子树,只有右子树且右子树的结点范围在(G F H)中;

5.G F H中以F为根,则C的右子树是F;

6.F的左子树为G,右子树为H;

推出下图:

image-20211101220038891
代码实现
BTNode *CreateBT(char pre[],char in[],int L1,int R1,int L2,int R2){
	if(L1>R1)
		return NULL;
	BTNode *s=(BTNode *)malloc(sizeof(BTNode));
	s->lChild = s->rChild = NULL;
	s->data = pre[L1];								//L1为当前子树的根结点
	int i;
	for(i=L2;i<=R2;++i){							//从中序序列中寻找当前字数根结点的位置
		if(in[i]==pre[L1]){
			break;
		}
	}
	s->lChild = CreateBT(pre,in,L1+1,L1+i-L2,L2,i-1);
	s->rChild = CreateBT(pre,in,L1+i-L2+1,R1,i+1,R2);
	return s;
}
image-20211101221102795 image-20211101221225079

已知后序和中序

逻辑推断

后序:D E B G H F C A 中序:D B E A C G F H

1.根结点为A,左子树结点范围 D B E,右子树结点范围 C G F H;

2.A的左子树根为B,右子树根为C(根据后序序列从左子树范围和右子树范围内找到的最靠后的那个结点);

3.B的左孩子为D,右孩子为E;

4.C没有左子树,只有右子树,右子树范围在(G F H)中;

5.G F H中以F为根,则C的右孩子是F;

6.F的左子树为G,右子树为H;

推出下图:

image-20211101222108106
代码实现
BTNode *CreateBT2(char post[],char in[],int L1,int R1,int L2,int R2){
	if(L1>R1)
		return NULL;
	BTNode *s=(BTNode *)malloc(sizeof(BTNode));
	s->lChild = s->rChild = NULL;
	s->data = post[R1];								//R1为当前子树的根结点
	int i;
	for(i=L2;i<=R2;++i){							//从中序序列中寻找当前字数根结点的位置
		if(in[i]==post[R1]){
			break;
		}
	}
	s->lChild = CreateBT2(post,in,L1,L1+i-L2-1,L2,i-1);
	s->rChild = CreateBT2(post,in,L1+i-L2,R1-1,i+1,R2);
	return s;
}

已知层次遍历序列和中序遍历序列

逻辑推断

层次:A B C D E F G H 中序:D B E A C G F H

1.A为根结点,左子树范围D B E,右子树范围C G F H;

2.B是左子树的根,C是右子树的根(根据层次遍历序列得到);

3.B的左子树为D,右子树为E;

4.C没有左子树,则C的右子树范围在G F H中;

5.F为C的右子树的根;

6.F的左子树为G,右子树为H;

推出下图:

image-20211101222810216
代码实现
int search(char arr[],char key,int L,int R){
	int idx;
	for(idx=L;idx<=R;++idx){
		if(arr[idx]==key){
			return idx;
		}
	}
	return -1;
}
void getSubLevel(char subLevel[],char level[],char in[],int n,int L,int R){
	int k=0;
	for(int i=0;i<n;++i){
		if(search(in,level[i],L,R)!=-1){
			subLevel[k++]=level[i];
		}
	}
}
BTNode *CreateBT3(char level[],char in[],int n,int L,int R){
	if(L>R)
		return NULL;
	BTNode *s=(BTNode *)malloc(sizeof(BTNode));
	s->lChild = s->rChild = NULL;
	s->data = level[0];								//level[0]为当前子树的根结点
	int i = search(in,level[0],L,R);				//从中序序列中查找根结点位置
	int LN= i-L; char LLevel[LN];					//在左子树的范围中
	int RN= R-i; char RLevel[RN];
	getSubLevel(LLevel,level,in,n,L,i-1);			//从level数组中挑出元素,如下图解
	getSubLevel(RLevel,level,in,n,i+1,R);
	s->lChild = CreateBT3(LLevel,in,LN,L,i-1);
	s->rChild = CreateBT3(RLevel,in,RN,i+1,R);
	return s;
}
image-20211101223807992

二叉树的估计和存储表达式

二叉树的估计

空树和只有根节点的树:

image-20211101225301050

所有结点都没有左子树得树,即右单分支树

image-20211101225342855 image-20211101225357368

1.前序=后序的二叉树为:T L R L R T

2.前序=中序的二叉树为:T L R L T R

3.中序=后序的二叉树为:L T R L R T

4.前序和后序相反的二叉树为:T L R L R T

5.前序和中序相反的二叉树为:T L R L T R

6.中序和后序相反的二叉树为: L T R L R T

二叉树的存储表达式

image-20211101230922638
int calSub(float opand1,char op,float opand2,float &result){
	if(op=='+')	result = opand1 + opand2;
	if(op=='-')	result = opand1 - opand2;
	if(op=='*')	result = opand1 * opand2;
	if(op=='/'){
		if(fabs(opand2)<MIN)	
			return 0;
		else
			result = opand1/opand2;
	}
	return 1;
}
float cal(BTNode *root){
	if(root->lChild ==	NULL&&root->rChild == NULL)
		return root->data - '0';
	else{
		float opand1 = cal(root->lChild);
		float opand2 = cal(root->rChild);
		float result;
		calSub(opand1,root->data,opand2,result);
		return result;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Black_Me_Bo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值