小萌新今天和大家聊聊什麼是循環隊列,話不多說,開門見山吧!
如果說可以用循環隊列解決隊列的虛假滿的狀態,那麼什麼是虛假滿狀態?
假設當前順序隊列分配的最大空間是6,當隊尾指針從5下標指向6下標時(6下標實際不存在),說明此時隊列已滿,然而依然可以進行出隊的操作,順序隊不能像順序棧那樣進行存儲再分配擴大數組空間,所以隊列的實際可用空間並未佔滿。
循環隊列就是將順序隊列構造成爲一個環狀的隊列空間,如圖:
注意:
此時當指針front=指針rear時,無法判斷隊列空間是滿還是空,有兩種解決思路:
1.另設一個標誌位區分隊列空間是滿還是空;
2.少用一個空間,約定以“隊列頭指針在隊列尾指針的下一位置”作爲隊列滿的狀態標誌。
來吧!舉個栗子吧,如圖:
【情況1】
初始化,隊頭指針front,隊尾指針rear都指向0下標;
當存入數據元素1後,隊尾指針rear從0下標指向1下標;
當存入數據元素2後,隊尾指針rear從1下標指向2下標;
當存入數據元素3後,隊尾指針rear從2下標指向3下標;
當存入數據元素4後,隊尾指針rear從3下標指向4下標;
…
當存入數據元素8後,隊尾指針rear從7下標指向0下標;(隊列已滿)
如何判斷隊頭指針front與隊尾指針rear重合時,隊列是空還是滿呢?我們假如用flag來標記隊頭指針front與隊尾指針rear的重合狀態,於是規定當flag=0時:隊列爲空;當flag=1時,隊列爲滿。
【情況2】
我們把7下標的空間空出不存放數據,當數據元素7進爲6下標的空間後,隊尾指針rear就指向了爲7下標的空間(此時7下標空間是空的),理論上8個空間只用了7個空間,但在邏輯上我們規定此時隊列已滿。當rear+1後,隊頭指針front與隊尾指針rear重合,說明此時隊列空間邏輯已滿,我們先將隊頭指針所指空間的元素出隊,隊頭指針前移指向1下標,此時再將元素進下標爲7的空間,隊尾指針rear前移(指向0下標),當rear+1後,隊頭指針front與隊尾指針rear又重合,說明此時隊列空間邏輯又已滿,依次循環往復,出隊—>隊頭指針front前移——>後面入隊——>隊尾指針rear前移(判斷條件是rear+1後,隊頭指針front與隊尾指針rear是否重合,不重合則可繼續入隊)。
但是,計算機是沒有環狀的存儲空間的,依然是以線性存儲的方式,那計算機如何判斷什麼時候隊尾指針再次與隊頭指針重合呢?
—————OK!那就是:進行模運算—————
具體模運算是怎麼實現的,文字顯得蒼白無力,還是那句話,把代碼跑起來,慢慢體會!
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#define MAXSIZE 8//定義隊列初始化存放8個數據元素
typedef int ElmeType;
/*給出順序隊列的結構*/
typedef struct Queue
{
ElmeType *base;//指針base指向有效的隊列空間
int front;//隊頭指針
int rear;//隊尾指針指向下一個有效的空間
}Queue;
/*初始化順序隊列*/
void InitQueue(Queue *Q)
{
Q->base=(ElmeType *)malloc(sizeof(ElmeType)*MAXSIZE);//開闢隊列空間
assert(Q->base!=NULL);//斷言——是否開闢空間成功
Q->front=Q->rear=0;//隊頭指針和隊尾指針都指向隊列的0下標,此時隊列爲空
}
/*入隊*/
void EnQueue(Queue *Q,ElmeType x)
{
//數據入隊後,隊尾指針從當前空間指向下一個有效的空間,此時稱隊尾指針是僞指針;
//當僞指針所指下標+1後正好等於隊列空間容量時,此時我們希望僞指針可以重新指向隊頭,而不是出界,於是進行模運算;
//(Q->rear+1)%MAXSIZE——若模爲0,則僞指針恰好指向隊列的最後一個有效空間,我們需要讓此時的僞指針重新指向0下標而不是最後一個有效空間;
//(始終都要將隊列最後一個有效空間空出)循環開始:
if((Q->rear+1)%MAXSIZE==Q->front)//若僞指針所指下標+1與隊頭指針指向相同的下標,此時判斷爲隊列邏輯已滿
return;//返回,理論上隊列保留了隊列最後一個有效空間
Q->base[Q->rear]=x;//否則隊列邏輯不滿,繼續在隊尾指針所指下標進行入隊,入隊完成後,隊尾指針又從當前空間指向下一個有效的空間
Q->rear=(Q->rear+1)%MAXSIZE;//當邏輯空間滿後,模運算實現隊尾指針重新指向0下標而不是最後一個有效空間;
}
/*展示順序隊列元素*/
void ShowQueue(Queue *Q)
{
printf("順序隊列中存放的元素:");
for(int i=Q->front;i!=Q->rear;)
{
printf("%d ",Q->base[i]);//依次打印隊頭指針所指下標中的數據到隊尾指針所指下標中的數據
i=(i+1)%MAXSIZE;//循環打印,7下標不能打印,重新回到0下標(循環時,隊尾下標-隊頭下標=-1)
}
printf("\n");
}
/*出隊*/
void DeQueue(Queue *Q)
{
//出隊一個元素,隊頭指針指向下一個有效的數據元素
if(Q->front==Q->rear)//隊頭隊尾指向相同,隊列爲空
return;
Q->front=(Q->front+1)%MAXSIZE;//隊頭指針循環,模運算實現隊頭指針重新指向0下標而不是最後一個有效空間;
}
/*取隊頭元素*/
void GetHead(Queue *Q,ElmeType *v)//指針v帶回隊頭元素
{
//要獲取隊頭,前提是隊列不空
if(Q->front==Q->rear)//隊列爲空
return;
*v=Q->base[Q->front];//必須在base所指的空間裏取元素
}
/*順序隊列的長度*/
int Length(Queue *Q)
{
return (Q->rear - Q->front);
//下標0開始存放數據,進隊後,隊尾指針指向下一個有效的新空間
//隊列中元素的個數正好是隊尾隊頭所指的下標之差
//但是當隊列邏輯空間滿後,再存儲數據需要先出隊,再進行入隊,此時隊尾隊頭所指的下標之差爲-1
}
/*清除順序隊列*/
void ClearQueue(Queue *Q)
{
Q->front=Q->rear=0;//隊列置爲空
}
/*銷燬順序隊列*/
void DestroyQueue(Queue *Q)
{
free(Q->base);//釋放base所指的隊列空間
Q->base=NULL;//預防野指針
}
/**/
void main()
{
Queue Q;
ElmeType e;//定義隊頭元素
InitQueue(&Q);//&Q是傳入隊列的地址
printf("將1,2,3,4,5,6,7,8依次入隊\n");
for(int i=1;i<=8;++i)
{
EnQueue(&Q,i);
}
ShowQueue(&Q);
printf("順序隊的長度爲:%d\n",Length(&Q));
printf("\n");
printf("進行出隊\n");
DeQueue(&Q);
ShowQueue(&Q);
GetHead(&Q,&e);
printf("隊頭元素爲:%d\n",e);
printf("\n");
printf("將元素10入隊\n");
EnQueue(&Q,10);
ShowQueue(&Q);
printf("\n");
printf("進行出隊\n");
DeQueue(&Q);
ShowQueue(&Q);
GetHead(&Q,&e);
printf("隊頭元素爲:%d\n",e);
printf("\n");
printf("將元素20入隊\n");
EnQueue(&Q,20);
ShowQueue(&Q);
printf("\n");
ClearQueue(&Q);
DestroyQueue(&Q);
}
運行結果: