隊列——循環隊列、鏈式隊列介紹和C語言實現源碼

本文大量飲用了相關公衆號和博客圖片和代碼,本人只做整理總結,便於學習和使用,如有涉及到相關作者原創內容,請聯繫,本人將及時刪除。

img

線性結構: 有且只有一個根節點,且每個節點最多有一個直接前驅和一個直接後繼的非空數據結構

非線性結構: 不滿足線性結構的數據結構

隊列

1、基本概念:

1.1 定義:

隊列是指允許在一端進行插入,在另一端進行刪除的線性表,又稱“先進先出”的線性表

隊列是一種特殊的線性結構,它只允許在隊列的首部(head)進行刪除操作,這稱爲出隊,在尾部進行加入數據操作,稱爲入隊;

隊列這種數據結構非常容易理解,就像我們平時去超市買東西,在收銀臺結賬的時候需要排隊,先去排隊的就先結賬出去,排在後面的就後結賬,有其他人再要過來結賬,必須排在隊尾不能在隊中間插隊。

1.2 概念補充:

1.2.1 數組

數組是一個有限的類型相同的數據的集合,在內存中是一段連續的內存區域。 數組在訪問操作方面有着獨特的性能優勢,因爲數組是支持隨機訪問的,也就是說我們可以通過下標隨機訪問數組中任何一個元素,其原理是因爲數組元素的存儲是連續的,所以我們可以通過數組內存空間的首地址加上元素的偏移量計算出某一個元素的內存地址;

因爲數組元素的連續性要求,所以導致數組在插入和刪除元素的時候效率比較低。如果要在數組中間插入一個新元素,就必須要將要相鄰的後面的元素全部往後移動一個位置,留出空位給這個新元素。

數組的缺陷
1.一個數組中所有元素的類型必須一致;

​ 2.數組元素個數必須事先制定並且制定後就不能更改

解決方法
1.第一個缺陷靠結構體去解決,結構體允許其中的元素的類型不相同;

2.第二個缺陷:普通數組的大小不可以實時收縮或擴展,鏈表可以解決;

1.2.2 鏈表

鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的,一般用於插入與刪除較爲頻繁的場景。

img

上圖是“單鏈表”示例,鏈表並不需要數組那樣的連續空間,它只需要一個個零散的內存空間即可,因此對內存空間的要求也比數組低。

鏈表的每一個節點通過“指針”鏈接起來,每一個節點有2部分組成,一部分是數據(上圖中的Data),另一部分是後繼指針(用來存儲後一個節點的地址),在這條鏈中,最開始的節點稱爲Head,最末尾節點的指針指向NULL。

「 鏈表 」也分爲好幾種,上圖是最簡單的一種,它的每一個節點只有一個指針(後繼指針)指向後面一個節點,這個鏈表稱爲:單向鏈表,除此之外還有 雙向鏈表、循環鏈表 等。

img

雙向鏈表與單向鏈表的區別是前者是2個方向都有指針,後者只有1個方向的指針。雙向鏈表的每一個節點都有2個指針,一個指向前節點,一個指向後節點。雙向鏈表在操作的時候比單向鏈表的效率要高很多,但是由於多一個指針空間,所以佔用內存也會多一點。

循環鏈表:

img

其實循環鏈表就是一種特殊的單向鏈表,只不過在單向鏈表的基礎上,將尾節點的指針指向了Head節點,使之首尾相連。

  • 鏈表的訪問

    鏈表的優勢並不在與訪問,因爲鏈表無法通過首地址和下標去計算出某一個節點的地址,所以鏈表中如果要查找某個節點,則需要一個節點一個節點的遍歷

  • 鏈表的插入與刪除

    也正式因爲鏈表內存空間是非連續的,所以它對元素的插入和刪除時,並不需要像數組那樣移動其它元素,只需要修改指針的指向即可。

例如:刪除一個元素E:

img

加入 一個元素:

img

2、 隊列常見分類:


順序隊列—>順序隊列是用數組實現的隊列

