本文参考:
参考文章1
参考文章2
代码中加入了一些自己的理解
/* 二叉树的四种遍历方式
*/
#include <iostream>
#include <stack>
using namespace std;
// 二叉树节点的定义
class TreeNode{
public:
char val;
//int val;
TreeNode *left, *right;
TreeNode(int val){
this->val = val;
this->left = this->right = NULL;
}
};
// 递归方式,每个结点只遍历一次,时间复杂度O(1),递归最多调用n次,空间复杂度O(n)
// 先序:根-》递归左-》递归右
void preOrder(TreeNode* root){
if(root==NULL)
return;
cout << root->val << endl;
preOrder(root->left);
preOrder(root->right);
}
// 中序:递归左-》根-》递归右
void inOrder(TreeNode* root){
if(root==NULL)
return;
inOrder(root->left);
cout << root->val << endl;
inOrder(root->right);
}
// 后序:递归左-》递归右-》根
void postOrder(TreeNode* root){
if(root==NULL)
return;
postOrder(root->left);
postOrder(root->right);
cout << root->val << endl;
}
// 非递归,使用栈,每个节点只需遍历一次,时间复杂度O(n),使用栈,只需压入或弹出各一次,空间复杂度O(n)
// 先序:根压入,从栈顶弹出结点,并访问,
// 压入当前右孩子,压入当前左孩子,则出栈顺序与先序遍历一致
void preOrder2(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
stk.push(root);
while(!stk.empty()){
TreeNode* pNode = stk.top();
stk.pop();
cout << pNode->val << endl;
if(pNode->right!=NULL)
stk.push(pNode->right);
if(pNode->left!=NULL)
stk.push(pNode->left);
}
}
// 中序:初始指针pNode指向根,
// 若pNode不空,则pNode压入,pNode指向当前左孩子,一直到最左,
// 若pNode为空,从栈顶弹出结点,并访问,pNode当前右孩子
void inOrder2(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
TreeNode* pNode = root;
while(pNode!=NULL || !stk.empty()){
if(pNode!=NULL){
stk.push(pNode);
pNode = pNode->left;
}
else{
pNode = stk.top();
cout << pNode->val << endl;
stk.pop();
pNode = pNode->right;
}
}
}
// 后序,使用2个栈:设置两个栈stk, stk2;
// 将根结点压入第一个栈stk;弹出stk栈顶的结点,并把该结点压入第二个栈stk2;
// 将当前结点的左孩子和右孩子先后分别入栈stk;当所有元素都压入stk2后,依次弹出stk2的栈顶结点,并访问之。
// 第一个栈的入栈顺序是:根结点,左孩子和右孩子;压入第二个栈的顺序是:根结点,右孩子和左孩子。
// 因此,第二个栈弹出的顺序就是:左孩子,右孩子和根结点。
void postOrder2(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
stack<TreeNode*> stk2;
stk.push(root);
while(!stk.empty()){
TreeNode* pNode = stk.top();
stk.pop();
stk2.push(pNode);
if(pNode->left!=NULL)
stk.push(pNode->left);
if(pNode->right!=NULL)
stk.push(pNode->right);
}
while(!stk2.empty()){
cout << stk2.top()->val << endl;
stk2.pop();
}
}
// 后序,使用1个栈:
// 每次循环开始时,当前结点更新为栈顶
// 首先向下遍历,指向之前访问的结点的指针prev为空时,或者当前访问的是prev的孩子时,继续向下访问左孩子并压入,左孩子没有则访问右孩子并压入
// 之后是从左孩子向上遍历,即当前访问的是prev的父结点时,如果父节点有右孩子,继续向下访问右孩子,并压入
// prev不为空,并且当前访问的节点也不是prev的孩子,说明已经到达叶子节点或父结点,之后输出当前节点,并弹出
// 每个步骤操作完成后,将prev更新为当前结点
void postOrder3(TreeNode* root){
if(root==NULL)
return;
stack<TreeNode*> stk;
stk.push(root);
TreeNode* prev = NULL;//指向上次访问的节点,即为父节点或者叶节点
while(!stk.empty()){
TreeNode *pNode = stk.top();
if(!prev || prev->left == pNode || prev->right == pNode){//之前没有刚问过的点,或者当前是prev的孩子,则继续向下遍历并压入
if(pNode->left)
stk.push(pNode->left);
else if(pNode->right)
stk.push(pNode->right);
}
else if(pNode->left==prev){//若左孩已经访问过
if(pNode->right)//则继续访问右孩,并压入
stk.push(pNode->right);
}
else{//到达叶节点,或者父节点
cout << pNode->val << endl;
stk.pop();
}
prev = pNode;
}
}
// Morris 遍历:线索二叉树,在O(1)空间内,O(n)时间内完成遍历,通过修改叶子结点的左右空指针指向前驱或后记
// 中序:
// 1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
// 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
// a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
// b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
// 3. 重复以上1、2直到当前节点为空。
// 使用两个辅助指针,空间复杂度O(1),
// 时间复杂度O(n):n个结点二叉树有n-1条边,每条边最多有两次,一次用于找前驱,一次用于访问该结点
void inOrderMorris(TreeNode* root){
TreeNode* cur = root;
TreeNode* prev = NULL;
while(cur!=NULL){
if(cur->left == NULL){ // 1. (表示左子树已经遍历完,访问其前驱)
cout << cur->val << endl;
cur = cur->right;
}
else{
//找到cur的前驱prev,即为cur的左孩子的最右孩子
prev = cur->left;
while(prev->right!=NULL && prev->right!=cur){
prev = prev->right;
}
//已经找到prev
if(prev->right==NULL){ // 2.a)
prev->right = cur; // 添加线索
cur = cur->left; // 向下左遍历继续为每个中结点找前驱
}
else{ // 2.b)
prev->right = NULL; // 第二次遍历到中结点,删除线索
cout << cur->val << endl; // 打印中结点
cur = cur->right; // 访问右子树,右子树不再有右子树时,cur为空,程序终止
}
}
}
}
// 前序:与中序相似,在于输出的顺序
// 1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
// 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
// a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。输出当前节点(在这里输出,这是与中序遍历唯一一点不同)。当前节点更新为当前节点的左孩子。
// b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。当前节点更新为当前节点的右孩子。
// 3. 重复以上1、2直到当前节点为空。
// 时间、空间复杂度与中序相同
void preOrderMorris(TreeNode* root){
TreeNode* cur = root;
TreeNode* prev = NULL;
while(cur!=NULL){
if(cur->left == NULL){
cout << cur->val << endl;
cur = cur->right;
}
else{
prev = cur->left;
while(prev->right!=NULL && prev->right!=cur){
prev = prev->right;
}
if(prev->right == NULL){ // 与中序唯一不同之处
cout << cur->val << endl;
prev->right = cur;
cur = cur->left;
}
else{
prev->right = NULL;
cur = cur->right;
}
}
}
}
// 后序:稍复杂,需要建立临时结点dump,令其左孩子是root,
// 需要子过程:倒序输出某两个节点之间路径上的各个节点
// 当前节点设置为临时节点dump。
// 1. 如果当前节点的左孩子为空,则将其右孩子作为当前节点。
// 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
// a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
// b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。
// 3. 重复以上1、2直到当前节点为空。
void reverseFromTo(TreeNode* from, TreeNode* to){
if(from==to)
return;
TreeNode *x = from;//x为路径头的结点
TreeNode *y = from->right;//y为待插入到路径头的节点
TreeNode *z;//下一带插入点
// y不断向x头位置前插
while(true){
z = y->right;
y->right = x;
x = y;
y = z;
if(x == to)
break;
}
}
void printReverse(TreeNode* from, TreeNode* to){
reverseFromTo(from,to);//颠倒from到to结点的路径
TreeNode* pNode = to;
while(true){
cout << pNode->val << endl;
if(pNode==from)
break;
pNode = pNode->right;
}
reverseFromTo(to,from);//恢复树的形状
}
void postOrderMorris(TreeNode* root){
if(root == NULL)
return;
TreeNode* dump = new TreeNode('z');
dump->left = root;
TreeNode* pNode = dump;
TreeNode* pPrev = NULL;
while(pNode!=NULL){//当前节点不为空时
if(pNode->left==NULL)
pNode = pNode->right;
else{
//找前驱pPrev
pPrev = pNode->left;
while(pPrev->right!=NULL && pPrev->right!=pNode){
pPrev = pPrev->right;
}
//前驱pPrev已找到
if(pPrev->right==NULL){
pPrev->right = pNode;
pNode = pNode->left;
}
else{
pPrev->right = NULL;
printReverse(pNode->left,pPrev);
pNode = pNode->right;
}
}
}
}
int main()
{
TreeNode* a = new TreeNode('A');
TreeNode* b = new TreeNode('B');
TreeNode* c = new TreeNode('C');
TreeNode* d = new TreeNode('D');
TreeNode* e = new TreeNode('E');
TreeNode* f = new TreeNode('F');
TreeNode* root = a;
root->left = b;
root->right = c;
b->left = d;
b->right = e;
c->left = f;
// 前序:ABDECF
// 中序:DBEAFC
// 后序:DEBFCA
cout << "先序遍历:递归"<< endl;
preOrder(root);
cout << "中序遍历:递归"<< endl;
inOrder(root);
cout << "后序遍历:递归"<< endl;
postOrder(root);
cout << "先序遍历:栈"<< endl;
preOrder2(root);
cout << "中序遍历:栈"<< endl;
inOrder2(root);
cout << "后序遍历:双栈"<< endl;
postOrder2(root);
cout << "后序遍历:单栈"<< endl;
postOrder3(root);
cout << "中序遍历:Morris"<< endl;
inOrderMorris(root);
cout << "后序遍历:Morris"<< endl;
postOrderMorris(root);
}