[英]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;
}
如所寫,這是一個“循環”隊列。 當項目從隊列中被推送和彈出時, head
和tail
指針將環繞。
與任何方法一樣,這具有優點和缺點。 這很簡單,並且避免了過多的內存管理(只需分配后備存儲)。 僅僅更新count
要比head
和tail
計算要簡單。
擴展后備存儲並不是那么簡單。 如果您的尾巴指針已纏繞,則必須在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.