隊列的基本概念介紹以及典型應用示例

一、隊列的概念介紹

        提到隊列這個詞,或許你不會感到陌生,在我們的生活中,應用到隊列這個概念的場景非常之多。我們日常的排隊買飯,總是第一個到達窗口的人先買到然後離開,後來的人總是後來離去;再有,去醫院掛號排隊,也總是遵循一般的先來先就醫服務的原則。計算機中的隊列數據結構的設計,也是爲了更好地解決這類先來先服務問題的。(更好的閱讀體驗,請訪問程序員在旅途)
        隊列是一種採用先來先服務(First in First Out,FIFO)思想的抽象數據結構。和棧一樣,它也是一種受限制的線性表。隊列是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。允許插入的一端稱爲隊尾,允許刪除的一端稱爲隊頭。隊列的插入操作稱之爲入隊列或進隊列,隊列的刪除操作稱之爲退隊列或出隊列。當線性表中沒有元素的時稱爲空隊列。示意圖如下:

棧示意圖
        在隊列上的基本操作有:①隊列初始化②判斷隊空③入隊操作④出隊操作⑤讀取隊頭元素⑥銷燬隊列。根據在物理實現方式上的不同,分爲順序隊列和鏈式隊列,基於數組實現的順序存儲的隊列叫作順序隊列,基於鏈表實現的隊列叫作鏈式隊列。

二、順序隊列的操作實現

        採用順序存儲的隊列稱爲順序隊列,在使用隊列前,要分配一塊連續的存儲空間來存放隊列裏面的元素。由於隊列的頭尾都是會變化移動的,因此,就有隊頭隊尾兩個指示變量,指向隊列的頭尾。順序隊列的類型定義如下:

#define MAXSIZE 100
typedef struct{
    ElementType data[MAXSIZE];
    int front,rear;
}SeqQuene,* PSeqQueue;

        定義一個指向隊列的指針:

PSeqQueue queue = (PSeqQueue)malloc(sizeof(SeqQuene));

        從隊列的類型定義上可以看到,隊列的數據區爲 queue->data[0] ~ queue->data[MAXSIZE-1]。隊頭變量queue->front取值範圍(0<= queue->front <=MAXSIZE-1),隊尾變量queue->rear取值範圍(0<= queue->rear <=MAXSIZE-1)。
        隊列是使用數組進行元素的存儲,它在內存分配上是靜態分配,一旦初始化之後,內存空間的大小就確定了。而隊列的操作是一個動態的過程,隨着入隊的進行,queue->rear的值會超過MAXSIZE,隨着出隊的進行,queue->front也會逐漸增加,就會導致數組空間實際上還有空閒的空間,但是不能進行元素的入隊了,從而出現假溢出的現象。爲了解決這種假溢出的問題,可以採用一種稱爲"循環隊列"的特殊隊列,循環隊列是將隊列的數據區data[0…MAXSIZE-1]看成頭尾相接的循環結構,頭尾的指示變量front、rear關係不變。循環隊列的示意圖如下:循環隊列示意圖
    對這種首尾相接的循環隊列結構來說,進隊出隊的操作會有一些小的修改,以滿足循環的需求。

入隊: queue->rear = (queue->rear + 1) % MAXSIZE;
出隊: queue->front = (queue->front +1) % MAXSIZE;

    瞭解了入隊和出隊的操作,我們需要判斷,什麼時候隊列滿了,什麼時候隊列爲空。空的情況很好判斷,只要 queue->front = queue->rear就可以認爲隊列爲空。隊滿的情況的討論,當隊列中元素逐漸增多,queue->rear 會最終追上queue->front的,這時候兩者也是相等,但此時隊列非空,相反卻是隊滿的情況,爲了方便判斷隊列的隊滿隊空情況,可以採用少用一個元素空間的方法,使得rear永遠追不上front,也就是queue->front所指的位置不用,當 (rear+1)%MAXSIZE = front 的時候,隊列是滿的。

2.1 隊列初始化

        隊列在使用之前需要進行初始化。初始化的過程包括分配隊列需要佔用的內存空間,以及給隊頭隊尾變量賦初值。

//初始化隊列
PSeqQueue init_seqqueue(){
 //申請隊列需要的內存空間
 PSeqQueue queue = (PSeqQueue)malloc(sizeof(SeqQueue));
 if(queue){
  queue->front = 0;
  queue->rear = 0;
 }
 return queue;
}

2.2 判斷隊列是否爲空

        在出隊或者獲取隊首元素的時候,都要進行隊列非空判斷

//判斷隊列是否爲空。1代表空,0代表非空
int empty_seqqueue(PSeqQueue queue){
 if(queue && (queue->front == queue->rear)){
  return 1;
 }else{
  return 0;
 }
}

2.3 入隊列

int in_seqqueue(PSeqQueue queue , ElementType x){
 //判斷隊列是否滿了
 if((queue->rear+1)%MAXSIZE == queue->front){
  printf(“隊列滿了”);
   return 0;
 }else{
  //入隊列.
  queue->rear = (queue->rear +1) % MAXSIZE;
  queue->data[queue->rear] = x;
  return 1;
 }
}

