前段时间瞄了一眼《剑指offer》上面有说要熟悉二叉树的6种遍历方式,说来你或许不信,我自己实现非递归遍历的时候硬是没有实现出来,看来自己的基本功还是有待加强的。话不多说,上代码加分析。
二叉树的实现:
这个基本上是轻车熟路了,平常写书的算法的时候都要求实现一遍,当然这里不是这篇文章的重点。
树的节点的定义和树的定义。
struct TreeNode {
TreeNode *lChild; //左孩子
TreeNode *rChild; //右孩子
EleType value; //节点中的值
};
struct Tree {
TreeNode *root; //树可以直接用一个节点的指针来表示
};
树的实现。
TreeNode *CreateTreeNode (EleType k) { //这个函数的意义是分配一个树的节点并且初始化
TreeNode *temp = (TreeNode *) malloc (sizeof (TreeNode));
temp->lChild = nullptr;
temp->rChild = nullptr;
temp->value = k;
return temp;
}
void CreateTree (TreeNode **root) {
int t;
cin >> t;
if (t != 0) { //为零的话代表其是叶节点
*root = CreateTreeNode (t);
CreateTree (&((*root)->lChild));//利用递归实现树
CreateTree (&((*root)->rChild));
}
}
二叉树的前序遍历:
二叉树的前序遍历的递归实现。
void PreTravelTree (TreeNode *node) {
if (node != nullptr) {
cout << node->value << " ";
PreTravelTree (node->lChild);
PreTravelTree (node->rChild);
}
}
二叉树的前序遍历的非递归实现。
void PreTravelTreeNonRecur (TreeNode *node) {
stack<TreeNode *> nodeStack;
TreeNode *cur = node;
while (cur != nullptr || !nodeStack.empty ()) {
while (cur != nullptr) {
cout << cur->value << " ";
nodeStack.push (cur);
cur = cur->lChild;
}
if (!nodeStack.empty ()) {
cur = nodeStack.top ();
nodeStack.pop ();
cur = cur->rChild;
}
}
二叉树的中序遍历:
二叉树的中序遍历的递归实现。
void MidTravelTree (TreeNode *node) {
if (node != nullptr) {
MidTravelTree (node->lChild);
cout << node->value << " ";
MidTravelTree (node->rChild);
}
}
二叉树的中序遍历的非递归实现。
void MidTravelTreeNonRecur (TreeNode *node) {
stack<TreeNode *> nodeStack;
TreeNode *cur = node;
while (cur != nullptr || !nodeStack.empty ()) {
while (cur != nullptr) {
nodeStack.push (cur);
cur = cur->lChild;
}
if (!nodeStack.empty ()) {
cur = nodeStack.top ();
cout << cur->value << " ";
nodeStack.pop ();
cur = cur->rChild;
}
}
}
二叉树的后序遍历:
二叉树的后序遍历的递归实现。
void PosTravelTree (TreeNode *node) {
if (node != nullptr) {
PosTravelTree (node->lChild);
PosTravelTree (node->rChild);
cout << node->value << " ";
}
}
二叉树的后序遍历的非递归实现。
这个是这篇文章的重点了,由于二叉树的后序遍历不同于前序遍历和中序遍历,必须要使用某种方式标记出左右字节点是否已经访问过了,那么在这里其实有两种方式,一种方式是改变底层的数的节点的数据结构,加上访问标记。第二种就是使用不同的进栈方式,在父节点进栈的时候同时将两个子节点进栈。下面先介绍第一种方式,改变底层的数据结构。
(底层数据结构只是加上了visited的字段,并没有其他的不同,不再赘述)
void PosTravelTreeNonRecurSec (TreeNodePlus *node) {
stack<TreeNodePlus *> nodeStack;
TreeNodePlus *cur = node;
while (cur != nullptr || !nodeStack.empty ()) {
while (cur != nullptr) { //在这个while循环当中,只将不是nullptr同时没有访问过的节点加入nodeStack
cur->visited++;
nodeStack.push (cur);
if (cur->lChild == nullptr) {
break;
}else if (cur->lChild->visited > 0) {
break;
}else {
cur = cur->lChild;
}
}
if (!nodeStack.empty ()) { //进入这里就说明其子节点的一个孩子或者两个孩子已经访问过了(这里有一个特殊情况
cur = nodeStack.top ();//就是子节点为nullptr也可以进入这里,但是子节点为nullptr也代表访问过了)
cur->visited++;
if (cur->visited == 3) { //说明这个节点的左右子节点已经访问过了,那么就可以将这个节点打印出来
cout << cur->value << " ";
nodeStack.pop ();
cur = nullptr; //将其设置为nullptr那么下个循环就可以跳过上面那么while循环,处理这个节点的父节点。
}else {
cur = cur->rChild;//说明这个节点的右节点还没有访问,那么把当前节点的值设置为右节点。
}
}
}
}
第二种方式就是使用不同的进栈方式。
void PosTravelTreeNonRecur (TreeNode *node) {
stack<pair<TreeNode * , bool>> nodeStack; //这个栈的节点是一个pair
TreeNode *cur = node;
nodeStack.push (make_pair (cur , false));
bool visited;
while (!nodeStack.empty ()) {
cur = nodeStack.top().first;
visited = nodeStack.top().second;
nodeStack.pop ();
if (cur == nullptr)
continue;
if (visited)
cout << cur->value << " ";
else { //使用精心设计的进栈方式,根节点放在最下面,然后再是右节点,再是坐节点,这样就保证取出来的时候
nodeStack.push (make_pair(cur , true)); //左节点可以最先取出来。同时,其他两种的遍历方式的非递归实现
nodeStack.push (make_pair(cur->rChild , false)); //也可以采用这种进栈方式,但是缺点是效率稍微低了一些,进栈
nodeStack.push (make_pair(cur->lChild , false)); //次数多了。
}
}
}