隊(duì)列(Queue)

一糖驴、隊(duì)列的概念

隊(duì)列是一個先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)僚祷。
聯(lián)想一下鏈表,在單鏈表中贮缕,只能對表尾進(jìn)行插入久妆,對表頭進(jìn)行結(jié)點(diǎn)的刪除,這樣強(qiáng)限制性的鏈表跷睦,就是所說的隊(duì)列筷弦。也就是說,隊(duì)列是限定在表的一端進(jìn)行插入抑诸,表的另一端進(jìn)行刪除的數(shù)據(jù)結(jié)構(gòu)烂琴。

如圖去買票排隊(duì),每一列隊(duì)伍都有一個隊(duì)尾和隊(duì)首蜕乡,先來的先買票奸绷,后來的后買,買好的就從隊(duì)首出去层玲,新來買票的就需要從隊(duì)尾繼續(xù)排隊(duì)号醉。

通常反症,稱進(jìn)數(shù)據(jù)的一端為隊(duì)尾,出數(shù)據(jù)的一端為隊(duì)首畔派,數(shù)據(jù)元素進(jìn)隊(duì)列的過程稱為入隊(duì)铅碍,出隊(duì)列的過程稱為出隊(duì)。

隊(duì)列是一個線性的數(shù)據(jù)結(jié)構(gòu)线椰,并且這個數(shù)據(jù)結(jié)構(gòu)只允許在一端進(jìn)行插入胞谈,另一端進(jìn)行刪除,禁止直接訪問除這兩端以外的一切數(shù)據(jù)憨愉,且隊(duì)列是一個先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)烦绳。

如上圖,隊(duì)列就像一個兩端相通的水管配紫,只允許一端插入径密,另一端取出,取出的球就不在水管里面了躺孝,而先放入管中的球就會先從管中拿出睹晒。

隊(duì)列存儲結(jié)構(gòu)的實(shí)現(xiàn)有以下兩種方式:
①順序隊(duì)列:在順序表的基礎(chǔ)上實(shí)現(xiàn)的隊(duì)列結(jié)構(gòu)。
②鏈隊(duì)列:在鏈表的基礎(chǔ)上實(shí)現(xiàn)的隊(duì)列結(jié)構(gòu)括细。

兩者的區(qū)別僅是順序表和鏈表的區(qū)別伪很,即在實(shí)際的物理空間中,數(shù)據(jù)集中存儲的隊(duì)列是順序隊(duì)列奋单,分散存儲的隊(duì)列是鏈隊(duì)列锉试。

二、隊(duì)列的結(jié)點(diǎn)設(shè)計(jì)與初始化

隊(duì)列只有鏈?zhǔn)降脑O(shè)計(jì)方法览濒,其本身分為多種隊(duì)列呆盖,如順序隊(duì)列和循環(huán)隊(duì)列,還有衍生的優(yōu)先隊(duì)列等等贷笛,以順序隊(duì)列的設(shè)計(jì)為例应又。

首先是隊(duì)列的結(jié)點(diǎn)設(shè)計(jì),可以設(shè)計(jì)出兩個結(jié)構(gòu)體乏苦,一個結(jié)構(gòu)體 Node 表示結(jié)點(diǎn)株扛,其中包含有 data 域和 next 指針,如圖:

其中 data 表示數(shù)據(jù)汇荐,其可以是簡單的類型洞就,也可以是復(fù)雜的結(jié)構(gòu)體。next 指針表示掀淘,下一個的指針旬蟋,其指向下一個結(jié)點(diǎn),通過 next 指針將各個結(jié)點(diǎn)鏈接革娄。

然后再添加一個結(jié)構(gòu)體倾贰,其包括了兩個分別永遠(yuǎn)指向隊(duì)列的隊(duì)尾和隊(duì)首的指針冕碟,看到這里是不是覺得和棧很像?

主要的操作只對這兩個指針進(jìn)行操作匆浙,如圖所示:

其結(jié)構(gòu)體設(shè)計(jì)的代碼可以表示為:

