面试复习-------算法与数据结构------二叉树

创建二叉树

(1)二叉树的反序列化

给定“6423####51##7##”(先序)这种序列,构造二叉树(假设只出现0~9数字,如果要扩展可以加空格)

TreeNode* preOrderToTree(string input,int& index)
{
    if(input.length() == 0)return NULL;
    if(input.at(index) == '#')
    {
        index++;
        return NULL;
    }
    TreeNode* Node = new TreeNode();
    Node->val = input.at(index) - '0';
    index++;
    Node->left = preOrderToTree(input, index);
    Node->right = preOrderToTree(input, index);
    return Node;
}

(2)前序+中序重建二叉树(剑指offer6

首先preOrder[begin]为根节点root;

每次在中序序列中找preOrder[begin],然后可以将中序分成两个子序列,左边一部分递归构造root->left;右边一部分构造root->right;

TreeNode* TreeBuild(vector<int> preOrder, vector<int> middleOrder,
int preBegin, int preEnd, int middleBegin,int middleEnd)
{
    if(preOrder.size() == 0 || middleOrder.size() == 0 || middleOrder.size() != preOrder.size())return NULL;
    int rootValue = preOrder.at(preBegin);
    TreeNode* root = new TreeNode();
    root->val = rootValue;
    root->left = root-> right = NULL;

    if(preBegin == preEnd){
        if(middleBegin == middleEnd && preOrder.at(preBegin) == middleOrder.at(middleBegin))
            return root;
        else
            throw std::exception("invalid input");
            //return NULL;
    }

    //在中序遍历中找rootValue
    int index = middleBegin;
    while(index <= middleEnd && middleOrder.at(index) != rootValue)++index;

    if(index == middleEnd && middleOrder.at(index) != rootValue)
        throw std::exception("invalid input");
        //return NULL;
    int leftLength = index - middleBegin;
    int rightLength = middleEnd - index;
    if(leftLength > 0)
        root->left = TreeBuild(preOrder, middleOrder,preBegin+1, preBegin + leftLength, middleBegin, index-1);
    if(rightLength > 0)
        root->right = TreeBuild(preOrder, middleOrder,preBegin + leftLength + 1, preEnd, index + 1, middleEnd);
    return root;
}

二叉树的遍历

要活用三种遍历的方式:

当问题中需要根左右、左根右、左右根的顺序对节点进行判断的时候,要能够立马想到这三种遍历方式。

(1)二叉树三种遍历的递归版本

void preOrder(TreeNode* Tree)
{
    if(Tree == NULL)return;
    cout<<Tree->val;
    preOrder(Tree->left);
    preOrder(Tree->right);
}

void middleOrder(TreeNode* Tree)
{
    if(Tree == NULL)return;
    middleOrder(Tree->left);
    cout << Tree->val;
    middleOrder(Tree->right);
}

void backOrder(TreeNode* Tree)
{
    if(Tree == NULL) return ;
    backOrder(Tree->left);
    backOrder(Tree->right);
    cout << Tree->val;
}

(2)二叉树的三种遍历非递归实现

非递归主要是利用栈的特性来实现。

//迭代的先序遍历的思想就是每次都向左下遍历,当访问到一个节点时,先
//将该节点的值打印,然后将该节点入栈,一直到为空
//下一步就是取出栈顶,该点是已经遍历过了的,所以只需要取其右子节点
//继续向左下遍历即可
void preOrder(TreeNode* Tree)
{
    if(Tree == NULL)return;
    stack<TreeNode*> s;
    TreeNode* pCur = Tree;
    while(pCur != NULL || !s.empty()){
        //首先遍历到最左下节点
        if(pCur != NULL){
            cout << pCur->val;
            s.push(pCur);
            pCur = pCur->left;
        }else{  //当左下为空的时候,回到上一个元素,继续不停向左下遍历
            pCur = s.top()->right;
            s.pop();
        }
    }
}
//迭代的中序遍历与先序类似,不同的地方是,先入栈,出栈的时候再输出
void middleOrder(TreeNode* Tree)
{
    if(Tree == NULL)return;
    stack<TreeNode*> s;
    TreeNode* pCur = Tree;
    while(pCur != NULL || !s.empty()){
        if(pCur != NULL){
            s.push(pCur);
            pCur = pCur->left;
        }else{
            pCur = s.top();
            cout << pCur->val;
            s.pop();
            pCur = pCur->right;
        }
    }

}
//迭代的后序遍历与之前的稍有不同
//采用的技巧是将孩子节点入栈后,对节点执行左右置空的操作
//只有遇到空节点才输出,此时保证了该节点的左右孩子节点已经被访问到
void backOrder(TreeNode* Tree)
{
    if(Tree == NULL)return;
    stack<TreeNode*> s;
    s.push(Tree);
    TreeNode* pCur = NULL;
    while(!s.empty()){
        pCur = s.top();
        if(pCur->left == NULL && pCur->right == NULL){
            cout << pCur->val;
            s.pop();
        }else{
            //先右后左入栈才能保障先左后右出栈
            if(pCur->right != NULL){
                s.push(pCur->right);
                pCur->right = NULL;
            }
            if(pCur->left != NULL){
                s.push(pCur->left);
                pCur->left = NULL;
            }
        }
    }
}

(3)二叉树的层次遍历

主要利用队列的先入先出特性

void levelOrder(TreeNode* Tree)
{
    if(Tree == NULL)return;
    queue<TreeNode*> q;
    q.push(Tree);
    TreeNode* pCur;
    while(!q.empty()){
        pCur = q.front();
        q.pop();
        cout << pCur->val;
        if(pCur->left != NULL)
            q.push(pCur->left);
        if(pCur->right != NULL)
            q.push(pCur->right);
    }
}

变形:按行打印二叉树(剑指offer 60

思路:维护两个变量,pLast表示当前层最右边的节点,pNextLast表示下一层最右边的节点

void levelOrderByline(TreeNode* Tree)
{
    if(Tree == NULL)return;
    queue<TreeNode*> q;
    q.push(Tree);
    TreeNode* pLast = Tree;
    TreeNode* pNextLast = NULL;
    while(!q.empty()){
        TreeNode* pCur = q.front();
        q.pop();
        cout << pCur->val;
        if(pCur->left != NULL)
        {
            q.push(pCur->left);
            pNextLast = pCur->left;
        }
        if(pCur->right != NULL)
        {
            q.push(pCur->right);
            pNextLast = pCur->right;

        }
        if(pCur == pLast){
            cout << endl;
            pLast = pNextLast;
            pNextLast = NULL;
        }
    }
}

其他的一些算法题

(1)求二叉树叶子节点个数

左子树叶子节点个数+右子树叶子节点个数;用递归实现


(2)求二叉树高度(剑指offer39

int getHeight(TreeNode* Tree)
{
    if(Tree == NULL)return 0;
    int left = getHeight(Tree->left);
    int right = getHeight(Tree->right);

    return 1 + max(left, right);
}

变形:判断一棵树是不是平衡二叉树

可以仿照上述做法,求出左右子树的高度然后进行判断,但是这个时候一个节点可能会遍历多次。

较好的做法:利用后序遍历最后遍历根节点的特性,记录一个深度信息

bool isBalance(TreeNode* Tree, int& depth)
{
    if(Tree == NULL){
        depth = 0;
        return true;
    }
    int left,right; //用来记录高度信息
    if(isBalance(Tree->left, left) && isBalance(Tree->right, right)){
        if(abs(left - right) <= 1){
            depth = 1 + max(left, right);
            return true;
        }
    }
    depth = 1 + max(left, right);
    return false;
}

(3)二叉树的镜像(剑指offer 19

解题思路:先序遍历并且交换左右子树,对左右子树执行镜像操作。

void mirror_tree(TreeNode* T)
{
    if(T==NULL)
        return;
    if((T->left==NULL) && (T->right==NULL))
        return;
    TreeNode* temp = T->left;
    T->left=T->right;
    T->right=temp;
    mirror_tree(T->left);
    mirror_tree(T->right);
}

(4)树的子结构判断(剑指offer 18

给pRoot1和pRoot2,判断pRoot2是不是pRoot1子结构。

第一步:遍历pRoot1中的每个节点,找pRoot2->val;(递归的先序遍历即可完成)

第二步:判断以该节点为根的子树是不是和pRoot2一样。

bool has_tree(TreeNode* T1,TreeNode* T2)
{
    if(T2==NULL)
        return true;
    if(T1==NULL)
        return false;
    if(T1->value!=T2->value)
        return false;
    return has_tree(T1->left,T2->left)&&has_tree(T1->right,T2->right);
}

bool has_sub(TreeNode* T1,TreeNode* T2)
{
    bool result=false;
    if(T1!=NULL && T2!=NULL)
    {
        if(T1->value==T2->value)
            result=has_tree(T1,T2);
        if(!result)
            result=has_sub(T1->left,T2);
        if(!result)
            result=has_sub(T1->right,T2);
    }
    return result;
}

(5)寻找二叉树中和为某一值的路径(剑指offer25

解题思路:保存当前已有路径,当循环到根节点时,进行判断

void findPath(TreeNode* Tree, vector<TreeNode*>& nodes, int num, int target)
{
    if(Tree == NULL)return;
    num += Tree->val;
    nodes.push_back(Tree);

    if(Tree->left == NULL && Tree->right == NULL){
        if(num == target){
            for(auto i : nodes)cout<<i->val;
            cout << endl;
        }
    }
    else{
        if(Tree->left != NULL){
            findPath(Tree->left, nodes, num, target);
        }
        if(Tree->right != NULL){
            findPath(Tree->right, nodes, num, target);
        }
    }
    nodes.pop_back();
}

(6)序列判断

判断某一序列是不是二叉搜索树的后序遍历序列(剑指offer24

思路:对某一个节点:①寻找前面全部小于根值的一部分为左子树序列,判断剩余一部分是不是都大于根值②判断左子树序列是不是二叉搜索树③判断右子树序列是不是二叉搜索树

bool back_tree(int* arr,int length)
{
    if(arr==NULL||length<=0)
        return false;
    int root=arr[length-1];
    int i=0;
    while(arr[i]<root && i<length-1)
        i++;

    int j=i;
    for(;j<length-1;j++)
    {
        if(arr[j]<root)
            return false;
    }
    bool left=true;
    if(i>0)
        left=back_tree(arr,i);

    bool right=true;
    if(i<length-1)
        right=back_tree(arr+i,length-i-1);

    return (left&&right);
}

(7)二叉搜索树转换成双向链表(剑指offer27

//找树的最左节点,即转换之后链表的头结点
TreeNode* find_most_left(TreeNode* T)
{
    if(T==NULL)
        return NULL;
    TreeNode* pCur = T;
    while(pCur->left!=NULL)
        pCur = pCur->left;
    return pCur;
}
//进行整棵树的转换
TreeNode* convert(TreeNode* T)
{
    TreeNode* phead=find_most_left(T);
    TreeNode* last_node=NULL;
    convert_node(T,&last_node);
    return phead;
}
//进行单个节点的转换
void convert_node(TreeNode* T,TreeNode** lastnode)
{
    if(T==NULL)
        return;
    if(T->left!=NULL)
        convert_node(T->left,lastnode);
    T->left=*lastnode;
    if(*lastnode!=NULL)
        (*lastnode)->right=T;
    *lastnode=T;
    if(T->right!=NULL)
        convert_node(T->right,lastnode);
}

类似的:二叉树转换成单链表(leetcode 114)

《面试复习-------算法与数据结构------二叉树》 《面试复习-------算法与数据结构------二叉树》

思路:采用一种类似于后序遍历的方式,先处理右子节点,再处理左子节点最后处理根节点,用一个指针记录之前处理的节点,代表当前节点应该衔接哪一个节点

public:
    //采用一种伪后序遍历的方式
    void flatten(TreeNode* root) {
        if(root == NULL)return;
        flatten(root->right);
        flatten(root->left);
        
        root->left = NULL;
        root->right = pPre;
        pPre = root;
    }
private:
   TreeNode* pPre = NULL; 

(8)树的两节点最低公共祖先问题(剑指offer50

①二叉搜索树

只需要判断节点的值,如果给的两节点val同时小于(或者大于)根节点val,那么公共节点一定在左子树(或者右子树)中,直到找到第一个节点值位于两个给定节点值之间

TreeNode* findParent(TreeNode* Tree, TreeNode* Node1, TreeNode* Node2)
{
    if(Tree == NULL)return NULL;
    if(Tree->val > Node1->val && Tree->val > Node2->val)
        return findParent(Tree->left, Node1, Node2);
    if(Tree->val < Node1->val && Tree->val < Node2->val)
        return findParent(Tree->right, Node1, Node2);
    return Tree;
}

②普通二叉树

也可以采用递归的形式判断是否在左右子树中,不过开销很大。

高效的做法:先求出根节点到两个节点的路径,进而转换成求两条路径的公共节点(类似于求两条单链表的第一个公共节点)

bool FindNodePath(TreeNode* T,vector<TreeNode*>& path,int nodeKey)
{
    path.push_back(T);
    if(T->value==nodeKey)
        return true;

    bool found = false;
    if(T->left!=NULL)
        found=FindNodePath(T->left,path,nodeKey);
    if(!found && T->right!=NULL)
        found=FindNodePath(T->right,path,nodeKey);
    if(!found)
        path.pop_back();
    return found;
}

TreeNode* GetLastCommonNode(vector<TreeNode*>& path1,vector<TreeNode*>& path2)
{
    vector<TreeNode*>::iterator it1=path1.begin();
    vector<TreeNode*>::iterator it2=path2.begin();
    TreeNode* result=NULL;
    while(it1!=path1.end() && it2!=path2.end())
    {
        if(*it1==*it2)
            result=*it1;
        it1++;
        it2++;
    }
    return result;
}

(9)二叉树中节点的最大距离

解题思路:最大距离为三种情况:①左子树中最大距离②右子树中最大距离③左子树到根最大+右子树到根最大

int FarestNode(TreeNode* Tree, int& leftMax, int& rightMax)
{
    if(Tree == NULL){
        leftMax = 0;
        rightMax = 0;
        return 0;
    }
    int maxLL,maxLR,maxRL,maxRR;
    int farestLeft,farestRight;   //左右子树的最大距离
    if(Tree->left != NULL){
        farestLeft = FarestNode(Tree->left, maxLL, maxLR);
        leftMax = max(maxLL, maxLR) + 1;
    }else{
        farestLeft = 0;
        leftMax = 0;
    }
    if(Tree->right != NULL){
        farestRight = FarestNode(Tree->right, maxRL, maxRR);
        rightMax = max(maxRL, maxRR) + 1;
    }else{
        farestRight = 0;
        rightMax = 0;
    }

    return max(max(farestLeft, farestRight), leftMax + rightMax);
}

:求深度的时候,通常将depth信息作为参数进行递归,这个时候参数必须是引用传参!!

(10)判断一棵树是否是完全二叉树

主要是利用广度优先搜索,如果遍历到了空节点那么,之后应该都为空节点,否则就不是完全二叉树

bool isCompleteBinaryTree(TreeNode* Tree)
{
    if(Tree == NULL)return false;

    queue<TreeNode*> q;
    q.push(Tree);
    TreeNode* pCur;
    //与正常的层次遍历不同的是如果左右节点为空节点,也将其放入到队列中
    while((pCur = q.front()) != NULL){
        q.push(pCur->left);
        q.push(pCur->right);
        q.pop();
    }
    //此时已经遍历到空节点,按照完全二叉树的定义,后面的节点应该都是空节点
    while(!q.empty()){
        pCur = q.front();
        q.pop();
        if(pCur != NULL)return false;
    }
    return true;
}

(11)判断一颗二叉树是否是对称的

解题思路:定义一种新的遍历序列(先右子树后左子树,然后看它与二叉树的前序遍历结果是否一样)

bool func(TreeNode* left,TreeNode* right)
{
    if(left==NULL&&right==NULL)
        return true;
    if(left==NULL||right==NULL)
        return false;
    if(left->value==right->value)
    {
        return func(left->left,right->right)&&func(left->right,right->left);
    }
    return false;
}


(12)二叉搜索树中的第k个节点

TreeNode* KthNodeCore(TreeNode* T,int& k)
{
    TreeNode* result= NULL;

    if(T->left!=NULL)
        result=KthNodeCore(T->left,k);

    if(result==NULL)
    {
        if(k==1)
            result=T;
        --k;
    }
    if(result==NULL&&T->right!=NULL)
        result = KthNodeCore(T->right,k);

    return result;
}

(13)给正整数n,用1,2,3….n构建所有可能的二叉搜索树(leetcode 95

解题思路:利用递归进行求解

    vector<TreeNode*> generateTrees(int n) {
        if(n < 1)return vector<TreeNode*>();
        return generateTreesCore(1, n);
    }
    
    vector<TreeNode*> generateTreesCore(int start, int end)
    {
        vector<TreeNode*> result;
        if(start > end){
            result.push_back(NULL);
            return result;
        }
        if(start == end){
            result.push_back(new TreeNode(start));
            return result;
        }
 
        vector<TreeNode*> left,right;
        for(int i = start; i <= end; ++i){

            left = generateTreesCore(start, i-1);
            right = generateTreesCore(i+1, end);
            
            for(auto lNode: left){
                for(auto rNode: right){
                    TreeNode* root = new TreeNode(i);
                    root->left = lNode;
                    root->right = rNode;
                    result.push_back(root);
                }
            }
        }
        return result;
    }

(14)之字形打印二叉树(剑指offer 61

解题思路:利用两个栈进行层次遍历,一个栈负责先左后右入栈,一个栈负责先右后左入栈

    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>>result;
        if(root == NULL)return result;
        stack<TreeNode*> s[2];
        
        int currentStack = 0;
        s[currentStack].push(root);
        vector<int> line;
        
        while(!s[0].empty() || !s[1].empty()){
            TreeNode* pCurrent = s[currentStack].top();
            s[currentStack].pop();
            
            line.push_back(pCurrent->val);
            if(currentStack == 0){  //如果是第一个栈就从左往右入栈,这样就能保证从右往左出栈
                if(pCurrent -> left != NULL)s[1 - currentStack].push(pCurrent->left);
                if(pCurrent -> right != NULL)s[1 - currentStack].push(pCurrent->right);
            }
            if(currentStack == 1){  //如果是第二个栈就从右往左入栈,这样就能保证从左往右出栈
                if(pCurrent -> right != NULL)s[1 - currentStack].push(pCurrent->right);
                if(pCurrent -> left != NULL)s[1 - currentStack].push(pCurrent->left);
            }
            if(s[currentStack].empty()){
                currentStack = 1 - currentStack;
                result.push_back(line);
                line.clear();
            }
            
        }
        return result;
    }

    原文作者:我真的是dj
    原文地址: https://blog.csdn.net/daijie2198492527/article/details/77703764
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