二叉树各种操作的总结

本篇文章部分参考轻松搞定面试中的二叉树题目实现。

    • 求二叉树中的节点个数
    • 求二叉树中叶子节点的个数
    • 求二叉树的深度
    • 求二叉树第K层的节点个数
    • 递归遍历前序中序后序
    • 非递归遍历前序中序后序层序
      • 1 前序遍历
      • 2 中序遍历
      • 3 后序遍历
      • 4 层序遍历
    • 将二叉查找树变为有序的双向链表
    • 求二叉树的镜像
    • 判断两棵二叉树是否结构相同
    • 判断二叉树是不是平衡二叉树
    • 判断二叉树是否是搜索二叉树
    • 判断二叉树是不是完全二叉树
    • 求二叉树中两个节点的最低公共祖先节点
    • 求二叉树中节点的最大距离
    • 由前序遍历序列和中序遍历序列重建二叉树

所有源码:https://github.com/qzxin/BinaryTree/blob/master/binary-tree-all.cpp

1. 求二叉树中的节点个数

递归求解,二叉树的节点个数等于 左子树的个数+右子树的个数+1(根)

//求二叉树中的节点个数
int GetNums(BinaryTreeNode* root) {
    if (root == NULL)
        return 0;
    return GetNums(root->m_pLeft) + GetNums(root->m_pRight) + 1;
}

求二叉树中叶子节点的个数

叶子节点的定义是:自身非空,左右为NULL;
和求二叉树中的节点个数类似,只是必须满足左右为NULL,才算1个

int GetLeafNodeNums(BinaryTreeNode* root) {
    if (root == NULL)
        return 0;
    if (root->m_pLeft == NULL && root->m_pRight == NULL)
        return 1;
    int leftNums = GetLeafNodeNums(root->m_pLeft);
    int rightNums = GetLeafNodeNums(root->m_pRight);
    return leftNums+rightNums;
}

2. 求二叉树的深度

递归求解,二叉树的深度等于 左右子树深度中的最大值+1(根)

// 求二叉树的深度
int GetDepth(BinaryTreeNode* root) {
    if (root == NULL)
        return 0;
    return max(GetDepth(root->m_pLeft), GetDepth(root->m_pRight)) + 1;
}

3. 求二叉树第K层的节点个数

递归实现
k = 1, nums = 1;
二叉树第K层的节点个数 = 左子树K-1层的节点个数 + 右子树K-1层的节点个数

// 求二叉树第K层的节点个数
int GetNLevelNums(BinaryTreeNode* root, int k) {
    if (root == NULL || k == 0)
        return 0;
    if (k == 1)
        return 1;
    // 左右子树k-1层节点数的和
    return GetNLevelNums(root->m_pLeft, k-1) + GetNLevelNums(root->m_pRight, k-1);
}

4 . 递归遍历:前序,中序,后序

// 递归遍历:前序遍历,中序遍历,后序遍历
void visit(BinaryTreeNode* root) {
    cout << root->m_val << " ";
}
void PreOrderTravel(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    visit(root);
    PreOrderTravel(root->m_pLeft);
    PreOrderTravel(root->m_pRight);
}
void InOrderTravel(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    InOrderTravel(root->m_pLeft);
    visit(root);
    InOrderTravel(root->m_pRight);
}
void PostOrderTravel(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    PostOrderTravel(root->m_pLeft);
    PostOrderTravel(root->m_pRight);
    visit(root);

}

5. 非递归遍历:前序,中序,后序,层序

5.1 前序遍历

先访问根,然后压右节点,左节点进栈。(访问时顺序则是,根,左,右)

void PreOrderTravel(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    stack s;
    s.push(root);
    while (s.empty() == false) {
        root = s.top();
        visit(root);
        s.pop();
        if (root->m_pRight)
            s.push(root->m_pRight);
        if (root->m_pLeft)
            s.push(root->m_pLeft);
    }
}

5.2 中序遍历

先入栈后访问。因为中序首先要访问左节点,所以要压到树的最后一个左节点,才开始访问;最后一个左节点没有左节点(相当于根),之后开始遍历它的右子树。

void InOrderTravelRecur(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    stack s;
    BinaryTreeNode* node  = root;
    while (node != NULL || s.empty() == false) {
        if (node) {
            s.push(node);
            node = node->m_pLeft;
        } else {
            node = s.top();
            s.pop();
            visit(node);
            node = node->m_pRight;
        }
    }
}

5.3 后序遍历

用2个栈实现,

// 辅助栈s1:压根,压左,压右; 
// 栈s2:   压根,压右,压左;(出栈访问则为:左,右,根)
void PostOrderTravelRecur(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    stack s1;
    stack s2;
    BinaryTreeNode* node = root;
    s1.push(node);
    while (s1.empty() == false) {
        node = s1.top();
        s1.pop();
        s2.push(node); //根压入s2
        if (node->m_pLeft)
            s1.push(node->m_pLeft);// 左 压入 s1
        if (node->m_pRight)
            s1.push(node->m_pRight); // 右 压入 s1
    }
    while (s2.empty() == false) {
        node = s2.top();
        s2.pop();
        visit(node);
    }
}

5.4 层序遍历

逐层访问,先入先出,使用队列实现。访问根,然后将左、右入队列

void LevelOrderTravel(BinaryTreeNode* root) {
    if (root == NULL)
        return;
    queue q;
    q.push(root);
    while (q.empty() == false) {
        root = q.front();
        visit(root);
        q.pop();
        if (root->m_pLeft)
            q.push(root->m_pLeft);
        if (root->m_pRight)
            q.push(root->m_pRight);
    }
}

6. 将二叉查找树变为有序的双向链表

递归实现,二叉树可以分为左子树、根、右子树
设双向链表的头尾是pFirst, pLast

  • 根为空: pFirst, pLast为NULL
  • 处理左子树
    • 左子树空:则根为双向链表的头部
    • 左子树非空:左子树双向链表的头,是最终双向链表的头;左子树双向链表的尾和根相连;
  • 处理右子树
    • 右子树空:根为双向链表的尾部
    • 右子树非空: 右子树双向链表的尾,是最终双向链表的尾;右子树双向链表的头和根相连;

因此,除了声明最终双向链表的头尾,还要声明左、右子树转换成双向链表的头尾。

// 将二叉查找树变为有序的双向链表
void CovertToList(BinaryTreeNode* root, BinaryTreeNode* &pFirst, BinaryTreeNode* &pLast) {
    BinaryTreeNode* pLeftFirst(NULL), *pLeftLast(NULL), *pRightFirst(NULL), *pRightLast(NULL);
    if (root == NULL) {
        pFirst = NULL;
        pLast = NULL;
        return;
    }

    if (root->m_pLeft == NULL) {
        pFirst = root;
    } else {
        CovertToList(root->m_pLeft, pLeftFirst, pLeftLast);
        pLeftLast->m_pRight = root; 
        root->m_pLeft = pLeftLast;
        pFirst = pLeftFirst;
    }

    if (root->m_pRight == NULL) {
        pLast = root;
    } else {
        CovertToList(root->m_pRight, pRightFirst, pRightLast);
        pRightFirst->m_pLeft = root;
        root->m_pRight = pRightFirst;
        pLast = pRightFirst;
    }

}

7. 求二叉树的镜像

递归的交换树的左右子树。

// 求二叉树的镜像
BinaryTreeNode* MirrorTree(BinaryTreeNode* root) {
    if (root == NULL)
        return NULL;
    BinaryTreeNode* pLeft = MirrorTree(root->m_pLeft);
    BinaryTreeNode* pRight = MirrorTree(root->m_pRight);
    root->m_pLeft = pRight;
    root->m_pRight = pLeft;
    return root;
}

8. 判断两棵二叉树是否结构相同

递归实现:先判断根是否相同,再判断左子树和右子树是否相同
// 判断两棵二叉树是否结构相同

bool TreeStructCmp(BinaryTreeNode* root1, BinaryTreeNode* root2) {
    if (root1 == NULL && root2 == NULL)
        return true;
    if (root1 != root2)
        return false;
    bool leftResult = TreeStructCmp(root1->m_pLeft, root2->m_pLeft);
    bool rightResult = TreeStructCmp(root1->m_pRight, root2->m_pRight);
    return leftResult && rightResult;
}

9. 判断二叉树是不是平衡二叉树

只有每一个子树都是平衡树,才能保证它是平衡二叉树。因此,先判断左右子树是否是平衡的并记录左右子树的深度,再判断树是否平衡

bool IsAVL(BinaryTreeNode* root) {
    int depth;
    return IsAVLTree(root, depth);
}
// 后序遍历,只访问1次节点
bool IsAVLTree(BinaryTreeNode* root, int &depth) {
    if (root == NULL) {
        depth = 0;
        return true;
    } 
    int leftDepth, rightDepth;
    // 后序遍历,先判断左右子树是否是平衡的,再判断树是否平衡
    if (IsAVLTree(root->m_pLeft, leftDepth) && IsAVLTree(root->m_pRight, rightDepth)) {
        if (abs(leftDepth-rightDepth) <= 1) {
            depth = max(leftDepth, rightDepth)+1;
            return true;
        }
    }
    return false;
}

10. 判断二叉树是否是搜索二叉树

注意:仅通过比较左节点 <= 根 <= 右节点 不能判断这是一个搜索二叉树,因为这不能保证右子树的所有节点值都大于左子树中的所有节点。如下图所示,它不是BST

    4
1       5
     0     6

通过中序遍历(从小到大的顺序)来判断是否是一个搜索二叉树

// 判断二叉树是否是搜索二叉树
bool IsBST(BinaryTreeNode* root) {
    int prev = INT_MIN; // 比较元素,设为最小
    return IsBSTreeHelper(root, prev);
}
// 中序遍历,判断遍历顺序是否是从小到大
bool IsBSTreeHelper(BinaryTreeNode* root, int &prev) {
    if (root == NULL)
        return true;
    if (IsBSTreeHelper(root->m_pLeft, prev)) {
        if (root->m_val >= prev) {
            prev = root->m_val;
            if (IsBSTreeHelper(root->m_pRight, prev))
                return true; // 此时左子树、根、右子树都满足要求
            else
                return false; // 右不满足
        } else {
            return false; // 根不满足
        }
    } else {
        return false; // 左不满足
    }
}

11. 判断二叉树是不是完全二叉树

完全二叉树,要求在最后一层的右侧才可以存在空节点。
层序遍历,一旦一个节点含有空子树后,之后所有的节点必须只含有NULL。

  • 不含有空子树时,按层序将当前节点的左右子树压入队列
  • 一旦含有空子树时,当前节点和队列中后续节点的左右子树,必须为空,才是完全二叉树。
/ 判断二叉树是不是完全二叉树
// 层序遍历,一旦一个节点含有空子树后,之后所有的节点必须只含有NULL
bool IsCompleteBinaryTree(BinaryTreeNode* root) {
    if (root == NULL)
        return true;
    queue<BinaryTreeNode* > q;
    bool HaveNULL = false;
    q.push(root);
    while (q.empty() == false) {
        root = q.front();
        q.pop();
        if (HaveNULL) {
            if (root->m_pLeft || root->m_pRight)
                return false;
        } else {
            if (root->m_pLeft && root->m_pRight) {
                // 左右非空
                q.push(root->m_pLeft);
                q.push(root->m_pRight);
            } else if (root->m_pLeft && root->m_pRight == NULL) {
                // 左非空,右空
                HaveNULL = true;
                q.push(root->m_pLeft);
            } else if (root->m_pLeft == NULL && root->m_pRight) {
                // 左空,右非空
                return false;
            } else {
                // 左右为空
                HaveNULL = true;
            }
        }
    }
    return true;
}

12. 求二叉树中两个节点的最低公共祖先节点

递归解法:
(1)如果两个节点,1个在左子树,1个在右子树,则最低公共祖先节点是根
(2)如果两个节点都在左子树,递归处理左子树;反之处理右子树

在树中寻找 node1 ,并保存路径,如果没找到node1,递归把路径的节点pop;
在树中寻找 node2 ,并保存路径,如果没找到node1,递归把路径的节点pop;
如果有1个没有找到,则返回NULL,如果都找到,则从2条路径的开始,向后遍历,找到最后一个共同的节点。

// 求二叉树中两个节点的最低公共祖先节点
BinaryTreeNode* GetLastCommonParent(BinaryTreeNode* root, BinaryTreeNode* node1, BinaryTreeNode* node2) {
    if (root == NULL || node1 == NULL || node2 == NULL)
        return NULL;
    list path1, path2;
    bool found1 = GetPath(root, node1, path1);
    bool found2 = GetPath(root, node2, path2);
    BinaryTreeNode* pLast = NULL;
    if (!found1 || !found2)
        return NULL;
    // 找到路径中最后一个共同节点
    list::const_iterator iter1 = path1.begin();
    list::const_iterator iter2 = path2.begin();
    while (iter1 != path1.end() && iter2 != path2.end()) {
        if (*iter1 != *iter2)
            break;
        pLast = *iter1;
        iter1++;
        iter2++;
    }
    return pLast;
}
// 获得根到该节点的路径
bool GetPath(BinaryTreeNode* root, BinaryTreeNode* node, list &path) {
    if (root == node) {
        path.push_back(root);
        return true;
    }
    if (root == NULL)
        return false;
    bool found = false;
    path.push_back(root); // 路径从根开始
    found = GetPath(root->m_pLeft, node, path); //左子树中查找
    if (!found)
        found = GetPath(root->m_pRight, node, path); // 右子树中查找
    if (!found)
        path.pop_back(); // 此子树中没找到,pop出已经压入的此子树的节点
    return found;
}

13. 求二叉树中节点的最大距离

二叉树中节点的最大距离maxDistance指,任意两个节点相连的路径长度的最大值。

  • 树为空,距离为0;
  • 树非空,最大距离有3种可能,左子树中的最大距离,右子树中的最大距离,从左子树到根然后到右子树的最大距离
int GetMaxDistance(BinaryTreeNode* root) {
    int maxLeft = 0, maxRight = 0; // 左右子树到根的最大距离
    return GetMaxDistRecur(root, maxLeft, maxRight); // 在计算最大距离时,记录左右子树到根的最大距离
}

//maxLeft, maxRight 左右子树中的节点到根的最大距离
int GetMaxDistRecur(BinaryTreeNode* root, int &maxLeft, int &maxRight) {
    if (root == NULL) {
        maxLeft = 0, maxRight = 0;
        return 0;
    }
    int maxLL, maxLR, maxRL, maxRR;
    int maxDistLeft, maxDistRight; // 左、右子树中的最大距离
    if (root->m_pLeft) {
        maxDistLeft = GetMaxDistRecur(root->m_pLeft, maxLL, maxLR);
        maxLeft = max(maxLL, maxRR) + 1;
    } else {
        maxDistLeft = 0;
        maxLeft = 0;
    }
    if (root->m_pRight) {
        maxDistRight = GetMaxDistRecur(root->m_pRight, maxRL, maxRR);
        maxRight = max(maxRL, maxRR) + 1;
    } else {
        maxDistRight = 0;
        maxRight = 0;
    }
    return max(maxLeft+maxRight, max(maxDistLeft, maxDistRight));
}

14. 由前序遍历序列和中序遍历序列重建二叉树

前序的开始是根;中序的一个节点的左侧是左子树,右侧是右子树。
因此先从前序中确定1个根,然后去中序中找到这个根,确定属于该根的左右子树的范围。递归的处理左右子树。

// 由前序遍历序列和中序遍历序列重建二叉树
BinaryTreeNode* RebuildBinaryTree(int* preorder, int* inorder, int nums) {
    if (preorder == NULL || inorder == NULL || nums <= 0)
        return NULL;
    BinaryTreeNode* root = new BinaryTreeNode(preorder[0]); // 根
    int rootPositionOnInorder = -1;
    for (int i = 0; i < nums; i++) {
        if (inorder[i] == root->m_val) {
            rootPositionOnInorder = i; // 中序中寻找根
            break;
        }
    }
    if (rootPositionOnInorder == -1) {
        cout << "Input Error." << endl;
    }
    // Rebuild Left Tree
    root->m_pLeft = RebuildBinaryTree(preorder+1, inorder, rootPositionOnInorder);
    // Rebuild Right Tree
    root->m_pRight = RebuildBinaryTree(preorder+1+rootPositionOnInorder, inorder+1+rootPositionOnInorder, nums-rootPositionOnInorder-1);
    return root;
}

你可能感兴趣的:(数据结构,笔试面试)