结点结构:
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;
}