我想使用动态分配的数组实现队列。这提出了一些我不确定如何处理的问题。如何检查队列是否为空?如何在一个瞬间跟踪队列中有多少元素?
对于第二个问题,我想我可以创建一个变量来跟踪队列中的元素数量,这些元素在我使用realloc()
的任何时候都会更新。我很欢迎其他建议。
如果您有任何需要考虑的事项,请提出来。
答案 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;
}
如上所述,这是一个“循环”队列;当从队列中推送和弹出项目时,head
和tail
指针将会回滚。
与任何方法一样,这有优点和缺点。它很简单,它避免了过多的内存管理(只是分配后备存储)。只是更新count
比尝试从head
和tail
计算更简单。
扩展后备存储并不是那么简单;如果你的尾指针已经缠绕,你将不得不在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;
};