二叉树的遍历

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树的遍历相关的知识,希望对你有一定的参考价值。

对任意给定的二叉树(顶点数自定)建立它的二叉链表存储结构,并利用栈的五种基本运算(置空栈、进栈、出栈、取栈顶元素、判栈空)实现二叉树的先序、中序、后序三种遍历,输出三种遍历的结果。

参考技术A 5.1树的概念

树的递归定义如下:(1)至少有一个结点(称为根)(2)其它是互不相交的子树

1.树的度——也即是宽度,简单地说,就是结点的分支数。以组成该树各结点中最大的度作为该树的度,如上图的树,其度为3;树中度为零的结点称为叶结点或终端结点。树中度不为零的结点称为分枝结点或非终端结点。除根结点外的分枝结点统称为内部结点。
2.树的深度——组成该树各结点的最大层次,如上图,其深度为4;
3.森林——指若干棵互不相交的树的集合,如上图,去掉根结点A,其原来的二棵子树T1、T2、T3的集合T1,T2,T3就为森林;
4.有序树——指树中同层结点从左到右有次序排列,它们之间的次序不能互换,这样的树称为有序树,否则称为无序树。
5.树的表示
树的表示方法有许多,常用的方法是用括号:先将根结点放入一对圆括号中,然后把它的子树由左至右的顺序放入括号中,而对子树也采用同样的方法处理;同层子树与它的根结点用圆括号括起来,同层子树之间用逗号隔开,最后用闭括号括起来。如上图可写成如下形式:
(A(B(E(K,L),F),C(G),D(H(M),I,J)))
5. 2 二叉树
1.二叉树的基本形态:
二叉树也是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:

(1)空二叉树——(a);
(2)只有一个根结点的二叉树——(b);
(3)右子树为空的二叉树——(c);
(4)左子树为空的二叉树——(d);
(5)完全二叉树——(e)
注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形。

2.两个重要的概念:
(1)完全二叉树——只有最下面的两层结点度小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树;
(2)满二叉树——除了叶结点外每一个结点都有左右子女且叶结点都处在最底层的二叉树,。
如下图:

完全二叉树

满二叉树

3.二叉树的性质
(1) 在二叉树中,第i层的结点总数不超过2^(i-1);

(2) 深度为h的二叉树最多有2h-1个结点(h>=1),最少有h个结点;
(3) 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,

则N0=N2+1;

(4) 具有n个结点的完全二叉树的深度为int(log2n)+1
(5)有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:

若I为结点编号则 如果I<>1,则其父结点的编号为I/2;
如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;
如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。
4.二叉树的存储结构:
(1)顺序存储方式

type node=record

data:datatype

l,r:integer;

end;

var tr:array[1..n] of node;

(2)链表存储方式,如:
type btree=^node;

node=record

data:datatye;

lchild,rchild:btree;

end;

5.普通树转换成二叉树:凡是兄弟就用线连起来,然后去掉父亲到儿子的连线,只留下父母到其第一个子女的连线。

6.二叉树的遍历运算(递归定义)
(1)先序遍历
访问根;按先序遍历左子树;按先序遍历右子树
(2)中序遍历
按中序遍历左子树;访问根;按中序遍历右子树
(3)后序遍历
按后序遍历左子树;按后序遍历右子树;访问根

例1.用顺序存储方式建立一棵有31个结点的满二叉树,并对其进行先序遍历。
program erchashu1;
var b:array[1..31] of char;
e:array[1..63] of byte;
n,h,i,k:integer;
procedure tree(t:integer);
begin
if e[t]=0 then exit
else
begin
write(b[t]);e[t]:=0;
t:=2*t;tree(t);
t:=t+1;tree(t);
end;
end;
begin
repeat
write('n=');readln(n);
until (n>0) and (n<6);
fillchar(e,sizeof(e),0);
k:=trunc(exp(n*ln(2)))-1;
for i:=1 to k do e[i]:=1;
for i:=1 to 26 do b[i]:=chr(64+i);
for i:=1 to 5 do b[26+i]:=chr(48+i);
h:=1 ;tree(h);
writeln;
end.

例2.用顺序存储方式建立一棵如图所示的二叉树,并对其进行先序遍历。

program tree1;
const n=15;
type node=record
data:char;
l,r:0..n;
end;
var tr:array[1..n] of node;
e:array[1..n] of 0..1;
i,j:integer;

procedure jtr;
var i:integer;
begin
for i:=1 to n do
with tr[i] do
readln(data,l,r);
end;
procedure search(m:integer);
begin
with tr[m] do
begin
write(data);
if l<>0 then search(l);
if r<>0 then search(r);
end;
end;
begin
jtr;search(1);writeln;
end.

例3 用链表存储方式生成上述二叉树,中序遍历之。

1.将上述二叉树用广义表表示为A(B(D,E(G)),C(F(,H)))

2.根据广义表串(以#结束)生成二叉树。

program ltree;
const n=8;
type trlist=^node;
node=record
da:char;
l,r:trlist;
end;
var s:array[1..n] of trlist;
p,root:trlist;
ch:char;
top,k:integer;
procedure creat(var head:trlist);
begin
read(ch);
top:=0;
while ch<>'#' do
begin
case ch of
'A'..'Z':begin new(p);p^.da:=ch;p^.l:=nil;p^.r:=nil;
if top<>0 then
case k of
1:s[top]^.l:=p;
2:s[top]^.r:=p;
end
end;
'(':begin top:=top+1;s[top]:=p;k:=1;end;
')': top:=top-1;
',': k:=2;
end;
read(ch);
end;
head:=s[1];
end;
procedure inorder(head:trlist);
begin
if head^.l<>nil then inorder(head^.l);
write(head^.da);
if head^.r<>nil then inorder(head^.r);
end;
begin
write('Input tree string:');
creat(root);
inorder(root);
end.

5.3 二叉树的应用
1. 哈夫曼树与哈夫曼码

树的路径长度:一棵树的每一个叶结点到根结点的路径长度的和。

带权二叉树:给树的叶结点赋上某个实数值(称叶结点的权)。

带权路径长度:各叶结点的路径长度与其权值的积的总和。

哈夫曼树(最优二叉树):带权路径长度最小的二叉树。

如何构建哈夫树:(思想是:权越大离跟越近)

program gojiantree;
const n=4;m=7;
type node=record
w:real;
parent,lchild,rchild:0..m
end;
htree=array[1..m] of node;
var htree1:htree;
procedure gjtree(var ht:htree);
var i,j:integer;
small1,small2:real;
p1,p2:0..m;
begin
for i:=1 to m do
with ht[i] do
begin
w:=0;lchild:=0;rchild:=0;parent:=0;
end;
for i:=1 to n do read(ht[i].w);
for i:=n+1 to m do
begin
p1:=0;p2:=0;
small1:=1000;small2:=1000;
for j:=1 to i-1 do
if ht[j].parent=0 then
if ht[j].w<small1 then
begin small2:=small1;small1:=ht[j].w;p2:=p1;p1:=j end
else if ht[j].w<small2 then begin small2:=ht[j].w;p2:=j end;
ht[p1].parent:=i;
ht[p2].parent:=i;
ht[i].lchild:=p1;
ht[i].rchild:=p2;
ht[i].w:=ht[p1].w+ht[p2].w;
end;
end;
begin
gjtree(htree1);
end.

哈夫曼码:哈夫曼树的非叶结点到左右孩子的路径分别用0,1 表示,从根到叶的路径序列即为哈夫曼码。

哈夫曼码是不会发生译码多义性的不等长编码,广泛应用实际中。

(原因是任何一字符的编码不是更长编码的前缀部分,为什么?)

2.排序二叉树

排序二叉树:每一个参加排列的数据对应二叉树的一个结点,且任一结点如果有左(右)子树,则左(右)子树各结点的数据必须小(大)于该结点的数据。中序遍历排序二叉树即得排序结果。程序如下:

program pxtree;
const
a:array[1..8] of integer=(10,18,3,8,12,2,7,3);
type point=^nod;
nod=record
w:integer;
right,left:point ;
end;
var root,first:point;k:boolean;i:integer;
procedure hyt(d:integer;var p:point);
begin
if p=nil then
begin
new(p);
with p^ do begin w:=d;right:=nil;left:=nil end;
if k then begin root:=p; k:=false end;
end
else with p^ do if d>=w then hyt(d,right) else hyt(d,left);
end;
procedure hyt1(p:point);
begin
with p^ do
begin
if left<>nil then hyt1(left);
write(w:4);
if right<>nil then hyt1(right);
end
end;
begin
first:=nil;k:=true;
for i:=1 to 8 do hyt(a[i],first);
hyt1(root);writeln;
end.

3.堆排序

堆:设有数据元素的集合(R1,R2,R3,...Rn)它们是一棵顺序二叉树的结点且有

Ri<=R2i 和Ri<=R2i+1(或>=)

堆的性质:堆的根结点上的元素是堆中的最小元素,且堆的每一条路径上的元素都是有序的。

堆排序的思想是:

1)建初始堆(将结点[n/2],[ n/2]-1,...3,2,1分别调成堆)

2)当未排序完时

输出堆顶元素,删除堆顶元素,将剩余的元素重新建堆。

程序如下:

program duipx;
const n=8;
type arr=array[1..n] of integer;
var a:arr;i:integer;
procedure sift(var a:arr;l,m:integer);
var i,j, t:integer;
begin
i:=l;j:=2*i;t:=a[i];
while j<=m do
begin
if (j<m) and (a[j]>a[j+1]) then j:=j+1;
if t>a[j] then
begin a[i]:=a[j];i:=j;j:=2*i; end
else exit;
end;
a[i]:=t;
end;
begin
for i:=1 to n do read(a[i]);
for i:=(n div 2) downto 1 do
sift(a,i,n);
for i:=n downto 2 do
begin
write(a[1]:4);
a[1]:=a[i];
sift(a,1,i-1);
end;
writeln(a[1]:4);
end本回答被提问者采纳
参考技术B 后序序列为:ghdebifca
可以推理得到整棵二叉树。
过程为:
根据前序遍历的顺序(先是根结点,再是左子树,最后右子树)及中序遍历的顺序(先是左子树,再是根结点,最后右子树)可得:
1.a是父结点(根据前序序列a在起始位置)
2.b是第二层左子树的根结点(根据前序序列b在a后)
3.e是b结点右子树的叶子结点(根据中序序列e在b后,在a前),所以d是b结点左子树的子结点
4.g和h分别为结点d的左子树和右子树的叶子结点(根据中序序列g→d→h的排列顺序)
5.由前序序列的cfi和后序序列的fic可得i是结点f的叶子结点,f为c的子结点,cfi构成了父结点a的右子树

二叉树(2.二叉树的遍历和实现)

2.5二叉树的遍历

什么是遍历?按照某种特性规则,对二叉树中的结点进行某种相应的操作,并且每个结点只操作一次。

2.5.1前序遍历

又称先根遍历,访问根结点的操作发生在遍历其左右子树之前。

//前序遍历
void preorder(node* root){
	if(root){
		printf("%d" ,root->data);
		preorder(root->left);
		preorder(root->right);
	}
}

在这里插入图片描述

2.5.2中序遍历

又称中根遍历,访问根结点的操作发生在遍历其左右子树之间。

//中序遍历
void midorder(node* root){
	if(root){
		midorder(root->left);
		printf("%d",root->data);
		midorder(root->right);
	}
}

在这里插入图片描述

2.5.3后序遍历

又称后根遍历,访问根结点的操作发生在遍历其左右子树之后。
在这里插入图片描述

postorder(node* root){
  	if(root)
  		{
  			postorder(root->left);
  			postorder(root->right);
  			printf("%d" ,root->data);
  		}
  }

2.5.4层序遍历

堆二叉树的每一层进行遍历。
1.定义一个队列的结构并初始化,然后将根结点入队列
2.只要队列不空,循环进行以下操作:
从队列中取一个结点,遍历该结点,如果该节点测左孩子存在,将其入队列,若该结点的右孩子存在,入队列。然后将队头元素删掉。
3.删除队列
在这里插入图片描述
队列实现的代码
此时我们在这里需要的函数:

void QueueInit(queue* q);
//获取队头元素
DType QueueQhead(queue* q);
//出队列(将结点的孩子节点入队列)
void Queuepop(queue* q);
//入队列(将父结点出队列)
void Queuepush(queue* q, DType data);
//判空
int Queueempty(queue* q);
//销毁队列
void Queuedestroy(queue* q);

实现层序遍历:

 leveOrder(node* root){
  	if(NULL==root) return;
  //先定义一个队列
  queue* q;
  //初始化队列,将根节点入队列
  QueueInit(&q);
  Queuepush(&q,root);
  while(!Queueempty(&q)){
  	//队列不空,先得到队头元素
  	node* p=QueueQhead(&q);
  	//打印队头元素
  	printf("%d",p->data);
  	//然后判断结点是否存在左右孩子,并将其入队列
  	if(p->left){
  		//作孩子入队列
  		Queuepush(&q,p->left);
  	}
  	if(p->right){
  		Queuepush(&q,p->right);
  	}
  	//然后出队列
  	ququepop(&q);
  }
  //再将队列进行销毁
  void Queuedestroy(&q);
  }

2.6二叉树的中的方法

2.6.1.二叉树的销毁

以遍历的思想堆二叉树进行销毁,此时我们采用后序遍历来进行销毁,如果采用前序遍历或者中序遍历,我们会再销毁孩子节点之前将父结点销毁掉,这样会再成数据丢失。

void destoryBinary(node** root){
  	//再进行销毁时,我们在最后一部要对root进行修改,因此我们在传参时要传二级指针
  	if(*root){
  		//在不为空的情况下进行销毁
  		destoryBinary(&(*root)->left);
  		destoryBinary(&(*root)->right);
  		free(*root);
  		*root=NULL;
  	}
  }

2.6.2.二叉树的结点

再求的二叉树的结点的时候,我们首先要知道二叉树的他有空树和非空树。
当它为空树时,直接结点个数返回0;当它为非空时,他的结点个数为根结点+左子树节点个数+右子树结点个数。
在这里插入图片描述

//求二叉树中的结点
  int getrootBinary(node* root){
  	if(NULL==root) return 0;
  	return 1+getrootBinary(root->left)+getrootBinary(root->right);
  }

2.6.3.求二叉树的叶子结点

在这里插入图片描述

 //求二叉树中的叶子结点
  int getleaftroot(node* root){
  	if(NULL==root) return 0;
  	if(root->left==NULL&&root->right==NULL){
  		return 1;
  	}
  	return getleaftroot(root->left)+getleaftroot(root->right);
  }

2.6.4.求二叉树的第k层的结点数

在这里我们需要考虑三点:
1.当树为空或者k=0时,直接返回0;
2.当树不为空且k=1时,返回1;
3.排除以上两种情况,我们求他的第k层元素个数直接去求是不好得出的,因此我们可以在其root的左右子树上求的k-1层即可,最后将其求得的个数+1。
在这里插入图片描述

int getbinarytreek(node* root,int k){
  	if(root==NULL||k==0) return 0;
  	if(k==1) return 1;
  	return getbinarytreek(root->left,k-1)+getbinarytreek(root->right,k-1);
  	
  }

2.6.5求树的高度

在这个中我们可以考虑求出他的左子树高度,并且求出他的右子树高度,然后比较左右子树的高度,选取大的值+1就是二叉树的高度。
在这里插入图片描述

//计算二叉树的高度
  int getbinarytreehight(node* root){
  	if(NULL==root){
  		return 0;
  	}
  	int lefthight=getbinarytreehight(root->left);
  	int righthight=getbinarytreehight(root->right);
  	int hight=lefthight>righthight?lefthight+1:righthight+1;
  	return hight;
  }

2.6.6查找二叉树中x结点

和前面的原理相似,利用遍历查找。

node* binaryTreeFind(node* root,int x){
  	node* cur=NULL;
  	if(root==NULL) return NULL;
  	if(root->data==x){
  		return root;
  	}
  	if(cur=binaryTreeFind(root->left,x))
  		return cur;
  	if(cur=binaryTreeFind(root->right,x))
  		return cur;

  }

2.6.7二叉树的创建

创建二叉树时提供的序列中只包含了有效元素,如果遇到那个结点没有左孩子或者右孩子,是无法区分的,我们在序列中必须标记那些父结点有孩子,那些没有孩子,因此我们借助顺序表来进行标记。
在这里插入图片描述

//构建二叉树
BTnode* _creatBTree(int array[], int size, int* index, int invalid){
	 BTnode* root = NULL;
	 //index以值的方式进行传递当他在递归过程中修改之后,不会将修改会的值传到上一层中,因此我们此时要传地址
	 if (*index < size&&array[*index] != invalid){
		 root = getBTnode(array[*index]);
		//此时,先对index进行++,
		(*index)++;
		root->left=_creatBTree(array, size, index, invalid);
		(*index)++;
		root->right=_creatBTree(array, size, index, invalid);
		return root;
	 }
	 else{
		 return NULL;
	 }

}
BTnode* creatBTree(int* array, int size, int invalid){
	int index = 0;
	return _creatBTree(array, size, &index, invalid);
}

(附所有代码及运行结果):

BinaryTree.h

#pragma once
#include<stdio.h>

typedef int DType;
typedef struct BTnode{
	//孩子表示法,分别是左孩子,右孩子
	struct BTnode* left;
	struct BTnode* right;
	DType data;
}BTnode;

