[ACM訓練] 算法初級 之 數據結構 之 棧stack+隊列queue (基礎+進階+POJ 1338+2442+1442)


再次面對像棧和隊列這樣的相當基礎的數據結構的學習,應該從多個方面,多維度去學習。

首先,這兩個數據結構都是比較常用的,在標准庫中都有對應的結構能夠直接使用,所以第一個階段應該是先學習直接來使用,下一個階段再去探究具體的實現,以及對基本結構的改造!


C++標准庫中的基本使用方法:

棧: #include<stack>

定義棧,以如下形式實現: stack<Type> s; 其中Type為數據類型(如 int,float,char等)

常用操作有:

s.push(item);       //將item壓入棧頂
s.pop();        //刪除棧頂的元素,但不會返回
s.top();        //返回棧頂的元素,但不會刪除,,,,,,,,,在出棧時需要進行兩步,即先top()獲得棧頂元素,再pop()刪除棧頂元素
s.size();      //返回棧中元素的個數
s.empty();      //檢查棧是否為空,如果為空返回true,否則返回false

 

最基本的用法就是:

stack<int> st;---------------------------------->棧

int first=1;

st.push(first);//入棧1

int second=2;

st.push(second);//入棧2

first=st.top();//first變成了2

st.pop();//出棧

 

隊列:#include<queue>

queue<int> q; //定義一個 int 型的隊列 ---------------------------------->隊列
q.empty()//如果隊列為空返回true,否則返回false
q.size() //返回隊列中元素的個數

q.push() //在隊尾壓入新元素
q.front() //返回隊首元素的值,但不刪除該元素
q.pop() //刪除隊列首元素但不返回其值
q.back()//返回隊列尾元素的值,但不刪除該元素

 

另外在隊列中還有優先隊列,priority_queue<.........>---------------------------------->優先隊列

要包含頭文件:

#include<functional>
#include<queue>

優先隊列支持的操作有:

q.empty()    //如果隊列為空,則返回true,否則返回false
q.size()     //返回隊列中元素的個數
q.pop()    //刪除隊首元素,但不返回其值
q.top()    //返回具有最高優先級的元素值,但不刪除該元素,注意與傳統隊列的不同以及和棧的相同點
q.push(item) //在基於優先級的適當位置插入新元素

 

優先隊列是我們比較不熟悉的一種結構,下面整體上做一個總結學習:

優先隊列是隊列的一種,不過它可以按照自定義的一種方式(數據的優先級)來對隊列中的數據進行動態的排序,每次的push和pop操作,隊列都會動態的調整,以達到我們預期的方式來存儲。

例如:我們常用的操作就是對數據排序,優先隊列默認的是數據大的優先級高,所以我們無論按照什么順序push一堆數,最終在隊列里總是top出最大的元素。

所以,優先隊列在一些定義了權重的地方很有用,priority_queue特別之處在於,允許用戶為隊列中存儲的元素設置優先級。這種隊列不是直接將新元素放置在隊列尾部,而是放在比它優先級低的元素前面。標准庫默認使用<操作符來確定對象之間的優先級關系,所以如果要使用自定義對象,需要重載 < 操作符。

 使用上有這么幾種類型:

 1 priority_queue<int>que;//采用默認優先級構造隊列 
 2 
 3 priority_queue<int,vector<int>,cmp1>que1;//最小值優先 ,,,,,這里定義底層實現以vector實現
 4 priority_queue<int,vector<int>,cmp2>que2;//最大值優先 
 5 
 6 priority_queue<int,vector<int>,greater<int> >que3;//最小值優先,另外需注意“>>”會被認為錯誤, 
 7 priority_queue<int,vector<int>,less<int> >que4;//最大值優先 
 8 
 9 priority_queue<number1>que5; //最小優先級隊列 --->針對自定義的數據結構
10 priority_queue<number2>que6; //最大優先級隊列

1、默認優先級隊列,默認的優先級是按照數據的大小來決定的。默認為最大值優先的。

2、設置的最小值或者最大值優先隊列,使用一個比較結構來標識來表明比較的方式,這里的cmp為:

 1 struct cmp1  
 2 {  
 3     bool operator ()(int &a,int &b)  
 4     {  
 5         return a>b;//最小值優先 
 6     }  
 7 }; 
 8 
 9 struct cmp2  
