美文网首页
一段二分查找代码

一段二分查找代码

作者: 明翼 | 来源:发表于2020-12-26 17:14 被阅读0次

一 背景

有个需求,要从大量的文件中过滤需要的文件,文件名那是以时间命令(UTC时间)的,要过滤的文件范围也是符合一定时间段的文件。

二 初始版本

代码比较简单和直接,通过直接读取目录下的文件,获取每个文件的文件名信息,判断每个文件名是否在要求的时间范围内,是的话就放入到文件名列表中,最后返回。
这段代码,从感觉上,性能一般,如果有几百万个文件,甚至更多个文件,则每个文件名都要判断一次耗时是惊人的,那能不能根据文件名直接做二分查找的方式来过滤文件名那。

int thisFileNeedSearch(char* fileName, int startTime, int endTime)
{
    if (fileName == NULL) {
        return -1;
    }
    char* flag = ".";
    char* ptmp = strdup(fileName);
    strtok(ptmp, flag);
    strtok(NULL, flag);
    char* ret = strtok(NULL, flag);
    if (ret != NULL) {
        int iret = atoi(ret);
        if (iret >= startTime && iret <= endTime) {
            free(ptmp);
            return 1;
        } else {
            free(ptmp);
            return 0;
        }
    } else {
        cl_error("file is error:%s", fileName);
        free(ptmp);
        return -1;
    }
}

fileLists* getNeedSearchFilesOld(char* dir_name, int startTime, int endTime)
{
    DIR* dir = opendir(dir_name);
    struct dirent* p = NULL;
    struct stat statbuf;
    fileLists* lst = NULL;
    cl_infos("Scan dir is:%s", dir_name);
    if (lstat(dir_name, &statbuf)) {
        cl_error("Stat error:%s", dir_name);
        return NULL;
    }
    if (!S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) {
        cl_error("Dir :%s is not dir mode is:%d!", dir_name, statbuf.st_mode);
        return NULL;
    }
    while ((p = readdir(dir)) != NULL) {
        // 过滤掉. 和..
        if (p->d_name[0] == '.') {
            continue;
        }
        if (thisFileNeedSearch(p->d_name, startTime, endTime) == 1) {
            if (lst == NULL) {
                lst = (fileLists*)malloc(sizeof(fileLists));
                assert(lst != NULL);
                lst->fileName = strdup(p->d_name);
                lst->next = NULL;
                lst->size = 1;
                lst->tail = lst;
            } else {
                fileLists* plst = (fileLists*)malloc(sizeof(fileLists));
                assert(plst != NULL);
                plst->fileName = strdup(p->d_name);
                plst->next = NULL;
                plst->size = 1;
                lst->tail->next = plst;
                lst->tail = plst;
                lst->size += 1;
            }
        } else {
            cl_debug("file:%s not in start:%d end:%d", p->d_name, startTime, endTime);
        }
    }
    closedir(dir);
    return lst;
}

三 二分查找版本

要查找文件的范围是个区间[starttime,endtime],一般endtime和starttime的差值并不算多大,那么我们可以先对文件名做个排序,然后根据二分查找的算法,查找到开始遍历starttime的文件名,逐步对后遍历即可。

直接读目录的方式,在排序挺麻烦,幸好linux有另外一个遍历文件的接口如下:

#include <dirent.h>

int scandir( const char *dir,  struct dirent **namelist,  int (*filter) (const void *b),  int ( * compare )( const struct dirent **, const struct dirent ** ) );
int alphasort(const void *a, const void *b);
int versionsort(const void *a, const void *b);

提供两个接口,一个是filter接口,即过滤需要的文件名,二是比较接口,即比较排序的时候使用,我们可以直接使用alphasort进行文件名的比较。如下:

// 保留文件名中包含file的文件
int file_filter(const struct dirent* dir)
{
    const char* name = dir->d_name;
    int len = strlen(name);
    if (MIN_FILE_NAME_LEN > len) {
        return 0;
    } else {
        char* pos = strstr(dir->d_name, "file");
        if (pos == NULL) {
            return 0;
        } else {
            return 1;
        }
    }
}