2.4 出隊列

int out_seqqueue(PSeqQueue queue , ElementType *x){
 //出隊列的時候要判斷隊列是否爲空
 if(empty_seqqueue(queue)){
  printf(“隊列爲空,不可出隊”);
  return 0;
 }else{
  //由於是循環隊列,queue->front指示位置的元素不用,因此要+1纔是要出隊的元素
  queue->front = (queue->front + 1) % MAXSIZE;
  *x = queue->data[queue->front];
  return 1;
 }
}

2.5 讀取隊頭元素

int getFront_seqqueue(PSeqQueue queue , ElementType *x){
 //出隊列的時候要判斷隊列是否爲空
 if(empty_seqqueue(queue)){
  printf(“隊列爲空,不可獲取隊頭元素”);
  return 0;
 }else{
  //由於是循環隊列,queue->front指示位置的元素不用,因此要+1纔是隊頭的元素
  *x = queue->data[(queue->front+1)%MAXSIZE];
  return 1;
 }
}

2.6 銷燬隊列

        由於是動態申請的空間,因此,在使用完成之後,要進行內存的釋放。

void destory_seqqueue(PSeqQueue *queue){
 if(*queue){
  free(*queue);
 }
 *queue = NULL;
}

三、鏈式隊列的操作實現

        隊列是線性結構的一種,因此,也可以用鏈式結構的方式來實現。並且,鏈式結構的隊列,由於節點空間都是在入隊的時候動態申請的 ,因此,在計算機內存空間足夠的情況下,一般不需要考慮隊滿的情況,也就不存在溢出的情況,所以不需要使用循環鏈式隊列來處理假溢出的情況。 鏈式隊列示意圖如下:鏈式隊列示意圖
        鏈隊節點的結構和單鏈表一樣,同時爲了操作上的方便,可以重新定義一個鏈隊的結構體,包含有頭尾指針指向隊列的頭部和尾部。鏈隊的結構體描述如下:

//節點結構
typedef struct node{
 ElementType data;
 struct node *next;
}Qnode,*PQnode;
//鏈棧結構
typedef struct {
 PQnode front,rear;
}LinkQueue,*PLinkqueue;

        定義一個指向鏈隊的指針

PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue));

        在使用鏈表來存儲隊列數據的時候,帶不帶頭結點完全看我們的需求,如果在操作上需要使用頭結點 來方便統一操作,就是用頭結點 ,否則就可以不用加頭結點。
        queue->front指向鏈隊的隊頭元素,queue->rear指向鏈隊的隊尾元素。出隊的時候刪除鏈表的首元結點,讓後繼節點成爲首元節點,然後修改隊頭指針使其指向新的首元節點即可,入隊是在鏈表末尾插入元素,然後修改隊尾指針指向新的鏈尾即可。

3.1 初始化鏈式隊列

        隊列的初始化就是爲隊列結構分配內存空間,同時給front、rear指針置空值,爲入隊列做準備。

PLinkqueue init_linkqueue(){
PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue));
 if(queue){
  queue->front = NULL;
  queue->rear = NULL;
 }
 return queue;
}

3.2 判斷隊列是否爲空

//判斷鏈隊爲空;1空隊列,0非空。
int empty_linkqueue(PLinkqueue queue){
 if(queue && (queue->front == NULL) && (queue->rear ==NULL)){
  return 1;
 }else{
  return 0;
 }
}

3.3 入隊

        鏈式隊列的入隊,就是在鏈表的鏈尾插入元素,然後讓隊尾指針指向鏈尾元素。

int in_linkqueue(PLinkqueue queue, ElementType x){
 PQnode p = (PQnode)malloc(sizeof(Qnode));
 if(!p){
  printf(“內存溢出,不能申請新的節點空間\n”);
  return 0;
 }
 p->data = x;
 p->next = NULL;
 //要先判斷隊列中是否有元素,兩者的的入隊是有一些差別的
 if(empty_linkqueue(queue)){
  queue->rear = p;
  queue->front = p;
 }else{
  //入隊的時候,在隊尾插入元素
  queue->rear->next = p;
  queue->rear = p;
 }
 return 1;
}

3.4 出隊

        鏈式隊列的出隊,就是將鏈表的首元節點從鏈表中刪去,讓其後繼節點成爲首元節點,然後讓隊頭指針指針指向該節點。

int out_linkqueue(PLinkqueue queue, ElementType *x){
 if(empty_linkqueue(queue)){
  printf(“隊空,無法出隊\n”);
  return 0;
 }
 //出隊首元素節點
 *x = queue->front->data;
 PQnode temp_front= queue->front;
 //隊首的下一個元素作爲隊首
 queue->front = temp_front->next;
 //釋放出隊節點
 free(temp_front);
 //如果隊首爲空,說明此時隊列中沒有元素節點了,則設置隊尾也爲空。
 if(!queue->front){
  queue->rear = NULL;
 }
 return 1;
}

3.5 獲取隊頭元素

        即獲取front指針指向的節點。

int getFront_linkqueue(PLinkqueue queue, ElementType *x){
 if(empty_linkqueue(queue)){
  printf(“隊空,無法獲取隊首 元素\n”);
  return 0;
 }
 *x = queue->front->data;
 return 1;
}

3.6 銷燬隊列

        由於動態分配的內存空間 ,在使用完隊列之後,要把申請的空間及時的釋放掉。具體做法就是遍歷單鏈表,釋放每一個節點。

//銷燬隊列。由於使用的是鏈表,因此,要釋放每一個鏈表節點的空間
void destory_linkqueue(PLinkqueue *queue){
 PQnode p;
 if(*queue){
  while((*queue)->front){
    p = (*queue)->front;
    (*queue)->front = (*queue)->front->next;
    free(p );
  }
  free(*queue);
 }
 *queue = NULL;
}

四、典型應用示例

        有n個元素存儲在數組A[n]中,設計一個算法,實現將這n個元素循環向左移動k(0<k<n)位。
        思路分析:將數組的A[0] ~ A[k-1]元素先順序放入一個隊列中,然後再將數組A[k] ~A[n-1]元素依次左移k位,然後再將隊列中保存的數組元素A[0] ~ A[k-1]順序出隊列,依次放入A[n-k] ~A[n-1]的位置。具體的算法如下:

void array_leftcircle_move(int a[], int n, int k){
 int i;
 PLinkqueue queue = init_linkqueue();
 for(i=0;i<k;i++){
  in_linkqueue(queue,a[i]);
 }
 for(i=k;i<n;i++){
  a[i-k] = a[i];
 }
 i = n-k;
 while(!empty_linkqueue(queue)){
  out_linkqueue(queue,&a[i]);
  i++;
 }
}

完整的程序源碼如下:

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

typedef int ElementType;
//節點結構
typedef struct node{
       ElementType data;
       struct node *next;
}Qnode,*PQnode;
//鏈棧結構
typedef struct {

	PQnode front, rear;
}LinkQueue,*PLinkqueue;

//鏈隊初始化
PLinkqueue init_linkqueue(){
	PLinkqueue  queue = (PLinkqueue)malloc(sizeof(LinkQueue));
	if(queue){
		queue->front = NULL;
		queue->rear = NULL;
	}
	return queue;
}

//判斷鏈隊爲空;1空隊列,0非空
int empty_linkqueue(PLinkqueue queue){
	if(queue && (queue->front==NULL) && (queue->rear==NULL)){
		return 1;
	}else{
		return 0;
	}
}

//入隊列

int in_linkqueue(PLinkqueue queue, ElementType x){
	PQnode p = (PQnode)malloc(sizeof(Qnode));
	if(!p){
		printf("內存溢出,不能申請新的節點空間\n");
		return 0;
	}
	p->data = x;
	p->next = NULL;
	//要先判斷隊列中是否有元素,兩者的的入隊是有一些差別的
	if(empty_linkqueue(queue)){
			queue->rear = p;
			queue->front = p;
	}else{
		//入隊的時候,在隊尾插入元素
			queue->rear->next = p;
        	queue->rear = p;
	}
	return 1;
}

//出隊
int out_linkqueue(PLinkqueue queue, ElementType *x){
	if(empty_linkqueue(queue)){
		printf("隊空,無法出隊\n");
		return 0;
	}

	//出隊首元素節點
	*x = queue->front->data;
	PQnode  temp_front= queue->front;

    //隊首的下一個元素作爲隊首
	queue->front = temp_front->next;
	//釋放出隊節點
	free(temp_front);
	//如果隊首爲空,說明此時隊列中沒有元素節點了,則設置隊尾也爲空。
	if(!queue->front){
		queue->rear = NULL;
	}
	return 1;
}

//讀取隊首元素
int getFront_linkqueue(PLinkqueue queue, ElementType *x){
		if(empty_linkqueue(queue)){
	     	printf("隊空,無法獲取隊首 元素\n");
		    return 0;
		}
		*x = queue->front->data;
		return 1;
}

//銷燬隊列。由於使用的是鏈表,因此,要釋放每一個鏈表節點的空間
void destory_linkqueue(PLinkqueue *queue){
	PQnode p;
	if(*queue){
		while((*queue)->front){
			p = (*queue)->front;
			(*queue)->front = (*queue)->front->next;
			free(p);
		}
		free(*queue);
	}
	*queue = NULL;
}

void array_leftcircle_move(int a[], int n, int k){
	int i;
    PLinkqueue  queue = init_linkqueue();

	for(i=0;i<k;i++){

		in_linkqueue(queue,a[i]);
	}
	for(i=k;i<n;i++){

		a[i-k] = a[i];
	}

	i = n-k;
	while(!empty_linkqueue(queue)){

		out_linkqueue(queue,&a[i]);
		i++;
	}
}

int main(){

	int a[10], i, k = 3;

	for(i=0;i<10;i++){
	
		a[i] = i+1;
	
	}

	array_leftcircle_move(a,10,k);

	for(i=0;i<10;i++){
	
	    printf("%d  ",a[i]);
	
	}
	printf("\n");

	return 0;

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