10 {  
11     bool operator ()(int &a,int &b)  
12     {  
13         return a<b;//最大值優先  
14     }  
15 }; 

3、采用頭文件"functional"內定義的優先級,即greater<int>/less<int>,來標識,除此之外可以不用包含此頭文件。

4、或者使用自定義的結構,但結構內需重載操作符<,比如這里的number1和number2:

//自定義數據結構  
struct number1  
{  
    int x;  
    bool operator < (const number1 &a) const  
    {  
        return x>a.x;//最小值優先  
    }  
};  
struct number2  
{  
    int x;  
    bool operator < (const number2 &a) const  
    {  
        return x<a.x;//最大值優先  
    }  
};  

總結,能直接進行數據大小比較的,就是默認為最大值優先a<b,如果要更改,就是用一個cmp,更改為a>b就變成了最小值優先了,而對於無法直接進行比較的數據結構,就自定義一個<運算符重載,制定一個元素進行比較,默認的按照最大值優先,即x<a.x,若要更改就改成x>a.x即可變成最小值優先。


基本用法就是這個樣子,需要從題目中進行鍛煉,對於棧和隊列基本都比較簡單,但是對於優先隊列就需要重點去掌握了!!!

針對於優先級隊列,有人總結出幾點常用的功能:(其實大致就是上面的四種類型的用法)

 1、優先隊列最基本的功能就是出隊時不是按照先進先出的規則,而是按照隊列中優先級順序出隊

  知識點:1、一般存放實型類型,可比較大小

      2、默認情況下底層以Vector實現

      3、默認情況下是大頂堆,也就是大者優先級高,可以自定義優先級比較規則

1 priority_queue<int> Q;
2 Q.push(2);
3 Q.push(5);
4 Q.push(3);
5 while(!Q.empty())
6 {
7        cout<<Q.top()<<endl;
8        Q.pop();
9 }//這樣就是一個按照順序排序的輸出

2、可以將一個存放實型類型的數據結構轉化為優先隊列,這里跟優先隊列的構造函數相關,

使用的是   priority_queue(InputIterator first,InputIterator last)

給出了一個容器的開口和結尾,然后把這個容器內容拷貝到底層實現(默認vector)中去構造出優先隊列。

1 int a[5]={3,4,5,2,1};
2 priority_queue<int> Q(a,a+5); 3 while(!Q.empty())
4 {
5       cout<<Q.top()<<endl;
6       Q.pop();
7 }  

3、可以定義了一個(Node),底層實現以vector實現(第二個參數),優先級為小頂堆(第三個參數)

前兩個參數沒什么說的,很好理解,其中第三個參數,默認有三寫法:

小頂堆:greater<TYPE>

大頂堆:less<TYPE>            --------->需要使用頭文件:#include<functional>

 

如果想自定義優先級而TYPE不是基本類型,而是復雜類型,例如結構體、類對象,則必須重載其中的operator(),即cmp


經典例題:Ugly Numbers http://poj.org/problem?id=1338

Input

Each line of the input contains a postisive integer n (n <= 1500).Input is terminated by a line with n=0.

Output

For each line, output the n’th ugly number .:Don’t deal with the line with n=0.

Sample Input

1
2
9
0

Sample Output

1
2
10

此題只使用優先級隊列不太能解決問題,解決問題需要兩個條件從小到大的排序+無重復,只使用優先級隊列可以解決排序,而無重復只能使用自定義邏輯來判斷,個人認為使用set能更優雅的解決問題。
下面給出代碼,使用set解決問題:
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <set>
 4 #include <cmath>
 5 
 6 using namespace std;
 7 
 8 unsigned long result[1500];
 9 
10 void getUgly()
11 {
12     set<unsigned long> s;
13     s.insert(2);
14     s.insert(3);
15     s.insert(5);
16 
17     int i=0;
18     result[i]=1;
19     unsigned long tmp=0;
20     while(i<1500)
21     {
22         i++;
23         tmp=*(s.begin());
24         result[i]=tmp;
25         s.erase(tmp);
26         s.insert(tmp*2);
27         s.insert(tmp*3);
28         s.insert(tmp*5);
29     }
30 }
31 
32 
33 int main()
34 {
35     int n;
36 
37     getUgly();
38     while(cin>>n)
39     {
40         if(n==0)
41             break;
42 
43         cout<<result[n-1]<<endl;
44     }
45     return 0;
46 }
View Code

