1 前序
struct node {
int data;
struct node* left;
struct node* right;
};
void visit(int data)
{
printf("%d ", data);
}
2 中序
void preOrder(struct node* root)
{
if (root == NULL)
return;
visit(root->data);
preOrder(root->left);
preOrder(root->right);
}
3 后序
void postOrder(struct node* root)
{
if (root == NULL)
return;
postOrder(root->left);
postOrder(root->right);
visit(root->data);
}
4, 层序(宽度优先)
void printLevel(struct node *p, int level)
{
if (!p) return;
if (level == 1) {
visit(p->data);
} else {
printLevel(p->left, level-1);
printLevel(p->right, level-1);
}
}
void printLevelOrder(struct node *root)
{
int height = maxHeight(root); //maxHeight计算二叉树高度,如二叉树实例高度为3
for (int level = 1; level <= height; level++) {
printLevel(root, level);
printf("\n");
}
}
当二叉树高度为N时,此时递归层序遍历为最坏情况,时间复杂度为O(N^2)。当二叉树左右子树基本平衡时,时间复杂度为O(N),分析如下:
设访问第K层时间为T(k),则T(k)存在如下的递归公式:
T(k) = 2T(k-1) + c = 2k-1 T(1) + c = 2k-1 + c当二叉树平衡时,则高度为O(lgN),则总时间为:
T(1) + T(2) + ... + T(lg N) = 1 + 2 + 22 + ... + 2lg N-1 + c = O(N)
非递归版本 用栈(队列)实现回溯 (访问完左子树再访问右子树,用栈或者队列记录已经访问过的节点访问信息,方便后来回溯)
1 先序//与二叉树镜像的做法类似
- void preOrder1(TNode* root)
- {
- Stack S;
- while ((root != NULL) || !S.empty())
- {
- if (root != NULL)
- {
- Visit(root);
- S.push(root); // 先序就体现在这里了,先访问,再入栈
- root = root->left; // 依次访问左子树
- }
- else
- {
- root = S.pop(); // 回溯至父亲节点
- root = root->right;
- }
- }
- }
-
- // 先序遍历伪代码:非递归版本,用栈实现,版本2
- void preOrder2(TNode* root)
- {
- if ( root != NULL)
- {
- Stack S;
- S.push(root);
- while (!S.empty())
- {
- TNode* node = S.pop();
- Visit(node); // 先访问根节点,然后根节点就无需入栈了
- S.push(node->right); // 先push的是右节点,再是左节点
- S.push(node->left);
- }
- }
- }
- 中序遍历伪代码:非递归版本,用栈实现,版本1
- void InOrder1(TNode* root)
- {
- Stack S;
- while ( root != NULL || !S.empty() )
- {
- while( root != NULL ) // 左子树入栈
- {
- S.push(root);
- root = root->left;
- }
- if ( !S.empty() )
- {
- root = S.pop();
- Visit(root->data); // 访问根结点
- root = root->right; // 通过下一次循环实现右子树遍历
- }
- }
- }
因为中序遍历节点序列有一点的连续性,而后续遍历则感觉有一定的跳跃性。先左,再右,最后才中间节点;访问左子树后,需要跳转到右子树,右子树访问完毕了再回溯至根节点并访问之。这种序列的不连续造成实现前面先序与中序类似的第1个与第3个版本比较困难。但是按照第2个思想,直接来模拟递归还是非常容易的。如下:
- / 后序遍历伪代码:非递归版本,用栈实现
- void PostOrder(TNode* root)
- {
- Stack S;
- if( root != NULL )
- {
- S.push(root);
- }
- while ( !S.empty() )
- {
- TNode* node = S.pop();
- if ( node->bPushed )
- { // 如果标识位为true,则表示其左右子树都已经入栈,那么现在就需要访问该节点了
- Visit(node);
- }
- else
- { // 左右子树尚未入栈,则依次将 右节点,左节点,根节点 入栈
-
- node-?bPushed=true;
-
s.push(node);
- if ( node->right != NULL )
- {
- node->right->bPushed = false; // 左右子树均设置为false
- S.push(node->right);
- }
- if ( node->left != NULL )
- {
- node->left->bPushed = false;
- S.push(node->left);
- }
- }
- }
- }
和中序遍历的第2个版本比较,仅仅只是把左孩子入栈和根节点入栈顺序调换一下;这种差别就跟递归版本的中序与后序一样。
4.层序遍历
这个很简单,就不说。
- // 层序遍历伪代码:非递归版本,用队列完成
- void LevelOrder(TNode *root)
- {
- Queue Q;
- Q.push(root);
- while (!Q.empty())
- {
- node = Q.front(); // 取出队首值并访问
- Visit(node);
- if (NULL != node->left) // 左孩子入队
- {
- Q.push(node->left);
- }
- if (NULL != node->right) // 右孩子入队
- {
- Q.push(node->right);
- }
- }
- }
结一下:
用栈来实现比增加指向父节点指针回溯更方便;
采用第一个思想,就是跟踪指针移动 用栈保存中间结果的实现方式,先序与中序难度一致,后序很困难。先序与中序只需要修改一下访问的位置即可。
采用第二个思想,直接用栈来模拟递归,先序非常简单;而中序与后序难度一致。先序简单是因为节点可以直接访问,访问完毕后无需记录。而中序与后序时,节点在弹栈后还不能立即访问,还需要等其他节点访问完毕后才能访问,因此节点需要设置标志位来判定,因此需要额外的O(n)空间。