数据结构——二叉树的遍历

二叉树的建立

建立二叉树之后我们才有能去遍历的二叉树,才能检验自己写的代码又没有问题,所以这里我们先来建立二叉树

二叉树的节点

typedef struct BTNode { //二叉树结点     
	char data;//每个节点中存放的数据
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

当然,一棵二叉树也会有除了节点之外的一些信息,比如节点个数,我们把节点无法记录下来的信息保存下来,可以 进一步封装一个二叉树结构体,如下:

typedef struct {//二叉树
	BTNode* root;
	int cnt; //节点数目
}BinaryTree;

下面再给出其他有关二叉树创建的函数

二叉树的节点创建函数(很多操作需要创建一个节点,这里单独写一个函数)

//二叉树的节点创建函数
BTNode* CreateBTNode(char e) //传入一个数据,创建相应的节点
{
	BTNode* bn = (BTNode*)malloc(sizeof(BTNode));
	bn->data = e;
	bn->right = bn->left = NULL;
	return bn;
}

二叉树的初始化函数

//二叉树的初始化函数
BinaryTree* InitBinaryTree(BTNode* e)//传入一个节点,创建以该节点为根节点的树
{
	BinaryTree* bt = (BinaryTree*)malloc(sizeof(BinaryTree));
	bt->root = e;
	bt->cnt++;
	return bt;
}

二叉树的节点插入函数(也是建树的核心函数)

//二叉树的节点插入函数(flag为1表示新节点为左孩子节点,为0表示新节点为右边孩子节点)
//child表示插入的节点,parent表示child节点的父节点
void InsertBTree(BinaryTree* tree, BTNode* child, BTNode* parent, int flag)
{
	if (flag == 1) parent->left = child;
	else parent->right = child;
	tree->cnt++;
}

下面再写一个函数建立一个具体如下图所示二叉树,方便在main函数中直接调用

在这里插入图片描述

BinaryTree* Tree() {
	BTNode* a = CreateBTNode('A');
	BTNode* b = CreateBTNode('B');
	BTNode* c = CreateBTNode('C');
	BTNode* d = CreateBTNode('D');
	BTNode* e = CreateBTNode('E');
	BTNode* f = CreateBTNode('F');
	BTNode* g = CreateBTNode('G');
	BTNode* h = CreateBTNode('H');
	BTNode* k = CreateBTNode('K');
	BinaryTree* tree = InitBinaryTree(a);
	InsertBTree(tree, b, a, 1);
	InsertBTree(tree, e, a, 0);
	InsertBTree(tree, c, b, 0);
	InsertBTree(tree, d, c, 1);
	InsertBTree(tree, f, e, 0);
	InsertBTree(tree, g, f, 1);
	InsertBTree(tree, h, g, 1);
	InsertBTree(tree, k, g, 0);
	return tree;
}

二叉树的遍历

下面的遍历都以打印的形式展现出来
先写一个访问节点的函数

//访问二叉树的节点的函数(这里只进行打印)
void VisitBTNode(BTNode* a)
{
	if (a) printf("%c ", a->data);
}

1. 二叉树的层次遍历

层次遍历就是对于一棵二叉树一层一层地遍历,每一层从左到右
在这里插入图片描述
上图的层次遍历顺序就为:A B E C F D G H K

手动人工实现这样的遍历当然简单,可是怎么实现通过代码让计算机能够这样遍历呢

我们引入队列来实现这一功能:

  1. 先将根节点入队
  2. 每次让队首元素出队,出队时可对其进行访问,然后(如果有的话),先后让其左右孩子入队。之后一直重复第二步,知道队列中无元素。

用上面那棵树来模拟:A先入队,A出队,B,E分别入队,B出队,C入队,E出队,F入队,然后一直重复……

可以看到,这样做使得入队的顺序都是从左到右,且入队都是每一层入队完之后,下一层再入队

在这里为了方便,我们就简单用一个循环数组来模拟队列,数组类型为BTNode类型,先定义出首尾指针

int head = 0; int tail = 0;//队列的头指针和尾指针

队列功能实现代码实现如下

int quefull(BTNode*)//BTNode*表示传入的是数组的首地址
{
	return ((head + 1) % (QUE_MAX) != tail);//循环数组用取余
}
//入队函数
void inqueue(BTNode* que, BTNode* e)//que表示队列的循环数组首地址,e表示入队数据
{
	if (quefull(que)) 
	{
		que[head] = *e;
		head = (head + 1) % QUE_MAX;
	}
	else printf("\n队满\n");
}
//判空函数,返回0为空,1为非空
int queempty(BTNode*)
{
	return (head != tail);
}
//出队函数
BTNode* outqueue(BTNode* que)
{
	if (queempty(que))
	{
		int tmp = tail;
		tail = (tail + 1) % QUE_MAX;
		return que + tmp;
	}
	else {
		printf("\n队空\n");
		return NULL;
	}
}

有了前面队列的一些操作后,直接开干
二叉树的层次遍历函数

//二叉树的层次遍历函数(用到队列)
#define QUE_MAX 101
void levelOrderBTree(BinaryTree* tree)
{
	BTNode que[QUE_MAX]; //先搞出来一个队列
	inqueue(que, tree->root);//将根节点入队
	//开始循环将树中入队实现遍历
	while (queempty(que)) {
		BTNode* node = outqueue(que);
		VisitBTNode(node);
		if (node->left) inqueue(que, node->left);
		if (node->right) inqueue(que,node->right);
	}
}

把上述代码组合起来得到一个完整的建立一棵二叉树及其实现层次遍历的函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define QUE_MAX 101

typedef struct BTNode { //二叉树结点     
	char data;//每个节点中存放的数据
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

typedef struct {//二叉树
	BTNode* root;
	int cnt; //节点数目
}BinaryTree;

//二叉树的节点创建函数
BTNode* CreateBTNode(char e) //传入一个数据,创建相应的节点
{
	BTNode* bn = (BTNode*)malloc(sizeof(BTNode));
	bn->data = e;
	bn->right = bn->left = NULL;
	return bn;
}
//二叉树的初始化函数
BinaryTree* InitBinaryTree(BTNode* e)//传入一个节点,创建以该节点为根节点的树
{
	BinaryTree* bt = (BinaryTree*)malloc(sizeof(BinaryTree));
	bt->root = e;
	bt->cnt++;
	return bt;
}

/*开始写队列有关的东西,为二叉树遍历做准备*/
int head = 0; int tail = 0;//队列的头指针和尾指针
//判满函数,返回0表示满了,1表示没满
int quefull(BTNode*)
{
	return ((head + 1) % (QUE_MAX) != tail);
}
//入队函数
void inqueue(BTNode* que, BTNode* e)//que表示队列的循环数组首地址,e表示入队数据
{
	if (quefull(que)) 
	{
		que[head] = *e;
		head = (head + 1) % QUE_MAX;
	}
	else printf("\n队满\n");
}
//判空函数,返回0为空,1为非空
int queempty(BTNode*)
{
	return (head != tail);
}
//出队函数
BTNode* outqueue(BTNode* que)
{
	if (queempty(que))
	{
		int tmp = tail;
		tail = (tail + 1) % QUE_MAX;
		return que + tmp;
	}
	else {
		printf("\n队空\n");
		return NULL;
	}
}

//二叉树的节点插入函数(flag为1表示新节点为左孩子节点,为0表示新节点为右边孩子节点)
//child表示插入的节点,parent表示child节点的父节点
void InsertBTree(BinaryTree* tree, BTNode* child, BTNode* parent, int flag)
{
	if (flag == 1) parent->left = child;
	else parent->right = child;
	tree->cnt++;
}
//访问二叉树的节点的函数(这里只进行打印)
void VisitBTNode(BTNode* a)
{
	if (a) printf("%c ", a->data);
}
//二叉树的层次遍历函数(用到队列)
void levelOrderBTree(BinaryTree* tree)
{
	BTNode que[QUE_MAX]; //先搞出来一个队列
	inqueue(que, tree->root);//将根节点入队
	//开始循环将树中入队实现遍历
	while (queempty(que)) {
		BTNode* node = outqueue(que);
		VisitBTNode(node);
		if (node->left) inqueue(que, node->left);
		if (node->right) inqueue(que,node->right);
	}
}
//初始化一棵具体的树 
BinaryTree* Tree() {
	BTNode* a = CreateBTNode('A');
	BTNode* b = CreateBTNode('B');
	BTNode* c = CreateBTNode('C');
	BTNode* d = CreateBTNode('D');
	BTNode* e = CreateBTNode('E');
	BTNode* f = CreateBTNode('F');
	BTNode* g = CreateBTNode('G');
	BTNode* h = CreateBTNode('H');
	BTNode* k = CreateBTNode('K');
	BinaryTree* tree = InitBinaryTree(a);
	InsertBTree(tree, b, a, 1);
	InsertBTree(tree, e, a, 0);
	InsertBTree(tree, c, b, 0);
	InsertBTree(tree, d, c, 1);
	InsertBTree(tree, f, e, 0);
	InsertBTree(tree, g, f, 1);
	InsertBTree(tree, h, g, 1);
	InsertBTree(tree, k, g, 0);
	return tree;
}
int main()
{
	BinaryTree* tree = Tree();
	levelOrderBTree(tree);
	printf("\n");
	return 0;
}

2. 二叉树的深度遍历

递归形式

以我较为浅显的理解,用递归来遍历二叉树就是利用了“树中有树”这一特征。

首先,一棵树可以分解成为:根,左子树和右子树
左子树又可以看成一棵独立的树,也有它的根,左子树和右子树
它的右子树也有它的根,左子树和右子树

对于“根”,“左子树”,“右子树”这三者之间,毫无疑问,根是最容也是最直接就能访问到的

这样我们就可以将一棵树给一直分解下去,直到分解成叶子节点这一最小单位(左右子树都为空,无法再分),然后从这个叶子节点返回,这个叶子节点可以看作以及走过了,不存在了,去掉这个叶子节点又能得到新的叶子节点,再重复这个过程,每个节点都能看成叶子节点了。

相当于我只需要根据“左右根”这种模式找到一个叶子节点,再忽略这个叶子节点,去寻找忽略这个节点后得到的树的叶子节点,直至把每一个节点都给忽略掉,而且没又重复。这些叶子节点我们也能看作是根,左右子树都为空。

这样也就能把任意一棵二叉树的每一个节点都涉及到,并且根据使这个最终得到的遍历序列具有某种规律。

这个过程和递归非常神似,在一个函数里面去调用改变了参数的本身的函数(对应在树中去管它的左子树),我们可以自然地联想到用递归去解决上述过程

1.先序遍历

其实通过递归来实现前中后序的代码区别不大,而且就是顺序的问题,下面直接给出递归代码(对照着代码结合上面的好几段的解释应该很清晰了)

void PerOrder(BTNode* node)
{
	if (node)
	{
		VisitBTNode(node);
		if (node->left) PerOrder(node->left);
		if (node->right) PerOrder(node->right);
	}
}

void PerOrderBTree(BinaryTree* tree)
{
	if (tree->root) PerOrder(tree->root);
}

这里不得不写两个函数实现嵌套调用。
因为我们将树这一结构封装了起来,调用遍历函数时应当将要遍历的树作为参数,但是递归调用时,我们无法得到这棵树的字树,只能得到根节点的子节点,故这里递归的实现是通过传入“BTNode”类型的参数实现的

2.中序遍历

就是改变了

        VisitBTNode(node);
		if (node->left) PerOrder(node->left);
		if (node->right) PerOrder(node->right);

这三个语句的顺序(后序也是)

直接上中序代码

//二叉树的中序遍历函数
void InOrder(BTNode* node)
{
	if (node)
	{
		if (node->left) InOrder(node->left);
		VisitBTNode(node);
		if (node->right) InOrder(node->right);
	}
}

void InOrderBTree(BinaryTree* tree)
{
	if (tree->root) InOrder(tree->root);
}
3.后序遍历

上代码:

//二叉树的后序遍历函数
void PostOrder(BTNode* node)
{
	if (node)
	{
		if (node->left) PostOrder(node->left);
		if (node->right) PostOrder(node->right);
		VisitBTNode(node);
	}
}

void PostOrderBTree(BinaryTree* tree)
{
	if (tree->root) PostOrder(tree->root);
}

下面再来个把三个代码整合在一起的调试程序,这里还是用上面那棵二叉树作为例子,把图再贴一遍:
在这里插入图片描述
调试程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct BTNode { //二叉树结点     
	char data;//每个节点中存放的数据
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

typedef struct {//二叉树
	BTNode* root;
	int cnt; //节点数目
}BinaryTree;

//二叉树的节点创建函数
BTNode* CreateBTNode(char e) //传入一个数据,创建相应的节点
{
	BTNode* bn = (BTNode*)malloc(sizeof(BTNode));
	bn->data = e;
	bn->right = bn->left = NULL;
	return bn;
}
//二叉树的初始化函数
BinaryTree* InitBinaryTree(BTNode* e)//传入一个节点,创建以该节点为根节点的树
{
	BinaryTree* bt = (BinaryTree*)malloc(sizeof(BinaryTree));
	bt->root = e;
	bt->cnt++;
	return bt;
}
//二叉树的节点插入函数(flag为1表示新节点为左孩子节点,为0表示新节点为右边孩子节点)
//child表示插入的节点,parent表示child节点的父节点
void InsertBTree(BinaryTree* tree, BTNode* child, BTNode* parent, int flag)
{
	if (flag == 1) parent->left = child;
	else parent->right = child;
	tree->cnt++;
}
//访问二叉树的节点的函数(这里只进行打印)
void VisitBTNode(BTNode* a)
{
	if (a) printf("%c ", a->data);
}
//初始化一棵具体的树 
BinaryTree* Tree() {
	BTNode* a = CreateBTNode('A');
	BTNode* b = CreateBTNode('B');
	BTNode* c = CreateBTNode('C');
	BTNode* d = CreateBTNode('D');
	BTNode* e = CreateBTNode('E');
	BTNode* f = CreateBTNode('F');
	BTNode* g = CreateBTNode('G');
	BTNode* h = CreateBTNode('H');
	BTNode* k = CreateBTNode('K');
	BinaryTree* tree = InitBinaryTree(a);
	InsertBTree(tree, b, a, 1);
	InsertBTree(tree, e, a, 0);
	InsertBTree(tree, c, b, 0);
	InsertBTree(tree, d, c, 1);
	InsertBTree(tree, f, e, 0);
	InsertBTree(tree, g, f, 1);
	InsertBTree(tree, h, g, 1);
	InsertBTree(tree, k, g, 0);
	return tree;
}
//二叉树的先序遍历函数
void PerOrder(BTNode* node)
{
	if (node)
	{
		VisitBTNode(node);
		if (node->left) PerOrder(node->left);
		if (node->right) PerOrder(node->right);
	}
}

void PerOrderBTree(BinaryTree* tree)
{
	if (tree->root) PerOrder(tree->root);
}

//二叉树的中序遍历函数
void InOrder(BTNode* node)
{
	if (node)
	{
		if (node->left) InOrder(node->left);
		VisitBTNode(node);
		if (node->right) InOrder(node->right);
	}
}

void InOrderBTree(BinaryTree* tree)
{
	if (tree->root) InOrder(tree->root);
}

//二叉树的后序遍历函数
void PostOrder(BTNode* node)
{
	if (node)
	{
		if (node->left) PostOrder(node->left);
		if (node->right) PostOrder(node->right);
		VisitBTNode(node);
	}
}

void PostOrderBTree(BinaryTree* tree)
{
	if (tree->root) PostOrder(tree->root);
}

int main()
{
	BinaryTree* tree = Tree();
	printf("先序序列为:");
	PerOrderBTree(tree);
	printf("\n");
	printf("中序序列为:");
	InOrderBTree(tree);
	printf("\n");
	printf("后序序列为:");
	PostOrderBTree(tree);
	printf("\n");
	return 0;
}

非递归形式

递归形式二叉树遍历需要不断地调用自身函数,创建副本,一旦数据量过大,会有爆内存地风险,而且递归本身不太好把握,稍不留神可能出现什么完全找不到原因地错误,那么我们能否不用递归来遍历二叉树呢。

当然可以。递归的实现本身就是靠栈来实现的,通过栈将每次递归调用的函数信息记录下来。于是,这也给了我们一些启发,去用栈来记录访问的节点的状态,出栈的时候也就相当于递归中的“归”,上一个节点变为栈顶元素

1.先序遍历

先来分析先序遍历
在这里插入图片描述

还是以这张图为例:先序遍历顺序为A B C D E F G H K

由于先序遍历中根节点是先被访问的,也即只要访问路径达到一个节点,就会立刻对它进行访问,然后去找它左子树中的所有节点,最后去访问右子树的所有节点。

但是左子树中所有节点的访问需要从左子树的根来开始,右子树中所有节点的访问需要从右子树的根开始,我们把左子树里的东西都访问完成后才回去访问右子树。那么,右子树的根这时候我们该怎么去找呢。(注意:这里不能直接去用root->right去找,因为这样只能找到根节点的右孩子节点,我们这里访问左右子树的时候,任然是沿用“先根,再左子树,最后右子树”的思路,这里的右子树根节点就不容易找到了)

而栈可以解决这一问题:

  1. 先把根节点入栈
  2. 然后弹出栈顶元素
  3. 这时左右孩子节点(即左右子树的根节点)都存放进栈中(入栈顺序是先右后左,出栈顺序为先左后右)。
  4. 重复上述2,3步骤

可以看到,这样一来,每次都将右子树的根节点压在了左子树的根节点的下面,在对左边进行操作的时候也对右边的根节点进行了一个记录

下面给出栈的定义以及相关函数

/*对栈及其中操作进行定义(这里栈也只用一个数组来实现)*/
typedef struct {
	int top;//定义栈顶指针
	BTNode array[STKMAX];//用数组模拟栈
}stack;
//初始化栈的函数
stack* initstk()
{
	stack* stk = (stack*)malloc(sizeof(stack));
	memset(stk, 0, sizeof(stack));
	stk->top = 0;
	return stk;
}
//判满函数,返回0为满,返回1为未满
int stkfull(stack* stk)
{
	return (stk->top < STKMAX);
}
//入栈函数
void pushstk(BTNode* e, stack* stk)
{
	if (stkfull(stk)) stk->array[++stk->top] = *e;
	else printf("\n栈满!\n");
}
//判空函数,返回0为空,返回1为未空
int stkempty(stack* stk)
{
	return stk->top >= 1;
}
//读取栈顶元素的函数
BTNode stktop(stack* stk)
{
	if (stkempty(stk)) return stk->array[stk->top];
}
//将栈顶数据弹出
void stkpop(stack* stk)
{
	if (stkempty(stk)) stk->top--;
}

非递归先序遍历函数

//非递归先序遍历函数
void PerOrderBTree2(BinaryTree* tree)
{
	if (tree && tree->root)
	{
		stack* stk = initstk();
		BTNode node;
		pushstk(tree->root, stk);
		while (stkempty(stk)) {
			node = stktop(stk);
			stkpop(stk);
			VisitBTNode(&node);
			if (node.right) pushstk(node.right, stk);
			if (node.left) pushstk(node.left, stk);
		}
	}
}

最后,和前面一样的二叉树的遍历调试程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STKMAX 100

typedef struct BTNode { //二叉树结点     
	char data;//每个节点中存放的数据
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

typedef struct {//二叉树
	BTNode* root;
	int cnt; //节点数目
}BinaryTree;

//二叉树的节点创建函数
BTNode* CreateBTNode(char e) //传入一个数据,创建相应的节点
{
	BTNode* bn = (BTNode*)malloc(sizeof(BTNode));
	bn->data = e;
	bn->right = bn->left = NULL;
	return bn;
}
//二叉树的初始化函数
BinaryTree* InitBinaryTree(BTNode* e)//传入一个节点,创建以该节点为根节点的树
{
	BinaryTree* bt = (BinaryTree*)malloc(sizeof(BinaryTree));
	bt->root = e;
	bt->cnt++;
	return bt;
}
//二叉树的节点插入函数(flag为1表示新节点为左孩子节点,为0表示新节点为右边孩子节点)
//child表示插入的节点,parent表示child节点的父节点
void InsertBTree(BinaryTree* tree, BTNode* child, BTNode* parent, int flag)
{
	if (flag == 1) parent->left = child;
	else parent->right = child;
	tree->cnt++;
}
//访问二叉树的节点的函数(这里只进行打印)
void VisitBTNode(BTNode* a)
{
	if (a) printf("%c ", a->data);
}
//初始化一棵具体的树 
BinaryTree* Tree() {
	BTNode* a = CreateBTNode('A');
	BTNode* b = CreateBTNode('B');
	BTNode* c = CreateBTNode('C');
	BTNode* d = CreateBTNode('D');
	BTNode* e = CreateBTNode('E');
	BTNode* f = CreateBTNode('F');
	BTNode* g = CreateBTNode('G');
	BTNode* h = CreateBTNode('H');
	BTNode* k = CreateBTNode('K');
	BinaryTree* tree = InitBinaryTree(a);
	InsertBTree(tree, b, a, 1);
	InsertBTree(tree, e, a, 0);
	InsertBTree(tree, c, b, 0);
	InsertBTree(tree, d, c, 1);
	InsertBTree(tree, f, e, 0);
	InsertBTree(tree, g, f, 1);
	InsertBTree(tree, h, g, 1);
	InsertBTree(tree, k, g, 0);
	return tree;
}

/*对栈及其中操作进行定义(这里栈也只用一个数组来实现)*/
typedef struct {
	int top;//定义栈顶指针
	BTNode array[STKMAX];//用数组模拟栈
}stack;
//初始化栈的函数
stack* initstk()
{
	stack* stk = (stack*)malloc(sizeof(stack));
	memset(stk, 0, sizeof(stack));
	stk->top = 0;
	return stk;
}
//判满函数,返回0为满,返回1为未满
int stkfull(stack* stk)
{
	return (stk->top < STKMAX);
}
//入栈函数
void pushstk(BTNode* e, stack* stk)
{
	if (stkfull(stk)) stk->array[++stk->top] = *e;
	else printf("\n栈满!\n");
}
//判空函数,返回0为空,返回1为未空
int stkempty(stack* stk)
{
	return stk->top >= 1;
}
//读取栈顶元素的函数
BTNode stktop(stack* stk)
{
	if (stkempty(stk)) return stk->array[stk->top];
}
//将栈顶数据弹出
void stkpop(stack* stk)
{
	if (stkempty(stk)) stk->top--;
}

//非递归先序遍历函数
void PerOrderBTree2(BinaryTree* tree)
{
	if (tree && tree->root)
	{
		stack* stk = initstk();
		BTNode node;
		pushstk(tree->root, stk);
		while (stkempty(stk)) {
			node = stktop(stk);
			stkpop(stk);
			VisitBTNode(&node);
			if (node.right) pushstk(node.right, stk);
			if (node.left) pushstk(node.left, stk);
		}
	}
}

int main()
{
	BinaryTree* tree = Tree();
	PerOrderBTree2(tree);
	return 0;
}
2.中序遍历

中序遍历和先序遍历不同,他并不是走到某个节点就会对其访问,“左根右”的顺序决定了,遍历时会”一路向左“,一直去优先遍历左子树,左子树的左子树……如果走到尽头了,也就是走到叶子节点了,这时候左边没有东西了,再去遍历根节点(这里指叶子节点,因为把叶子节点当作一个子树),然后往右子树走,这时,右子树又会有它的左子树,再次一路向左……

但是走完某个右子树时,“左根右”完成了,我们又要回到上一个根节点。
比如这里在这里插入图片描述
先走过了B、D。这时,以D为根节点的子树走完后,将返回C这个节点,进行对C的访问,然后走C的右子树,然后返回到A

这个返回去的过程,需要我们将一路向左而来的节点都记录下来,比如这里的A、B。这样我才能在访问完B为根节点的子树后,将B从栈中弹出,使得A成为栈顶元素

用代码的形式展现如下,这里不占过多篇幅,就给出中序遍历函数,可将该函数替换上一个调试程序的先序遍历函数完成调试

//非递归中序遍历函数
void InOrderBTree2(BinaryTree* tree)
{
	stack* stk = initstk();
	BTNode* node;
	if (tree && tree->root)
	{
		node = tree->root;
		while (stkempty(stk)||node) {
			if (node)
			{
				pushstk(node, stk);
				node = node->left;
			}
			else
			{
				node = stktop(stk);
				stkpop(stk);
				VisitBTNode(node);
				node = node->right;
			}
		}
	}
}
3.后序遍历
3.1.单栈实现

后序遍历采用“左右根”的形式进行遍历,和上述中序遍历有着一些类似的地方,比如,都是需要先一路向左,但是后序遍历和中序遍历不同的是:
如果左边走不通,会向右走一步,再看看能不能一路向左

还是以上面的那棵树为例:

中序遍历我们走的时候(这里不是指遍历顺序,单纯是我们遍历的时候顺着节点走的路径):A->B->NULL(NULL指B的左孩子节点),然后访问B(这个才是遍历的开始),然后去B的右子树到达C->D->NULL(D的左边)->访问D
->NULL(D的右边)->访问C……

后序遍历的时候:A->B->C->D->NULL(D的左边)->NULL(D的右边)->访问D->NULL(C的右边)->访问C……

可以看到,中序遍历一路向左到B的时候就停下来开始访问了,后序遍历则是一直走到了D才开始遍历,因为他会在到达B的时候向右转

这个特点也决定了后序遍历的实现和中序遍历有相似之处,而且会比中序遍历更加复杂

  1. 在向左碰壁后还需要向右走一步再重新一路向左
  2. 需要判断什么时候该访问根节点,因为我们从左边回去会到达一次根节点,然后通过根节点去走右边,从右边走回去的时候也会到达一次根节点,这时候需要访问根节点,同时表明以该根节点为根的子树走完了。所以判断什么时候改访问根节点又是需要考虑的

针对上面两个问题我们可以直接在中序遍历的基础上改进,首先给出不需要改的地方

void PostOrderBTree3(BinaryTree* tree)
{
	stack* stk = initstk();
	if (tree && tree->root)
	{
		BTNode* node = tree->root;
		while (node || stkempty(stk)) 
		{
			if (node)
			{
				pushstk(node, stk);
				node = node->left;
			}
			else
			{
				
			}
		}
	}
}

解决第一个问题 else里面首先肯定需要有

if (node->right)
{
	node = node->right;
}

解决第二个问题,我们还需定义一个变量,用来存储上一次访问到的节点的地址

BTNode* flag = NULL;

那么上面的if判断条件也要随之变化

if (node->right && flag != node->right)
{
	node = node->right;
}

注意这里不是 flag==node->left 而是 flag != node->right

是因为我们下面的else里面是关于从右边返回之后就要遍历根节点的代码,下面的条件含有flag == node->right,而且我们没有从右边返回的时候未必上一次访问的节点就是node的左孩子节点,因为返回之后可能会向右再一路向左,不知道跑到哪里去了,但是从右边返回之后,上一次访问的节点必然是右孩子节点,这一点读者可以尝试一下两种写法最终调试结果的区别

下面给出else部分的完整代码

else
{
	node = stktop(stk);
	if (node->right && flag != node->right)
	{
		node = node->right;
	}
	else
	{
	    VisitBTNode(node);
		stkpop(stk);
		flag = node;
		node = NULL;
	}
}

里面最后两行首先是给flag标记上这次访问的节点,其次给node赋空值,是为了下次不要跳到最上层的if(node)里面去,跳进死循环,直接进入else,执行node = stktop(stk);

完整后序遍历代码如下:

//非递归后序遍历函数(单栈)
void PostOrderBTree3(BinaryTree* tree)
{
	stack* stk = initstk();
	if (tree && tree->root)
	{
		BTNode* node = tree->root;
		BTNode* flag = NULL;
		while (node || stkempty(stk)) 
		{
			if (node)
			{
				pushstk(node, stk);
				node = node->left;
			}
			else
			{
				node = stktop(stk);
				if (node->right && flag != node->right)
				{
					node = node->right;
				}
				else
				{
					VisitBTNode(node);
					stkpop(stk);
					flag = node;
					node = NULL;
				}
			}
		}
	}
}
3.2.双栈实现

主要思路如下图,不多解释
在这里插入图片描述

但是实际操作,我们是先进行左右孩子节点的入栈操作的,我们通过父亲找孩子容易,但是难以做到通过孩子找父亲。也就是“左右根”这种入栈顺序从一开始就是很难行得通的

为了解决这个问题,我们可以看stk2,根在最下面

反正我们最终是为了得到stk2这样的栈,然后把元素挨个出栈,那为什么不能先把根入stk1,再把根捣鼓到stk2中去,同时把根的左右孩子依次入stk1呢,这样实现了根在最下面,而且能够避免了先操作孩子再操作父亲的局面

这样来看,这样又很像先序遍历的做法,事实上,先序遍历和后序遍历确实是顺序相反,利用两个栈实现了从先序变成后序

具体代码如下:

//非递归后序遍历函数(双栈)
void PostOrderBTree2(BinaryTree* tree)
{
	if (tree && tree->root)
	{
		stack* stk1 = initstk();
		stack* stk2 = initstk();
		BTNode* node = tree->root;
		pushstk(node, stk1);
		while (stkempty(stk1))
		{
			node = stktop(stk1);
			stkpop(stk1);
			pushstk(node, stk2);
			if (node->left) pushstk(node->left, stk1);
			if (node->right) pushstk(node->right, stk1);
		}
		while (stkempty(stk2))
		{
			node = stktop(stk2);
			stkpop(stk2);
			VisitBTNode(node);
		}
	}
}

到此,二叉树的各种遍历方式都讲完力

最后,把所有非递归的代码包括调试整到一起(主要是方便我自己查看doge)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STKMAX 100

typedef struct BTNode { //二叉树结点     
	char data;//每个节点中存放的数据
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

typedef struct {//二叉树
	BTNode* root;
	int cnt; //节点数目
}BinaryTree;

//二叉树的节点创建函数
BTNode* CreateBTNode(char e) //传入一个数据,创建相应的节点
{
	BTNode* bn = (BTNode*)malloc(sizeof(BTNode));
	bn->data = e;
	bn->right = bn->left = NULL;
	return bn;
}
//二叉树的初始化函数
BinaryTree* InitBinaryTree(BTNode* e)//传入一个节点,创建以该节点为根节点的树
{
	BinaryTree* bt = (BinaryTree*)malloc(sizeof(BinaryTree));
	bt->root = e;
	bt->cnt++;
	return bt;
}
//二叉树的节点插入函数(flag为1表示新节点为左孩子节点,为0表示新节点为右边孩子节点)
//child表示插入的节点,parent表示child节点的父节点
void InsertBTree(BinaryTree* tree, BTNode* child, BTNode* parent, int flag)
{
	if (flag == 1) parent->left = child;
	else parent->right = child;
	tree->cnt++;
}
//访问二叉树的节点的函数(这里只进行打印)
void VisitBTNode(BTNode* a)
{
	if (a) printf("%c ", a->data);
}
//初始化一棵具体的树 
BinaryTree* Tree() {
	BTNode* a = CreateBTNode('A');
	BTNode* b = CreateBTNode('B');
	BTNode* c = CreateBTNode('C');
	BTNode* d = CreateBTNode('D');
	BTNode* e = CreateBTNode('E');
	BTNode* f = CreateBTNode('F');
	BTNode* g = CreateBTNode('G');
	BTNode* h = CreateBTNode('H');
	BTNode* k = CreateBTNode('K');
	BinaryTree* tree = InitBinaryTree(a);
	InsertBTree(tree, b, a, 1);
	InsertBTree(tree, e, a, 0);
	InsertBTree(tree, c, b, 0);
	InsertBTree(tree, d, c, 1);
	InsertBTree(tree, f, e, 0);
	InsertBTree(tree, g, f, 1);
	InsertBTree(tree, h, g, 1);
	InsertBTree(tree, k, g, 0);
	return tree;
}

/*对栈及其中操作进行定义(这里栈也只用一个数组来实现)*/
typedef struct {
	int top;//定义栈顶指针
	BTNode* array[STKMAX];//用指针数组模拟栈
}stack;
//初始化栈的函数
stack* initstk()
{
	stack* stk = (stack*)malloc(sizeof(stack));
	memset(stk, 0, sizeof(stack));
	stk->top = 0;
	return stk;
}
//判满函数,返回0为满,返回1为未满
int stkfull(stack* stk)
{
	return (stk->top < STKMAX);
}
//入栈函数
void pushstk(BTNode* e, stack* stk)
{
	if (stkfull(stk)) stk->array[++stk->top] = e;
	else printf("\n栈满!\n");
}
//判空函数,返回0为空,返回1为未空
int stkempty(stack* stk)
{
	return stk->top >= 1;
}
//读取栈顶元素的函数
BTNode* stktop(stack* stk)
{
	if (stkempty(stk)) return stk->array[stk->top];
}
//将栈顶数据弹出
void stkpop(stack* stk)
{
	if (stkempty(stk)) stk->top--;
}

//非递归先序遍历函数
void PerOrderBTree2(BinaryTree* tree)
{
	if (tree && tree->root)
	{
		stack* stk = initstk();
		BTNode* node;
		pushstk(tree->root, stk);
		while (stkempty(stk)) {
			node = stktop(stk);
			stkpop(stk);
			VisitBTNode(node);
			if (node->right) pushstk(node->right, stk);
			if (node->left) pushstk(node->left, stk);
		}
	}
}

//非递归中序遍历函数
void InOrderBTree2(BinaryTree* tree)
{
	stack* stk = initstk();
	if (tree && tree->root)
	{
		BTNode* node = tree->root;
		while (node || stkempty(stk)) {
			if (node)
			{
				pushstk(node, stk);
				node = node->left;
			}
			else
			{
				node = stktop(stk);
				stkpop(stk);
				VisitBTNode(node);
				node = node->right;
			}
		}
	}
}



//非递归后序遍历函数(双栈)
void PostOrderBTree2(BinaryTree* tree)
{
	if (tree && tree->root)
	{
		stack* stk1 = initstk();
		stack* stk2 = initstk();
		BTNode* node = tree->root;
		pushstk(node, stk1);
		while (stkempty(stk1))
		{
			node = stktop(stk1);
			stkpop(stk1);
			pushstk(node, stk2);
			if (node->left) pushstk(node->left, stk1);
			if (node->right) pushstk(node->right, stk1);
		}
		while (stkempty(stk2))
		{
			node = stktop(stk2);
			stkpop(stk2);
			VisitBTNode(node);
		}
	}
}

//非递归后序遍历函数(单栈)
void PostOrderBTree3(BinaryTree* tree)
{
	stack* stk = initstk();
	if (tree && tree->root)
	{
		BTNode* node = tree->root;
		BTNode* flag = NULL;
		while (node || stkempty(stk)) 
		{
			if (node)
			{
				pushstk(node, stk);
				node = node->left;
			}
			else
			{
				node = stktop(stk);
				if (node->right && flag != node->right)
				{
					node = node->right;
				}
				else
				{
					VisitBTNode(node);
					stkpop(stk);
					flag = node;
					node = NULL;
				}
			}
		}
	}
}

int main()
{
	BinaryTree* tree = Tree();

	printf("先序遍历:");
	PerOrderBTree2(tree);
	printf("\n");

	printf("中序遍历:");
	InOrderBTree2(tree);
	printf("\n");

	printf("后序遍历:");
	PostOrderBTree3(tree);
	//PostOrderBTree2(tree);
	printf("\n");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值