在字符串范围列表中搜索字符串

时间:2012-02-17 12:42:12

标签: c++ string algorithm

例如,我有一个词典范围列表 [a,an) [an,bb) [bb,d) [c,h) 给定一个字符串apple,我需要找到它所属的范围。在这种情况下,它在第二个范围内。如果字符串可能属于多个范围,则需要返回第一个字符串。例如:cat应该返回range3而不是range4。

蛮力方法是按顺序遍历列表并检查字符串是否适合那里。 更好的方法是首先解决重叠,对范围进行排序并进行二分查找。 有关进一步优化算法的建议吗?还欢迎c ++的实现技巧。这种逻辑碰巧发生在关键的执行路径上,必须很快。

更新的 是的,范围可能存在差距。 是二进制搜索可以使它O(log(n))。有没有什么我可以想出一个哈希,让它更好?哈希怎么样?我们可以假设我们在所有字符串和范围中只有小写字符。

7 个答案:

答案 0 :(得分:2)

这是你应该做的:

首先按字典顺序对范围的起点进行排序。然后你应该对它们进行以下预处理 - 对于每个范围,它使它开始于它的开始和前一个范围的结束(如果这使得当前范围为空,只需忽略它)。你这样做是因为如果一个单词在前一个范围的结尾之前,那么它将属于某些先前的范围,并且永远不会被归类为当前范围。在此预处理之后,所有范围都不重叠,因此您搜索的每个单词最多只属于其中一个。因此,您需要做的就是对结果预处理范围执行二进制搜索,这些范围将是O(log(n))复杂度。我怀疑你能否为这个问题找到更好的复杂性。

答案 1 :(得分:1)

每个范围开头的某种索引,也许是二叉树,可能是个好主意。不确定是否需要索引到每个范围的末尾,除非可能存在间隙。

答案 2 :(得分:0)

我想到了一个解决方案,可能是你可以对单词apple进行排序并识别a-z顺序中最后出现的字符。只需检查范围内的那一个字符。多思考......

答案 3 :(得分:0)

如果你有多余的内存并且限制为小写,你可以构建一个多路树。顶级节点有一个包含26个指针的数组。如果没有范围从该字符开始,则指针为空。如果以该字符开头的所有单词都属于该范围,则它们指向范围,如果范围在后续字符上分割,则指向另一个节点。 (所以给定[aa-ap],[ar-bl];'a'条目将指向另一个节点,其中条目'a'到'p'指向范围1,条目'q'为空,'r'通过'z'指向范围2.)

这应该是O(max(range_specifier))。

答案 4 :(得分:0)

你可以通过“网格化”来解决这个问题。

使用包含与第一个字母对应的26个条目的数组。每个bin包含与其具有非空交集的范围列表。像

'a' -> [a,an) [an,bb), 'b' -> [an,bb) [bb,d), 'c' -> [bb,d) [c,h) ...

您可以轻松地将该想法概括为几个字母的前缀

'aaa' -> [a,an), 'aab' -> [a,an), 'aac' -> [a,an) ...

这可以大大缩短要尝试的范围列表,尤其是在存在重叠的情况下,以存储和预处理时间为代价。

可以使用特殊约定来表明垃圾箱完全被覆盖。

我想,快乐的发行版可以导致O(1)。

答案 5 :(得分:0)

