数据结构——遍历二叉树

数据结构------树、二叉树基础概念-CSDN博客https://blog.csdn.net/wy_05136/article/details/163543888?spm=1001.2014.3001.5502

一、二叉树遍历原理

二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

二、二叉树结点定义

cpp 复制代码
/* 二叉链表有效结点 */
typedef int ElemType;
 
typedef struct BTNode
{
    ElemType data;             //数据域
    struct BTNode* leftchild;  //左孩子指针域
    struct BTNode* rightchild; //右孩子指针域
}BTNode;

三、二叉树遍历方法

(一)前序遍历(根---左---右)

1.遍历规则

若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。

2.遍历算法及代码实现

(1)递归版
cpp 复制代码
void preOrder(BTNode* root)
{
	if (root == NULL) return;

	//处理根结点
	printf("%c ", root->data);
	
	//递归处理左子树
	preOrder(root->leftchild);

	//递归处理右子树
	preOrder(root->rightchild);
}
(2)非递归版(单栈法)
cpp 复制代码
void preOrder_NoRecursion(BTNode* root)
{
	//0.判空,空树直接返回
	if (root == NULL) return;

    //1.申请一个栈,并将根节点入栈
	std::stack<BTNode*> st;
	st.push(root);

    //2.进入while循环,循环条件:栈不为空
	while (!st.empty())
	{
        //3.取出栈顶结点,并将其值打印处理
		BTNode* tmp = st.top();
		printf("%c ", tmp->data);
		st.pop();

        //4.将刚处理的tmp结点的两个孩子按照先右再左的顺序进行判断处理,如果存在则压入栈中
		if (tmp->rightchild != NULL)
		{
			st.push(tmp->rightchild);
		}
		if (tmp->leftchild != NULL)
		{
			st.push(tmp->leftchild);
		}
	}
    //5.当while结束,即栈空,遍历结束
}

(二)中序遍历(左---根---右)

1.遍历规则

若二叉树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

2.遍历算法及代码实现

(1)递归版
cpp 复制代码
void inOrder(BTNode* root)
{
	if (root == NULL) return;

    //递归处理左子树
    inOrder(root->leftchild);

	//处理根结点
	printf("%c ", root->data);

	//递归处理右子树
	inOrder(root->rightchild);
}
(2)非递归版(单栈法)
cpp 复制代码
void inOrder_NoRecursion(BTNode* root)
{
	//0.判空,空树直接返回
	if (root == NULL) return;

	//1.申请栈存储节点,tag标记节点是否为第一次遇见
	bool tag = true;
	std::stack<BTNode*> st;
	st.push(root);

	//2.栈不为空持续循环遍历
	while (!st.empty())
	{
		//3.首次遇见节点且存在左子树,持续向左入栈,捋完所有左分支
		while (tag && st.top()->leftchild != NULL)
		{
			st.push(st.top()->leftchild);
		}

		//4.左子树处理完毕,访问当前根节点、出栈
		BTNode* tmp = st.top();
		printf("%c ", tmp->data);
		st.pop();

		//5.判断并处理右子树,更新标记位状态
		if (tmp->rightchild != NULL)
		{
			//有右孩子:右节点入栈,标记为首次访问
			st.push(tmp->rightchild);
			tag = true;
		}
		else
		{
			//无右孩子:当前节点分支遍历完毕,后续节点为回退二次访问
			tag = false;
		}
	}
	//6.栈空,整棵树中序遍历结束
}

(三)后续遍历(左---右---根)

1.遍历规则

若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

2.遍历算法及代码实现

