循環隊列原理及在單片機串口通訊的應用(一)


前言

當代碼,不再是簡單的完成需求,對代碼進行堆砌,而是開始思考如何寫出優美代碼的時候,我們的代碼水平必然會不斷提升,今天,咱們來學習環形隊列結構。

環形隊列的基本概念

相信對數據結構有過接觸的小伙伴,對隊列肯定不會陌生,隊列相對來說是比較簡單的數據結構,典型特點是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的更多精彩內容哦!

如果你覺得對自己有幫助的話,給個贊,點個關注,點個在看,感謝前進的道路上有你的陪伴!

所有公眾號文章資料源碼已上傳,關注公眾號回復資料即可獲取哦,歡迎加群一起炸起來!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM