61序列化二叉树--62二叉搜索树的第k个结点--63数据流中的中位数

本文探讨了二叉树的序列化与反序列化算法,深入解析了二叉搜索树中查找第k个结点的策略,以及利用双堆解决数据流中位数计算的高效方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

61序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

  1. 序列化:发送数据时将结构化数据按照一定规则转换成字符串。
  2. 反序列化:接受到数据再按照相同的规则把字符串转化回原结构类型。
  3. 采用层序递归遍历将二叉树节点的值按层转换的字符型,并且添加到一个string中,以,分隔开来。遍历左右节点
  4. 递归出口是结点指向空,说明到了叶子节点,用‘#’标记叶子节点。
  5. 反序列化时候,采用找以,分隔开来的字符串,那原本是一个整数,将它还原成整数,构建节点,递归左右孩子。
/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {  
public:
    char* Serialize(TreeNode *root) {
       if(root == NULL)
           return NULL;
        string str;
        Serialize(root, str);
        char *ret = new char[str.length() + 1];
        strcpy(ret,str.c_str());
        return ret;
    }
    void Serialize(TreeNode *root, string& str){
        if(root == NULL){
            str += '#';
            return ;
        }
        string r = to_string(root->val);
        str += r;
        str += ',';
        Serialize(root->left, str);
        Serialize(root->right, str);
    }
     
    TreeNode* Deserialize(char *str) {
        if(str == NULL)
            return NULL;
        TreeNode *ret = Deserialize(&str);
 
        return ret;
    }
    TreeNode* Deserialize(char **str){//由于递归时,会不断的向后读取字符串
        if(**str == '#'){  //所以一定要用**str,
            ++(*str);         //以保证得到递归后指针str指向未被读取的字符
            return NULL;
        }
        int num = 0;
        while(**str != '\0' && **str != ','){
            num = num*10 + ((**str) - '0');
            ++(*str);
        }
        TreeNode *root = new TreeNode(num);
        if(**str == '\0')
            return root;
        else
            (*str)++;
        root->left = Deserialize(str);
        root->right = Deserialize(str);
        return root;
    }
};

牛客网链接

62二叉搜索树的第k个结点

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

  1. 二叉搜索树左子树值均比根节点值小,右子树值均比根节点大。从小到达的第k个节点,那么如果采用中序递归遍历,应该先找左子树,只要左子树存在,左子树值就是最小的。
  2. 左子树为空的节点,是此时第count的由小到大的节点,当count==k时候的当前节点就是目标节点。
/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    int count=0;
    TreeNode* ret=nullptr;
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot!=nullptr){
            if(pRoot->left!=nullptr)
                ret=KthNode(pRoot->left,k);
            if(ret!=nullptr)return ret;
            count++;
            if(count==k)return pRoot;
            if(pRoot->right!=nullptr)
                ret=KthNode(pRoot->right,k);
            if(ret!=nullptr)return ret;
        }
        return nullptr;
    }
};

牛客网链接

63数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

1.采用vector存储数据流时候,sort排序是O( n ∗ l o g N n*logN nlogN),插入效率是O(1)。
2. 获取中位数的效率是 O ( n ∗ l o g N ) O(n*logN) O(nlogN),每一次获取中位数都需要sort排序。

class Solution {
public:
    void Insert(int num)
    {
        number.push_back(num);
    }
 
    double GetMedian()
    {
        sort(number.begin(),number.end());
        int size=number.size();
        if(size%2==0)
            return (double)((double)((number[size/2-1])+(number[size/2]))/2);
        else
            return (double)number[size/2];
    }
private:
    vector<int> number;
};

插入排序,插入数据时间效率 O ( n ) O(n) O(n),获取一个中位数的时间复杂度是 O ( 1 ) O(1) O(1)

class Solution {
public:
    vector<int> median;
    void Insert(int num)
    {
        vector<int>::iterator pos=upper_bound(median.begin(),median.end(),num);
        median.insert(pos,num);
    }
 
    double GetMedian()
    {
        if(median.size()==0) return 0.0;
        int pos=median.size()/2;
        if(median.size()%2==1){
            return median[pos];
        }
        return (median[pos-1]+median[pos])/2.0;
    }
};

实际上我们知道AVL树,这种十分平衡的二叉树搜索树来存储这样的数据流,效果很好,但是构造这样的结构很不划算。
在这里插入图片描述

  1. 我们可以用两个堆来代替AVL树:左子树用最大堆来实现,右子树用最小堆来实现。如上图所示,P1即为左子树最大堆堆顶,P2则为右子树最小堆堆顶。
  2. 当数组为偶数个时候,插入到最小堆,也就是右半部分,右半部分的数值应该都比左半部分的值大,但是插入的这个数比最大堆的堆顶数要小,怎么办?那么将这个数值更新到最大堆即可,因为我只关心最大堆的最大值和最小堆的最小值,不更新可能会影响这两个值的正确性。
  3. 获取中位数时候,我们只需判断当前数组是奇数个还是偶数个,偶数个就需要求两个堆顶的平均数,否则直接取小堆堆顶数。
class Solution {
private:
    vector<int> min;
    vector<int> max;
public:
        void Insert(int num)
        {
           int size=min.size()+max.size();
           if((size&1)==0)
           {
              if(max.size()>0 && num<max[0])
              {
                 max.push_back(num);
                 push_heap(max.begin(),max.end(),less<int>());
                 num=max[0];
                 pop_heap(max.begin(),max.end(),less<int>());
                 max.pop_back();
              }
              min.push_back(num);
              push_heap(min.begin(),min.end(),greater<int>());
           }
           else
           {
              if(min.size()>0 && num>min[0])
              {
                min.push_back(num);
                 push_heap(min.begin(),min.end(),greater<int>());
                 num=min[0];
                 pop_heap(min.begin(),min.end(),greater<int>());
                 min.pop_back();
              }
              max.push_back(num);
              push_heap(max.begin(),max.end(),less<int>());
           }   
        }
         
        double GetMedian()
        {
            int size=min.size()+max.size();
            if(size<=0)
                return 0;
            if((size&1)==0)
                return (max[0]+min[0])/2.0;
            else
                return min[0];
        } 
};

牛客网链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值