//結(jié)點(diǎn)定義
typedef struct node{ 
  int data;
  struct node *next;
}
  node;
//隊(duì)列定義安寺,隊(duì)首指針和隊(duì)尾指針
typedef struct queue{ 
  node *front;
//頭指針 
  node *rear; 
//尾指針
}
  queue;

對于初始化需要初始化兩個類型,一個是初始化結(jié)點(diǎn)吞彤,一個是初始化隊(duì)列我衬。代碼中的描述叹放,初始化隊(duì)列有些不同饰恕,當(dāng)初始化隊(duì)列的時候,需要將頭尾兩個結(jié)點(diǎn)指向的內(nèi)容統(tǒng)統(tǒng)置為空井仰,表示是一個空隊(duì)列埋嵌,兩個創(chuàng)建的函數(shù)代碼可以表示為:

//初始化結(jié)點(diǎn)
node *init_node{
  node *n=(node*)malloc(sizeof(node)); 
  if(n==){
//建立失敗,退出 
    exit(0);
  }
  return n;
}
//初始化隊(duì)列
queue *init_queue { 
  queue *q=(queue*)malloc(sizeof(queue)); 
  if(q==) { 
//建立失敗俱恶,退出 
    exit(0);
  } 
//頭尾結(jié)點(diǎn)均賦值 
  q->front=;
  q->rear=; 
  return q;
}

三雹嗦、判斷隊(duì)列是否為空

這是一個既簡單也很要緊的操作,判斷隊(duì)列是否為空直接就是判斷隊(duì)列頭指針是否是空值即可合是。其代碼可以表示為:

//隊(duì)列判空
int empty(queue *q) { 
  if(q->front==) { 
    return 1; 
//1--表示真了罪,說明隊(duì)列非空 
  }else{
    return 0;
//0--表示假,說明隊(duì)列為空 
  }
}

或者直接利用返回值進(jìn)行更簡單的判斷也可以聪全,代碼如下:

int empty (queue *q) { 
  return q->front==;
}

四泊藕、入隊(duì)操作

入隊(duì)操作變化圖:

進(jìn)行入隊(duì)(push)操作的時候,同樣的难礼,首先需要判斷隊(duì)列是否為空娃圆,如果隊(duì)列為空的話,需要將頭指針和尾指針一同指向第一個結(jié)點(diǎn)蛾茉,代碼如下:

front=n;
rear=n;

如圖:

如果隊(duì)列不為空的時候讼呢,這時只需要將尾結(jié)點(diǎn)向后移動,通過不斷移動 next指針指向新的結(jié)點(diǎn)構(gòu)成隊(duì)列即可谦炬。如圖:

其代碼可以表示為:

//入隊(duì)操作
void push(queue *q,int data) { 
  node *n =init_node;
  n->data=data;
  n->next=; 
//采用尾插入法 //if(q->rear==){ 
//使用此方法也可以 
  if(empty(q)) { 
    q->front=n; q->rear=n; 
  }else{ 
    q->rear->next=n; 
//n成為當(dāng)前尾結(jié)點(diǎn)的下一結(jié)點(diǎn) 
    q->rear=n; 
//讓尾指針指向n 
  }
}

五悦屏、出隊(duì)操作

出隊(duì)操作變化圖:

出隊(duì)(pop)操作,是指在隊(duì)列不為空的情況下進(jìn)行的一個判斷键思,當(dāng)然在此也一定要進(jìn)行隊(duì)列判空的操窜管。

如圖,如果隊(duì)列只有一個元素了稚机,也就是說頭尾指針均指向了同一個結(jié)點(diǎn)幕帆,那么直接將頭尾兩指針置空,并釋放這一個結(jié)點(diǎn)即可赖条,如圖:

當(dāng)隊(duì)列含有以上個元素時失乾,需要將隊(duì)列的頭指針指向頭指針當(dāng)前指向的下一個元素常熙,并釋放掉當(dāng)前元素即可,如圖:

