实施数据结构以容纳字符流

时间:2018-07-21 07:59:42

标签: c data-structures

给定一个字符流(或未知数量的字符),我想实现一个数据结构以容纳这些字符,并具有以下功能和属性。

功能

  1. 插入新字符(末尾)
  2. 按收到的顺序打印所有现有字符
  3. 删除一定范围内的字符(不一定从末尾开始)

属性

(对于N-当前字符数)。

复杂度:

  1. 插入-O(1)
  2. 打印-尽可能快,但不超过O(N)
  3. 删除-关于O(log(N))

内存:

成比例,并且不比N大。

当前解决方案

  
      
  • 数组-删除太复杂了。
  •   
  • 链接列表-删除速度很快,但是在所需范围内查找索引很复杂。
  •   
  • 哈希表-无法考虑使用简单的重新平衡方法实现。
  •   

我相信解决方案将是使用带有快速索引数组的某些链表,但是更新此类数组始终会很复杂。


实现将在C语言中完成,但是任何不使用任何特殊功能的伪代码都可以解决问题。

您能想到实现这些属性的任何实现吗?

4 个答案:

答案 0 :(得分:1)

我想说一个简单的动态数组应该为您提供所需的属性:

  • 打印很简单O(N)。您必须复制所有字符,并且由于它们在内存中是连续的,因此不需要额外的复杂性。另请参阅我的评论。

  • 附加一个字符(这不是插入字符,正如您所说的始终在末尾)是O(1)。假设您为每个字符分配内存:那么每次调用都会有此分配的开销,但这是恒定的。实际上,您可以通过分配块来加快代码的速度:仅在例如第1024个调用,但这仍然意味着持续的开销。

  • 删除有点复杂。当然,您必须更新计数器,但这是恒定不变的。然后,您将不得不移动现有数据。在最坏的情况下(仅删除第一个字符),这意味着移动N-1个字符-> O(N)。在最佳情况下(仅删除最后一个字符),这意味着完全没有移动,因此只需O(1)即可更新计数器。如前所述,我不会在这里做数学运算(您必须考虑要删除的序列的开始和长度的每种可能组合),但是结果将低于O(N)

  • 内存复杂度是存储的字符数,再加上两个大小变量(计数和容量),再加上0和要分配的块大小之间的一些未使用空间。似乎匹配,它是成比例的并且接近N


C中可能的数据结构:

# define CHUNKSIZE 1024

struct container
{
    size_t capacity;
    size_t count;
    char *content;
};

struct container *create_container(void)
{
    struct container *c = malloc(sizeof *c);
    if (c)
    {
        c->capacity = CHUNKSIZE;
        c->count = 0;
        c->content = malloc(CHUNKSIZE);
        if (!c->content)
        {
            free(c);
            c = 0;
        }
    }
    return c;
}

void delete_container(struct container *c)
{
    if (!c) return;
    free(c->content);
    free(c);
}

“插入”:

int container_append(struct container *c, char chr)
{
    if (c->count == c->capacity)
    {
        char *newcontent = realloc(c->content, c->capacity + CHUNKSIZE);
        if (!newcontent) return -1;
        c->capacity += CHUNKSIZE;
        c->content = newcontent;
    }
    c->content[c->count++] = chr;
    return 0;
}

打印:

void container_print(const struct container *c, FILE *out)
{
    fwrite(c->content, 1, c->count, out);
}

删除:

int container_deleterange(struct container *c, size_t start, size_t n)
{
    if (start >= c->count || n > c->count || start + n > c->count)
    {
        return -1;
    }
    memmove(c->content + start, c->content + start + n,
            c->count - start - n);
    c->count -= n;
    return 0;
}

免责声明:代码可能仍包含错误,请直接在此处编写。只是为了演示一种可行的方法。

答案 1 :(得分:1)

我建议使用向量链接列表。列表中的每个向量可能具有不同的大小。对于大多数应用程序,第一个块应较小,并针对短字符串进行优化,随后的块应分配系统页面大小的倍数。

追加:O(1)时间。如果最后一块的空间不足,请分配一个新块。请注意,对于一个简单的动态数组,它变为O(n),因为可能需要重新分配整个数组并将其复制到新位置。

打印:O(n)时间。按顺序打印列表中的每个块。

删除:从理论上讲,查找范围需要O(n)时间(用绳索或绳索是O(log n)。)但实际上,字符串通常不会包含大量的块。其余操作需要O(1)时间。如果要删除的范围覆盖整个块,则将其删除。如果它与一个块的末尾重叠,则截断该块。如果它与块的开头重叠,则C允许您将块内的指针用作字符串,因此只需使用指针算术跳过已删除的字符。如果块小于64KiB,则偏移量可以为unsigned short,以节省一些字节的内存。 (您仍然需要跟踪指向该存储块的原始指针,以便free()。)

请注意,对于单个动态数组,要删除中间的某个范围,需要进行数组移位。

另一个优点是,连接临时字符串也是O(1)时间操作,如果使用指向块的智能指针,则复制和拼接也会变得很快。

如果需要在中间插入任意字符,则可能需要rope or cord

答案 2 :(得分:0)

可变长度字符串的链表的问题在于,如果您要与设备通信,则字符串中总是会得到零值(十六进制00),而零字节将终止字符串。一个链表在每个元素或列表中的相关结构内恰好包含一个uint8_t值,可以解决此问题。否则,零星(零星)作为零值接收会遇到各种各样的问题。

答案 3 :(得分:0)

我使用块的链表,从容量16字节开始,每次加倍至最大容量1048576字节。当代码发现您正在缓冲越来越多的字符时,这种想法就是要适应的。

https://github.com/chkoreff/Fexl/blob/master/src/buffer.c

该代码无法满足您的删除要求,因为我使用它来实现不需要可变字符串的功能编程语言。但是您可以通过简单地将要保留的所有字符复制到新缓冲区中来实现删除。您甚至可以更改顶级指针,使其在位置上可变。