二叉树的遍历以及遍历序列构建二叉树

结点结构:

typedef struct Node{
	ElementType value;
	Node* left;
	Node* right;
	Node(ElementType value_, left_ = NULL, right_ = NULL)
		:value(value_), left(left_), right(right_){}
}Node;

二叉树的遍历

我们以链式存储的二叉树为例,二叉树的遍历有:

  • 前序遍历(NLR):结点->左子树->右子树
  • 中序遍历(LNR):左子树->结点->右子树
  • 后序遍历(LRN):左子树->右子树->结点

显然,我们所说的“序”指的是我们对结点进行访问的先后顺序

NLR 前序遍历

由两个函数组成,一个递归函数,以及调用这个递归函数的函数:

void preOrder(){
	preOrder(this->root);
}
void preOrder(Node* p){
	// do sth with p
	visit(p);
	preOrder(p->left);
	preOrder(p->right);
}

LNR 中序遍历

同理,很简单我们也可以实现中序遍历:

void inOrder(){
	preOrder(this->root);
}
void inOrder(Node* p){
	inOrder(p->left);
	// do sth with p, define by yourself
	visit(p);
	inOrder(p->right);
}
表达式二叉树

中序遍历可以用于表达式二叉树输出中缀表达式:

void inOrder(Node* p){
	if(p == NULL){
		return;
	}
	// print left ear
	std::cout << "(";
	inOrder(p->left);
	// print value
	std::cout << p->value;
	inOrder(p->right);
	std::cout << ")";
	return;
}

我们不需要考虑运算符优先级,因为结点层次已经将运算优先顺序决定好了,一个分支结点就是一次运算,因此我们需要在每个分支结点的运算开始前和结束之后打印左右括号,当然根结点则不需要,因此我们可以在调用的时候直接传入根结点的左右后继:

if(this->root != NULL){
	inOrder(this->root->left);
	std::cout << this->root->value;
	inOrder(this->root->right);
	std::cout << std::endl;
}

LRN 后序遍历

代码如下:

void postOrder(){
	postOrder(this->root);
}
void postOrder(Node* p){
	postOrder(p->left);
	postOrder(p->right);
	// do sth with p
	visit(p);
}

后序遍历的一些应用:

  • 路径打印(比如从根结点到某个值为 x x x 的结点的路径,因为在访问一个结点最后,后序遍历会返回祖先结点)
  • 找两个结点的最近公共祖先(ancestor),实际上就是找路径,然后从路径起点逐个匹配,在第一组不相等的结点之前的公共结点就是最近公共祖先

通过遍历序列构造二叉树

可以通过:

  • 中序序列 + 后序序列
  • 中序序列 + 前序序列

唯一确定一棵二叉树。根据遍历的方式,我们都可以在这三个序列中特定出一些结点,然后通过特定出来的结点将序列划分出子序列(通常是左子树和右子树)。显然,我们对划分出来的序列可以进行同样的操作。也就是这样,我们可以不断找出结点的左子树和右子树,最后得到整棵二叉树的结构。

中序 + 后序

首先,对于一个后序序列,根据后序遍历的遍历顺序我们可以知道:这个序列的最后一个结点就是树的根结点;而对于中序序列来说,如果我们找到它的根结点,那么,我们可以将这个序列分成两部分:左子树的中序序列右子树的中序序列。这样,我们反过来又可以根据左子树的结点数量在后序序列中找到相应的左子树的后序序列,同理也可以找到右子树的后序序列。这样方法就很明显了,我们只需要不断进行这样的操作,最终可以找到每一个结点的位置,特定出二叉树的结构。这也是为什么必须要有中序序列才能确定二叉树的结构,因为只有中序序列可以得到左右子树的结点数量。
也就是说,后序序列总能看成左子树的前序序列+右子树的前序序列+根结点,而中序序列可以看成左子树的前序序列+根结点+右子树的前序序列,然后不断确定根结点,左子树和右子树,最后特定出二叉树的结构:

void postInBuildTree(ElementType* post, int postLen, ElementType* in, int inLen){
	// tree is empty
	if(postLen == 0){
		this->root = NULL;
		return;
	}
	// only has one node
	else if(postLen == 1){
		this->root = new Node(post[0]);
		return;
	}
	// build tree
	this->root = postInRecsv(post, postLen, in, inLen);
}
Node* postInRecsv(ElementType* post, int postLen, ElementType* in, int inLen){
	if(postLen == 1){
		return new Node(post[0]);
	}
	Node* currentRoot = new Node(post[postLen - 1]);
	int i = 0;
	// find root of current tree
	for(;i < inLen;i++){
		if(in[i] == currentRoot->value){
			break;
		}
	}
	// determine left
	currentRoot->left = postInRecsv(post, i, in, i);
	// determine right
	currentRoot->right = postInRecsv(post + i, inLen - i - 1, in + i + 1, inLen - i - 1);
	return currentRoot;
}
中序 + 前序

同理,和后序一样,前序也可以看成:根结点+左子树的前序序列+右子树的前序序列,我们采用同样的方法,最后特定出二叉树的结构:

void preInBuildTree(ElementType* pre, int preLen, ElementType* in, int inLen){
	// tree is empty
	if(preLen == 0){
		this->root = NULL;
		return;
	}
	// only has one node
	else if(preLen == 1){
		this->root = new Node(pre[0]);
		return;
	}
	// build tree
	this->root = preInRecsv(pre, preLen, in, inLen);
}
Node* preInRecsv(ElementType* pre, int preLen, ElementType* in, int inLen){
	if(preLen == 1){
		return new Node(pre[0]);
	}
	Node* currentRoot = new Node(pre[0]);
	int i = 0;
	// find root of current tree
	for(;i < inLen;i++){
		if(in[i] == currentRoot->value){
			break;
		}
	}
	// determine left
	currentRoot->left = preInRecsv(pre + 1, i, in, i);
	// determine right
	currentRoot->right = preInRecsv(pre + i + 1, inLen - i - 1, in + i + 1, inLen - i - 1);
	return currentRoot;
}

满二叉树的前序序列转后序序列

对于满二叉树来说,任意一种遍历序列都可以唯一确定这一棵满二叉树,因为我们知道每一层的结点数量,以及左右子树的结点数量(而且是相等的),所以三种序列之间可以互相转换,思想类似,这里简单的介绍一下前序和后序的转换,首先我们知道,对任意一棵子树都有:

  • 前序序列 = 当前根结点 + 左子树 + 右子树
  • 后续序列 = 左子树 + 右子树 + 当前根结点

则仅针对当前的根结点及其左右后继,只需要把当前根结点放到最前面,就完成了前序到后序的转换。这样,我们只需要对每一棵子树都进行这样的操作,最终就完成了前序到后序的转换:

void preToPost(ElementType* pre, ElementType post, int length){
	// let post's last element be the first of pre
	post[length - 1] = pre[0];
	// then set post's elements in front as following elements in pre
	for(int i = 0;i < length - 1;i++){
		post[i] = pre[i + 1];
	}
	// length for both of the left and right
	length = length / 2;
	// repeat the same operation for children
	// for left
	preToPost(pre + 1, post, length);
	// for right
	preToPost(pre + length + 1, post + length, length);
	return;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值