高级数据结构 | 二叉树遍历 —递归与非递归实现:先序、中序、后序遍历二叉树 ...

递归遍历二叉树
/* 先序遍历 */
void PreOrder(struct BtNode* p)
{
	if (NULL != p)
	{
		printf("%c ", p->data);
		PreOrder(p->leftchild);
		PreOrder(p->rightchild);
	}
}
/* 中序遍历 */
void InOrder(struct BtNode* p)
{
	if (NULL != p)
	{
		InOrder(p->leftchild);
		printf("%c ", p->data);
		InOrder(p->rightchild);
	}
}
/* 后序遍历 */
void PastOrder(struct BtNode* p)
{
	if (NULL != p)
	{
		PastOrder(p->leftchild);
		PastOrder(p->rightchild);
		printf("%c ", p->data);
	}
}

非递归

对于非递归的二叉树的遍历,可以看做是对栈的一种应用。其中对先序和中序的遍历是相同的,只是对获取到的结点数值输出顺序上有些略微不同罢了。而对于后序遍历,也只需要进行一些小的改动即可。

分析对于先序和中序的便利过程:

  1. 对于一个二叉树,我们在遍历它的左孩子时,可以把左孩子当做一个新的二叉树根结点继续向下遍历。而停止的条件就是找到叶子结点,也就是说该结点没有左右孩子,度为0 。

  2. 此时,我们利用栈回退至上层父结点,试图访问父结点的右孩子。如果该右孩子存在,那我们把该右孩子当做新的二叉树根结点向下遍历。如果该右孩子不存在,那么我们继续回退至再上一层,重复判断操作。

  3. 最后,不论是先序还是中序,在访问最后一个结点时都是也只能是二叉树最右边的叶子结点。并且,我们从左子树向上回退时进行了“根”的出栈操作。也就是说,在遍历右子树时,该右子树的根结点已经不再栈中。结合以上两点,在遍历完所有结点时,退出的条件应为 栈为空 && 结点度为0

下图为从根结点至最左边的叶子结点的一次遍历(图左),在遍历至叶子结点 C 的左孩子后发现为空结点(图左),后回退至父节点 C 试图访问右结点发现为空结点(图中),则回退至再上一层的 B 结点处,发现其有右孩子,将此右孩子当做新的二叉树根结点(图中)。最后重复上述步骤直至满足退出条件栈为空 && 结点度为0
在这里插入图片描述
分析对于后序的便利过程:

后序遍历的顺序为,左、右、根。很明显,上面使用的方法不适用与后序遍历,因为在我们遍历至右结点时,根结点是没有保存的。在上述的算法中,我们采用的是先将根结点入栈,如果左孩子为空,指针回退(出栈)至父节点,继续操作右孩子。因此,我们无法绕过根结点对右孩子进行访问。

因此,我们需要改变出入栈的方式,使我们在遍历至右孩子时,栈中任然保留着父节点的信息。

重新设计出入栈的方式:

  • 在出栈时,判断是否对该子树已完整遍历。也就是说在出栈时,判断该结点的右结点是否已经被遍历过。
    • 如果没有遍历过,则该次回退为左结点处回退,需要继续遍历右结点。那么该节点继续入栈,并且遍历其右子树。
    • 如果已经遍历过,则该次回退为右结点处回退,证明右子树已经被遍历完整。同时,左子树也已经遍历完整,可以输出根结点。
  • 根据以上的分析,我们需要一个flg 标记某个结点的右孩子是否被访问过。
    • 标记的方式为,每次回退前,将当前的有效结点(不为NULL的结点) 保存。

如下图所示,在 C 出栈时,发现C的右子树还没有遍历,则继续将C入栈(图左) 。直至发现 C 的左右子树都为空时,再次将 C 出栈回退至C结点处。此时C子树已经遍历完整,使用 flg 标记,再次回退至 B 结点处(图中)遍历右子树。 . . .  . . .  经过若干步后,如(图右)所示,在遍历完 F 结点后,将栈顶元素D出栈,回退至 D 结点处,而 F 结点已经遍历完整并且使用 flg 标记。
  此时,对于D结点来说左右子树都遍历完整了。输出D结点的值后,我们继续使用 flg 标记D结点,而自身回退直B结点。对于B结点来说,左右子树都已经访问完,输出B结点后回退至A结点 。继续按此方法进行下去,直到我们到达 H 结点后依据栈中保留的信息逐步回退至根节点A处,而对于此时的A来说, 栈为空即是终止条件。