鏈式隊列—>鏈式隊列即用鏈表實現的隊列

循環隊列–> 循環隊列是指隊列是前後連成一個圓圈,它以循環的方式去存儲元素,但還是會按照隊列的先進先出的原則去操作

2.1順序隊列

用數組實現的隊列,叫做 順序隊列

用數組實現的思路是這樣的:初始化一個長度爲n的數組,創建2個變量指針front和rear,front用來標識隊頭的下標,而rear用來標識隊尾的下標。因爲隊列總是從對頭取元素,從隊尾插入數據。因此我們在操作這個隊列的時候通過移動front和rear這兩個指針的指向即可。初始化的時候front和rear都指向第0個位置。

當有元素需要入隊的時候,首先判斷一下隊列是否已經滿了,通過rear與n的大小比較可以進行判斷,如果相等則說明隊列已滿(隊尾沒有空間了),不能再插入了。如果不相等則允許插入,將新元素賦值到數組中rear指向的位置,然後rear指針遞增加一(即向後移動了一位),不停的往隊列中插入元素,rear不停的移動,如圖:

img

當隊列裝滿的時候,則是如下情況

img

當需要做出隊操作時,首先要判斷隊列是否爲空,如果front指針和rear指針指向同一個位置(即front==rear)則說明隊列是空的,無法做出隊操作。如果隊列不爲空,則可以進行出隊操作,將front指針所指向的元素出隊,然後front指針遞增加一(即向後移動了一位),加入上圖的隊列出隊了2個元素:

img

所以對於數組實現的隊列而言,需要用2個指針來控制(front和rear),並且無論是做入隊操作還是出隊操作,front或rear都是往後移動,並不會往前移動。入隊的時候是rear往後移動,出隊的時候是front往後移動。

2.2 循環隊列

在順序隊列中,當隊尾指針已經到數組的上界,不能再有入隊操作,但其實數組中還有空位置,這就叫做“假溢出”,解決假溢出的途徑----採用循環隊列。

( 當然也可以在數組尾部已滿的這種情況下,去移動數據,把數據所有的元素都往前移動以填滿前面的空間,釋放出尾部的空間,以便尾部還可以繼續插入新元素。但是這個移動也是消耗時間複雜度的。 )

循環隊列就可以天然的解決這個問題,下面是循環隊列的示意圖:

img

可以看到,整個隊列的入隊和出隊的過程,就是頭指針 _front 和尾指針 _rear互相追趕的過程,如果 _rear追趕上了 _front就說明隊滿了(前提是相隔一個空閒單元),如果 _front追趕上了 _rear就說明隊列空了。

新問題:
在循環隊列中,空隊特徵是front = rear, 隊滿時也會有front = rear; 判斷條件將出現二義性
解決方案有三種:

  1. 加設標誌位,讓刪除動作使其爲1,插入動作使其爲0, 則可識別當前front == rear;
  2. 使用一個計數器記錄隊列中元素個數(即隊列長度)
  3. 人爲浪費一個單元,令隊滿特徵爲 front = (rear +1)%N—空閒單元法

這裏分享2、3兩種方法的C語言實現代碼

  1. 方法2:計數器記錄隊列中元素的個數
							
#define	mQBufNormal			0							
#define	mQBufFull			1							
#define	mQBufEmpty			2

typedef	struct
{												
	UINT8	*pu8In;								
	UINT8	*pu8Out;								
	UINT8	*pu8Start;								
	UINT8	u8Length;								
	UINT8 	u8Size;								
}STQUEUE;	

UINT8 u8QBuff[64] = {0};
STQUEUE   stRxQueue;