(1)递归版
cpp 复制代码
void postOrder(BTNode* root)
{
	if (root == NULL) return;

    //递归处理左子树
    postOrder(root->leftchild);

    //递归处理右子树
	postOrder(root->rightchild);

	//处理根结点
	printf("%c ", root->data);
}
(2)非递归版
法一:双栈法
cpp 复制代码
void postOrder_NoRecursion1(BTNode* root)
{
    //0.判空,空树直接返回
    if (root == NULL) return;

    //1.申请两个栈:S1用于遍历结点,S2用于逆序存储后序结果
    std::stack<BTNode*> S1;
    std::stack<BTNode*> S2;

    //2.根结点先入遍历栈S1
    S1.push(root);

    //3.S1不为空,持续遍历所有结点
    while (!S1.empty())
    {
        //4.S1栈顶结点出栈,存入结果栈S2(暂不打印)
        BTNode* tmp = S1.top();
        S2.push(tmp);
        S1.pop();

        //5.先左、后右入S1,保证后续S2出栈顺序为:左---右---根
        if (tmp->leftchild != NULL) S1.push(tmp->leftchild);
        if (tmp->rightchild != NULL) S1.push(tmp->rightchild);
    }

    //6.S1遍历完毕,S2中结点逆序输出即为后序遍历结果
    while (!S2.empty())
    {
        printf("%c ", S2.top()->data);
        S2.pop();
    }
}
法二:单栈法
cpp 复制代码
void postOrder_NoRecursion2(BTNode* root)
{
	//0.判空,空树直接返回
	if (root == NULL) return;

    ///1.申请一个栈,额外申请bool tag,再额外申请BTNode *preNode
	std::stack<BTNode*> st;
	st.push(root);
	bool tag = true;         //标记是否首次访问节点
	BTNode* preNode = NULL;  //记录上一个已访问的节点

	//3.进入while循环,循环条件是栈不空即可
	while (!st.empty())
	{
		//4.1 tag为true:栈顶是新节点,优先遍历左子树,捋完所有左分支
		while (tag == true && st.top()->leftchild != NULL)
		{
			st.push(st.top()->leftchild);
			tag = false;
		}
		//4.2 tag为false:栈顶是回溯老节点,左子树已处理完毕,准备处理右子树

		//5.判定右子树是否未被处理
		//右孩子存在且不是上一个访问节点 说明右子树未遍历
		if (st.top()->rightchild != NULL && st.top()->rightchild != preNode)
		{
			st.push(st.top()->rightchild);
			tag = true;
		}

		//5.2 右子树为空 / 右子树已处理完毕:左右处理完成,访问根节点
		else
		{
			//6.处理根节点:出栈打印,更新标记位与前驱结点
			BTNode* tmp = st.top();
			printf("%c ", tmp->data);
			st.pop();
			tag = false;
			preNode = tmp;
		}
	}
	//7.栈空,后序遍历结束
}

(四)层序遍历

1.普通

(1)遍历规则

若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

(2)遍历算法及代码实现
cpp 复制代码
void Level_Traverse(BTNode* root)
{
	//0.判空,空树直接返回
	if (root == NULL) return;

	//1.申请队列,根结点入队
	std::queue<BTNode*> q;
	q.push(root);

	//2.队列不为空,持续循环遍历
	while (!q.empty())
	{
		//3.取出队头结点、访问数据、出队
		BTNode* tmp = q.front();
		printf("%c ", tmp->data);
		q.pop();

		//先左后右,子结点存在则入队,保证层序从左到右
		if (tmp->leftchild != NULL)
		{
			q.push(tmp->leftchild);
		}
		if (tmp->rightchild != NULL)
		{
			q.push(tmp->rightchild);
		}
	}
	//4.队列为空,遍历完成
}

2.正S

(1)遍历规则

若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在奇数层中,按从左到右的顺序对结点逐个访问,在偶数层中,按从右到左的顺序对结点逐个访问。

