【經常使用數據結構——隊列&&棧】(最喜歡STL自帶的東西了)

隊列(queue)

簡介

  隊列,就像他的名字同樣,排隊的人從後面接上,前面的人辦完事情就離開。前端

  隊列是一種特殊的線性表,特殊之處在於它只容許在表的前端(front)進行刪除操做,而在表的後端(rear)進行插入操做,和棧同樣,隊列是一種操做受限制的線性表。進行插入操做的端稱爲隊尾,進行刪除操做的端稱爲隊頭。隊列中沒有元素時,稱爲空隊列node

  隊列的數據元素又稱爲隊列元素。在隊列中插入一個隊列元素稱爲入隊,從隊列中刪除一個隊列元素稱爲出隊。由於隊列只容許在一端插入,在另外一端刪除,因此只有最先進入隊列的元素才能最早從隊列中刪除,故隊列又稱爲先進先出(FIFO-first in first out)線性表。c++

  而隊列一般也分爲兩種類型:後端

順序隊列

  創建這種隊列必須爲其靜態分配或者動態申請一段空間,並用隊頭指針(front)和隊尾指針(rear)進行管理,當插入一個數時,隊頭指針加1,彈出一個數時,隊尾指針加1,當隊頭指針等於隊尾指針,則是一個空隊列,當隊頭指針超出分配的空間以外時,就沒法插入。這種隊列的缺點就是用過一次的空間不會再次運用,大大浪費空間。數據結構

循環隊列

  因此爲了讓空間重複利用,當咱們對這個隊列進行插入或者刪除的操做時,只要超出了咱們分配的空間,就讓指針指向這片空間的起始位置,用取餘操做實現,可是注意空間要開的足夠大,不然會出現隊頭指針追上隊尾指針的狀況。優化

基本操做(STL)

size() 獲取隊列長度
push() 插入一個數,進行入隊操做(隊頭)
pop() 刪除一個數,進行出對操做(隊尾)
front() 獲取隊頭元素
empty() 判斷隊列是否爲空,是就返回1

不說了,我愛STLspa

實際運用

  咱們常常在BFS中用到隊列(你們都知道吧)而後........咱們就上一篇最蒟蒻的約瑟夫問題讓你們康康吧(話說這真是個萬能的題呢)指針

  首先,咱們來看看用STL中的queue的方法作的吧code

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 queue<int>q;
 4 int n,out,now=1;
 5 int main()
 6 {
 7     scanf("%d%d",&n,&out); 
 8     for (int i=1;i<=n;i++)
 9         q.push(i);//初始化
10     while (!q.empty())//隊列不爲空 
11     {
12         if(now==out)
13         {
14             cout<<q.front()<<" ";//打印編號
15             q.pop();//出局
16             now=1;//初始化
17         }
18         else
19         {
20             now++;
21             q.push(q.front());//排到隊尾
22             q.pop();
23         }
24     }
25     cout<<endl;
26     return 0;
27 }

至於手寫的.....我有點懶,之後補充吧blog

優先隊列

簡介

  普通的隊列是一種先進先出的數據結構,元素在隊列尾追加,而從隊列頭刪除。在優先隊列中,元素被賦予優先級。當訪問元素時,具備最高優先級的元素最早刪除。優先隊列具備最高級先出 (first in, largest out)的行爲特徵。一般採用堆數據結構來實現。(不要管堆是什麼玩意兒,安心用你的STL就好了)

基本操做(STL)

定義

  定義優先隊列時,默認從大到小的順序出隊,咱們也能夠用重載運算符自定義優先級

  priority_queue<int> q;                 //經過操做,按照元素從大到小的順序出隊
  priority_queue<int,vector<int>, greater<int> > q;   //經過操做,按照元素從小到大的順序出隊

而後寫一段程序看看腫麼樣

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 priority_queue<int,vector<int>, greater<int> > q;
 4 int main()
 5 {
 6     q.push(10),q.push(5),q.push(15),q.push(2),q.push(7);
 7     for(int i=1;i<=5;i++)
 8     {
 9         cout<<q.top()<<endl;
10         q.pop();
11     }
12     return 0;
13 }
2
5
7
10
15
size() 獲取隊列長度
empty() 判斷隊列是否爲空
push() 在隊列末尾插入一個數
pop() 刪除隊首元素
top() 獲取隊首元素

實際應用

  最多見的就是用在拓撲排序和優化最短路中的dijkstra.........因此,排隊,又是一道例題。

Description

今天,學校老師讓同窗們排成一隊,準備帶你們出去玩,一共有 n 名同窗,
排隊的時候同窗們向老師提了 m 條要求,
每一條要求是說同窗 x 必定要排在同窗 y 以前,
老師如今想找到一種排隊方式能夠知足全部的要求,你能幫幫他嗎?