void  sQInit(STQUEUE *pstQ,UINT8 *pu8Start,UINT8 u8SizeTemp);
UINT8 su8QDataIn(STQUEUE *pstQ,UINT8 u8Data);
UINT8 su8QDataOut(STQUEUE *pstQ, UINT8 *pData);
/************************************************************************************
*Function name: sQInit                                *
*Parameters:  pq: pointer to queue structure to be initialized          *
*       start:start address of ring buffer                  *
*       size:the size of the ring buffer                  *
*Description: initialize a queue structure                    *
*************************************************************************************/
void  sQInit(STQUEUE *pstQ,UINT8 *pu8Start,UINT8 u8SizeTemp)
{
    pstQ->pu8In = pu8Start;
    pstQ->pu8Out = pu8Start;
    pstQ->pu8Start = pu8Start;
    pstQ->u8Length = 0;
    pstQ->u8Size = u8SizeTemp;
}

/************************************************************************************
*Function name: sQDataIn                              *
*Parameters:  pq: pointer to queue structure to be initialized          *
*       data:the data to be inserted into the queue             *
*       option:how to deal with the data when the buffer is full      *
*       cQCoverFirst:cover the first data                 *
*       cQCoverLast:cover the latest data                 *
*Returns:   cQBufNormal:data has been inserted into the queue         *
*       cQBufFull:the buffer is full                    *
*Description: insert a data into the queue                    *
*************************************************************************************/
UINT8 su8QDataIn(STQUEUE *pstQ,UINT8 u8Data)
{ 
	if(pstQ->u8Length >= pstQ->u8Size)			// use (>=) just in case
	{
		return(mQBufFull);
	}
	else
	{
		*(pstQ->pu8In) = u8Data;
		pstQ->u8Length++;
		if(pstQ->pu8In >= pstQ->pu8Start + pstQ->u8Size - 1)	// use (>=) just in case 
		{
			pstQ->pu8In = pstQ->pu8Start;
		}
		else
		{
			pstQ->pu8In++;
		}
		return(mQBufNormal);
	}
}
/************************************************************************************
*Function name: sQDataOut                             *
*Parameters:  pq: pointer to queue structure to be initialized          *
*       pdata:the address to save the data                  *
*Returns:   cQBufNormal:data has been inserted into the queue         *
*       cQBufEmpty:the buffer is empty                    *
*Description: Get a data from the queue                     *
*************************************************************************************/
UINT8 su8QDataOut(STQUEUE *pstQ, UINT8 *pData)
{
	if(pstQ->u8Length == 0)
	{
		return(mQBufEmpty);
	}
	*pData = *(pstQ->pu8Out);
	pstQ->u8Length--;
	if(pstQ->pu8Out >= pstQ->pu8Start + pstQ->u8Size - 1)	// use (>=) just in case 
	{
		pstQ->pu8Out = pstQ->pu8Start;
	}
	else
	{
		pstQ->pu8Out++;
	}	
	return(mQBufNormal);
}



  1. 空閒單元法

img

#include <stdio.h>
#include <stdlib.h>
#define Maxsize 10

typedef int dataType; 
typedef struct
{
	dataType *base;
	int front;
	int rear;
}CyQueue;

int create(CyQueue *q)
{
	q->base=(dataType *)malloc(Maxsize*sizeof(dataType));
	if(!q->base)
	{
		printf("Space allocation failed!\n");
		return;
	}
	q->front=q->rear=0;
	return;
}

int EnQueue(CyQueue *q,dataType value)
{
	if((q->rear+1)%Maxsize==q->front)
	{
		printf("Cyclic Queue is Full!\n");
		return;
	}
	q->base[q->rear]=value;
	q->rear=(q->rear+1)%Maxsize;
	printf("EnQueue Element is %d\n",value);
	return;
}

int DeQueue(CyQueue *q,dataType *value)
{
	if(q->front==q->rear)
	{
		printf("Cyclic Queue is Empty!\n");
		return;
	}
	*value=q->base[q->front];
	q->front=(q->front+1)%Maxsize;
	printf("DeQueue Element is %d\n",*value);
	return;
} 

dataType getHead(CyQueue *q)
{
	if(q->front==q->rear)
	{
		printf("Cyclic Queue is Empty! Unable to fetch Header of Cyclic Queue\n");
		return;
	}
	return(q->base[q->front]);
}

