繁体   English   中英

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

[英]How do I implement a queue with a dynamically allocated array?

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

对于第二个问题,我认为我可以创建一个变量来跟踪队列中的元素数量,该变量在我使用realloc()随时更新。 我欢迎其他建议。

如果您还有其他考虑,请考虑一下。

这是一个非常简单的基于数组的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;
}

流行音乐相似

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更复杂的内容,则可能需要使用其他数据结构作为后备存储。

通常,您将指针变量保留到队列的“ Head”。 当此指针为null时,列表为空,否则为空,它指向第一个节点。

现在,对于给定时间队列中的元素数量,您建议的另一种解决方案是实际遍历所有节点并进行计数,但是取决于元素数量,这可能会很慢。

为您的数量,只需保留已插入多少个元素的参考数量

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

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

SIZE(){
   return size;
}

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

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

清单是这个

struct Node{
    T* Value;
    ptr Next;
 }

在其中按顺序排列了一堆,就可以创建一个列表。 每次插入都会产生一个新节点,而移除将取出该节点并重新附加列表。

如果您使用的是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实现意味着更多的跳跃,因此可能构思不当。

数据结构示例...

假设int是uint。

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

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

循环静态队列:

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

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

圆形动态初始尺寸:

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

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

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

创建后可以更改空间或将其用作向量。 但是,调整大小操作可能会非常昂贵,因为您可能必须移动多个元素以使其移动(而不是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;
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM