目录:
一、二叉树的非递归遍历【前中的差别只有一句位置不同! 后序最特殊】
两种方法:1.用栈 2.用Morris
栈:
因为所谓的前中后序都是深度优先遍历,所以用栈数据结构。广度(按层/之字)遍历时才用队列数据结构。
例:树从上往下 从左往右是abcdefghi。
前序:a b d h i e c f g
中序:h d i b e a f c g
后序:h i d e b f g c a
三种写法特点:
1.下划线处是内层while循环的依据。
2.外层的while循环体内都是6句:
压入栈
压入vector(后序是插入)
root更新为左子树
root更新为右子树
root = 栈顶
pop栈顶
*********
preorder:
*********
vector<int> PreorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> nodeStack;
while (root || !nodeStack.empty()) {
while (root) {
nodeStack.push(root);
result.push_back(root->val);
root = root->left;
}
root = nodeStack.top();
nodeStack.pop();
root = root->right;
}
return result;
}
*********
inorder:
*********
vector<int> InorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> nodeStack;
while (root || !nodeStack.empty()) {
while (root) {
nodeStack.push(root);
root = root->left;
}
root = nodeStack.top();
nodeStack.pop();
result.push_back(root->val); //与前序的唯一区别:把result.push_back挪到了这里
root = root->right;
}
return result;
}
*********
postorder:
*********
vector<int> PostorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> nodeStack;
while (root || !nodeStack.empty()) {
while (root) {
nodeStack.push(root);
result.insert(result.begin(),root->val);//与前序的区别:1.把result从前面开始插入 2.left right换了顺序
root = root->right;
}
root = nodeStack.top();
nodeStack.pop();
root = root->left;
}
return result;
}
Morris分析:
要使用O(1)空间进行遍历,最大的难点在于,遍历到子节点的时候怎样重新返回到父节点(假设节点中没有指向父节点的p指针),由于不能用栈作为辅助空间。为了解决这个问题,Morris方法用到了线索二叉树(threaded binary tree)的概念。在Morris方法中不需要为每个节点额外分配指针指向其前驱(predecessor)和后继节点(successor),只需要利用叶子节点中的左右空指针指向某种顺序遍历下的前驱节点或后继节点就可以了。
设当前节点为curr,前驱节点为prev。
Morris中序遍历思想:前后都是基于中序的改动
1. 如果curr的左孩子为空,则 1.printf curr。 2.curr更新为curr的右孩子(意为左边没法遍历了,操作下一个点)。
2. 否则(如果curr的左孩子不为空),while循环:在curr的左子树中找到curr在中序遍历下的前驱节点prev。
a) 如果prev的右孩子为空,则 1.将prev的右孩子指向curr(连起来~)。 2.curr更新为curr的左孩子(意为操作下一个点)。
b) 如果prev的右孩子为curr(即已经执行过“连起来”了),则 1.printf curr。 2将prev的右孩子重新置为NULL(恢复树的形状)。 3.curr更新为curr的右孩子(意为左边都遍历完了,操作下一个点)。
3. 重复以上1、2步,直至curr为空。
图示:
下图为每一步迭代的结果(从左至右,从上到下),cur代表当前节点,蓝色节点表示该节点已输出。
代码:
void InorderMorrisTraversal(TreeNode *root) {
TreeNode *cur = root, *prev = NULL;
while (cur != NULL)
{
if (cur->left == NULL)
{
printf("%d ", cur->val);
cur = cur->right;
}
else
{
prev = cur->left;
while (prev->right != NULL && prev->right != cur)
prev = prev->right;
if (prev->right == NULL)
{
//前序在这里输出
prev->right = cur;
cur = cur->left;
}
else
{
printf("%d ", cur->val); // 与前序的唯一区别
prev->right = NULL;
cur = cur->right;
}
}
}
}
Morris前序遍历思想:前序中序唯一的区别就是红字处,即printf curr是在2.a(前)还是2.b(中)
1. 如果curr的左孩子为空,则 1.printf curr。 2.curr更新为curr的右孩子(意为左边没法遍历了,操作下一个点)。
2. 否则(如果curr的左孩子不为空),while循环:在curr的左子树中找到curr在前序遍历下的前驱节点prev。
a) 如果prev的右孩子为空,则 1.printf curr。2.将prev的右孩子指向curr(连起来~)。 3.curr更新为curr的左孩子(意为操作下一个点)。
b) 如果prev的右孩子为curr(即已经执行过“连起来”了),则 1.将prev的右孩子重新置为NULL(恢复树的形状)。 2.curr更新为curr的右孩子(意为左边都遍历完了,操作下一个点)。
3. 重复以上1、2步,直至curr为空。
图示:
复杂度分析:
空间复杂度:O(1),因为只用了两个辅助指针。
时间复杂度:O(n)。证明时间复杂度为O(n),最大的疑惑在于寻找中序遍历下二叉树中所有节点的前驱节点的时间复杂度是多少,即以下两行代码:
while (prev->right != NULL && prev->right != cur)
prev = prev->right;
直觉上,认为它的复杂度是O(nlgn),因为找单个节点的前驱节点与树的高度有关。但事实上,对于单个节点来讲,平均情况下查找复杂度是O(lgn),最好时是O(1)。考虑所有节点总的查找复杂度的话,是O(n)。n个节点的二叉树中一共有n-1条边,整个过程中每条边最多只走3次,一次是为了定位到某个节点,另一次是为了寻找上面某个节点的前驱节点,如下图所示,其中红色是为了定位到某个节点,黑色线是为了找到前驱节点。所以复杂度为O(n)。

Morris后序遍历思想:
过阵再整理
http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
http://blog.youkuaiyun.com/wdq347/article/details/8853371