其代碼可以表示為:

//出隊(duì)操作
void pop(queue *q) { 
  node *n=q->front;
  if(empty(q)){
    return ; 
//此時隊(duì)列為空碱茁,直接返回函數(shù)結(jié)束 
  } 
  if(q->front==q->rear){ 
    q->front=; 
//只有一個元素時直接將兩端指向置為空即可 
    q->rear=; free(n); 
//記得歸還內(nèi)存空間 
  }else{ 
    q->front=q->front->next; free(n);
  }
}

六裸卫、打印隊(duì)列元素(遍歷)

打印隊(duì)列的全部元素可以幫助調(diào)試,看到隊(duì)列中具體的數(shù)據(jù)纽竣,在隊(duì)列不為空的情況下墓贿,通過結(jié)點(diǎn)的 **next **指向依次遍歷并輸出元素既可。其代碼可以表示為:

//打印隊(duì)列元素
void print_queue(queue *q){ 
node *n = init_node; 
n=q->front; 
if(empty(q)){ return ; 
//此時隊(duì)列為空蜓氨,直接返回函數(shù)結(jié)束 } 
while (n!=){ 
printf("%d\t",n->data); 
n=n->next; 
} 
printf("\n");
 //記得換行}

遍歷操作還有很多別的表示方法聋袋,比如說進(jìn)行計(jì)算隊(duì)列中含有多少元素,代碼如下:

int calac(queue *q){ 
node *n = init_node; 
n=q->front; 
int cnt=0; 
//計(jì)數(shù)器設(shè)計(jì) 
if(empty(q)){ 
return 0; 
//此時隊(duì)列為空穴吹,直接返回函數(shù)結(jié)束 
} 
while (n!=) {
 n=n->next; cnt++; 
} 
return cnt;
}

七幽勒、順序隊(duì)列的假溢出

什么是假溢出?這里需要考慮到順序隊(duì)列有什么缺點(diǎn)港令,對于順序隊(duì)列而言啥容,其存在已經(jīng)足夠解決大多時候的設(shè)計(jì)問題了顷霹,但是其依舊存在一些缺陷和不足咪惠。

從上面的解析中看到,入隊(duì)和出隊(duì)操作均是直接在其后面進(jìn)行結(jié)點(diǎn)的鏈接和刪除淋淀,這種操作會造成其使用空間不斷向出隊(duì)的那一邊偏移遥昧,產(chǎn)生假溢出。

來打一個比方绅喉,先看圖:

上圖所示渠鸽,有一個順序隊(duì)列,這個隊(duì)列的大小為5柴罐,其已經(jīng)包含了四個元素 data1 , data2 , data3 , data4徽缚。

接著,對這個隊(duì)列進(jìn)行出隊(duì)操作革屠,出隊(duì)2個元素凿试,隊(duì)列就變成了這個樣子,如圖:

從圖上看到似乎沒有什么問題似芝,但是當(dāng)接著再進(jìn)行入隊(duì)操作,比如入隊(duì)2個元素党瓮,分別是 data5和** data6**详炬。

此時已經(jīng)發(fā)現(xiàn)問題了,尾指針移動到可以進(jìn)行隊(duì)列操作的范圍之外去了寞奸,有沒有發(fā)現(xiàn)呛谜?

這種現(xiàn)象稱呼作為隊(duì)列用的存儲區(qū)還沒有滿在跳,但隊(duì)列卻發(fā)生了溢出,把這種現(xiàn)象稱為假溢出隐岛。如圖:

那么有什么辦法解決這個問題呢猫妙?這就要涉及到循環(huán)隊(duì)列的性質(zhì)了!

八聚凹、循環(huán)隊(duì)列的概念

可能這個時候會產(chǎn)生一個疑問割坠,學(xué)習(xí)的隊(duì)列不是使用鏈表實(shí)現(xiàn)的動態(tài)隊(duì)列么?沒有空間的時候會開辟空間妒牙,這難道還會產(chǎn)生假溢出么彼哼?的確,當(dāng)進(jìn)行動態(tài)創(chuàng)建隊(duì)列的時候单旁,也只不過是向后繼續(xù)不斷的申請內(nèi)存空間沪羔;即使前面出隊(duì)操作釋放掉了前面的空間饥伊,但是指針依舊會向后進(jìn)行移動象浑,直到達(dá)到系統(tǒng)預(yù)留給程序的內(nèi)存上界被強(qiáng)行終止;這對于極為頻繁的隊(duì)列操作和程序而言是致命的琅豆,這時候愉豺,就需要對我們的隊(duì)列進(jìn)行優(yōu)化,使用更為優(yōu)秀的結(jié)構(gòu)——循環(huán)隊(duì)列茫因。

循環(huán)隊(duì)列就是將隊(duì)列存儲空間的最后一個位置轉(zhuǎn)而繞到第一個位置蚪拦,形成邏輯上的環(huán)狀空間,以此來供隊(duì)列循環(huán)使用冻押,如圖:

循環(huán)隊(duì)列就是給定隊(duì)列的大小范圍驰贷,在原有隊(duì)列的基礎(chǔ)上,只要隊(duì)列的后方滿了洛巢,就從這個隊(duì)列的前面開始進(jìn)行插入括袒,以達(dá)到重復(fù)利用空間的效果;由于循環(huán)隊(duì)列的設(shè)計(jì)思維更像一個環(huán)稿茉,因此常使用一個環(huán)圖來表示锹锰,但我們需要注意,實(shí)際上循環(huán)隊(duì)列不是一個真正的環(huán)漓库,它依舊是單線性的恃慧。

九、循環(huán)隊(duì)列的結(jié)構(gòu)設(shè)計(jì)

由于循環(huán)對列給定了數(shù)據(jù)范圍的大小渺蒿,所以不需要使用鏈?zhǔn)降膭討B(tài)創(chuàng)建方法了痢士。因?yàn)槿绻褂面準(zhǔn)酱鎯Γ瑫o法確定何時再回到隊(duì)頭進(jìn)行插入操作茂装,所以采用模擬的方法怠蹂,如圖:

