二叉树的存储与恢复问题。先,中,后,任意两个序列就能完全确定一个二叉树。恢复该二叉树可用递归函数,先序的第一个节点为根节点,它把中序分成两部分,左部为该根节点的左子树,右边为右子树,因此,可递归的构造出完整的二叉树。
具体的递归实现可参考:
http://www.dutor.net/index.php/2009/08/generate-btree/
这里,主要想讨论非递归算法。设给出一颗二叉树如图:
可看出先序与中序序列确定的规律:
从先序开始,节点1在中序序列中索引为5(索引从0开始),5>0 && 5<(N=10),因此,节点1即有左节点,又有右节点,这里标记为:YY,第一个Y代表有左,第二个Y代表有右,N代表没有。对已经访问过的节点,我们需要一个标记isVisited来标记是否已访问,一会将用的到;依次遍历可得节点2也是YY;接下来,节点4,在中序的左边缘,因此是NY;然后,是节点9,注意!由于节点9的左右元素4与2已经被访问过了(isVisited == true),因此,节点9是NN!依此类推,可得到如下一张表:
中序 | 4 | 9 | 2 | 10 | 5 | 1 | 12 | 6 | 13 | 3 | 7 |
访问顺序 | 3 | 4 | 2 | 6 | 5 | 1 | 9 | 8 | 10 | 7 | 11 |
有左 | N | N | Y | N | Y | Y | N | Y | N | Y | N |
有右 | Y | N | Y | N | N | Y | N | Y | N | Y | N |
其实当把这张表建起来的过程中,整个二叉树已经建好了。我们根据上述这思路再细化一下可更好理解:
首先,先为中序列表建一个结构体,用于存在该节点对应的信息:
//定义一个结构体,用于记录中序记录的信息
typedef struct tagIN_MARK
{
int idx; //对应的中序节点索引
TREE* node; //中序序列节点
bool isVisited; //是否已访问
bool isLeft; //该节点是否有左节点
bool isRight; //该节点是否有右节点
}IN_MARK;
其次,需要两个临时变量:
int oldIdx = -1, curIdx = -1; //记录上一节点(父)与当前节点(子)在中序序列中的索引
算法步骤开始:
第一步:得到下一节点。当然,先需要保存当前节点:oldIdx = curIdx. 先序节点1,搜索中序列表,得到其在中序的索引curIdx, 根据前后序列对应的isVisited标记可确定该节点的左右子树存在情况。
第二步:如果是根节点(第一次执行),返回;否则,根据上一节点的左右子树存在关系,建立上一节点与当前节点的关系,这里,上一节点有四种情况:
a. YN,则关系就是左,
b.NY,则关系就是右,
c. YY,那么,这里的关系是左,然后需要将上一节点压栈保存,以后再处理右关系,
d. NN,即叶节点,则当前节点的父节点正是最后一个入栈的元素,而且关系为右!这也是情况c要把存在右节点的节点压栈的原因
循环上述两步,即可构造出整个二叉树。
注意上述算法过程中,由先序序列节点去搜索中序序列匹配的过程,总是从中序的起始点开始搜索的,这其实完全没有必要,因为根据先序与中序的关系可知,对于中序里的某一节点A,要搜索的下一节点B与A是存在左右关系的,或者像步骤二的情况d那样,A与B不存在直接关系,但与B存在父子关系的节点肯定在栈中。因此,如果能得到将要搜索的节点B的父节点,并且知道其左右关系,就可以在中序序列中,从父节点开始直接往左或往右搜索即可!文字描述会使人发蒙,这里举个例子:
比如对于先序节点2,将要搜索的下一节点是4,并且又知道2有左节点,因此,可以确定在中序序列中,4在2的左边,因此,确定2在中序中的索引位置,直接从2的位置往左搜。又如先序节点9,将要搜索的下一节点是5,并且又知道节点9是叶节点,它与5不存在直接关系,由此确定与5存在关系的父节点保存在栈中,因此出栈的节点即是5的父节点,又根据第二步情况C的规定,其关系必是右关系。
由此可见,加快搜索的算法其实只是利用了第一步对节点所确定的左右存在关系,当前循环确定下一次循环的搜索起点与方向,同时也确定了当前节点与下一节点的关系 - - 因为往左或右搜索即已经表明当前节点与下一节点的左右关系。
总结之,画出流程图如下:
另附上代码:
// HelloWorld.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <time.h>
#include <math.h>
#include <iomanip>
typedef struct tagTREE
{
int val;
tagTREE* left;
tagTREE* right;
tagTREE* parent; //一下次定义多个指针变量不行?
bool isVisited;
}TREE;
template<class T>
class Stack
{
private:
typedef struct tagLINK
{
T val;
tagLINK* pNext;
}LINK;
LINK* head;
public:
Stack():head(0){}
void push( T t )
{
LINK* pLINK = new LINK();
pLINK->val = t;
pLINK->pNext = head;
head = pLINK;
}
T pop()
{
if (head != NULL)
{
T val = head->val;
LINK* temp = head->pNext;
delete head;
head = temp;
return val;
}
return NULL;
}
bool empty()
{
return head ? false : true;
}
};
void NonRecursePreorder(TREE* node)
{
Stack<TREE*> stk;
while( node || !stk.empty() )
{
while(node)
{
cout<<node->val<<",";
if( node->right )
stk.push(node->right);
node = node->left;
}
node = stk.pop();
}
}
void NonRecurseInorder(TREE* node)
{
Stack<TREE*> stk;
while( node || !stk.empty())
{
while(node) //当节点存在时,先将它入栈,然后不停的索引左子节点
{
stk.push(node);
node = node->left;
} //当无左子节点时,最后一个入栈的节点正是要访问的
node = stk.pop();
cout<<node->val<<",";
node = node->right; //取出右节点,返回循环,如果右节点为空,则需要继续出栈
}
}
void NonRecursePostorder(TREE* node)
{
Stack<TREE*> stk;
while (node || !stk.empty())
{
while(node)
{
stk.push(node);
node = node->left;
}
node = stk.pop();
if (!node->isVisited)
{
if (node->right)
{
node->isVisited = true;
stk.push(node);
node = node->right;
continue;
}
}
cout<<node->val<<",";
node = NULL;
}
}
//非递归,由先,中序恢复二叉树
TREE* ReBuildTree(int* preOrder,int* inOrder,int N)
{
if(N <= 0)
return NULL;
TREE** testNodeList = new TREE*[N]; //仅仅用于测试,打印二叉树的结构而已,并不参与二叉树恢复的核心算法
TREE* head = NULL; //头结点
int oldIdx = -1, curIdx = -1; //记录上一节点(父)与当前节点(子)在中序序列中的索引
//定义一个结构体,用于记录中序记录的信息
typedef struct tagIN_MARK
{
int idx; //对应的中序节点索引
TREE* node; //中序序列节点
bool isVisited; //是否已访问
bool isLeft; //该节点是否有左节点
bool isRight; //该节点是否有右节点
}IN_MARK;
IN_MARK* pIN_MARK = new IN_MARK[N];
for (int i=0;i<N;++i)
{
pIN_MARK[i].idx = i;
pIN_MARK[i].node = NULL;
pIN_MARK[i].isVisited = false;
pIN_MARK[i].isLeft = false;
pIN_MARK[i].isRight = false;
}
typedef enum {LEFT,RIGHT}DIR;
int startIdx = -1; //下一先序节点在中序序列中的搜索起点,一开始只能从左往右搜
DIR dir = RIGHT; //下一先序节点在中序序列中的搜索方向
Stack<IN_MARK*> stk;
static int cnt = 0; //计数器,仅用于查看整个算法过程中,中序被搜索的次数
for (int idx=0;idx<N;++idx) //遍历先序序列
{
TREE* curNode = new TREE();
curNode->val = preOrder[idx];
curNode->parent = NULL;
curNode->left = NULL;
curNode->right = NULL;
testNodeList[idx] = curNode;
int i=startIdx;
int offset = (dir == LEFT) ? -1 : 1;//中序搜索方向,比起每次都从头搜索要快
//根据预设好的起点与方向搜索下一节点,并标记新节点的信息
while (1)
{
cnt++;
i += offset;
if( i>=N || i < 0 )
break;
if( curNode->val == inOrder[i] )
{
oldIdx = curIdx;
curIdx = i;
pIN_MARK[i].node = curNode;
pIN_MARK[i].isVisited = true;
if ( i > 0 && !pIN_MARK[i-1].isVisited )
pIN_MARK[i].isLeft = true;
if ( i < N-1 && !pIN_MARK[i+1].isVisited )
pIN_MARK[i].isRight = true;
break;
}
}
if( idx == 0 ) //第一次循环,是根节点
{
head = pIN_MARK[curIdx].node;
}
else
{
if( dir == LEFT )//当前节点是上一节点的左节点
{
pIN_MARK[oldIdx].node->left = pIN_MARK[curIdx].node;
pIN_MARK[curIdx].node->parent = pIN_MARK[oldIdx].node;
if (pIN_MARK[oldIdx].isRight) //若上一节点存在右节点,则先入栈保存
stk.push( &pIN_MARK[oldIdx] );
}
else //当前节点是上一节点的右节点
{
pIN_MARK[oldIdx].node->right = pIN_MARK[curIdx].node;
pIN_MARK[curIdx].node->parent = pIN_MARK[oldIdx].node;
}
}
if( pIN_MARK[curIdx].isLeft ) //当前节点有左节点
{
dir = LEFT;
}
else if( pIN_MARK[curIdx].isRight ) //当前节点有右节点
{
dir = RIGHT;
}
else if ( !stk.empty() ) //当前节点是叶节点,则下一的搜索起点与方向由其栈出的父节点确定
{
IN_MARK* parentMARK = stk.pop();
curIdx = parentMARK->idx;
dir = RIGHT;
}
startIdx = curIdx;
}
cout<<endl<<"cnt="<<cnt<<endl;
//test
cout<<endl<<"TREE STRUCTURE:"<<endl;
for (int i=0;i<N;++i)
{
cout<<"val="<<testNodeList[i]->val;
cout<<" left=";
if (testNodeList[i]->left)
{
cout<<testNodeList[i]->left->val;
}
cout<<" right=";
if (testNodeList[i]->right)
{
cout<<testNodeList[i]->right->val;
}
cout<<" parent=";
if (testNodeList[i]->parent)
{
cout<<testNodeList[i]->parent->val;
}
cout<<endl;
}
delete[] testNodeList;
delete[] pIN_MARK;
return head;
}
int _tmain(int argc, _TCHAR* argv[])
{
const int K = 11;
int preOrder[K] = { 1,2,4,9,5,10,3,6,12,13,7 };
int inOrder[K] = { 4,9,2,10,5,1,12,6,13,3,7 };
TREE* node = ReBuildTree(preOrder,inOrder,K);
cout<<endl<<"Front:"<<endl;
NonRecursePreorder(node);
cout<<endl<<"Mid:"<<endl;
NonRecurseInorder(node);
cout<<endl<<"Back:"<<endl;
NonRecursePostorder(node);
return 0;
}
输出结果:
cnt=24
TREE STRUCTURE:
val=1 left=2 right=3 parent=
val=2 left=4 right=5 parent=1
val=4 left= right=9 parent=2
val=9 left= right= parent=4
val=5 left=10 right= parent=2
val=10 left= right= parent=5
val=3 left=6 right=7 parent=1
val=6 left=12 right=13 parent=3
val=12 left= right= parent=6
val=13 left= right= parent=6
val=7 left= right= parent=3
Front:
1,2,4,9,5,10,3,6,12,13,7,
Mid:
4,9,2,10,5,1,12,6,13,3,7,
Back:
9,4,10,5,2,12,13,6,7,3,1,请按任意键继续. . .