前言
當代碼,不再是簡單的完成需求,對代碼進行堆砌,而是開始思考如何寫出優美代碼的時候,我們的代碼水平必然會不斷提升,今天,咱們來學習環形隊列結構。
環形隊列的基本概念
相信對數據結構有過接觸的小伙伴,對隊列肯定不會陌生,隊列相對來說是比較簡單的數據結構,典型特點是FIFO,即First in First out,先進先出,就像我們日常排隊買票一樣,先到的人先買票,先從購票口出去,從下面的圖中,可以比較形象的了解隊列的特性。
用數組創建一個普通隊列,當有數據存儲時,隊列尾指針不斷增加,直到空間用完,當數據出隊列時,隊列頭指針不斷增加,直至和隊列尾相同時,所有數據完成出隊列,那么這時候會引入一個問題,全部出隊后,將無法繼續入隊,這樣的情況也叫做“假溢出”,即使數組中,明明還有空間可以利用,但是卻無法使用(平時我們做串口接收的時候,往往是通過清零計數器,清空數組,重新接收來解決這一問題)。
只要思想不滑坡,方法總比困難多。為了解決上面“假溢出”的現象,環形隊列應運而生,即通常將一維數組Queue[0]到Queue[MAXSIZE - 1]看成是一個首尾相連接的圓環,即Queue[0]與Queue[MAXSIZE - 1]相連接在一起,將這樣形式的隊列成為循環隊列。
環形隊列實現原理
在計算機的內存中,是不存在所謂的環形內存區域的,所以,需要程序員認為的“畫個圈圈”,從圖示環形隊列來看,存儲空間有限,當數據存到末端時,如何處理呢,只需要重新轉回0的地址區域,有點像“驢拉磨”的意思......
環列隊列的是邏輯上將數組元素q[0]與q[MAXN-1]連接,形成一個存放隊列的環形空間。
環形隊列設計的重要部分是,確定隊列的狀態,即隊列時空還是滿狀態。根據上面介紹的存儲順序,數據存儲時,隊列尾指針移動,頭指針不動,數據讀取時,頭指針移動,尾指針不動,但是如果從單純的“tail==head”還無法得出是處於空還是滿狀態,需要加些判斷手段:
- 附件標志法
1、tail追上head時,tag=1,隊列為滿狀態
2、head追上tail時,tag=0,隊列為空狀態
- 預留位置法
在存儲數據時,最后一個位置與隊列頭預留至少一個單位的空間
1、head==tail 隊列為空
2、(tail+1)% MAXN ==head 隊列滿
c語言代碼實現
環形隊列的原理也算比較簡單,弄清楚了原理之后,進行代碼的編寫。
- 方法1:附加標志法
先來定義個結構體:
typedef struct Squeue
{ /*順序循環隊列的類型定義*/
DataType queue[QUEUESIZE];
int front, rear; /*隊頭指針和隊尾指針*/
int tag; /*隊列空、滿的標志*/
} SCQueue;
初始化隊列:
/*將順序循環隊列初始化為空隊列,需要把隊頭指針和隊尾指針同時置為0,且標志位置為0*/
void InitQueue(SCQueue *SCQ)
{
SCQ->front = SCQ->rear = 0; /*隊頭指針和隊尾指針都置為0*/
SCQ->tag = 0; /*標志位置為0*/
}
判斷是否為空隊列:
/*判斷順序循環隊列是否為空,隊列為空返回1,否則返回0*/
int QueueEmpty(SCQueue SCQ)
{
if (SCQ.front == SCQ.rear && SCQ.tag == 0) /*隊頭指針和隊尾指針都為0且標志位為0表示隊列已空*/
return 1;
else
return 0;
}
插入元素:
/*將元素e插入到順序循環隊列SQ中,插入成功返回1,否則返回0*/
int EnQueue(SCQueue *SCQ, DataType e)
{
if (SCQ->front == SCQ->rear && SCQ->tag == 1)
/*在插入新的元素之前,判斷是否隊尾指針到達數組的最大值,即是否上溢*/
{
printf("順序循環隊列已滿,不能入隊!");
return 1;
}
else
{
SCQ->queue[SCQ->rear] = e; /*在隊尾插入元素e */
SCQ->rear = (SCQ->rear + 1) % QUEUESIZE;
//SCQ->rear = SCQ->rear + 1; /*隊尾指針向后移動一個位置,指向新的隊尾*/
}
if (SCQ->rear == SCQ->front)
{
SCQ->tag = 1; /*隊列已滿,標志位置為1 */
}
return SCQ->tag;
}
讀取元素:
/*刪除順序循環隊列中的隊頭元素,並將該元素賦值給e,刪除成功返回1,否則返回0*/
int DeQueue(SCQueue *SCQ, DataType *e)
{
if (QueueEmpty(*SCQ)) /*在刪除元素之前,判斷隊列是否為空*/
{
printf("順序循環隊列已經是空隊列,不能再進行出隊列操作!");
return 0;
}
else
{
*e = SCQ->queue[SCQ->front]; /*要出隊列的元素值賦值給e */
SCQ->front = (SCQ->front + 1) % QUEUESIZE; /*隊頭指針向后移動一個位置,指向新的隊頭元素*/
SCQ->tag = 0; /*刪除成功,標志位置為0 */
}
if (SCQ->rear == SCQ->front)
{
SCQ->tag = 0; /*隊列已空,標志位置為0 */
}
return SCQ->tag;
}
先來對這種方法進行測試:
void main()
{
SCQueue Q; /*定義一個順序循環隊列*/
int e; /*定義一個字符類型變量,用於存放出隊列的元素*/
int a[] = {1, 2, 3, 4,5}, i;
InitQueue(&Q); /*初始化順序循環隊列*/
/*將數組中的4個元素依次入列*/
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
EnQueue(&Q, a[i]);
/*將順序循環隊列中的元素顯示輸出*/
printf("隊列中元素:");
// DisplayQueue(Q);
/*將順序循環隊列中的隊頭元素出隊列*/
i = 0;
while (!QueueEmpty(Q))
{
printf("隊頭元素第%d次出隊\n", ++i);
DeQueue(&Q, &e);
printf("出隊的元素:");
printf("%d\n",e);
// PrintData(e);
}
}
測試結果:
- 預留位置法
代碼與第一種方法區別不大,主要在空、滿狀態的判斷上,代碼如下:
/*將順序循環隊列初始化為空隊列,需要把隊頭指針和隊尾指針同時置為0,且標志位置為0*/
void InitQueue(SCQueue *SCQ)
{
SCQ->front = SCQ->rear = 0; /*隊頭指針和隊尾指針都置為0*/
SCQ->tag = 0; /*標志位置為0*/
}
//獲取隊列長度
int QueueLength(SCQueue *SCQ)
{
return (SCQ->rear - SCQ->front + QUEUESIZE) % QUEUESIZE;
}
/*判斷順序循環隊列是否為空,隊列為空返回1,否則返回0*/
int QueueEmpty(SCQueue SCQ)
{
if (SCQ.front == SCQ.rear) /*隊頭指針和隊尾指針都為0且標志位為0表示隊列已空*/
return 1;
else
return 0;
}
/*將元素e插入到順序循環隊列SQ中,插入成功返回1,否則返回0*/
int EnQueue(SCQueue *SCQ, DataType e)
{
/*在插入新的元素之前,判斷是否隊尾指針到達數組的最大值,即是否上溢*/
if ((SCQ->rear + 1) % QUEUESIZE == SCQ->front)
{
printf("順序循環隊列已滿,不能入隊!");
return 0;
}
SCQ->queue[SCQ->rear] = e; /*在隊尾插入元素e */
SCQ->rear = (SCQ->rear + 1) % QUEUESIZE; /*隊尾指針向后移動一個位置,指向新的隊尾*/
printf("數據已插入");
}
/*刪除順序循環隊列中的隊頭元素,並將該元素賦值給e,刪除成功返回1,否則返回0*/
int DeQueue(SCQueue *SCQ, DataType *e)
{
if (QueueEmpty(*SCQ)) /*在刪除元素之前,判斷隊列是否為空*/
{
printf("順序循環隊列已經是空隊列,不能再進行出隊列操作!");
return 0;
}
else
{
*e = SCQ->queue[SCQ->front]; /*要出隊列的元素值賦值給e */
SCQ->front = (SCQ->front + 1) % QUEUESIZE; /*隊頭指針向后移動一個位置,指向新的隊頭元素*/
return 1;
}
}
main函數與上面相同:
void main()
{
SCQueue Q; /*定義一個順序循環隊列*/
int e; /*定義一個字符類型變量,用於存放出隊列的元素*/
int a[] = {1, 2, 3, 4,5}, i;
InitQueue(&Q); /*初始化順序循環隊列*/
/*將數組中的4個元素依次入列*/
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
EnQueue(&Q, a[i]);
/*將順序循環隊列中的元素顯示輸出*/
printf("隊列中元素:");
// DisplayQueue(Q);
/*將順序循環隊列中的隊頭元素出隊列*/
i = 0;
while (!QueueEmpty(Q))
{
printf("隊頭元素第%d次出隊\n", ++i);
DeQueue(&Q, &e);
printf("出隊的元素:");
printf("%d\n",e);
//PrintData(e);
}
}
測試結果:
相比較上面的測試結果,小伙伴們有沒有發現什么不同之處呢,我們main函數想把5個元素寫入隊列,實際上只寫進去了4個,原因在與,我們預留了一個單元空間,用於判斷隊列空或者滿的狀態。
本次的介紹就到這里啦,下章介紹:環形隊列在單片機中的應用,歡迎大家持續關注嵌入式實驗基地,來這里還可以學習HAL庫+cubemx的更多精彩內容哦!
如果你覺得對自己有幫助的話,給個贊,點個關注,點個在看,感謝前進的道路上有你的陪伴!
所有公眾號文章資料源碼已上傳,關注公眾號回復資料即可獲取哦,歡迎加群一起炸起來!