其中陪汽,data 表示一個數(shù)據(jù)域,int 為類型褥蚯,其可以修改為任意自定義的類型挚冤,比如說簡單的 char,float 類型等等赞庶,也可以是復(fù)雜的結(jié)構(gòu)體類型训挡。

  • maxsize表示循環(huán)隊(duì)列的最大容納量,其表示隊(duì)列的全部可操作空間歧强。

  • rear代表尾指針澜薄,入隊(duì)時移動。

  • front代表頭指針摊册,出隊(duì)時移動肤京。

其代碼可以表示為:

#define maxsize 10 
//表示循環(huán)隊(duì)列的最大容量
//循環(huán)隊(duì)列的結(jié)構(gòu)設(shè)計(jì)
typedef struct cir_queue{ 
int data[maxsize]; 
int rear; 
int front;
}
cir_queue;

十、循環(huán)隊(duì)列的初始化

循環(huán)隊(duì)列的初始化核心就在于申請空間茅特,并且將 front 指針和 rear 指針內(nèi)容賦值為0忘分,即指向第0個元素即可,這里要注意第 0個元素內(nèi)容為空白修,如圖:

其代碼可以表示為:

//初始化
cir_queue *init{ 
cir_queue *q = (cir_queue*)malloc(sizeof(cir_queue)); 
if(q==){ exit(0); 
//申請內(nèi)存失敗妒峦,退出程序 
} 
q->front=0; 
q->rear=0; 
return q;
}

十一、循環(huán)隊(duì)列的入隊(duì)操作

入隊(duì)操作同順序隊(duì)列的方法兵睛,直接將 rear 向后移動即可肯骇。但是要注意判斷,如果 rea r達(dá)到了隊(duì)列的空間上線祖很,將要從頭繼續(xù)開始移動笛丙。這里推薦使用余數(shù)法,即無論如何求余都是在這片空間內(nèi)進(jìn)行操作假颇,防止一次錯誤執(zhí)行就直接整體崩潰胚鸯,而且也相對而言更為簡潔,不推薦使用 if 語句拆融,這樣顯得比較累贅蠢琳。