然后再思考一下此題目使用優先級隊列完成了排序,再使用什么樣的外部邏輯才能解決無重復的問題呢?????

在隊列中看來是沒有辦法解決掉重復問題了,但是考慮到從中取數據進行保存時肯定是取小的優先,那么當有兩個重復的取出來肯定是連續的,所以在保存數據是向前查看result中是否重復即可。

參考代碼:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <queue>
 4 #include <cmath>
 5 
 6 using namespace std;
 7 
 8 unsigned long result[1500];
 9 
10 void getUgly()
11 {
12     priority_queue<unsigned long,vector<unsigned long>,greater<unsigned long> > q;
13 
14     q.push(1);
15 
16     int i=0;
17     unsigned long tmp;
18     while(i<1500)
19     {
20         tmp=q.top();
21         q.pop();
22 
23         if(i>0 && tmp==result[i-1])//重復
24             continue;
25         result[i++]=tmp;
26 
27         q.push(tmp*2);
28         q.push(tmp*3);
29         q.push(tmp*5);
30 
31     }
32 }
33 
34 
35 int main()
36 {
37     int n;
38 
39     getUgly();
40     while(cin>>n)
41     {
42         if(n==0)
43             break;
44 
45         cout<<result[n-1]<<endl;
46     }
47 
48     return 0;
49 }
View Code

這里注意這個優先級隊列的定義:默認數值大優先

兩組三種定義格式:

最小值優先  1、priority_queue<T,vector<T>,greater<T> > q  2、priority_queue<T,vector<T>,cmp1> q  3、priority_queue<TYPE> q

最大值優先  1、priority_queue<T,vector<T>,less<T> > q     2、priority_queue<T,vector<T>,cmp2> q  3、priority_queue<TYPE> q

