数据结构 - 二叉树遍历(递归,非递归)与构造

二叉树遍历和构造

  二叉树的遍历

  二叉树遍历其实很简单,三种遍历都是一样的,只不过顺序先后不一样罢了。

  先序遍历

  访问根结点,先序遍历左子树,先序遍历右子树

void PreOrder(BitTree T){
    if( T != nullptr){
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

  中序遍历

  中序遍历左子树,访问根结点,中序遍历右子树

void InOrder(BitTree T){
    if(T != nullptr){
        InOrder(T->lchild);
        visit(T);
        InOrder(T->rchild);
    }
}

  后序遍历

后序遍历左子树,后序遍历右子树,访问根结点

void PostOrder(BitTree T){
    if( T != nullptr ){
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        visit(T);
    }
}

  时间复杂度都为O(n),在递归遍历中,递归工作栈的栈深恰好为树的深度,在最坏情况下,二叉树是有n个结点且深度为 n 的单支树,遍历算法的空间复杂度为O(n)。

  利用非递归方式遍历二叉树

  借助栈,可以将二叉树的递归遍历转换为非递归算法。以中序遍历为例,给出中序遍历非递归算法。先扫描(并非访问)根结点的所有左结点并将它们一一进栈,然后出栈一个结点*p,访问它。然后继续扫描该结点的右孩子结点,将其进栈,在扫描该孩子结点的所有左结点并一一进栈,如此继续,直到栈空。

  中序遍历的非递归算法如下:

void InOrderWithoutRecursion(BitTree T){
    Stack<BitTree> S;           //辅助栈
    BitTree p = T;				//遍历指针
    while( p || !IsEmpty(S) ){	//栈不为空或p不为空时
        if( p ){				
            S.push(p);			//根指针进栈
            p = p->lchild;		//遍历左子树
        }else{
            S.pop(p);			//根指针退栈
            visit(p);			//cout << p->data;
            p = p->rchild;      //向右子树走
        }
    }    
}

  前序遍历的非递归算法:

void PreOrderWithoutRecursion(BitTree T)
{
	if (T == nullptr)
		return;
	BTNode* p = T;
	stack<BitNode*> s;
	while (!s.empty() || p)
	{
		if (p)
		{
			visit(p);		// cout << p->data;
			s.push(p);
			p = p->lchild;
		}
		else
		{
			p = s.top();
			s.pop();
			p = p->rchild;
		}
	}
	cout << endl;
}

  后序遍历的非递归算法:后序遍历的难点在于:需要判断上次访问的节点是位于左子树,还是右子树。若是位于左子树,则需跳过根节点,先进入右子树,再回头访问根节点;若是位于右子树,则直接访问根节点。直接看代码,代码中有详细的注释。

void PostOrderWithoutRecursion(BTNode* root)
{
	if (root == NULL)
		return;
	stack<BTNode*> s;
	//pCur:当前访问节点,pLastVisit:上次访问节点
	BTNode* pCur, *pLastVisit;
	//pCur = root;
	pCur = root;
	pLastVisit = NULL;
	//先把pCur移动到左子树最下边
	while (pCur)
	{
		s.push(pCur);
		pCur = pCur->lchild;
	}
	while (!s.empty())
	{
		//走到这里,pCur都是空,并已经遍历到左子树底端(看成扩充二叉树,则空,亦是某棵树的左孩子)
		pCur = s.top();
		s.pop();
		//一个根节点被访问的前提是:无右子树或右子树已被访问过
		if (pCur->rchild == NULL || pCur->rchild == pLastVisit)
		{
			cout << setw(4) << pCur->data;
			//修改最近被访问的节点
			pLastVisit = pCur;
		}
		/*这里的else语句可换成带条件的else if:
		else if (pCur->lchild == pLastVisit)//若左子树刚被访问过,则需先进入右子树(根节点需再次入栈)
		因为:上面的条件没通过就一定是下面的条件满足。仔细想想!
		*/
		else
		{
			//根节点再次入栈
			s.push(pCur);
			//进入右子树,且可肯定右子树一定不为空
			pCur = pCur->rchild;
			while (pCur)
			{
				s.push(pCur);
				pCur = pCur->lchild;
			}
		}
	}
	cout << endl;
}

  层次遍历

  层次遍历需要借助队列。先将二叉树根结点入队,然后初岁,访问该结点,若它有左子树,则将左子树根节点入队;若它有右子树,则将右子树根结点入队。然后出队,对出队结点访问,如此反复,直到队列为空。

void LevelOrder(BitTree T){
    if( T == nullptr){
        return ;
    }
    Queue<BitNode*> q;					//辅助队列
    q.push(T);							//将根结点入队	
    while(!q.empty()){					//队列不为空时
        int sz = q.size();				
        for(int i = 0; i < sz; i++){
            BitNode* cur = Q.front();	
            q.pop();					//队头元素出队	
            if(p->lchild != nullptr){
                q.push(p->lchild);		//左子树不为空时,左子树入队
            }
            if(p->rchild != nullptr){
                q.push(p->rchild);		//右子树不为空时,右子树入队
            }
        }
    }
}

  由遍历序列构造二叉树

  由二叉树的先序和中序序列可以唯一确定一棵二叉树,同样后序和中序序列也可以唯一确定一棵二叉树,但先序和后序不能确定一棵二叉树。

  先序和中序序列构造一棵二叉树

  思路:我们知道先序序列的第一个值其实就是根节点的值,这样我们就可以在中序序列中找到对应的根节点,那么根节点左边的数就全为左子树的结点,根节点右边的数就全为右子树的结点。根据这个想法,再递归的进行构造,见代码。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
private:
    unordered_map<int, int> index;  //借助哈希表,让我们快速的找到分割点,不用每次都去循环一遍
public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) {
            return nullptr;
        }
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};

  后序和中序序列构造一棵二叉树

  思路:我们知道后序序列的最后一个值其实就是根节点的值,这样我们就可以在中序序列中找到对应的根节点,那么根节点左边的数就全为左子树的结点,根节点右边的数就全为右子树的结点。根据这个想法,再递归的进行构造,见代码。

class Solution {
private:
    unordered_map<int,int> index; //借助哈希表,让我们快速的找到分割点,不用每次都去循环一遍
public:
    TreeNode* myBuildTree(vector<int>& postorder,vector<int>& inorder,int postorder_left,int postorder_right,int inorder_left,int inorder_right){
        if(postorder_left > postorder_right){
            return nullptr;
        }
        //后序遍历的最后一个节点其实就是根节点
        int postorder_root = postorder_right;
        //在中序遍历中定位根节点
        int inorder_root = index[postorder[postorder_root]];
        //先建立根节点
        TreeNode* root=new TreeNode(postorder[postorder_root]);
        //得到左子树节点数量
        int size_left_subtree=inorder_root-inorder_left;
        //注意区间边界范围 +1,-1 一定要注意判别好
        //递归构造左右子树
        root->left=myBuildTree(postorder,inorder,postorder_left,postorder_left+size_left_subtree-1,inorder_left,inorder_root-1);
        root->right=myBuildTree(postorder,inorder,postorder_left+size_left_subtree,postorder_root-1,inorder_root+1,inorder_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder){
        int n=postorder.size();
        //借助哈希表,让我们快速的找到分割点,不用每次都去循环一遍
        for(int i=0;i<n;i++){
            index[inorder[i]]=i;
        }
        return myBuildTree(postorder,inorder,0,n-1,0,n-1);
    }
};

如果觉得本文对你有帮助的话,不妨关注作者一波,小小的关注其实对我很重要。更多高质量内容与资料请访问:数据结构简单学,个人主页:修心的小屋
如果喜欢的话,不妨关注一波,谢谢啦。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值