对于树结构,最重要的部分莫过于遍历,因为对于树的其他操作,都离不开遍历操作,而其中最典型的遍历,便是对二叉树的遍历,说到二叉树的遍历,我们首先想到的肯定是用递归实现的先序、中序和后序遍历,因为代码简洁明了,也很容易理解,但是众所周知,递归实现的代码效率肯定不高,因此在这里,除了介绍常用的递归实现之外,还将介绍非递归实现的方法。
这里使用的是C++实现。
准备工作
首先这里使用的树结构如下所示,可以看到,这是一颗二叉排序树:
相应的代码如下所示:
typedef struct BiTNode {
int data;
struct BiTNode* lChild, * rChild; //左右孩子指针
}BiTNode;
typedef struct {
BiTNode* root;
}BiTree;
void insert(BiTree &tree, int data) { //根据二叉排序树的思想插入结点
BiTNode* node = new(BiTNode);
node->data = data;
node->lChild = node->rChild = nullptr;
if (tree.root == nullptr) {
tree.root = node;
} else {
BiTNode* temp = tree.root;
while (temp != nullptr) {
if (node->data < temp->data) {
if (temp->lChild == nullptr) {
temp->lChild = node;
return;
} else {
temp = temp->lChild;
}
} else {
if (temp->rChild == nullptr) {
temp->rChild = node;
return;
} else {
temp = temp->rChild;
}
}
}
}
}
然后在main()函数中添加上这段代码:
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
那么接下来就进入到正题了。
递归版本实现二叉树遍历
void preOrder1(BiTNode* root) { //先序遍历递归版
if (root) {
cout << root->data << " ";
preOrder1(root->lChild);
preOrder1(root->rChild);
}
}
void inOrder1(BiTNode* root) { //中序遍历递归版
if (root) {
inOrder1(root->lChild);
cout << root->data << " ";
inOrder1(root->rChild);
}
}
void postOrder1(BiTNode* root) { //后序遍历递归版
if (root) {
postOrder1(root->lChild);
postOrder1(root->rChild);
cout << root->data << " ";
}
}
main()函数中显示:
int main() {
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
cout << "递归版先序遍历:" << endl;
preOrder1(tree.root);
cout << endl << "递归版中序遍历:" << endl;
inOrder1(tree.root);
cout << endl << "递归版后序遍历:" << endl;
postOrder1(tree.root);
delTree(tree.root);
cout << endl << "释放成功!" << endl;
return 0;
}
得到的运行结果如下:
非递归版本实现二叉树遍历
递归版本转换为非递归版本往往需要使用到栈结构。
非递归版本中先序遍历和中序遍历的思想是一致的:
void preOrder2(BiTNode* root) { //先序遍历非递归版
stack<BiTNode*> S;
BiTNode* t = root;
while (t || !S.empty()) {
if (t) { //若当前节点不为空,则先访问当前节点,入栈,再访问左子树
cout << t->data << " ";
S.push(t);
t = t->lChild;
} else { //否则就访问栈顶结点的右子树
t = S.top();
S.pop();
t = t->rChild;
}
}
}
void inOrder2(BiTNode* root) { //中序遍历非递归版
stack<BiTNode*> S;
BiTNode* t = root;
while (t || !S.empty()) {
if (t) {
S.push(t);
t = t->lChild;
} else {
t = S.top();
S.pop();
cout << t->data << " ";
t = t->rChild;
}
}
}
以先序遍历为例,首先判断当前节点是否为空,若不为空,则首先访问当前节点,之后需要将该节点入栈,这是为了后面访问该节点的右子树做准备的,然后指针指向该节点的左子树;若当前节点为空(这种情况就相当于是叶子节点的左指针,当然也可能是上一个节点只有右子树),说明上一个节点的左子树已经访问过了,那么根据先序遍历的思想,我们需要访问上一个节点的右子树,而上一个节点便是栈顶元素,因此首先需要出栈,然后指针指向其右子树,中序遍历类似。
非递归实现的后序遍历就要稍微复杂一点了,因为根节点是最后才访问的:
void postOrder2(BiTNode* root) { //后序遍历非递归版
stack<BiTNode*> S;
BiTNode* t = root;
BiTNode* r = nullptr;
while (t || !S.empty()) {
if (t) {
S.push(t);
t = t->lChild;
} else {
t = S.top(); //这里只是得到栈顶元素并未出栈
if (t->rChild && t->rChild != r) { //若有右子树且右子树未被访问过
t = t->rChild;
} else {
S.pop(); //因为左右子树都已经被访问过了,所以栈顶元素应该被弹出栈了
cout << t->data << " ";
r = t; //记录最近访问过的节点
t = nullptr; //节点访问完以后,需要将指针重置
}
}
}
}
对于后序遍历,这里可能会有些难以理解,所以我们以前面的二叉树为例,大致地走一下流程:
首先根节点是5,根据代码我们可以知道,开始的几步都是进入if语句中,依次入栈的节点分别为5、3、2、1,然后1入栈之后,由于1是叶子节点,因此它的左子树为空,于是进入到else语句中,然后得到栈顶元素即1,注意这里并没有弹出栈顶元素,因为到这一步仅仅只是该节点的左子树遍历完了,再判断它的右子树是否存在且未被访问过,因为右子树为空,所以进入到else语句中,到了这里就说明对于以当前节点为根节点的子树,它的左右子树都已经遍历过了,所以需要将栈顶节点出栈,然后用r记录节点1,表示最近访问过的节点,同时我们需要将t指向nullptr,后面以此类推。
问题一、为什么在访问过节点t之后,需要将t指向nullptr?
这是因为若不将t指向nullptr,则接下来就会进入到if语句中。如上面介绍过的,t指向节点1,访问完节点1之后不指向nullptr的话,那么又会进入到if语句中,然后又是将节点1入栈,访问它的左子树,之后又进入到else语句中,访问节点1,以此往复,没错,进入死循环了!所以这里必须将t指向nullptr。
问题二、为什么要添加一个指针r来记录最近访问过的节点?
我们同样用实例来说明,在访问完1、2以后,我们便通过else语句进入到了节点3,由于右子树存在,于是我们进入到了节点4,由于节点4是叶子节点,因此访问完节点4之后,我们重新通过else语句进入到节点3,但是此时的节点3左右子树都已经访问过了,因此我们应该访问的节点应该是节点3,但是若没有指针r记录最近访问的节点的话,我们又进入到了节点4,于是乎,我们又进入死循环了!所以我们必须有一个指针r来记录最近访问的节点。
由上面的两个问题我们也可以发现,后序遍历的非递归实现确实要比先序遍历、中序遍历的非递归实现难一些。
main()函数中显示:
int main() {
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
cout << "非递归版先序遍历:" << endl;
preOrder2(tree.root);
cout << endl << "非递归版中序遍历:" << endl;
inOrder2(tree.root);
cout << endl << "非递归版后序遍历:" << endl;
postOrder2(tree.root);
delTree(tree.root);
cout << endl << "释放成功!" << endl;
return 0;
}
运行结果如下:
层序遍历
那么我们接下来再来介绍一下二叉树的层序遍历,层序遍历其实就是树结构中的BFS,因此层序遍历也要用到队列,还是以上面的二叉树为例:
层序遍历部分的代码:
void levelOrder(BiTNode* root) {
queue<BiTNode*> Q;
BiTNode* t;
Q.push(root);
while (!Q.empty()) {
t = Q.front();
Q.pop();
cout << t->data << " ";
if (t->lChild) {
Q.push(t->lChild);
}
if (t->rChild) {
Q.push(t->rChild);
}
}
}
在main()函数中调用:
int main() {
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
cout << "层序遍历:" << endl;
levelOrder(tree.root);
delTree(tree.root);
cout << endl << "释放成功!" << endl;
return 0;
}
运行结果如下:
完整代码
#include<iostream>
#include<stack>
using namespace std;
typedef struct BiTNode {
int data;
struct BiTNode* lChild, * rChild; //左右孩子指针
}BiTNode;
typedef struct {
BiTNode* root;
}BiTree;
void insert(BiTree &tree, int data) { //根据二叉排序树的思想插入结点
BiTNode* node = new(BiTNode);
node->data = data;
node->lChild = node->rChild = nullptr;
if (tree.root == nullptr) {
tree.root = node;
} else {
BiTNode* temp = tree.root;
while (temp != nullptr) {
if (node->data < temp->data) {
if (temp->lChild == nullptr) {
temp->lChild = node;
return;
} else {
temp = temp->lChild;
}
} else {
if (temp->rChild == nullptr) {
temp->rChild = node;
return;
} else {
temp = temp->rChild;
}
}
}
}
}
void delTree(BiTNode* root) {
if (root == nullptr) return;
delTree(root->lChild);
delTree(root->rChild);
delete(root);
}
void preOrder1(BiTNode* root) { //先序遍历递归版
if (root) {
cout << root->data << " ";
preOrder1(root->lChild);
preOrder1(root->rChild);
}
}
void inOrder1(BiTNode* root) { //中序遍历递归版
if (root) {
inOrder1(root->lChild);
cout << root->data << " ";
inOrder1(root->rChild);
}
}
void postOrder1(BiTNode* root) { //后序遍历递归版
if (root) {
postOrder1(root->lChild);
postOrder1(root->rChild);
cout << root->data << " ";
}
}
void preOrder2(BiTNode* root) { //先序遍历非递归版
stack<BiTNode*> S;
BiTNode* t = root;
while (t || !S.empty()) {
if (t) { //若当前节点不为空,则先访问当前节点,入栈,再访问左子树
cout << t->data << " ";
S.push(t);
t = t->lChild;
} else { //否则就访问栈顶结点的右子树
t = S.top();
S.pop();
t = t->rChild;
}
}
}
void inOrder2(BiTNode* root) { //中序遍历非递归版
stack<BiTNode*> S;
BiTNode* t = root;
while (t || !S.empty()) {
if (t) {
S.push(t);
t = t->lChild;
} else {
t = S.top();
S.pop();
cout << t->data << " ";
t = t->rChild;
}
}
}
void postOrder2(BiTNode* root) { //后序遍历非递归版
stack<BiTNode*> S;
BiTNode* t = root;
BiTNode* r = nullptr;
while (t || !S.empty()) {
if (t) {
S.push(t);
t = t->lChild;
} else {
t = S.top(); //这里只是得到栈顶元素并未出栈
if (t->rChild && t->rChild != r) { //若有右子树且右子树未被访问过
t = t->rChild;
} else {
S.pop(); //因为左右子树都已经被访问过了,所以栈顶元素应该被弹出栈了
cout << t->data << " ";
r = t; //记录最近访问过的节点
t = nullptr; //节点访问完以后,需要将指针重置
}
}
}
}
int main() {
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
cout << "递归版先序遍历:" << endl;
preOrder1(tree.root);
cout << endl << "递归版中序遍历:" << endl;
inOrder1(tree.root);
cout << endl << "递归版后序遍历:" << endl;
postOrder1(tree.root);
cout << endl;
cout << "非递归版先序遍历:" << endl;
preOrder2(tree.root);
cout << endl << "非递归版中序遍历:" << endl;
inOrder2(tree.root);
cout << endl << "非递归版后序遍历:" << endl;
postOrder2(tree.root);
delTree(tree.root);
cout << endl << "释放成功!" << endl;
return 0;
}
运行结果如下: