如何使用动态分配的数组实现队列?

时间:2016-05-13 19:57:29

标签: c queue dma

我想使用动态分配的数组实现队列。这提出了一些我不确定如何处理的问题。如何检查队列是否为空?如何在一个瞬间跟踪队列中有多少元素?

对于第二个问题,我想我可以创建一个变量来跟踪队列中的元素数量,这些元素在我使用realloc()的任何时候都会更新。我很欢迎其他建议。

如果您有任何需要考虑的事项,请提出来。

4 个答案:

答案 0 :(得分:3)

这是一个相当简单的基于数组的FIFO队列:

struct queue {
  T *store;     // where T is the data type you're working with
  size_t size;  // the physical array size
  size_t count; // number of items in queue
  size_t head;  // location to pop from
  size_t tail;  // location to push to
};

struct queue q;
q.store = malloc( sizeof *q.store * SIZE );
if ( q.store )
{
  q.size = SIZE;
  q.count = q.head = q.tail = 0;
}

要推送项目,请执行以下操作:

int push( struct queue q, T new_value )
{
  if ( q.count == q.size )
  {
    // queue full, handle as appropriate
    return 0;
  }
  else
  {
    q.store[q.tail] = new_value;
    q.count++;
    q.tail = ( q.tail + 1 ) % q.size;
  }
  return 1;
}

Pops类似

int pop( struct queue q, T *value )
{
  if ( q.count == 0 )
  {
    // queue is empty, handle as appropriate
    return 0;
  }
  else
  {
    *value = q.store[q.head];
    q.count--;
    q.head = ( queue.head + 1 ) % q.size;
  }

  return 1;
}

如上所述,这是一个“循环”队列;当从队列中推送和弹出项目时,headtail指针将会回滚。

与任何方法一样,这有优点和缺点。它很简单,它避免了过多的内存管理(只是分配后备存储)。只是更新count比尝试从headtail计算更简单。

扩展后备存储并不是那么简单;如果你的尾指针已经缠绕,你将不得不在head之后移动所有内容:

Before:

+---+---+---+---+---+
| x | x | x | x | x |
+---+---+---+---+---+
          ^   ^
          |   |
          |   +---  head
          +-------  tail

After:        
+---+---+---+---+---+---+---+---+---+---+
| x | x | x |   |   |   |   |   | x | x |
+---+---+---+---+---+---+---+---+---+---+
          ^                       ^
          |                       |
          |                       +---  head
          +-------  tail

此外,如果您想要比简单FIFO更复杂的东西,您可能希望使用不同的数据结构作为后备存储。

答案 1 :(得分:0)

通常,您将指针变量保留到队列的“头部”。当此指针为空时,列表为空,如果不为,则指向第一个节点。

现在,当谈到队列中给定时间内元素的数量时,你建议的另一个解决方案是实际运行所有节点并计数,但是根据元素的数量,这可能非常慢。

答案 2 :(得分:0)

为了计算,只需保留已插入元素数量的引用计数

INSERT () { 
    //code to insert element
    size++;
}

POP(){
    //code to remove element
    size--;
}

SIZE(){
   return size;
}

接下来,您将不得不决定使用哪种策略来插入元素。

大多数人只使用列表。而且由于队列通常是FILO(先进先出)或LILO(最后输出),因此可能会有点棘手。

列表就是这个

struct Node{
    T* Value;
    ptr Next;
 }

你有一堆这些按顺序创建一个列表。每个插入都会生成一个新节点,删除将取出节点并重新附加列表。

答案 3 :(得分:0)

如果您使用的是realloc,则地址可以更改,因此您希望下一个,上一个,首尾使用索引。

使用固定大小的数组,您可以使用旋转缓冲区,其中仅需要保留偏移量和大小以及值的数组,就不需要按顺序保留值的节点结构,只要值是大小不变。

对于动态尺寸,您可以在弹出菜单中将要删除的一个与最后一个交换。但是,这需要为每个节点存储上一个和下一个。您需要存储前一个节点,因为最后交换节点时,您需要更新其在其父节点中的位置(下一个)。本质上,您最终会获得一个双向链接列表。

这样做的一个好处是,您可以避免将一个数组用于多个链表。但是,这对线程化应用程序不利,因为您将在单个资源上进行全局争用。

标准方法是对每个节点使用malloc和free。除了更多的内存管理之外,其他方面的影响没有太大区别。您只需要存储每个节点的下一个地址。您也可以使用指针而不是索引。尽管对于许多可能永远不会发生或几乎不会发生的用例,销毁队列是O(N)。

malloc与realloc的性能特征可能会因许多因素而异。这是要记住的事情。

根据经验,当将b = malloc(size + amount);memcopy(a, b, size);free(a);a = b;之类的东西自然地替换为a = realloc(a, size + amount);时,realloc很好,但是如果您必须做一些奇怪的事情才能使realloc正常工作,则可能会被认为是错误的。重新分配应该可以解决您的问题。如果您的问题解决了realloc,那么realloc可能就是您的问题。如果您使用realloc替换与realloc相同的代码,那很好,但否则请问一下自己,如果那确实是最简单的可行方法,并且是否需要修改代码以使其与realloc一起使用,并且重新使用realloc来完成realloc的工作。也就是说,如果您更换更多 少用或少用以使其正常工作可能是好的,但如果用少用更多或需要更多以使其起作用,则可能很不好。简而言之,使其保持简单。您会在这里注意到realloc实现意味着更多的跳跃,因此可能构思不当。

数据结构示例...

假设int为uint。

成员是指您实际存储的内容。在此示例中,为此使用了空指针,以便它可以容纳任何类型。但是,您可以将其更改为带类型的指针,或者如果类型始终相同,甚至可以更改为类型本身。

当分配的内存量可能大于用于存储项目的内存时,将使用空间。

循环静态队列:

struct queue {
 void* members[SPACE];
 int offset;
 int size;
};

成员可以由指针类型组成,用于长度可变的任意类型。您可以使用偏移量,大小而不是头,尾。

圆形动态初始尺寸:

struct queue {
 void* members[];
 int offset;
 int size;
 int space;
};

还可以询问指针具有多少内存,而不是存储空间。

尾部为偏移量+大小-1.您需要按空间使用模数来获取实际偏移量。

创建后可以更改空间或将其用作向量。但是,调整大小操作可能会非常昂贵,因为您可能必须移动多个元素以使其变为O(N)而不是O(1)才能进行移位和推动。

重新分配向量队列:

struct queue {
 node* nodes;
 int head;
 int tail;
 int size;
};

struct node {
 void* member;
 int next;
 int prev;
};

Malloc节点队列:

struct queue {
 void* head;
 node* head;
 node* tail;
};

struct node {
 void* member;
 node* next;
};