• [复习整理] (一)二叉树遍历


    二叉树遍历

    介绍常规的三种递归遍历方法(先中后)以及非递归的先序、中序、后序、层序遍历
    

    Prerequisite

    对二叉树这种数据结构有一定了解
    遍历二叉树的目的是为了访问到每个结点且按照某一特定的顺序

    二叉树

    先序遍历

    1.遍历顺序

    一定要记住 子树的->->

    2.递归遍历

    递归的特点就是代码可读性高,容易理解

    /*先序遍历,以rt为根的树*/ //根->左->右
    template <class T>
    void BinaryTree<T>::PreOrder(const Node* rt, void(*Visit)(const T&)) const {
    	if (rt != nullptr) {
    		(*Visit)(rt->data);
    		PreOrder(rt->left_child, Visit);
    		PreOrder(rt->right_child, Visit);
    	}
    } 
    

    3.非递归遍历

    先分析先序遍历效果:如上图先序遍历的顺序为ABCDEFGHK
    根总是先被访问,之后被访问的就是他的左孩子,左孩子又称为新的根,于是继续访问他的左孩子,直到这个根没有左孩子,之后访问他的右儿子,如果他没有右儿子,如图中的D ,追溯到他的父节点去访问右儿子,一直追溯,直到有了右儿子,如EA的右儿子, E又作为新的根节点先被访问 ,继续一开始的遍历。
    可能要被我绕晕了,简言之,得到根节点之后先对其访问,然后要访问全部的左儿子,最后再是右儿子。所以,一颗子树的右儿子要放在最后访问,就好比压在最下面,等上面的根和左儿子访问过了在出来,于是,这里我们用到了
    上代码!

    /*非递归先序遍历*/
    template <class T>
    void BinaryTree<T>::NoRecurringPreOrder(const Node* rt, void(*Visit)(const T&)) const {
    	if (rt == nullptr) {
    		return;
    	}
    	stack<Node*> s;
    	Node* temp = nullptr;
    	s.push(rt);
    	while (s.empty() == false) {
    		temp = s.top();
    		s.pop();
    		(*Visit)(temp->data);
    		if (temp->right_child != nullptr) {		//右节点后访问,先压入栈
    			s.push(temp->right_child);
    		}
    		if (temp->left_child != nullptr) {
    			s.push(temp->left_child);
    		}
    	}
    }
    

    中序遍历

    1.遍历顺序

    子树的->->

    2.递归遍历

    代码:

    /*中序遍历*/   //左->根->右
    template <class T>
    void BinaryTree<T>::InOrder(const Node* rt, void(*Visit)(const T&)) const {
    	if (rt != nullptr) {
    		InOrder(rt->left_child, Visit);
    		(*Visit)(rt->data);
    		InOrder(rt->right_child, Visit);
    	}
    }
    

    3.非递归遍历

    遍历效果:上图遍历顺序为BDCAEHGKF

    遍历思路:一路向左!直到这个左孩子作为根的时候没有它自己的左孩子,那就先访问他,因为此时按照遍历顺序->->,他被当成根了(没有左孩子),访问他完之后就取访问他的右儿子,继续一路向左,重复过程...

    伪代码:当前根不为空的时候=>压入=>他成为他的左儿子=>循环,如果当前是空=>取栈顶(当前根)访问他,并且pop()=>他成为他的右儿子=>循环,栈空或者最初根为nullptr的时候退出。

    上代码:

    /*非递归中序遍历*/
    template <class T>
    void BinaryTree<T>::NoRecurringInOrder(Node* rt, void(*Visit)(const T&)) const {
    	Node* temp = rt;
    	stack<Node*> s;
    	while (temp || s.empty() == false) {
    		if (temp) {
    			s.push(temp);
    			temp = temp->left_child;
    		}
    		else {						//没有左孩子了,就取当前栈顶(是上一个根的左孩子)访问,并pop
    			temp = s.top();				
    			s.pop();
    			(*Visit)(temp->data);
    			temp = temp->right_child;
    		}
    	}
    }
    

    后序遍历

    1.遍历顺序

    子树的->->

    2.递归遍历

    代码:

    /*后序遍历*/ //左->右->根
    template <class T>
    void BinaryTree<T>::PostOrder(const Node* rt, void(*Visit)(const T&)) const {
    	if (rt) {
    		PostOrder(rt->left_child, Visit);
    		PostOrder(rt->right_child, Visit);
    		(*Visit)(rt->data);
    	}
    }
    

    3.非递归遍历

    遍历效果:DCBHKGFEA

    关于思路

    后序遍历的非递归就比前两种的要复杂一点了,也比较难理解,因为根是最后被访问的,而在栈顶的元素当做根的话并不知道他的儿子们有没有被访问过,所以开一个栈不够,起码还要一个栈来保存一个标记,这个标记用来记录他的儿子们有没有被访问过。

    关于方法

    方法有很多种,可以开一个布尔栈放入对应根节点的标记,判断当前根已经被访问过儿子们了即可出栈,否则就压入他的两个儿子(按顺序是先左后右,又因为栈的关系,所以先压入右儿子)。

    这里呢我就介绍一种方法:开一个栈,每次节点压入都压两次,出栈的时候判断栈顶元素是否和他相同,如果两个相同,就说明他的儿子没有被访问(压入栈),就先压入他的右孩子,再左孩子,注意每次压入都是压两个,空节点不入栈。

    代码:

    /*非递归后序遍历*/
    void BinaryTree<T>::NoRecurringPostOrder(Node* rt, void(*Visit)(const T&)) const {
    	if (rt == nullptr) {
    		return;
    	}
    	stack<Node*> s;
    	Node* temp = rt;
    	s.push(temp);
    	s.push(temp);
    	while (s.empty() == false) {
    		temp = s.top();
    		s.pop();
    		if (!s.empty() && temp == s.top()) {//栈空了就只剩最后一个,直接访问
    			if (temp->right_child) {		//先压入右孩子
    				s.push(temp->right_child);
    				s.push(temp->right_child);
    			}
    			if (temp->left_child) {	
    				s.push(temp->left_child);
    				s.push(temp->left_child);
    			}
    		}
    		else {
    			(*Visit)(temp->data);
    		}
    	}
    }
    
    

    目前我看到的方法不管是开两个栈还是一个栈,需要的空间都是总节点数的两倍。

    层序遍历

    1.遍历顺序

    按照层数从左往右依次遍历

    上图遍历效果:ABECFDGHK

    2.利用队列层序遍历

    思路:访问的顺序像排队一样逐层遍历,每层的下面一层,按照从左到右的顺序在当前层的最后排好队,当前层也是按照从左往右的顺序被排好了,那在访问当前层的节点时将儿子们(下一层)排入尾,这样就可以实现了。可以看成是一种递推,把根看成最初的一个人在排队

    上代码:

    /*层序遍历,利用队列,也可以利用数组,然后用两个指针去控制入和出*/
    template <class T>
    void BinaryTree<T>::LevelOrder(Node* rt, void(*Visit)(const T&)) const {
    	queue<Node*> q;
    	Node* temp;
    	if (rt != nullptr) {
    		q.push(rt);
    	}
    	while (!q.empty()) {
    		temp = q.front();
    		q.pop();		//pop的位置?
    		(*Visit)(temp->data);
    		if (temp->left_child) {
    			q.push(temp->left_child);
    		}
    		if (temp->right_child) {
    			q.push(temp->right_child);
    		}
    	}
    }
    

    3.利用数组实现

    还有一种利用数组来实现的方法,网上找到的,大致思路其实就是用数组实现了队列的功能:通过两个指针 in out(都是下标)控制,个人认为会浪费资源空间,不过思路很好。

    void FloorPrint(pTreeNode Tree)  //层序遍历
    {
    	pTreeNode temp[100];   //创建pTreeNode指针类型的指针数组
    	int in = 0;
    	int out = 0;
    
    	temp[in++] = Tree;  //先保存二叉树根节点
    
    	while (in > out)
    	{
    		if (temp[out])
    		{
    			cout << temp[out]->data << " → ";
    			temp[in++] = temp[out]->leftPtr;
    			temp[in++] = temp[out]->rightPtr;
    		}
    		out++;
    	}
    }
    作者:askunix_hjh
    来源:CSDN
    原文:https://blog.csdn.net/m0_37925202/article/details/80796010
    版权声明:本文为博主原创文章,转载请附上博文链接!
    

    小结

    二叉树的遍历很重要(我们老师是这么说的。。可能只是为了应付考试吧)我还是比较推荐在理解的基础上掌握知识,要不断的在脑海中模拟遍历的过程,递归的和非递归的。

  • 相关阅读:
    ASP.NET伪静态配置
    拖动条控件 (UISlider)
    进度环控件 (UIActivityIndicatorView)
    进度条控件 (UIProgressView)
    图像控件 (UIImageView)
    分段控件 (UISegmentedControl)
    CocoaPods安装和使用教程
    iOS 在UILabel显示不同的字体和颜色
    iOS开源项目(新)
    iOS开源项目(旧)
  • 原文地址:https://www.cnblogs.com/coyote-waltz/p/10482092.html
Copyright © 2020-2023  润新知