我不会惊讶你的范围可以用trie(http://en.wikipedia.org/wiki/Trie)来表示。一旦填充了trie,查询时间不应超过最长范围的长度,也不应超过查询字符串的长度。

这在查询时间方面是最佳的(实际上在计算模型中为O(1))。

答案 6 :(得分:0)

我的方法是

  • 范围有两个限制(下限和上限)
  • 每个范围将空间分为三个部分(下方,内部,上方)
  • 每个限制将空间分为两部分(下方,上方_或等)

所以方法可以是:

  • 编号范围
  • 将范围分解为两个限制
  • 将限制放入树中,在包含两个列表的节点中,这些列表具有引用它们的范围(一个列表用于使用此限制作为下限的节点,一个列表用于上限)
    • 这些列表可以是位图,因为范围是编号
  • 找到一个字符串
    • 你走在树上,每当你下台时,你实际上越过一个极限,并获得关于你的右/左限制的知识,以及你左/右/内的范围。
    • 您需要两个额外的列表(范围编号)才能进行此遍历。
    • 这些列表可以是位图
    • 每次越过边框时,都会从其中一个列表中添加范围编号,然后将其从另一个列表中删除。
  • 一旦你在一个范围内(x> =下限&& x<上限; ,其限制对应于相同的课程范围),algorihtm就会完成。
    • (假设这实际上是数字最小的范围:第一场比赛)
    • 如果两个列表共享一个或多个成员,则可以检测到这一点
    • 我们想要编号最小的重叠成员。

由于此方法是树搜索,因此它具有O(log(N))复杂度。

更新:第二个想法,位图不是存储使用列表或结果的好方法。链表(实际上是两个)更好。代码是300行。我应该在这里贴出来吗?

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define COUNTOF(a) (sizeof a / sizeof a[0])
#define WANT_DUPLICATES 0

struct range {
                /* separate linked lists for {lo,hi}) 
                ** for limits that this range participates in. */
        struct range *next_lo;
        struct range *next_hi;
        unsigned num;
        char *str_lo, *str_hi;
        } ranges[] =
{{NULL,NULL,0, "a","an"}, {NULL,NULL,1, "an", "bb"}
,{NULL,NULL,2, "bb", "d"}, {NULL,NULL,3, "c", "h"}
,{NULL,NULL,4, "bb", "c"}, {NULL,NULL,5, "c", "d"}
};
#define NRANGE COUNTOF(ranges)

void add_range(struct range *pp);
void list_dmp(FILE *fp, int isupper, struct range *bp);

struct treetwo {
        struct treetwo *prev;
        struct treetwo *next;
        char *str;
        struct range *list_lo; /* ranges that use str as lower limit */
        struct range *list_hi; /* ranges that use str as upper limit */
        };

struct treetwo *root = NULL;
struct treetwo ** find_hnd(struct treetwo **tpp, char *str);
void tree_dmp(FILE *fp, struct treetwo *tp, char *msg, unsigned indent);

struct result {
        unsigned size;
        unsigned used;
        struct {
                unsigned num;
                unsigned mask;
                } *entries;
        };
#define MASK_BELOW_LO 1
#define MASK_ABOVE_LO 2
#define MASK_BELOW_HI 4
#define MASK_ABOVE_HI 8

int result_resize(struct result *res, unsigned newsize);
void init_structures(void);
struct result *find_matches (char *str);
unsigned update_state(struct result *rp, struct treetwo *tp, int isabove);

int main (void)
{
char buff[100];
struct result *res;
size_t pos;
unsigned idx;
static char *legend[4] = { "unknown", "below", "above",  "impossible"};

init_structures();
tree_dmp(stderr, root, "Root", 0);

while (fgets (buff, sizeof buff, stdin) ) {
        pos=strcspn(buff, "\r\n");
        buff[pos] = 0;
        res = find_matches (buff);
        for (idx=0; idx < res->used; idx++) {
                unsigned num = res->entries[idx].num;
                unsigned mask = res->entries[idx].mask;
                fprintf(stdout, "[%u]Range%u %x: '%s' %s '%s' and '%s' %s '%s'\n"
                        , idx, num, mask
                        , buff, legend[mask & 3], ranges[num].str_lo
                        , buff, legend[(mask>>2) & 3], ranges[num].str_hi
                        );
                }
        }

return 0;
}

unsigned update_state(struct result *rp, struct treetwo *tp, int isabove)
{
struct range *p;
unsigned mask_lo, mask_hi;
unsigned hitcnt,idx;
/* State: (lower limit)
** 0 : unknown
** MASK_BELOW_LO: below limit
** MASK_ABOVE_LO: above limit
** 3: impossible
** State: (upper limit)
** 0 : unknown
** MASK_BELOW_HI: below limit
** MASK_ABOVE_HI: above limit
** c: impossible
** Combined states:
** required state 2|4 := 6
** 5: unreachable
** a: unreachable
** 9: impossible
** f: impossible
*/

if (!tp) return 0;
hitcnt=0;
mask_lo = (isabove>=0) ? MASK_ABOVE_LO : MASK_BELOW_LO;
mask_hi = (isabove>=0) ? MASK_ABOVE_HI : MASK_BELOW_HI;

fprintf(stderr , "Update_state(start{%s}, isabove=%d, mask=%x,%x)\n"
        , tp->str , isabove, mask_lo, mask_hi);
fprintf(stderr , "Update_state(Lo=%s)=", tp->str);
list_dmp(stderr , 0, tp->list_lo);
idx=0;
for (p = tp->list_lo; p ; p = p->next_lo) {
        unsigned num = p->num;
        fprintf(stderr , "Update_state:[%u] |= %u", num, mask_lo );
        for (   ;idx < rp->used;idx++) { if (rp->entries[idx].num >= num) break; }
        if ( idx < rp->used ) {
                fprintf(stderr , " Old was:%u\n", rp->entries[idx].mask );
                rp->entries[idx].mask |= mask_lo;
                if (rp->entries[idx].mask == (MASK_ABOVE_LO|MASK_BELOW_HI)) hitcnt++;
                continue;
                }
        if ( idx >= rp->used) {
                if ( rp->used >= rp->size && result_resize(rp, rp->size ? rp->size*2 : 8)) break;
                fprintf(stderr , " New at:%u\n", idx );
                rp->entries[idx].num = num;
                rp->entries[idx].mask = mask_lo;
                rp->used++;
                }
        }

fprintf(stderr , "Update_state(Hi=%s)=", tp->str);
list_dmp(stderr , 1, tp->list_hi);
idx=0;
for (p = tp->list_hi; p ; p = p->next_hi) {
        unsigned num = p->num;
        fprintf(stderr , "Update_state:[%u] |= %u", num, mask_lo );
        for (   ;idx < rp->used;idx++) { if (rp->entries[idx].num >= num) break; }
        if ( idx < rp->used ) {
                fprintf(stderr , " Old was:%u\n", rp->entries[idx].mask );
                rp->entries[idx].mask |= mask_hi;
                if (rp->entries[idx].mask == (MASK_ABOVE_LO|MASK_BELOW_HI)) hitcnt++;
                continue;
                }
        if ( idx >= rp->used) {
                if ( rp->used >= rp->size && result_resize(rp, rp->size ? rp->size*2 : 8)) break;
                fprintf(stderr , " New at:%u\n", idx );
                rp->entries[idx].num = num;
                rp->entries[idx].mask = mask_hi;
                rp->used++;
                }
        }
return hitcnt;
}

struct result *find_matches (char *str)
{
int rc;
struct treetwo **hnd;
struct result *res = malloc (sizeof *res);
unsigned dst,src;
res->used=res->size=0; res->entries=0;

for (hnd= &root; *hnd; hnd = (rc < 0) ? &(*hnd)->prev : &(*hnd)->next ) {
        rc = strcmp( str, (*hnd)->str);
        fprintf(stderr, "####\nStr=%s Node={%s} rc=%d\n"
                , str, (*hnd)->str, rc );
        list_dmp(stderr , 0, (*hnd)->list_lo );
        list_dmp(stderr , 1, (*hnd)->list_hi );
        rc = update_state(res, *hnd , rc);
#if WANT_DUPLICATES
        continue;
#else
        /* if we don't want duplicates we can bail out on the first match */
        if (rc) break;
#endif
        }


/* Now cleanup the results.
** Below(lower limit) and above(upper limit) and variations can be removed.
** Some results are incomplete, because one of there limits is out
** of reach (shadowed by a narrower range).  We'll have to recompute these.
** The result structure is compacted: if entries are deleted, the remaining ones are shifted down.
** Note: part of this cleanup (removal of unreacheables) could be done in update_state(),
** that would keep the array with partial results as short as possible.
*/
for (dst=src=0; src < res->used; src++) {
        int rc;
        unsigned num = res->entries[src].num;
rescan:
        switch (res->entries[src].mask & 0xf) {
        default: break;
        case 0: /* impossible */
                goto rescan;
#if WANT_DUPLICATES
        case MASK_ABOVE_LO:
                rc = strcmp(str, ranges[num].str_hi);
                res->entries[src].mask |= (rc >=0) ? MASK_ABOVE_HI : MASK_BELOW_HI;
                goto rescan;
        case MASK_BELOW_HI:
                rc = strcmp(str, ranges[num].str_lo);
                res->entries[src].mask |= (rc >=0) ? MASK_ABOVE_LO : MASK_BELOW_LO;
                goto rescan;
#endif
        case MASK_BELOW_HI|MASK_ABOVE_LO:
                if (dst != src) res->entries[dst] = res->entries[src];
                dst++;
                }
        }
fprintf(stderr, "####\nFinal pass: %u/%u\n", dst, res->used );
res->used = dst;
return res;
}

void init_structures(void)
{
unsigned idx;

for (idx = 0; idx < NRANGE; idx++) {
        add_range( &ranges[idx]);
        }
}

void list_dmp(FILE *fp, int isupper, struct range *bp)
{

fprintf(fp, "%s", (isupper) ? "Upper" :"Lower"  );
for (   ; bp  ; bp = (isupper) ? bp->next_hi : bp->next_lo) {
        fprintf(fp, " %u:{%s,%s}"
        , bp->num , bp->str_lo , bp->str_hi
        );
        }
fprintf( stdout, "\n" );
}

void add_range(struct range *pp)
{
struct treetwo **tpp;
struct range **rpp;

fprintf(stderr, "Inserting range %u->{%s,%s}\n", pp->num, pp->str_lo, pp->str_hi);
        /* find low boundary for this interval */
tpp = find_hnd (&root, pp->str_lo);
if (!*tpp) {
        fprintf(stderr, "Creating node for %u->%s (low)\n", pp->num, pp->str_lo);
        *tpp = malloc(sizeof **tpp);
        (*tpp)->list_lo = NULL;
        (*tpp)->list_hi = NULL;
        (*tpp)->str = pp->str_lo;
        }
for (rpp = &(*tpp)->list_lo; *rpp ; rpp = &(*rpp)->next_lo) {;}
*rpp = pp;
fprintf(stderr, "Added range %u->{%s,%s} to treenode(%s)->list_lo\n"
        , pp->num, pp->str_lo, pp->str_hi
        , (*tpp)->str
        );

        /* find high boundary */
tpp = find_hnd (&root, pp->str_hi);
if (!*tpp) {
        fprintf(stderr, "Creating node for %u->%s (High)\n", pp->num, pp->str_hi);
        *tpp = malloc(sizeof **tpp);
        (*tpp)->list_lo = NULL;
        (*tpp)->list_hi = NULL;
        (*tpp)->str = pp->str_hi;
        }
for (rpp = &(*tpp)->list_hi; *rpp ; rpp = &(*rpp)->next_hi) {;}
*rpp = pp;
fprintf(stderr, "Added range %u->{%s,%s} to treenode(%s)->list_hi\n"
        , pp->num, pp->str_lo, pp->str_hi
        , (*tpp)->str
        );
}

struct treetwo ** find_hnd(struct treetwo **tpp, char *str)
{
int rc;

for (   ; *tpp; tpp = (rc < 0) ? &(*tpp)->prev : &(*tpp)->next ) {
        rc = strcmp( str, (*tpp)->str);
        if (!rc) break;
        }
return tpp;
}

void tree_dmp(FILE *fp, struct treetwo *tp, char *msg, unsigned indent)
{
unsigned uu;
if (!tp) return;
if (!msg) msg = "";

for (uu=0; uu < indent; uu++) { fputc( ' ', fp); }
fprintf(fp, "%s:{%s}\n", msg, tp->str );

for (uu=0; uu < indent+1; uu++) { fputc( ' ', fp); }
list_dmp(fp , 0, tp->list_lo);

for (uu=0; uu < indent+1; uu++) { fputc( ' ', fp); }
list_dmp(fp , 1, tp->list_hi);

tree_dmp(fp, tp->prev, "Prev", indent+2);
tree_dmp(fp, tp->next, "Next", indent+2);
}
int result_resize(struct result *res, unsigned newsize)
{
void *old;

old = res->entries;
res->entries = realloc ( res->entries , newsize * sizeof *res->entries);
if ( !res->entries) {
        res->entries = old; return -1;
        }
res->size = newsize;
if (res->used > newsize) res->used = newsize;
return 0;
}