Kth largest element in a stream (Way of Search Tree)

本文介绍了一种基于排序二叉树实现KthLargest类的方法,通过维护一个能够快速定位第K大元素的数据结构,使得每次添加新元素后都能高效获取当前序列中的第K大元素。

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

Kth largest element in a stream

Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Your KthLargest class will have a constructor which accepts an integer k and an integer array nums, which contains initial elements from the stream. For each call to the method KthLargest.add, return the element representing the kth largest element in the stream.

寻找一个序列中的第K个最大的数,其中有add函数和类函数的预定义。

该问题看似简单,但是考虑到时间复杂度的话,普通的算法n的平方就难以接受了。涉及到有序查找,故考虑使用排序二叉树。我们知道,排序二叉树按照中序遍历即为升序序列,但如果反着来,从整个二叉树的最右边结点开始依次向左寻找,代码如下:

BiTreeNode *p;
p=root;
BiTreeNode *Mystack[1000];
int top=0;
while(p!=NULL||top!=0){  //在指针不为空或者栈不为空时,进行循环。
    while(p!=NULL){
    Mystack[top++]=p;
    p=p->right;          //从根节点root开始依次压栈,寻找整个二叉树最右边的结点,也就是数值最大的结点。
}
    p=Mystack[--top];    //出栈,访问结点。
    cout<<p->val<<"  ";
    if(p->left!=NULL){
    p=p->left;          //如果结点有左子树,则开始访问左子树
}
    else
    p=NULL;            //如果没有,则将指针指控,以便于下一次跳过第二层循环,直接出栈。
}

这样一来,在查找过程中设置好标记量K,就可以实现降序查找Kth largest element。但是这种算法还有一个最大的问题,那就是虽然将时间复杂度缩减到了树高,但毕竟要处理的数据太多,排序二叉树建立以后,不管K的值为多少,时间复杂度都为树高,很不划算。那么应该如何改进算法呢?

我们注意到,查找 Kth largest element ,其实就是在有序序列中排除掉N-K个小的数,然后输出下一个即可。根据排序二叉树特点,我们依次从根节点排除,其中小于 Kth largest element 的数的个数,可以由经过的每个结点的左子树的数目累加来得到。大致思路为从根结点一直向右寻找,在寻找过程中依次减去所经过结点的左子树的结点数,判断所减去的结点数目和需要减去的数目的关系来得到 Kth largest element。具体代码参考如下:

class BiTreeNode{   //二叉树结点定义
    public:
    int rep;
    int leftCount;
    int val;
    BiTreeNode *left;
    BiTreeNode *right;
    BiTreeNode(){
        rep=0;
        leftCount=0;
        left=NULL;
        right=NULL;
    }
};


void InsertBiTree(BiTreeNode *root,int val){  //排序二叉树的建立,同时记录每个结点的左子树


的数目。
    if(root->val==val){
        root->rep++;    //rep:需要插入的结点数值与所访问到的结点数值相等,则不再插入,将所访


问结点的rep属性值++。
        root->leftCount++;  //同时将leftCount属性值++。即将相等的数值也计入左子树结点数。
        return ;
    }
    else if(root->val<val){
        if(root->right!=NULL)
            InsertBiTree(root->right,val);
        else{
            BiTreeNode *right=new BiTreeNode();
            right->val=val;
            root->right=right;
            return ;
        }
    }
    else{
        root->leftCount++;   //小于所访问结点的数值,则该结点左子树结点数++。
        if(root->left!=NULL)
            InsertBiTree(root->left,val);
        else{
            BiTreeNode *left=new BiTreeNode();
            left->val=val;
            root->left=left;
            return ;
        }
    }
    return;
}




class KthLargest {
public:
    BiTreeNode *SearchTree;
    int k;
    int node;
    KthLargest(int k, vector<int> nums) {  //KthLargest 类构造函数。
        node=0;
        if(nums.size()==0){
            SearchTree=NULL;
            this->k=k;
            return ;
        }
        else{
            BiTreeNode *root=new BiTreeNode();
            root->val=nums[0];
            node++;
            for(int i=1;i<nums.size();i++){  //将初始化序列构造为排序二叉树。
                InsertBiTree(root,nums[i]);
                node++;
            }
            SearchTree=root;
            this->k=k;
        }
    }
    
    int FindKth(BiTreeNode *root,int k){  //重头戏来了!
        int EndFlag=node-k;   //EndFlag为需要减掉的结点数
        BiTreeNode *p;
        p=root;
        if(p!=NULL){
            while(EndFlag!=0||p==root){  // p==root:为了查找第N个最大的数
                if(p->leftCount<EndFlag){
                EndFlag=EndFlag-p->leftCount-1; //该结点还不够减           
                 p=p->right; //向该结点的右子树战略转移!为什么不用判断p是否具有右子树就贸然赋值?因为若p没有右子树,则证明p为最大值,则减掉的结点数为整个树的所有结点。
            }
            else if(p->leftCount==EndFlag){ // 如果p的左子树的数目正好等于还需要减的数目,则p就是下一个数了,即输出p。
                return p->val;
            }
            else{
                if(p->leftCount-p->rep<=EndFlag)  // 因为考虑到在序列中还有相等的数存在,所以要考虑真正小于该结点的数目与还需要减去的结点数的关系。
                    return p->val;  // 如果真正小于该结点的数目比还需减去的结点数小,则 Kth largest element 必然落到该结点的数值上。
                else{
                        p=p->left;  //如果真正小于该结点的数目大于还需减去的结点数,说明要往该结点的真正的左子树继续探寻。
                }
            }
          }
            while(p->left!=NULL)
                p=p->left;      // 退出循环:即p!=root&&EndFlag==0,这时说明已经减去了需要减去的结点数,
                              //但不能贸然输出,因为无法保证返回的p即为真正序列中的后继。
            return p->val;    // 找到真实后继后,则输出。
        }   
        else
            return -1234;   //输入的K值不合法,输出错误代码。
    }
    
    int add(int val) {   //调用寻找函数
        if(SearchTree==NULL){  // 若初始序列为空,则建立根结点。
            BiTreeNode *temp=new BiTreeNode();
            temp->val=val;
            SearchTree=temp;
            node++;
            return SearchTree->val;
        }
        else{
            InsertBiTree(SearchTree,val);  // 先插入结点
            node++;
            cout<<"root->val= "<<SearchTree->val<<"  root->leftCount= "<<SearchTree->leftCount<<endl;
            return FindKth(SearchTree,k);   //再寻找 Kth largest element。
        }
       
    }
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest obj = new KthLargest(k, nums);
 * int param_1 = obj.add(val);
 */

与此算法同理的还有:从根结点一直向左寻找,同时减去右子树结点数目,判断需要减去的数目和已经减去的数目的关系,来决定寻找方向和输出值,不在话下。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值