1.理论基础

(1)二叉树的种类

两种主要形式:满二叉树完全二叉树

满二叉树:一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上。

完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。

二叉搜索树:前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

平衡二叉搜索树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。C++中map、set、multimap,multiset的底层实现都是红黑树(平衡二叉搜索树的一种),所以map、set的增删操作时间时间复杂度是logn。

(2)二叉树的存储方式

可以链式存储(用指针),也可以顺序存储(用数组)

所以顺序存储的元素在内存是连续分布的,而链式存储是通过指针把分布在各个地址的节点串联在一起。

用数组存储二叉树如何遍历?如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

但我们一般用链式存储。

(3)二叉树的遍历方式

二叉树主要有两种遍历方式:

  1. 深度优先遍历:先往深走,遇到叶子节点再往回走。可以借助栈来实现。
  • 前序遍历(递归法,迭代法)
  • 中序遍历(递归法,迭代法)
  • 后序遍历(递归法,迭代法)
  1. 广度优先遍历:一层一层的去遍历。一般用队列来实现。
  • 层次遍历(迭代法)

深度优先遍历中的前中后,指的是中间节点的遍历顺序。

  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中

(4)二叉树的定义

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x): val(x), left(NULL), right(NULL) {}
    //这个是二叉树节点的构造函数
}

如果有构造函数,那么new一个新节点就可以是

TreeNode* a = new TreeNode(9);

没有构造函数的话就是

TreeNode* a = new TreeNode();
a->val=9;
a->left=NULL;
a->right=NULL;

2.二叉树的递归遍历

递归算法三要素:

  1. 确定递归函数的参数和返回值: 因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void。
  2. 确定终止条件: 递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return。
  3. 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,中序和后序同理。

前序:144

class Solution {
public:

    //先要自己写一个前序遍历的递归
    void traversal(TreeNode* cur, vector<int>& vec)//传入一个节点指针和用来存放遍历结果的数组
    {
        if(cur==NULL) return; //节点为空,说明本层遍历结束了,直接返回
        vec.push_back(cur->val);  //前序遍历,先把中间节点的值放进去
        traversal(cur->left,vec);
        traversal(cur->right,vec);
    }

    //开始进行前序遍历,需要根节点的指针
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;  //定义一个数组来存放遍历结果
        traversal(root,result);   //传入根节点指针和结果数组
        return result;
    }
};

后序:145

class Solution {
public:

    //先自己写一个后序的递归
    void traversal(TreeNode* cur, vector<int>& vec)
    {
        if(cur==NULL) return;
        traversal(cur->left,vec);
        traversal(cur->right,vec);
        vec.push_back(cur->val);
    }

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

中序:94

class Solution {
public:

    //先自己写一个中序遍历
    void traversal(TreeNode* cur, vector<int>& vec)
    {
        if(cur==NULL)  return;
        traversal(cur->left,vec);
        vec.push_back(cur->val);
        traversal(cur->right,vec);
    }

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

589.N叉树的前序遍历

class Solution {
public:
    void traversal(Node* cur, vector<int>& vec)
    {
        if(cur==NULL) return;
        vec.push_back(cur->val);
        for(int i=0; i<cur->children.size(); i++)
        {
            traversal(cur->children[i],vec);
        }
    }

    vector<int> preorder(Node* root) {
        //定义一个用来存放结果的数组
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

590.N叉树的后序遍历

class Solution {
public:
    void traversal(Node* cur, vector<int>& vec)
    {
        if(cur==NULL) return;
        for(int i=0; i<cur->children.size(); i++)
        {
            traversal(cur->children[i],vec);
        }
        vec.push_back(cur->val);
    }

    vector<int> postorder(Node* root) {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

3.二叉树的迭代遍历(深度优先,用栈)

前序:144

class Solution {
public:

    vector<int> preorderTraversal(TreeNode* root) {
       stack<TreeNode*> st;   //定义一个栈来存放二叉树的指针
       vector<int> result;   //定义一个数组来存放遍历结果

       if(root==NULL) return result;  //如果根节点是空,直接返回
       st.push(root);

       while(!st.empty())
       {
           TreeNode* node=st.top();
           st.pop();
           result.push_back(node->val);

            //因为遍历是中左右,所以先把根节点放进去再弹出,然后先让右边入栈,这样弹出来的时候左边才能在前面
           if(node->right) st.push(node->right);  //右孩子不为空时,将其入栈
           if(node->left)  st.push(node->left);  //左孩子不为空时,将其入栈
       }

       return result;

    }
};

中序:和前后序的逻辑不一样94

class Solution {
public:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur=root;

        while(cur!=NULL || !st.empty())  //没有遍历到叶子节点,或者栈里的元素没有全部弹出,都要继续循环
        {
            if(cur!=NULL) //没有遍历到叶子节点,就继续一路向左
            {
                st.push(cur);  //把遍历过的节点放进栈里
                cur=cur->left;   
            }
            else  //遍历到了叶子节点,left为空,就开始处理
            {
                cur=st.top();
                st.pop();
                result.push_back(cur->val);

                //第一次访问到叶子节点(左)的时候,它的right也为空,那么就要再循环一次
                //根据访问的顺序,这一次是把中间节点弹出去,这样就从左节点访问到中间节点了
                //中间节点的right不为空,那么就又访问到右节点了
                cur=cur->right;
            }
        }

        return result;
    }
};

android 父节点点击 父节点下标_二叉树

 后序:145

前序是中左右,后序先把迭代顺序调整为中右左,然后对数组进行翻转,就可以得到左右中。

class Solution {
public:

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root==NULL) return result;
        st.push(root);

        while(!st.empty())
        {
            TreeNode* node=st.top();
            st.pop();
            result.push_back(node->val);

            //这里要先把左节点放进去
            if(node->left) st.push(node->left);
            if(node->right) st.push(node->right);
        }

        reverse(result.begin(),result.end());
        return result;
    }
};

4.二叉树的统一迭代法(没懂)

5.二叉树层序遍历(队列)

递归和迭代都是深度优先遍历,而层序遍历是广度优先。

栈先进后出适合模拟深度优先遍历,队列先进先出适合一层一层遍历的逻辑。

102 二叉树的层序遍历

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        
        queue<TreeNode*> que; //先定义一个队列来存放二叉树节点
        vector<vector<int>> result;  //定义一个二维数组来存放层序遍历结果

        if(root!=NULL) que.push(root);

        //开始层序遍历
        while(!que.empty())
        {
            int size=que.size(); //每遍历完一层,要重新给size赋值
            //size用来表征这一层有多少个元素,就可以用来控制循环,保证在一层的遍历中能够把这一层的元素全部遍历到,不多不少
            //后面不能用que.size,因为这个值一直在变
            
            vector<int> vec;  //每一层遍历结果存进一个数组

            //遍历每一层
            for(int i=0; i<size; i++)  //size保证能够准确地把每一层的元素在遍历这一层的时候弹出
            {
                TreeNode* node=que.front();
                que.pop();
                vec.push_back(node->val);
                if(node->left)  que.push(node->left);
                if(node->right) que.push(node->right);
                //上一层的元素还没有全部弹出的时候,就会有下一层元素加入
                //所以用size来表示到底应该有多少元素被弹出
            }

            //每一层遍历结果放进一个vec里,所以每次循环开始时要重新定义vec
            result.push_back(vec);
        }

        return result;
    }
};

107 二叉树的层序遍历II

把102最后得到的数组反转即可

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        queue<TreeNode*> que;
        vector<vector<int>> result;

        if(root!=NULL) que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            vector<int> vec;

            for(int i=0; i<size; i++)
            {
                TreeNode* node=que.front();
                que.pop();
                vec.push_back(node->val);

                if(node->left)  que.push(node->left);
                if(node->right) que.push(node->right);
            }

            result.push_back(vec);
        }

        reverse(result.begin(),result.end());
        return result;
    }
};

199.二叉树的右视图

只输出每一层的最右边的元素,加一条判断条件即可,判断到达每一层的最后一个元素再输出。

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> que;
        vector<int> result;

        if(root!=NULL) que.push(root);

        while(!que.empty())
        {
            int size=que.size();

            for(int i=0; i<size; i++)
            {
                TreeNode* node=que.front();
                que.pop();
                if(i==(size-1)) result.push_back(node->val);

                if(node->left)  que.push(node->left);
                if(node->right) que.push(node->right);
            }

           
        }

        return result;
    }
};

637.二叉树的层平均值

思路:遍历每一层并求和,再除以该层对应的size即可

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        queue<TreeNode*> que;
        vector<double> result;

        if(root!=NULL) que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            double sum=0;
            //遍历每一层,将这一层的数相加
            for(int i=0; i<size; i++)
            {
                TreeNode* node=que.front();
                que.pop();
                sum=sum+node->val;
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }

            //每层遍历完得到一个sum,除以本层的size即可得到平均值
            result.push_back(sum/size);
        }

        return result;
    }
};

429.N叉树的层序遍历

和二叉树的不同只在孩子的数量上

注意已经不是TreeNode了

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        queue<Node*> que;  //不是二叉树,就不是treenode了
        vector<vector<int>> result;

        if(root!=NULL) que.push(root);

        while(!que.empty())
        {
            vector<int> vec;
            int size=que.size();

            for(int i=0; i<size; i++)
            {
                Node* node=que.front();
                que.pop();
                vec.push_back(node->val);

                //有不止一个孩子
                for(int i=0; i<node->children.size(); i++)
                {
                    if(node->children[i])  que.push(node->children[i]);
                }
            }

            result.push_back(vec);

        }

        return result;
    }
};

515.在每个树行中找最大值

class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        queue<TreeNode*> que;
        vector<int> result;
        if(root!=NULL) que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            int maxValue=INT_MIN;
            for(int i=0; i<size; i++)
            {
                TreeNode* node=que.front();
                que.pop();
                maxValue=node->val>maxValue ? node->val : maxValue;
                if(node->left) que.push(node->left);
                if(node->right)  que.push(node->right);
            }

            //每一层得到一个最大值之后,存入数组
            result.push_back(maxValue);
        }

        return result;
    }
};

116、117.填充每个节点的下一个右侧节点

116 用的递归

class Solution {
public:
    Node* connect(Node* root) {
        if(root==NULL) return root;

        if(root->left!=NULL)
        {
            root->left->next=root->right;
            if(root->next!=NULL)
            {
                root->right->next=root->next->left;
            }
        }   
        
        //再把相同的逻辑对左右节点分别走一遍
        connect(root->left);
        connect(root->right);

        return root;
    }
};

104.二叉树的最大深度

思路:一层一层遍历二叉树,遍历循环的次数就是二叉树的深度。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root==NULL) return 0;
        int depth=0;

        queue<TreeNode*> que;
        que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            depth++;

            for(int i=0; i<size; i++)
            {
               TreeNode* node=que.front();
               que.pop(); 
               if(node->left) que.push(node->left);
               if(node->right) que.push(node->right);
            }
        }

        return depth;
    }
};

111.二叉树的最小深度

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

只有左右孩子均为空,才到叶子节点。

跟最大深度不同的是,最大深度要把所有层都遍历完才能得出结果,而在最小深度,只要检测到某个节点的左右孩子都为空(即叶子结点),直接返回深度。

class Solution {
public:
    int minDepth(TreeNode* root) {
        queue<TreeNode*> que;
        int depth=0;

        if(root==NULL) return 0;
        que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            depth++;
            for(int i=0; i<size; i++)
            {
                TreeNode* node=que.front();
                que.pop();
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
                //只要到叶子节点,就返回深度
                if(!node->left && !node->right) return depth;  
            }
        }

        return depth;
    }
};

6.翻转二叉树  226

递归三部曲:函数参数及返回值、递归终止条件、递归逻辑

本题递归解法有三种:前序、后序、中序

前序是先将中间的节点交换,注意交换的时候会带着各自的孩子节点一起交换,然后再以同样的逻辑处理左子树,最后再以同样的逻辑处理右子树。

后序是先处理左子树、再处理右子树,最后写交换中间节点的逻辑。

android 父节点点击 父节点下标_二叉树_02

 

前序:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        //递归终止条件
        if(root==NULL) return root;

        //单层递归逻辑:前序遍历
        swap(root->left,root->right);  //这里的root不局限于根节点,而是可以指代当前要处理的节点
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};

后序:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        //递归终止条件
        if(root==NULL) return root;

        //单层递归逻辑:后序遍历
        invertTree(root->left);
        invertTree(root->right);
        swap(root->left,root->right);  //这里的root不局限于根节点,而是可以指代当前要处理的节点
        return root;
    }
};

中序:

中序不同的是,如果仍然按照前序和后序的逻辑,会把同一棵子树处理两遍,而另一边完全没处理。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        //递归终止条件
        if(root==NULL) return root;

        //单层递归逻辑:中序遍历
        invertTree(root->left);
        swap(root->left,root->right);
        invertTree(root->left);
        return root;
    }
};

android 父节点点击 父节点下标_android 父节点点击_03