fileLists* getNeedSearchFiles(char* dir_name, int startTime, int endTime)
{
    struct dirent** entry_list = NULL;
    int count = scandir(dir_name, &entry_list, pcap_filter, alphasort);
    cl_infos("files number:%d", count);
    if (count < 1) {
        cl_infos("No find file is dir:%d", dir_name);
        return NULL;
    }
    int fileStart = fileNameToInt(entry_list[0]->d_name);
    cl_infos("start filename:%s", entry_list[0]->d_name);
    // 最小值大于要查询的最大值 直接返回
    if (fileStart > endTime) {
        freeEntryList(entry_list, count);
        free(entry_list);
        cl_infos("file start time:%d is bigger than endtime:%d", fileStart, endTime);
        return NULL;
    }
    if (count > 1) {
        // 最大值比最小值都小 直接返回
        int fileEnd = fileNameToInt(entry_list[count - 1]->d_name);
        cl_infos("end filename:%s", entry_list[count - 1]->d_name);
        if (fileEnd < startTime) {
            freeEntryList(entry_list, count);
            free(entry_list);
            cl_infos("file end time:%d is small than start:%d", fileEnd, startTime);
            return NULL;
        }
    }
    int low = 0;
    int high = count - 1;
    fileLists* lst = NULL;
    int startIndex = 0;
   // 二分查找文件范围
    while (low <= high) {
        int mid = low + (high - low) / 2;
        struct dirent* entry = entry_list[mid];
        int filetime = fileNameToInt(entry->d_name);
        // 说明要取得范围在左边
        if (filetime > startTime) {
            startIndex = mid;
            high = mid - 1;
            cl_infos("set startIndex:%d", startIndex);
        }
        // 说明文件范围在右边
        else if (filetime < startTime) {
            low = mid + 1;
        } else {
            // 找到最小值了
            startIndex = mid;
            break;
        }
    }
    for (int j = startIndex; j < count; j++) {
        int filetime = fileNameToInt(entry_list[j]->d_name);
        if (filetime >= startTime && filetime <= endTime) {
            addToFileList(&lst, entry_list[j]->d_name);
        }
    }
    freeEntryList(entry_list, count);
    free(entry_list);
    return lst;
}

说明下 : int mid = low + (high - low) / 2; 只所以有这种写法是为了防止low+high越界。

代码不算啥,给大家一个二分查找一段范围的文件的一点思路吧。

四 测试

写完了,免不了测试下,结果有点令人惊讶,如果文件数目少于9万个,其实老版本的性能反而更好,差别不是特别大,原因是scandir的过滤和排序拉慢了速度,而且核心逻辑只是转int和比较,在较小的数量级下,二分查找的性能不一定好于普通的查找。

五 诗词鉴赏

满庭芳·赠于瓦罐先生

朝代:[元代] 
      作者:[马钰] 

有荣有辱,有利有害。有喜有忧相待。
有得有失,自是有成有败。

为人有生有死,但有形、必然有坏。休著有,自古来著,有有谁存在。
好认无为无作,道无情无念,无憎无爱。

无我无人无染,无著无碍。
无心有消业障,这无无、人还悟解。
无中趣,得无生无灭,超越三界。

相关文章

  • python笔试面试项目实战2020百练1二分查找法(虾皮面试题

    题目1:请补充完整如下非递归二分查找的代码 题目2:请补充完整如下递归二分查找的代码 基础 二分查找是一种算法,其...

  • 查找算法

    查找算法 1顺序查找 2二分查找 2.1二分查找思路分析 2.2代码实现 3插值查找 3.1插值查找原理介绍: ​...

  • 二分查找

    网上找到的图片便于理解 二分查找递归实现与循环实现代码: /** 二分查找 1.二分查找又称折半查找,它是一种效率...

  • 一段二分查找代码

    一 背景 有个需求,要从大量的文件中过滤需要的文件,文件名那是以时间命令(UTC时间)的,要过滤的文件范围也是符合...

  • 查找

    查找一般要掌握顺序查找、二分查找、哈希表查找和二叉排序树查找。要能够快速准确地写出二分查找的代码。 1. 顺序查找...

  • LeetCode 数组专题 1:二分查找

    二分查找法 说明:二分查找法在代码实现上有模板方法,一定要掌握。 1、二分查找法的使用前提:数组一定要是排好序的,...

  • 数据结构与算法系列 (5) 查找算法

    1.概述 2.常见的查找算法 2.1 顺序(线性)查找 2.1.2 代码示例 2.2 二分查找/折半查找 2.2....

  • 二分查找(折半查找)

    二分查找源代码(Java) package sort; public class serach { public ...

  • 算法 -- 二分查找

    二分查找有两种实现:通过递归或循环 二分查找的前提是先要保证数组有序 递归 循环 github 完整代码 -- b...

  • 二分查找算法及其扩展

    二分查找是面试中手写代码经常遇到的题目, 昨天还有同事说有个面试, 手写代码这一环节就是二分查找.在下面两个版本的...

网友评论

      本文标题:一段二分查找代码

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