初等數據結構之隊列

             數據結構中還有個比較常用的就是隊列,相比較於之前一篇介紹的棧的“後入先出”的特點,隊列的特點就是“先進先出”

             類比於棧,其實很容易就能知道隊列的“先進先出”特點的具體意義,不過下面我還是會用圖片的形式形象介紹下~在介紹之前,首先要知道隊列的幾個比較重要的方法,我們利用enqueue(x)向隊列隊尾添加元素x,dequeue()則是彈出隊首元素,同時類似於棧的top指針,我們還使用head指針指向隊首元素,tail指向隊尾元素的後一個位置(這樣指定的原因是爲了進行隊空的判斷)。

1.隊列初始狀態,head=tail,這時隊空
2.enqueue(3)向隊尾添加元素,tail++,head不變

3.enqueue(7)向隊尾添加元素,tail++,head不變
4.enqueue(8)向隊尾添加元素,tail++,head不變
5.dequeue(),從隊首彈出元素,head++,tail不變
6.enqueue(1)向隊尾添加元素,tail++,head不變
7.dequeue(),從隊首彈出元素,head++,tail不變
8.dequeue(),從隊首彈出元素,head++,tail不變

這裏我們只進行了隊空的判斷,即head=tail,至於隊滿,我們只需要判斷tail指針,比如這裏我們如果保證隊列中最多隻有7個元素,即元素下標最大爲6,則此時tail=7,則表示隊滿。因此隊滿的判斷是不一定的,是根據你分配的控件大小決定的。

小結下:

在一般隊列中,我們使用head=tail判定隊空

                                        tai=指定的下標判定隊滿

此時隊列中實際的元素個數=tail-head



其實通過上面的圖示介紹,大家可能會發現一個問題,那就是,隨着元素的插入和刪除的進行,整個隊列想數組中下標比較大的位置移動過去,從而就產生了隊列的"單向移動性"。當元素被插入到隊列中下標最大的位置上之後,隊列的空間就被用光了,即便這時隊列下標較小處還有空閒空間。這種現象就被稱爲隊列的“假溢出”。

如果想要解決這個問題,有兩個方法,其中一個方法就是每次執行dequeue()刪除隊首元素後,讓隊列中的所有元素整體向下標較小的一端移動。這時一個比較容易想到的方法,卻又是比較複雜的方法,其時間複雜度爲O(n)。

其實如果想解決這個方法,我們還有一種方法,那就是利用head,tail指針,因爲每次插入和刪除元素,我們都是通過這兩個指針,所以每當head和tail移動到下標最大的地方時,就回頭重新指向隊首,也就是下標最小的地方,這樣我們就形成了一種特殊的數據結構-----循環隊列。

循環隊列的主要實現方式就是通過指針的循環,當指針達到最大下標時,從頭開始指向隊首,具體的插入和刪除的操作我就不使用圖示來演示了,其基本原理和一般的隊列是一致的,都是滿足“先進先出”的原則。

但是循環隊列的一個注意點就是隊空和隊滿的判斷,我們還是規定head指針指向隊首元素,tail指向隊尾元素的後一個位置,現在我們考慮下面的兩種情況:


1.隊列中只有一個空閒位置(隊滿的臨界情況)

                                                 圖1.1
       此時執行入隊操作
  
                                                  圖1.2

我們可以看到head=tail,此時隊滿~

2.隊列中只有一個元素(隊空的臨界情況)

                                          圖2.1
此時執行出隊操作

                                                圖2.2

這時我們可以看到head和tail仍然是相等的。


如果是這樣的我們又怎麼區分循環隊列的隊空和隊滿情況?

大家還記得我們上面提到過得利用tail的指向判斷隊滿的情況嗎,這裏我們就利用tail,並且規定隊列中最多隻能存放queue_size-1個元素,其中queue_size是原本分配的空間個數,那麼這樣的話,我們圖1.1中就是隊滿的情況,此時隊空我們就仍然可以使用head=tail來進行判斷。

這樣的話我們通過浪費一個分配空間的方法實現了循環隊列的隊空和隊滿的判斷。

小結下:

在循環隊列中,我們使用head=tail判定隊空

                                        head=(tail+1)/queue_size判定隊滿

此時隊列中實際的元素個數=(tail-head+queue_size)/queue_size


現在以一個例子作爲這篇博文的結束:

現有名稱爲name[i]且處理事件爲time[i]的n個任務按順序排成一列,CPU通過循環調度法逐一處理這些任務,每個任務最多處理q ms(這個時間稱爲時間片)。如果q ms之後任務尚未處理完畢,那麼該任務將被移動至隊列最末尾,CPU開始執行下一個任務

輸入:  輸入形式如下

          name[1]  time[1]

          name[2]  time[2]

           ......

         name[n]  time[n]

     第一行輸入表示任務數的整數n與表示時間片的整數q,用1個空格隔開。

   接下來n行輸入個任務的信息。字符串name[i]與time[i]用一個空格隔開。

輸出:按照任務完成的先後順序輸出各任務名以及各結束時間,任務名與對應結束時間用空格隔開,每一對任務名與結束時間佔一行。

限制:1<=n<=100000

            1<=q<=1000

            1<=time[i]<=50000

            1<=字符串name[i]的長度<=10

            1<=name[i]的和<=1000000

輸入示例:                                                                                   輸出示例:

 5 100                                                                   p2 180

p1 150                                                                  p5 400

p2 80                                                                    p1 450

p3 200                                                                  p3 550

p4 350                                                                  p4 800

p5 20


分析:其實這是最簡單的隊列問題,每次從隊首取出進程進行執行,在時間片內未執行完則放在隊尾。當隊列爲空則執行結束~

#include<iostream>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
int main()
{
	int n,q,t;
	string name;
	queue<pair<string,int> > Q;      //注意這裏使用pair後面兩個> >要用空格隔開 
	cin>>n>>q;
	for(int i=0;i<n;i++)
	{
	   cin>>name>>t;
	   Q.push(make_pair(name,t));   //Q.push(pair(name,t));
	}
	
	pair<string ,int> u;
	int elaps=0,a;                    //elaps記錄當前已經使用的時間 
	while(!Q.empty())
	{
		u=Q.front();Q.pop();
		a=min(u.second,q);   //u.second取得pair對的第二個值,也就是時間,取兩者小的值,作爲本次時間片運行的時間 
		u.second-=a;
		elaps+=a; 
		if(u.second>0)       //進程u還未運行完,就添加到隊尾 
		   Q.push(u);
		else
			cout<<u.first<<" "<<elaps<<endl; 
	}
	return 0;
} 


 PS:本來打算今天不寫博客回老家,可是我媽太能磨蹭了,於是到下午才走。於是想到昨天看的隊列的知識,就寫下這篇博客~不說了



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