注意進(jìn)行加一移動位置操作的時候,不能直接 **q->rear++ **這樣的操作镜豹,這樣計(jì)算機(jī)判斷優(yōu)先級會產(chǎn)生讓自己意想不到的后果傲须。此外這里還需要進(jìn)行一次是否隊(duì)列已滿的判斷,當(dāng)我們 rear 指針的下一個位置就是front的位置的時候趟脂,即改循環(huán)隊(duì)列已滿泰讽。如圖:

其代碼可以表示為:

//入隊(duì)操作
pushvoid push(cir_queue *q,int data){ 
if((q->rear+1)%maxsize==q->front){ 
printf("溢出,無法入隊(duì)\n"); 
return; 
}else{ 
q->rear=(q->rear+1)%maxsize;
 q->data[q->rear]=data; 
}
}

十二、循環(huán)隊(duì)列的出隊(duì)操作

如果順序隊(duì)列的出隊(duì)操作已卸,直接將 front 進(jìn)行后移一位即可佛玄。

這里上面很多地方都提過了,有一個需要留意的地方累澡,即隊(duì)列是否為空梦抢,當(dāng)隊(duì)列為空的時候是無法進(jìn)行出隊(duì)操作的。

其代碼可以表示為:

//出隊(duì)操作
popvoid pop(cir_queue *q){ 
if(q->rear==q->front){ 
printf("隊(duì)列為空愧哟,無法出隊(duì)\n"); 
return; 
}else{ 
q->data[q->front]=0; 
q->front=(q->front+1)%maxsize;
 }
}

十三奥吩、循環(huán)隊(duì)列的遍歷操作

遍歷操作需要借助一個臨時變量儲存位置 front 的位置信息,利用i逐步向后移動蕊梧,直到 i 到達(dá)了 rear 的位置即可宣告遍歷的結(jié)束霞赫。

//遍歷隊(duì)列
void print(cir_queue *q){ 
int i=q->front; 
while(i!=q->rear){ i=(i+1)%maxsize; 
printf("%d\t",q->data[i]);  
} 
printf("\n"); 
//記得換行
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肥矢,隨后出現(xiàn)的幾起案子端衰,更是在濱河造成了極大的恐慌甘改,老刑警劉巖旅东,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異楼誓,居然都是意外死亡玉锌,警方通過查閱死者的電腦和手機(jī)名挥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門疟羹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人禀倔,你說我怎么就攤上這事榄融。” “怎么了救湖?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵愧杯,是天一觀的道長。 經(jīng)常有香客問我鞋既,道長力九,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任邑闺,我火速辦了婚禮跌前,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘陡舅。我一直安慰自己抵乓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灾炭,像睡著了一般茎芋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜈出,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天田弥,我揣著相機(jī)與錄音,去河邊找鬼铡原。 笑死皱蹦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的眷蜈。 我是一名探鬼主播沪哺,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酌儒!你這毒婦竟也來了辜妓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤忌怎,失蹤者是張志新(化名)和其女友劉穎籍滴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榴啸,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孽惰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸥印。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勋功。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖库说,靈堂內(nèi)的尸體忽然破棺而出狂鞋,到底是詐尸還是另有隱情,我是刑警寧澤潜的,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布骚揍,位于F島的核電站,受9級特大地震影響啰挪,放射性物質(zhì)發(fā)生泄漏信不。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一亡呵、第九天 我趴在偏房一處隱蔽的房頂上張望抽活。 院中可真熱鬧,春花似錦政己、人聲如沸酌壕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卵牍。三九已至果港,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糊昙,已是汗流浹背辛掠。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留释牺,地道東北人萝衩。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像没咙,于是被迫代替她去往敵國和親猩谊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354