(2)遍历算法及代码实现
cpp 复制代码
void S_Level_Traverse(BTNode* root)
{
	//0.判空,空树直接返回
	if (root == NULL) return;

    //1.根结点(第一层/奇数层)入栈S1
	std::stack<BTNode*> s1, s2;
	s1.push(root);

	//2.两个栈任意一个非空,持续遍历
	while (!s1.empty() || !s2.empty())
	{
		//3.处理奇数层:S1非空、S2为空,从左向右打印
		while (!s1.empty())
		{
			BTNode* tmp = s1.top();
			printf("%c ", tmp->data);
			s1.pop();

			//先右后左入栈,保证下一层(偶数层)正序输出
			if (tmp->rightchild != NULL)
			{
				s2.push(tmp->rightchild);
			}
			if (tmp->leftchild != NULL)
			{
				s2.push(tmp->leftchild);
			}
		}

		//4.处理偶数层:S2非空、S1为空,从右向左打印
		while (!s2.empty())
		{
			BTNode* tmp = s2.top();
			printf("%c ", tmp->data);
			s2.pop();

			//先左后右入栈,保证下一层(奇数层)逆序输出
			if (tmp->leftchild != NULL)
			{
				s1.push(tmp->leftchild);
			}
			if (tmp->rightchild != NULL)
			{
				s1.push(tmp->rightchild);
			}
		}
	}
}

3.倒S

(1)遍历规则

若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在奇数层中,按从右到左的顺序对结点逐个访问,在偶数层中,按从左到右的顺序对结点逐个访问。

(2)遍历算法及代码实现
cpp 复制代码
void S_Level_Traverse(BTNode* root)
{
	//0.判空,空树直接返回
	if (root == NULL) return;

    //1.根结点(第一层/奇数层)入栈S1
	std::stack<BTNode*> s1, s2;
	s1.push(root);

	//2.两个栈任意一个非空,持续遍历
	while (!s1.empty() || !s2.empty())
	{
		//3.处理奇数层:S1非空、S2为空,从右向左打印
		while (!s1.empty())
		{
			BTNode* tmp = s1.top();
			printf("%c ", tmp->data);
			s1.pop();

			//先左后右入栈,保证下一层(偶数层)逆序输出
            if (tmp->leftchild != NULL)
			{
				s2.push(tmp->leftchild);
			}
			if (tmp->rightchild != NULL)
			{
				s2.push(tmp->rightchild);
			}
		}

		//4.处理偶数层:S2非空、S1为空,从左向右打印
		while (!s2.empty())
		{
			BTNode* tmp = s2.top();
			printf("%c ", tmp->data);
			s2.pop();

			//先右后左入栈,保证下一层(奇数层)正序输出
			if (tmp->rightchild != NULL)
			{
				s1.push(tmp->rightchild);
			}
			if (tmp->leftchild != NULL)
			{
				s1.push(tmp->leftchild);
			}

		}
	}
}
相关推荐
萧西待水16 小时前
奥赛一本通 1447 靶形数独
算法·深度优先
梦帮科技18 小时前
AI 音乐产品的发布工程:验证门、数据发布、回滚与生产运维纪律
数据结构·数据库·架构·node.js·音视频·动态规划·推荐算法
泯泷18 小时前
那段文字是谁删的?Yjs 14 正式版之前,一套删除归属方案的实现与边界
前端·javascript·算法
hold?fish:palm18 小时前
34 合并K个升序链表
javascript·算法·链表
C++ 老炮儿的技术栈19 小时前
我们在设计tcp协议时,要传一个字符串过去,报文:头十长度十内容,是否要把‘\0‘也填入,长度是否包含‘\0‘
开发语言·数据结构·c++·mfc·c
Navigator_Z20 小时前
LeetCode //C - 1252. Cells with Odd Values in a Matrix
c语言·算法·leetcode
语戚1 天前
力扣 1621. 大小为K的不重叠线段的数目:动态规划(Java 实现)
java·算法·leetcode·动态规划·力扣·dp
青山木1 天前
Hot 100 --- 最长有效括号
java·数据结构·算法·leetcode·动态规划
这料鬼有毒1 天前
二刷hot100-1143.最长公共子序列
算法·leetcode·动态规划
政企项目老覃1 天前
边缘 AI 推理部署:安防零售场景下的模型裁剪与端侧落地实践
人工智能·程序人生·算法·性能优化·vllm