二叉树遍历与构造
二叉树的遍历
二叉树遍历其实很简单,三种遍历都是一样的,只不过顺序先后不一样罢了。
先序遍历
访问根结点,先序遍历左子树,先序遍历右子树
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);
}
};
Last updated