Input

第一行兩個整數 n,m(1≤n≤10410^4104,1≤m≤10510^5105),表示同窗數和要求數;

如下 m 行,每行兩個整數 x,y,之間用一個空格隔開,
表示某個要求是第 x 號同窗要排在第 y 號同窗以前,全部同窗的編號由 1 開始;

輸入保證有解。

Output

輸出一行,包括 n 個整數,表示從前日後同窗的編號,用空格隔開,若是答案不惟一,輸出字典序最小的答案。

Sample Input 1

2 1
1 2

Sample Output 1

1 2

Hint

提示:使用優先隊列進行拓撲排序
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 priority_queue<int,vector<int>,greater<int> >q;//字典序小的在前面 
 4 vector<int> lt[10010];
 5 vector<int> ans;
 6 int in[10010]; 
 7 int n,m;
 8 int main()
 9 {
10     cin>>n>>m;
11     while(m--)
12     {
13         int a,b;
14         cin>>a>>b;
15         in[b]++;//入度(限制數) 
16         lt[a].push_back(b);//存圖 
17     }
18     for(int i=1;i<=n;i++)
19     {
20         if(in[i]==0)//沒有限制就隨便排 
21             q.push(i);
22     }
23     while(!q.empty())
24     {
25         int sum=q.top();
26         q.pop();
27         ans.push_back(sum);
28         for(int j=0;j<lt[sum].size();j++)
29         {
30             in[lt[sum][j]]--;
31             if(in[lt[sum][j]]==0)
32                 q.push(lt[sum][j]);
33         }
34     }
35     for(int i=0;i<ans.size();i++)
36         cout<<ans[i]<<" ";
37     return 0;
38 }

單調隊列

簡介

  單調隊列,即單調遞減或單調遞增的隊列。使用頻率不高,但在有些程序中會有非同尋常的做用。

  他有兩個特色

  1,隊列中的元素其對應在原來的列表中的順序必須是單調遞增的。

  2,隊列中元素的大小必須是單調遞(增/減/甚至是自定義也能夠)

  他的玄學在於能夠隊首出隊,也能夠隊尾出隊(????)

  而後通常用於求區間最值問題(RMQ)

基本操做

front() 返回隊首元素
back() 返回隊尾元素
pop_back() 刪除隊尾元素
pop_front() 刪除隊首元素
push_back() 插入隊尾元素
push_front() 插入隊首元素

 

實際運用

  咱們通常採用雙端隊列(deque)(STL大法好!!!,固然是方便啦)固然,你也能夠手寫咱們這裏全程拿滑動窗口做爲例題吧。

  爲何它能夠用來求RMQ呢?別急,那樣例作解釋:

  咱們拿求這一段的最小值做爲說明

 咱們用q表示隊列,p表示對應下標

首先你有這樣一串數,而後輸出連續每三個中的最小值

由於隊列沒有元素,因此第一個果斷進去

q={1} p={1}

而後咱們看向3,設想若是後面兩個數都比它大,那它就還有機會啊是否是(不像我已經沒有機會了)

q={1,3} p={1,2}

而後就是-1,由於它比3小,只要-1進了隊,在後面中,有3的每個區間確定有-1,因此3徹底就是多餘的,不必再留着了,1同理

q={-1} p={3}

-3同理

q={-3} p={4}

5來了,由於大於-3,而且還有機會因此能夠加進去

q={-35} p={45}

3來了,由於小於5,因此5沒機會了,可是-3還有機會

q={-33} p={46}

6,由於6>3,因此進去,可是-3已經沒在窗口內了,因此踢出

q={36} p={67}

7來了,同理

q={367} p={678}

由於隊列是單調遞(增/減/自定義),因此輸出隊首就好了(確定是最優的)

這一段我用的STL

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,k,x;
 4 struct node{//編號和值 
 5     int num;
 6     int val;
 7 };
 8 deque<node> q_max;//求最大值 
 9 deque<node> q_min;//求最小值 
10 int ans[2][1000005];//每一段的答案 
11 int cnt;
12 int main()
13 {
14     scanf("%d%d",&n,&k);
15     for(int i=1;i<=n;i++)
16     {
17         scanf("%d",&x);
18         node head;
19         head.num=i;
20         head.val=x;
21         //最大 
22         while(!q_max.empty()&&x>=q_max.back().val)//不爲空而且比隊尾大,證實隊尾的那些之後沒機會最大了了 
23             q_max.pop_back();
24         q_max.push_back(head);//插入 
25         while(i-k>=q_max.front().num)//判斷隊首是否在窗內,不是就刪掉 
26             q_max.pop_front();
27         //最小 ,同上 
28         while(!q_min.empty()&&x<=q_min.back().val)
29             q_min.pop_back();
30         q_min.push_back(head);
31         while(i-k>=q_min.front().num)
32             q_min.pop_front();
33             
34         if(i>=k)
35         {
36             cnt++;//記錄,隊首確定是最優的 
37             ans[0][cnt]=q_max.front().val;
38             ans[1][cnt]=q_min.front().val;
39         }
40     }
41     //輸出 
42     for(int i=1;i<cnt;i++)
43         cout<<ans[1][i]<<" ";
44     cout<<ans[1][cnt]<<endl;
45     for(int i=1;i<cnt;i++)
46         cout<<ans[0][i]<<" ";
47     cout<<ans[0][cnt]<<endl;
48 }

下面是手寫的(怎麼感受簡單一些)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,k;
 4 int a[1000005];
 5 int q[1000005];
 6 void deque_max()
 7 {
 8     int h=1,t=0;//頭指針,尾指針 
 9     for(int i=1;i<=n;i++)
10     {
11         while(h<=t&&q[h]+k<=i)h++;//超出窗口就彈出 
12         while(h<=t&&a[i]<=a[q[t]])t--;//比隊尾大,隊尾的沒機會了也彈出 
13         q[++t]=i;//入隊 
14         if(i>=k)printf("%d ",a[q[h]]);//輸出 
15     }
16     cout<<endl;
17 }
18 void deque_min()//同上 
19 {
20     int h=1,t=0;
21     for(int i=1;i<=n;i++)
22     {
23         while(h<=t&&q[h]+k<=i)h++;
24         while(h<=t&&a[i]>=a[q[t]])t--;
25         q[++t]=i;
26         if(i>=k)printf("%d ",a[q[h]]);
27     }
28     cout<<endl;
29 }
30 int main()
31 {
32     scanf("%d%d",&n,&k);
33     for(int i=1;i<=n;i++)
34         scanf("%d",&a[i]);
35     deque_max();
36     memset(q,0,sizeof(q));//清空隊列 
37     deque_min();
38 }

 棧(stack)

簡介

   棧又名堆棧,它是一種運算受限的線性表。限定僅在表尾進行插入和刪除操做的線性表。這一端被稱爲棧頂,相對地,把另外一端稱爲棧底。向一個棧插入新元素又稱做進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成爲新的棧頂元素;從一個棧刪除元素又稱做出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成爲新的棧頂元素。因此又叫先進後出

 

基本操做(STL)

size() 獲取棧的深度
top() 獲取棧頂元素
pop() 彈出棧頂元素
push() 向棧頂插入元素
empty() 判斷棧是否爲空

單調棧

簡介

  單調遞增或單調減的棧,跟單調隊列差很少,可是隻用到它的一端,利用它能夠用來解決一些ACM/ICPC和OI的題目,如RQNOJ 的諾諾的隊列等。

 基本操做

  由於單調棧仍是隻用它的一段,因此和普通的棧沒什麼明顯的區別。一樣的,能夠手寫也能夠用STL自帶的

 實踐運用

直接單調遞減的的棧,仍是拿樣例過一遍(本身編的不要在乎)

2進來

s={2} p={1}

6進來,由於6>2,因此2不可能成爲後面的數左邊第一個比本身的大的數,因此能夠踢掉,而後2的右邊最大的就是6

s={6} p={2}

8進來,同理,而後6右邊第一個比本身大的就是8

s={8} p={3}

1進來,萬一遇到比一小的還有但願,而後8就是1右邊最大的

s={8,1} p={3,4}

5進來,同理,1出去,5是1右邊大的,8是5右邊大的

s={8,5} p={3,5}

而後康康代碼

 1 #include<bits/stdc++.h>
 2 #define MAX 1000005
 3 using namespace std;
 4 int n,top,ans;
 5 int h[MAX],v[MAX],sum[MAX];
 6 int s[MAX];
 7 int main()
 8 {
 9     scanf("%d",&n);
10     for(int i=1;i<=n;i++)
11     {
12         scanf("%lld%d",&h[i],&v[i]);//高度 和能量 
13         while(top&&h[s[top]]<h[i])sum[i]+=v[s[top--]];//遇到小的就退出,而後小的右邊第一個大的就是它,加上去 
14         sum[s[top]]+=v[i];//而後棧頂比本身大,確定是左邊第一個比本身大的 
15         s[++top]=i;//進棧 
16     }
17     for(int i=1;i<=n;i++)
18         ans=max(ans,sum[i]);//循環過一遍 
19     cout<<ans;    
20 }
21     

好啦,但願這篇博客對大家理解棧和隊列有更好的幫助!

相關文章
相關標籤/搜索