淺談隊列及棧的用法

淺談隊列及棧的用法

STL中的queue以及stack是兩個十分好用的數據結構,也是最簡單的數據結構。在這裏簡單的介紹一下它們的用法。


隊列

隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。 —— [ 百度百科 ]

正常來講,如果不用STL的話,我們則需要自己動手手寫隊列,但大家可以先看一下下面的代碼:
Code:

#include<stdio.h>
struct queue
{
    int data[100];
    int head;
    int tail;
};
int main()
{
    struct queue q;
    //初始化隊列 
    q.head=1;
    q.tail=1;
    for(int i=1;i<=9;i++)
    {
        scanf("%d",&q.data[q.tail]);
        q.tail++; 
    }
    while(q.head<q.tail)//當隊列不爲空的時候執行循環 
    {
        //打印隊首並將隊首出隊 
        printf("%d ",q.data[q.head]);
        q.head++;
        //先將新隊首的數添加到隊尾 
        q.data[q.tail]=q.data[q.head];
        q.tail++;
        //再將隊首出隊 
        q.head++;
    }
    return 0;
}

手寫隊列使用一個que[]數組來模擬一個隊列,head,tail,分別代表着隊列的頭和尾,這樣的方法不僅麻煩而且看起來也不美觀,而STL就不一樣了。
形象的講,隊列是這個樣子:

因此,隊列的重要性質就是:
先進先出(FIFO)——先進隊列的元素先出隊列。來源於我們生活中的隊列(先排隊的先辦完事)。

一些常用函數:

  • back() 返回最後一個元素
  • empty() 如果隊列空則返回真
  • front() 返回第一個元素
  • pop() 刪除第一個元素
  • push() 在末尾加入一個元素
  • size() 返回隊列中元素的個數

Add:queue的工作效率一般不高,如想優化可以採用循環的方式,即像一個動態的圈圈的“循環隊列”.

優先隊列 (Priority queue)

之所以叫優先隊列是因爲在這個隊列中,我們可以讓其自動排好順序,然後再O(1)時間內得到我們想要的答案。它的好處就不多說了,誰都有過體會。

下面介紹一下寫的兩種姿勢:

[NOIP2004]的合併果子就是一道十分經典的優先隊列的題目。

#include<stdio.h>
#include <cstdio>
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
    int x;
};
bool operator< (node a,node b)
{
    return a.x > b.x;
}
priority_queue<node,vector<node> >Q;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        node t;
        scanf("%d",&t.x);
        Q.push(t);
    }
    int head,end;
    int sum=0;
    for(int i=1;i<n;i++)
    {
        node head=Q.top();
        Q.pop();
        node end=Q.top();
        Q.pop();
        sum+=head.x+end.x;
        head.x=head.x+end.x;
        Q.push(head);
    }
    printf("%d",sum);
}

我們可以稱這種書寫方式爲“結構體”版,因爲我們可以不斷構建新的結構體來進行操作,但個人感覺會很亂,因爲誰沒事會往結構體裏放數啊,用個數組不行嗎?。。
因此,隆重介紹第二種方式,我姑且先稱之爲“數組”版:
POJ2823 是一道優先隊列的模板題:

#include<stdio.h>
#include<string.h>
#define MAXN 1000000+100 
#include<queue>
using namespace std;
int a[MAXN],min_num[MAXN],max_num[MAXN],cnt1,cnt2;
struct cmp1
{
    bool operator()(const int a1,const int a2)
    {
        return a[a1]>a[a2];
    }
};
struct cmp2
{
    bool operator()(const int a1,const int a2)
    {
        return a[a1]<a[a2];
    }
};
priority_queue<int,vector<int>,cmp1>q1;
priority_queue<int,vector<int>,cmp2>q2;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
    }
    for(int i=1;i<=k;i++)
    {
        q1.push(i);
        q2.push(i);
    }
    min_num[++cnt1]=a[q1.top()];
    max_num[++cnt2]=a[q2.top()];
    for(int i=k+1;i<=n;i++)
    {
        q1.push(i),q2.push(i);
        while(i-q1.top()>=k)
        {
            q1.pop();
        }
        min_num[++cnt1]=a[q1.top()];
        while(i-q2.top()>=k)
        {
            q2.pop();
        }
        max_num[++cnt2]=a[q2.top()];
    }
    for(int i=1;i<=cnt1;i++)
    {
        printf("%d ",min_num[i]);
    }
    printf("\n");
    for(int i=1;i<=cnt2;i++)
    {
        printf("%d ",max_num[i]);
    }
    return 0;
}

在這裏可以看到,我們還是正常的用數組,只不過用一個結構體重載一下cmp,不僅看起來美觀,而且用起來十分方便。但不管怎麼說,習慣什麼用什麼纔是做題的第一準則。


棧(stack)又名堆棧,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。這一端被稱爲棧頂,相對地,把另一端稱爲棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成爲新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成爲新的棧頂元素。 —— [ 百度百科 ]

棧這種東西就好理解多了,先上圖:

與隊列不同的是,棧內的元素不是先進先出,相反,是一種類似於“後來居上”的感覺,先進去的處在棧底,而後來的則在上面。

一些常用函數:

  • empty() 堆棧爲空則返回真
  • pop() 移除棧頂元素
  • push() 在棧頂增加元素
  • size() 返回棧中元素數目
  • top() 返回棧頂元素

先舉個小例子,我們可以用棧來判斷一個數字是否迴文:
Code:

#include<stdio.h>
#include<string.h>
char a[101],s[101]; 
int main()
{
    gets(a);
    int len=strlen(a);
    int mid,next;//找到中點 以及 需要進行字符匹配的起始下標 
    if(len%2==0)
    {
        mid=len/2-1;
        next=mid+1;
    }
    else
    {
        mid=len/2-1;
        next=mid+2;
    }
    int top=0;//棧的初始化 
    for(int i=0;i<=mid;i++)//將mid前的字符依次入棧 
    {
        s[++top]=a[i];
    }
    for(int i=next;i<=len-1;i++)//開始匹配 
    {
        if(a[i]!=s[top])
        {
            break;
        }
        top--;
    }
    if(top==0)
    {
        printf("YES.\n"); 
    }
    else
    {
        printf("NO.\n");
    }
    return 0;
}

看起來沒什麼不同是吧,而且好像更麻煩了。也許我的例子舉得並不恰當,但棧的應用還是比較廣泛的。在繼續往下談論之前,一個特別重要的知識一定要想清楚,那就是:
出棧順序!!
最開始很容易出現這樣的一個思想誤區,那就是比如:12345進棧,則只有54321這一種出棧順序,但是事實並非如此。
Because,有可能1剛進棧就出棧了,其它數全進去了纔出,就會產生15432,以此類推就可以;相反43512就不行,因爲當4首先出棧,則說明1,2,3三個元素已經入棧,則出棧序列中1不可能在2之前。
爲了解決這個問題,POJ有一道十分好的題,POJ1363赤裸裸的判斷出棧順序是否合法。如果正常的模擬時間複雜度爲O(n^2),但O(n)的算法就是簡單的模擬入棧出棧,So easy.
Code:

#include<stdio.h>
#include<string.h>
#include<stack>
using namespace std;
int n;
int a[1500];
bool simulate()
{
    stack<int>s;
    int tmp=1;
    for(int i=1;i<=n;i++)
    {
        while(tmp<=a[i])
        {
            s.push(tmp++);
        }
        int x=s.top();
        s.pop();
        if(x!=a[i])
            return false;
    }
    return true;
}
int main()
{
    while(~scanf("%d",&n)&&n)
    {
        while(~scanf("%d",&a[1])&&a[1])
        {
            for(int i=2;i<=n;i++)
            {
                scanf("%d",&a[i]);
            }   
            if(simulate())
            {
                puts("Yes");
            }
            else
            {
                puts("No");
            }
        }
        printf("\n");
    }
    return 0;
}

單調棧

就像隊列有優先隊列一樣,爲什麼我們的棧不能有類似的性質??
這個可以有。
單調棧與單調隊列很相似。首先棧是後進先出的,單調性指的是嚴格的遞增或者遞減。
PS:
單調棧有以下兩個性質:
1、若是單調遞增棧,則從棧頂到棧底的元素是嚴格遞增的。若是單調遞減棧,則從棧頂到棧底的元素是嚴格遞減的。
2、越靠近棧頂的元素越後進棧。
單調棧與單調隊列不同的地方在於棧只能在棧頂操作,因此一般在應用單調棧的地方不限定它的大小,否則會造成元素無法進棧。
元素進棧過程:對於單調遞增棧,若當前進棧元素爲e,從棧頂開始遍歷元素,把小於e或者等於e的元素彈出棧,直接遇到一個大於e的元素或者棧爲空爲止,然後再把e壓入棧中。對於單調遞減棧,則每次彈出的是大於e或者等於e的元素。
舉一個單調遞增棧的例子:

進棧元素分別爲3,4,2,6,4,5,2,3
3進棧:(3)
3出棧,4進棧:(4)
2進棧:(4,2)
2出棧,4出棧,6進棧:(6)
4進棧:(6,4)
4出棧,5進棧:(6,5)
2進棧:(6,5,2)
2出棧,3進棧:(6,5,3)

還是上一道題吧:
POJ2559Largest Rectangle in a Histogram
題意就是在單位長度內,每一個矩形的寬都爲1,但長度可變,題意需要求最大矩形的面積。
因此我們可以用兩個數組l[i],r[i]表示,第i個點向左/右 最長能擴展到第幾個點,也就是第一個小於它的點。
Ans=max{a[i]*(r[i]-l[i]+1)};
在這裏我們就要用的“單調棧”來進行這神奇的功能,讓時間複雜度由O(n^2)變爲O(n).

#include<stdio.h>
#include<string.h>
#include<stack>
#define MAXN 100005
typedef long long ll;
using namespace std;
ll n,x=0;
ll a[MAXN],l[MAXN],r[MAXN];
stack<int>s;
ll max(ll a,ll b){return a>b?a:b;}
int main()
{
    while(~scanf("%lld",&n)&&n)
    {
        for(ll i=1;i<=n;i++)
        {
            scanf("%lld",a+i);
        }
        while(!s.empty())s.pop();
        s.push(0); 
        a[0]=a[n+1]=-1;
        for(ll i=1;i<=n;i++)
        {
            for(x=s.top();a[x]>=a[i];x=s.top())
            {
                s.pop();
            }
            l[i]=x+1;
            s.push(i);
        }
        while(!s.empty())s.pop();
        s.push(n+1); 
        for(ll i=n;i>=1;i--)
        {
            for(x=s.top();a[x]>=a[i];x=s.top())
            {
                s.pop();
            }
            r[i]=x-1;
            s.push(i);
        }
        ll max_num=-1;
        for(ll i=1;i<=n;i++)
        {
            max_num=max(max_num,(r[i]-l[i]+1)*a[i]);
        }
        printf("%lld\n",max_num);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章