cmp中需要重載bool operator ()(int &a,int &b){return a>b;//最小值為>,最大值;為<}

TYPE中需要重載bool operator < (const number1 &a) const { return x>a.x;//最小值優先>,最大值優先為<}

只需要記住大致的格式,如果記不住具體的大於或者小於號,調試一下即可!!!


New one: Sequence:http://poj.org/problem?id=2442

Input

The first line is an integer T, which shows the number of test cases, and then T test cases follow. The first line of each case contains two integers m, n (0 < m <= 100, 0 < n <= 2000). The following m lines indicate the m sequence respectively. No integer in the sequence is greater than 10000.

Output

For each test case, print a line with the smallest n sums in increasing order, which is separated by a space.

Sample Input

1
2 3
1 2 3
2 2 3

Sample Output

3 3 4

幾點注意點:
1、看到 non-negative的定義,那么在code中的變量最好定義為 unsigned int/long

2、mxn的矩陣的每行一個的全組合,需要使用棧來實現m個n叉深林的深度優先遍歷,或者是用遞歸來實現遍歷,需要重點實現!

全組合實現:

 1、使用棧,可以類似二叉樹的遍歷,但是無法求和。

 1 unsigned int matrix[100][2000];
 2 int m;//實際行數
 3 int n;//實際列數
 4 
 5 struct node
 6 {
 7     unsigned int num;
 8     int level;
 9 };
10 
11 void get_all()
12 {
13     stack<node> st;
14 
15     int i=0,j=0;
16     node op;
17     for(;i<n;i++)
18     {
19         op.num=matrix[0][i];
20         op.level=0;
21         st.push(op);
22 
23         while(!st.empty())
24         {
25             op=st.top();
26             st.pop();
27             cout<<op.num<<endl;
28             if(op.level+1<m)
29             {
30                 op.level+=1;
31                 for(j=n-1;j>=0;j--)
32                 {
33                     op.num=matrix[op.level][j];
34                     st.push(op);
35                 }
36             }
37         }
38     }
39 }

 2、另外還可以使用遞歸,是用遞歸是利用了系統堆棧,此方法可以設計一個求和,遞歸返回。

遞歸部分設計遺忘嚴重,先留空!!!

 

 

 

整體參考代碼:

 


Next one: Black Box:http://poj.org/problem?id=1442

Input

Input contains (in given order): M, N, A(1), A(2), ..., A(M), u(1), u(2), ..., u(N). All numbers are divided by spaces and (or) carriage return characters.

Output

Write to the output Black Box answers sequence for a given sequence of transactions, one number each line.

Sample Input

7 4
3 1 -4 2 8 -1000 2
1 2 6 6

Sample Output

3
3
1
2

數據最大量超過11位的一定考慮long long 或者unsigned long long,int和long能表示的最大值為-2147483648 ~ +2147483647 

 留空。。。

 


這里記錄一個經典的關於棧和隊列的面試題目

題目:實現一個棧,帶有出棧(pop),入棧(push),取最小元素(getMin)三個方法。要保證這三個方法的時間復雜度都是O(1)

思路:重點是getMin()函數的設計,普通思路是設計一個額外的整形變量用來保存最小值的索引值,每次入棧的時候都講最小值與入棧值比較,若更小則更新。

誤區:此思路的一個非常關鍵的思考誤區在於,沒有考慮出棧情況下,如果恰好是該最小值出棧,那么之后就無法獲取最小值了!

進一步思考:就是需要將整個過程中的最小值都保存下來,設置一個額外的棧來保存依次獲取的最小值,這樣即使當前的最小值出棧了,那么次小的值會變成最小值,仍然在輔助棧的棧頂。

誤區:再思考一下此設計的過程,如果依次入棧遞增的一個序列,比如3,4,5,6,7,那么在輔助棧中卻只能記錄下3作為最小值保存,那么直接使用getMin后3就出棧了,那么輔助棧中卻沒有了最小值的信息了!

再進一步:需要解決此漏洞,可以采用:

使用輔助棧,首先要明確棧是先入后出的結構,對於3,4,5這個棧結構,無法直接出棧3或者4,必須先出棧5才可以!!!

所以輔助棧要隨數據棧同時出棧或進棧就不用直接排序保存所有數據了,可以按照以下思路:(參考自這里)

輔助棧和數據棧同時出棧和入棧,第一次入棧時均入棧第一個元素,再次入棧,先直接入棧數據棧,針對輔助棧先將要入棧數據與輔助棧棧頂元素進行比較,如果<棧頂元素,則同時入棧輔助棧,否則將棧頂元素復制一份再次入棧輔助棧即可。出棧時二者同時出棧即可。而getMin()只是用來獲取當前的數據棧中的最小值即可,並不需要將其出棧。如圖:

C++實現:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <stack>
 4 using namespace std;
 5 
 6 template <typename T>//使用模板類T
 7 class StackWithMin
 8 {
 9 public:
10     stack<T> dataStack;
11     stack<T> minStack;
12 
13     void push(T t)//入棧,數據棧直接入棧,輔助棧需要比較大小后入棧
14     {
15         dataStack.push(t);
16         if(minStack.size()==0 || t<minStack.top())
17             minStack.push(t);
18         else
19             minStack.push(minStack.top());
20     }
21 
22     T top()
23     {
24         return dataStack.top();
25     }
26 
27 
28     void pop()//出棧時數據棧和輔助棧同時操作即可,仍然設計遵循標准用法
29     {
30         dataStack.pop();
31         minStack.pop();
32     }
33 
34     T getMin()
35     {
36         return minStack.top();
37     }
38 };
39 
40 
41 int main()
42 {
43     StackWithMin<int> mstack;
44 
45     int eles[4] = {3,4,2,5};
46     for (int i=0;i<4;i++)
47         mstack.push(eles[i]);
48 
49     cout<<mstack.getMin()<<endl;//2
50     mstack.pop();//5
51     mstack.pop();//2
52     cout<<mstack.getMin()<<endl;//3
53     mstack.push(1);
54     cout<<mstack.getMin()<<endl;//1
55 
56     return 0;
57 }
View Code

 

拓展:帶取最小值的隊列

實現一個隊列,帶有出隊(deQueue),入隊(enQueue),取最小元素(getMin)三個方法。要保證這三個方法的時間復雜度都盡可能小。

與上面相似,實現為:

 沒有想好實現方式,先暫時留空。

 


 

下面是牛客網關於隊列和棧的學習相關總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM