美文网首页
剑指offer学习笔记:8.4 树

剑指offer学习笔记:8.4 树

作者: 小逗比儿 | 来源:发表于2020-07-11 15:41 被阅读0次

    面试题58:二叉树的下一个节点

    给定一个二叉树和其中一个节点,如何找到中序遍历顺序的下一个节点?树中的节点除了有两个分别指向左右子节点的指针外,还有一个指向父节点的指针。
    牛客网链接https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&&tqId=11210&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

    /*
    struct TreeLinkNode {
       int val;
       struct TreeLinkNode *left;
       struct TreeLinkNode *right;
       struct TreeLinkNode *next;
       TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
           
       }
    };
    */
    class Solution {
    public:
       TreeLinkNode* GetNext(TreeLinkNode* pNode)
       {
           if (pNode == NULL)
           {
               return NULL;
           }
           // 第一种情况当前节点有右子节点,找到右子树最左节点
           TreeLinkNode* result = NULL;
           if (pNode->right != NULL)
           {
               TreeLinkNode* cur = pNode->right;
               while(cur->left != NULL)
               {
                   cur = cur->left;
               }
               result = cur;
           } else if (pNode->next != NULL)
           {
              if (pNode->next->left == pNode)
              {
                  // 第二种情况,当前节点没有右子树,但是是其根节点的左子节点。则根节点即为要找节点
                  result = pNode->next;
              } else 
              {
                  // 第三种情况,也是最复杂的一种情况
                  // 当前节点没有右子节点,且为其父节点的右子节点
                  // 沿着父节点向上遍历,直到找到某节点,该节点为其父节点的左子节点,则该节点父节点即为所求
                  while(pNode != NULL && pNode->next != NULL && pNode->next->left != pNode)
                  {
                      pNode = pNode->next;
                  }
                  if (pNode != NULL)
                  {
                      result = pNode->next;
                  }
              }
           }
           return result;
       }
    };
    

    解题思路:
    中序遍历的遍历顺序是左根右,根据遍历顺序进行分析。中序遍历用栈实现的过程是遍历左子树,并将当前节点入栈,当节点不再有左子树,栈顶出,同时入栈顶元素右子树节点,再重复不断遍历右子树节点的左子树,直到没有左子节点再次出栈的过程。如图二叉树,其中序遍历结果为d,b,h,e,i,a,f,c,g


    二叉树没画指向父节点指针

    如果当前节点有右子树,则其中序遍历的下一个节点为右子树的最左的节点。譬如a的下一个节点是f。
    如果当前节点没有右子树,但是有父节点且当前节点为根节点的左子树,则当前节点的下一个节点为其父节点。譬如g的下一个节点是c。
    如果当前节点没有右子树,且其为其父节点的右子树节点,譬如节点i,需要沿着父节点向上遍历,直到找到一个节点,该节点为其父节点的左子节点,则其父节点就是我们要找的节点。此图中,b是a的子节点,则a就是我们要找的节点。
    如果当前节点没有右子树且当前节点没有父节点,即当前节点为只有左子树的根节点,则根节点为中序遍历终止,没有下一遍历节点。


    寻找过程

    面试题59:对称二叉树

    请实现一个函数,用来判断一棵树是不是对称的
    leetcode链接 https://leetcode-cn.com/problems/symmetric-tree/

    /**
    * 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 {
    public:
       bool isSymmetric(TreeNode* root1, TreeNode* root2)
       {
           if (root1 == NULL && root2 == NULL)
           {
               return true;
           }
           if (root1 == NULL || root2 == NULL)
           {
               return false;
           }
           if (root1->val != root2->val)
           {
               return false;
           }
           return isSymmetric(root1->left, root2->right) && isSymmetric(root1->right, root2->left);
       }
       bool isSymmetric(TreeNode* root) {
           if (root == NULL)
           {
               return true;
           }
           return isSymmetric(root->left, root->right);
       }
    };
    

    解题思路:递归。左子树的左子树和右子树的右子树,左子树的右子树和右子树的左子树需要相同。

    面试题60:把二叉树打印成多行

    从上到下按层打印二叉树,同一层的节点按照从左到右打印,每一层打印到下一层。
    牛客网链接 https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&&tqId=11213&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

    /*
    struct TreeNode {
       int val;
       struct TreeNode *left;
       struct TreeNode *right;
       TreeNode(int x) :
               val(x), left(NULL), right(NULL) {
       }
    };
    */
    class Solution {
    public:
           vector<vector<int> > Print(TreeNode* pRoot) {
               queue<TreeNode*> tree;
               vector<vector<int>> result;
               if (pRoot == NULL)
               {
                   return result;
               }
               tree.push(pRoot);
               while(!tree.empty())
               {
                   int len = tree.size();
                   vector<int> level;
                   for(int i = 0; i < len; i++)
                   {
                       TreeNode* tmp = tree.front();
                       level.push_back(tmp->val);
                       if (tmp->left)
                       {
                           tree.push(tmp->left);
                       }
                       if (tmp->right)
                       {
                           tree.push(tmp->right);
                       }
                       tree.pop();
                   }
                   result.push_back(level);
               }
               return result;
           }
    };
    

    解题思路:广度优先遍历,用队列

    面试题61:按之字形顺序打印二叉树

    按之字形顺序打印二叉树,即第一行那从左到右打印,第二行按从右到左打印,第三行从左到右打印,其余行依次类推。
    牛客网链接 https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&&tqId=11212&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

    /*
    struct TreeNode {
       int val;
       struct TreeNode *left;
       struct TreeNode *right;
       TreeNode(int x) :
               val(x), left(NULL), right(NULL) {
       }
    };
    */
    class Solution {
    public:
       vector<vector<int> > Print(TreeNode* pRoot) {
           vector<vector<int>> result;
           if (pRoot == NULL)
           {
               return result;
           }
           stack<TreeNode*> level1, level2;
           level1.push(pRoot);
           while(!level1.empty() || !level2.empty())
           {
               vector<int> level;
               while (!level1.empty())
               {
                   TreeNode* tmp = level1.top();
                   level1.pop();
                   level.push_back(tmp->val);
                   if (tmp->left)
                   {
                       level2.push(tmp->left);
                   }
                   if (tmp->right)
                   {
                       level2.push(tmp->right);
                   }
               }
               if (level.size() > 0)
               {
                   result.push_back(level);
                   continue;
               }
               while (!level2.empty())
               {
                   TreeNode* tmp = level2.top();
                   level2.pop();
                   level.push_back(tmp->val);
                   if (tmp->right)
                   {
                       level1.push(tmp->right);   // 注意这一行要先入右节点
                   }
                   if (tmp->left)
                   {
                       level1.push(tmp->left);
                   }
               }
               result.push_back(level);
           }
           return result;
       }   
    };
    

    解题思路:因为是之字形,下一行遍历顺序和上一行遍历顺序相反,因此需要用先进后出的栈结构。因为栈结构是先进后出,而层序遍历需要每行遍历完全才能遍历下一行,因此当前行和下一行不能用一个栈。设计两个栈结构,分别用于存当前遍历行元素,和当前遍历元素的左右子树,即下一即将遍历行元素。

    面试题62:序列化二叉树

    请实现两个函数,分别用来序列化和反序列化二叉树
    leetcode链接 https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/

    解题思路:
    剑指offer上的思路是将树序列化成前序遍历,与前序遍历的区别是空节点不直接跳过,而是使用特殊字符进行替代。
    反序列化时因为前序遍历是根左右,譬如序列化得到的是"1,2,4,$,$,$,3,5,$,$,6,$,$",分析1即为根节点,2为当前根节点左子节点,4为2的左子节点,遇到空字符,说明4的左子节点为空,接下来还是空字符,代表4的右子节点也为空,接下来重建2的右子节点,还是空,接下来重建1的右子节点,为3,依次向后重建。

    我感觉用层次遍历进行序列化和重建更加直观和简单,既然没有要求,可以用层次遍历来重建。

    面试题63:二叉搜索树的第k个节点

    给定一个二叉搜索树,求其中第k大节点
    leetcode链接 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/

    /**
    * 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 {
    public:
       void orderTree(TreeNode* root, vector<int>& tree, int k)
       {
           if (root == NULL || tree.size() >= k)
           {
               return;
           }
           if (root->right)
           {
               orderTree(root->right, tree, k);
           }
           tree.push_back(root->val);
           if (root->left)
           {
               orderTree(root->left, tree, k);
           }
           return;
       }
       int kthLargest(TreeNode* root, int k) {
           vector<int> tree;
           orderTree(root, tree, k);
           /*
           for(int i = 0; i < tree.size(); i++)
           {
               cout << tree[i] << endl;
           }
           */
           if (tree.size() < k)
           {
               return tree[0];
           }
           return tree[k - 1];
       }
    };
    

    解题思路:
    中序遍历二叉搜索树得到的是有序递增序列,很容易得到第k大节点。
    升级了下,因为是第k大,因此递减数组比较容易操作,将中序遍历左右根改为右左根。当遍历数组tree长度大于k的时候,说明第k大元素已经遍历过了,可以终止遍历。

    面试题64:数据流中的中位数

    如果从数据流中读出奇数个数值,那么中位数就是所有数值排序后,位于中间的那个数值。如果是偶数个数,那么中位数就是中间两个数的平均值
    leetcode链接 https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/

    解题思路:
    考虑使用什么容器来存储数据流。当有新的数据从数据流中读出来,这些数据就插入到这个容器中,那这个数据结构选用什么比较合适呢?
    思路1:数组
    数组是简单的数据容器,如果数组没有排序,用快排中partition函数来找出数组中的中位数,在没有排序的数组中插入和找到中位数的时间复杂度分别为o(1)和o(n)

    我们还可以在往数组里插入新元素的时候使数组保持有序。这时由于可能移动o(n)个元素,因此需要o(n)时间完成插入。找到中位数的时间复杂度为o(1)

    可以用链表或有序链表代替数组,时间复杂度同数组。
    思路2:二叉搜索树可以把插入新元素的平均时间降低到o(logn)。但是当二叉搜索树极度不平衡,将退化成有序链表。为了避免二叉搜索树的最差情况,可以利用平衡二叉树,即avl树,但是大部分编程语言库中没有实现这个数据结构,因此分析其余方法。试试用set的红黑树来进行操作

    思路3:使用大顶堆和小顶堆。将元素以中位数为分隔,小于中位数在左面,大于中位数在右边。左面用大顶堆来表示,右边用小顶堆来表示。往堆中插入元素的时间复杂度是log(n),获取中位数的时间是o(1)。
    接下来考虑一些细节:首先要保证数据均匀分配到两个堆中,因此两个堆中数据的数目差不超过1。为了实现平均分配,可以在数据总数目是偶数的时候把新数据插入小顶堆中,否则插入大顶堆中。
    还要保证小顶堆中元素都大于大顶堆中元素,当数据总数是偶数,按照前面规则,新数据将插入小顶堆中,但是当前数据要比大顶堆中一些数据要小时,该怎么办呢
    可以把新数据先插入大顶堆中,然后把大顶堆中最大数据插到小顶堆中。同理当当前数据需要插入小顶堆但是较小的情况。

    数据结构 插入的时间效率 得到中位数的时间效率
    没有排序的数组 1 n
    排序的数组 n 1
    排序的链表 n 1
    二叉搜索树 平均logn,最差n 平均logn,最差n
    avl树 logn 1
    最大堆和最小堆 logn 1

    【c++拾遗】avl树和红黑树(RB)区别
    参考链接:https://blog.csdn.net/Gosick_Geass_Gate/article/details/88556840

    简单来说,都是平衡二叉搜索树,只是对平衡的要求不同,维持平衡的方式不同。avl是严格平衡二叉树,红黑树是弱平衡二叉树。

    相关文章

      网友评论

          本文标题:剑指offer学习笔记:8.4 树

          本文链接:https://www.haomeiwen.com/subject/jxqpqktx.html