int main()
{
	CyQueue q;
	dataType elem;
	int i;
	create(&q);
	
	for(i=1;i<11;i++)
	EnQueue(&q,i);
	printf("The Header is %d\n",getHead(&q));
	
	for(i=0;i<10;i++)
	DeQueue(&q,&elem);
	printf("The Header is %d\n",getHead(&q));
	return 0;
} 

2.3 鏈式隊列

用鏈表實現的隊列,叫做 鏈式隊列: 鏈式隊列就是一個操作受限的單向鏈表

在这里插å¥å›¾ç‰‡æè¿°

簡單描述一下上圖的步驟

第一步:初始化隊列(就是添加一個頭節點在隊列中),頭結點不存儲數據,使隊首指針和隊尾指針都指向頭結點

第二步:入隊(添加節點),使隊尾指針指向頭新建的節點,隊首指針不變仍然指向頭結點

出隊時隊首指針的位置是不變的,隊首指針始終指向頭節點,出隊時頭節點的指針域指向出隊節點的後一節點,並將出隊的節點用free()函數釋放掉

注:

1.根據隊列的入隊,出隊規則,設置頭結點便於實現出隊操作;
2.頭指針front始終指向頭結點,尾指針rear指向隊列最後一個元素,length用於記錄隊列中元素個數,遍歷操作會用到length。

#include <stdio.h>
#include <stdlib.h>

typedef int dataType;

typedef struct Node
{
	dataType data;
	struct Node *next;
}QueueNode;

typedef struct
{
	QueueNode *front;
	QueueNode *rear;
	int length;
}LinkQueue;

int create(LinkQueue *q)
{
	q->front=q->rear=(QueueNode *)malloc(sizeof(QueueNode));
	q->front->next=NULL;
	q->front->data=0;
	q->length=0;
	return;
}

int EnQueue(LinkQueue *q,dataType value)
{
	QueueNode *p;
	
	p=(QueueNode *)malloc(sizeof(QueueNode));
    p->data=value;
    p->next=NULL;
    q->length++;
    
    q->rear->next=p;
    q->rear=p;
	return; 
}

int DeQueue(LinkQueue *q,dataType *value)
{
    QueueNode *p;
    
    if(q->front->next==NULL)
    {
    	printf("LinkQueue is Empty!\n");
    	return;
	}
	
    p=q->front->next;
	*value=p->data;
    q->front->next=p->next;
    q->length--;
    printf("DeQueue Element is %d.\n",*value);
    free(p);
	return;
}

dataType getHead(LinkQueue *q)
{
	if(q->front->next==NULL)
    {
    	printf("LinkQueue is Empty!\n");
    	return;
	}
	return(q->front->next->data);
}

int traverse(LinkQueue *q)
{
	QueueNode *p;
	int i;
	
	if(q->front->next==NULL)
    {
    	printf("LinkQueue is Empty!\n");
    	return;
	}
	
	p=q->front->next;
	printf("Head -> ");
	for(i=0;i<q->length;i++)
	{
     	printf("%d -> ",p->data);
	    p=p->next;
	}
	printf("NULL\n");
	return;
}

int main()
{
	LinkQueue q;
	int i;
	dataType value;
	
	create(&q);
	
	for(i=1;i<10;i++)
	EnQueue(&q,i);
	traverse(&q);
	printf("\n");
	
	for(i=1;i<10;i++)
	{
	   DeQueue(&q,&value);
	   printf("First Element is %d.\n",getHead(&q));
	   traverse(&q);
	   printf("\n");
	}
	
	return 0;
}

3、小結

1.順序、循環隊列是用數組實現的,首指針在出隊的時候移動,尾指針在入隊的時候移動,需要考慮隊列爲空和隊列爲滿的兩種情況

2.鏈式隊列是用鏈表實現的,首指針不移動始終指向頭節點,尾指針在入隊的時候移動,只考慮隊列爲空的情況(不用考慮滿是因爲鏈表長度在程序運行過程中可以不斷增加,只要存儲空間夠malloc就能申請內存空間來存放節點)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章