以区间为单位的二分查找

【2019回家过年前的一记】

问题场景:

区间的格式如下,整体为一个Json字符串,每个区间的startTime和endTime都是按照从小到大的顺序排列的。

"[{\"endTime\":1578633955,\"startTime\":1578626876},{\"endTime\":1578640268,\"startTime\":1578634050},{\"endTime\":1578642086,\"startTime\":1578640291},{\"endTime\":1578643084,\"startTime\":1578642639}]"

现在给定一个用户需要查找的时间段:

"startTime":1578585600,
"endTime":1578644000

要求:返回该时间段内的所有区间,若查找的时间点位于某个区间中间,需要对该区间进行分割只返回所需要的那一段。

代码思路:

●首先将每个小区间的startTime和endTime依次存入一个数组中;

●使用二分查找该数组快速确定用户请求的时间段与原始区间的交集起始点位置index;

●从该位置开始将符合要求的区间依次放入新的json串中,期间需要对首尾两个区间进行特殊处理

【0,1】【2,3】【4,5】【6,7】  

begin=0,end=7,mid=3

因为小区间都是两个元素,故mid在最后一步(begin+1 == end)之前都是奇数,即某个小区间的endTime。

然后最后一步有两种情况:

●用户请求的时间段的startTime落在【2,3】中;(begin==2;end==3;mid=(2+3)>>1即mid == begin)

●用户请求的时间段的startTime落在1】,【2中,即落在一个无效的点上。(begin==1;end==2;mid=(1+2)>>1即mid == begin)

可见两种情况下都有mid == begin,因此可将其作为一个结束条件

不论mid为奇数还是偶数,当mid指向的startTime或者endTime和用户请求的startTime相等时,说明找到交集的起点位置了,二分查找结束,这是另一个结束条件,那么index记录下该位置是原始区间的第几个,然后在进行后续处理。index=(mid+1)/2(当mid为偶数时index本为mid/2,但为了统一起见,将其写成(mid+1)/2了,并不影响最后的结果)

以下两种极端情况的处理都放在二分查找完毕后,这样可以保证二分查找过程的清晰度:

●用户请求的区间在所有原始区间的前面且无交集,这样最后的结果应该为空;

●用户请求的区间在所有原始区间的后面且无交集,这样最后的结果应该为空;

函数代码如下:

/**Writen By: zzg**/
/**Date: 2020/01/20**/

static void filterConcretInterval(long int startTime, long int endTime, JsonParser & sliceList)
    {
        unsigned int begin = 0;
        unsigned int end = 0;
        unsigned int mid = 0; 
        unsigned int index = 0;
        unsigned int listSize = 0;
        JsonParser newList;

        if(sliceList.size() == 0){
            return ;
        }
        if(startTime == endTime){
            sliceList.clear();
            return ;
        }
        listSize = sliceList.size();

        //将所有区间转化为一维数组
        long int timeArray[listSize << 1];

        for(unsigned int i = 0, j = 0; i < listSize, j < ((listSize << 1) - 1); i++, j++){
            if(sliceList[i]["startTime"].isInt64() && sliceList[i]["endTime"].isInt64()){
                timeArray[j] = sliceList[i]["startTime"].asInt64();
                j++;
                timeArray[j] = sliceList[i]["endTime"].asInt64();
            }
        }
        end = (listSize << 1) - 1;
        while(begin <= end){    //取等号意义在于当begin==end,即只有一个区间时,index的取值
            mid = (begin + end) >> 1;
            if((timeArray[mid] == startTime) || (mid == begin)){
                index = (mid+1) >> 1;
                break;
            }else if(timeArray[mid] > startTime){
                end = mid;
            }else{
                begin = mid;
            }
        }

        if(sliceList[index]["endTime"] <= startTime){
            index++;
        }
        if(index == listSize){
            sliceList.clear();
            return ;
        }
        if(sliceList[index]["startTime"] < startTime){
            sliceList[index]["startTime"] = startTime;
        }

        for(unsigned int i = index; i < sliceList.size(); i++){
            if(sliceList[i]["endTime"].isInt64() && sliceList[i]["startTime"].isInt64()){
                if(sliceList[i]["startTime"] >= endTime) break;
                newList[i-index] = sliceList[i];
                if(sliceList[i]["endTime"] >= endTime){
                    newList[i-index]["endTime"] = endTime;
                    break;
                }
            }
        }
        sliceList.clear();
        sliceList = newList;
    }

另解:

当然,如果不考虑时间复杂度,可以构造一个包含startTime和endTime的结构体,直接将每个小区间当做一个结构体对象插入一个set容器(以红黑树为底层实现)中,此时构造了一颗红黑树。然后将用户请求的startTime和endTime分别作为两个结构体对象插入到该set中,如果插入失败,则说明set中已经存在该时间点,返回该节点的iterator。最后只需遍历两次插入位置之间的节点即可得到用户请求的区间了。(^_^)

发布了40 篇原创文章 · 获赞 16 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章