在这里插入图片描述

非递归先序遍历
void NicePreOrder(struct BtNode* p)
{
	if (NULL == p)	return;
	std::stack<struct BtNode*> st;
	while (!st.empty() || p != NULL)			// 最终的退出条件
	{
		while (p != NULL)						// 一直向左遍历左子树
		{
			st.push(p);
			std::cout << p->data << " ";		// 在遍历时,每一步访问的都是根,可以输出
			p = p->leftchild;
		}		/* while 解释  表示,此结点没有左孩子 */
		p = st.top();	st.pop();				// 回退至父节点
		p = p->rightchild;						// 试探右结点
	}
	std::cout << std::endl;
}
非递归中序遍历
void NiceInOrder(struct BtNode* p)
{
	if (NULL == p)	return;
	std::stack<struct BtNode*> st;
	while (!st.empty() || p != NULL)
	{
		while (p != NULL)
		{
			st.push(p);
			p = p->leftchild;
		}
		p = st.top();	st.pop();
		std::cout << p->data << " ";
		p = p->rightchild;	/* 碰到的叶子结点,回退父节点,右子树碰到叶子结点。
								这三步的顺序刚好是中序遍历的顺序 */
	}
	std::cout << std::endl;
}
非递归后序遍历
void NicePastOrder(struct BtNode* p)
{
	if (NULL == p)	return;
	std::stack<struct BtNode*> st;
	struct BtNode* flg = NULL;
	while (!st.empty() || p != NULL)
	{
		while (p != NULL)
		{
			st.push(p);
			p = p->leftchild;
		}

		p = st.top(); st.pop();

		if (p->rightchild == NULL || p->rightchild == flg)
		{
			std::cout << p->data << " ";	/* NULL输出叶子结点,falg是回退的根结点 */
			flg = p;		/* 回退前使用 flg 保存已遍历的结点信息 */
			p = NULL;		/* p置空,表明无左树可遍历,在下一轮的循环中直接进行出栈回退操作 */
		}
		else
		{
			st.push(p);		// 继续入栈
			p = p->rightchild;
		}
	}
	std::cout << std::endl;
}
测试输出

下面对比非递归与递归两种方式的输出结果。

int main()
{
	const char* str[] = {
			"ABC##DE##F##G#H##",
			"ABCDEFGH#########",
			"A#B#C#D#E#F#G#H##",
			"ABCD##EF##G#H####",
			"A#B#CDF###E##" };
	
	int i = sizeof(str)/sizeof(str[0]);
	while (i--)
	{
		const char* pstr = str[i];
		BinaryTree root = NULL;
		root = CreateTree1(&pstr);
		strPreOrder(root);	printf("\n");
		std::cout << "---------------" << std::endl;

		/* 递归输出结果 */				/* 非递归输出结果 */
		PreOrder(root); printf("%*s\t",i,"");		 NicePreOrder(root);
		InOrder(root);	printf("%*s\t", i,"");		 NiceInOrder(root);
		PastOrder(root); printf("%*s\t", i,"");		 NicePastOrder(root);


		std::cout << std::endl;
	}
	return 0;
}

297. 二叉树的序列化与反序列化
根据中序遍历创建二叉树(非递归实现)

这里使用先序遍历,然后通过先序再创建二叉树。除此之外,中序,后序,层序或者其他方式实现均可。