BTnode* getBTnode(DType data);
BTnode* _creatBTree(int* array, int size, int* index, int invalid);
BTnode* creatBTree(int* array, int size,int invalid);
void preorder(BTnode* root);
void midorder(BTnode* root);
void postorder(BTnode* root);
//二叉树的销毁
void destoryBinary(BTnode** root);
//求二叉树的结点
int getBinaryroot(BTnode* root);
//求二叉树的叶子结点个数
int getleafroot(BTnode* root);
//求二叉树第k层的结点数
int getbinarytreek(BTnode* root, int k);
//求树的高度
int getvinarytreehight(BTnode* root);
//查找二叉树中的x结点,如果找到返回其地址,若未找到返回0
BTnode* binaryTreefind(BTnode* root, DType x);

binaryTree.c

#include"BinaryTree.h"
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
//构建结点
BTnode* getBTnode(DType data){
    struct BTnode* node = (struct BTnode*)malloc(sizeof(BTnode));
	if (node == NULL){
		assert(0);
		return NULL;
	}
	node->data = data;
	node->left = node->right = NULL;
	return node;
}
//构建二叉树
BTnode* _creatBTree(DType array[], int size, int* index, DType invalid){
	 BTnode* root = NULL;
	 //index以值的方式进行传递当他在递归过程中修改之后,不会将修改会的值传到上一层中,因此我们此时要传地址
	 if (*index < size&&array[*index] != invalid){
		 root = getBTnode(array[*index]);
		//此时,先对index进行++,
		(*index)++;
		root->left=_creatBTree(array, size, index, invalid);
		(*index)++;
		root->right=_creatBTree(array, size, index, invalid);
		return root;
	 }
	 else{
		 return NULL;
	 }

}
BTnode* creatBTree(DType array[] , int size, DType invalid){
	int index = 0;
	return _creatBTree(array, size, &index, invalid);
}
void preorder(BTnode* root){
	if (root == NULL){
		return;
	}
	//先输出根结点
	printf("%d\\t", root->data);
	//递归遍历左子树
	preorder(root->left);
	//递归遍历右子树
	preorder(root->right);
}

void midorder(BTnode* root){
	if (root == NULL) return;
	//递归遍历左子树
	midorder(root->left);
	//输出根结点
	printf("%d\\t", root->data);
	//递归遍历右子树
	midorder(root->right);
}

void postorder(BTnode* root){
	if (root == NULL) return;
	//递归遍历左子树
	postorder(root->left);
	//递归遍历右子树
	postorder(root->right);
	//输出根结点
	printf("%d\\t", root->data);
	
}


void destoryBinary(BTnode** root){
	if (*root == NULL) return;
	//在不为空的的情况下进行销毁
	destoryBinary(&(*root)->left);
	destoryBinary(&(*root)->right);
	free(*root);
	*root = NULL;
}

int getBinaryroot(BTnode* root){
	if (root == NULL) return 0;
	return 1 + getBinaryroot(root->left) + getBinaryroot(root->right);
}

int getleafroot(BTnode* root){
	if (root == NULL) return 0;
	if (root->left == NULL&&root->right == NULL){
		return 1;
	}
	return getleafroot(root->left) + getleafroot(root->right);

}

int getbinarytreek(BTnode* root, int k){
	if (root == NULL || k == 0) return 0;
	if (k == 1) return 1;
	return getbinarytreek(root->left, k - 1) + getbinarytreek(root->right, k - 1);
}


int getbinarytreehight(BTnode* root){
	if (root == NULL) return 0;
	//得到左子树的高度
	int lefthight = getbinarytreehight(root->left);
	int righthight = getbinarytreehight(root->right);
	int hight = lefthight > righthight ? lefthight + 1 : righthight + 1;
	return hight;
}

BTnode* binaryTreefind(BTnode* root, DType x){
	BTnode* cur = NULL;
	if (root == NULL) return NULL;
	if (root->data == x) return root;
	if (cur = binaryTreefind(root->left, x)){
		//如果在左子树中找到,返回
		return cur;
	}
	if (cur = binaryTreefind(root->right, x)){
		//在右子树中找打,返回
		return cur;
	}
}

test.c

#include<stdio.h>
#include"binaryTree.h"
int main(){
	int array[] = { 1, 2, 3, -1, -1, -1, 4, 5 ,-1,-1,6};
	int size = sizeof(array) / sizeof(array[0]);
	BTnode* root = creatBTree(array, size, -1);
	printf("先序遍历:");
	preorder(root);
	printf("\\n中序遍历:");
	midorder二叉树的后续遍历是啥意思啊?

二叉树的遍历

为啥树的后根遍历对应二叉树的中序遍历

二叉树 详解

图解 二叉树的四种遍历

二叉树的遍历