注:以下代码不能通过全部用例,因为在序列化时,数字字符使用的ascii码存放,因此只能存放[0,255]之间的数字。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:
     // Encodes a tree to a single string.
    string serialize(TreeNode* p) {
        string str = "";    // preorder
        if (nullptr == p)	return str;
        std::stack<TreeNode*> st;
        while (!st.empty() || p != nullptr)
        {
            while (p != nullptr)
            {
                st.push(p);
                str += p->val;	// 这里直接以ascii码存储,无法通过全部用例
                p = p->left;
            }
            str += "#";
            p = st.top();	st.pop();
            p = p->right;
        }
        str += "#";
        return str;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if (data.empty())	return nullptr;
        TreeNode* root = new TreeNode(data[0])
            , * p = root;
        int n = (int)data.size();
        stack<TreeNode*> st;
        st.push(p);
        for (int i = 1; i < n; ++i)
        {
            while (i < n && data[i] != '#') {       // 添加左孩子    //遇到左孩子为空时退出循环
                p->left = new TreeNode(data[i++]);
                st.push(p);
                p = p->left;
            }
            i += 1;
            while (i < n && data[i] == '#') {       // 处理右孩子空   //从左孩子回退的,同时右孩子也为空,回退至上一节点
                p = st.top(); st.pop();
                i += 1;
                while (!st.empty() && p->right != nullptr)     // 是从右孩子退回的(左右都遍历完了,继续回退)
                {
                    p = st.top(); st.pop();
                }
            }
            if (i < n && data[i] != '#') {          // 添加右孩子
                p->right = new TreeNode(data[i]);
                st.push(p);
                p = p->right;
            }
        }
        return root;
    }


};

// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));

这里我们可以约定一种简单协议。即数字和空全部用 ‘|’ 进行分割。例如下列树形结构,先序遍历结果使用序列化输出:在这里插入图片描述
由此,可以通过设计一个 parse 函数将序列化后的字符串,解析成为字符串数组。

void parse(string data, vector<string>& nums, bool is_print = false)
    {
        auto i = data.find('|');    // 从data[0]开始
        if (is_print)cout << "parse : "; // 调试,用于打印
        while (string::npos != i)
        {
            auto k = data.find('|', i + 1);    // 找第二个'|'
            int n = k - i - 1;      // 计算,两个'|'之间单词的长度
            if (k == string::npos)   // 结尾,退出
            {
                break;
            }
            string tmp = data.substr(i + 1, n);    // 从data中提取出单词
            if (is_print) cout << tmp << " ";		// 调试,打印
            nums.push_back(tmp);
            i = k;
        }
    }

全部代码参考:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
    void parse(string data, vector<string>& nums, bool is_print = false)
    {
        auto i = data.find('|');    // 
        if (is_print)cout << "parse : "; // 调试,用于打印
        while (string::npos != i)
        {
            auto k = data.find('|', i + 1);    // 找第二个'|'
            int n = k - i - 1;      // 计算,两个'|'之间单词的长度
            if (k == string::npos)   // 结尾,退出
            {
                break;
            }
            string tmp = data.substr(i + 1, n);    // 从data中提取出单词
            if (is_print) cout << tmp << " ";
            nums.push_back(tmp);
            i = k;
        }
    }
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* p) {
        if (nullptr == p)	return "";
        string str = "|";    // preorder
        std::stack<TreeNode*> st;
        while (!st.empty() || p != nullptr)
        {
            while (p != nullptr)
            {
                st.push(p);
                str += to_string(p->val) + "|";	// 将所有数字都用 '|' 分隔
                p = p->left;
            }
            str += "#|";
            p = st.top();	st.pop();
            p = p->right;
        }
        str += "#|";
        cout << "序列化:" << str << endl;
        return str;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if (data.empty())	return nullptr;

        stack<TreeNode*> st;
        vector<string> nums;
        parse(data, nums, true);
        int n = (int)nums.size();
        TreeNode* root = new TreeNode(stoi(nums[0]))
            , * p = root;
        st.push(root);

        for (int i = 1; i < n; ++i)
        {
            while (i < n && nums[i] != "#") {       // 添加左孩子    //遇到左孩子为空时退出循环
                p->left = new TreeNode(stoi(nums[i++]));
                st.push(p);
                p = p->left;
            }
            i += 1;
            while (i < n && nums[i] == "#") {       // 处理右孩子空   //从左孩子回退的,同时右孩子也为空,回退至上一节点
                p = st.top(); st.pop();
                i += 1;
                while (!st.empty() && p->right != nullptr)     // 是从右孩子退回的(左右都遍历完了,继续回退)
                {
                    p = st.top(); st.pop();
                }
            }
            if (i < n && nums[i] != "#") {          // 添加右孩子
                p->right = new TreeNode(stoi(nums[i]));
                st.push(p);
                p = p->right;
            }
        }
        return root;
    }
};

// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值