1.浏览器的返回功能可以用栈来实现,当前浏览的页面我们叫它为栈顶元素,跳转到一个新页面我们叫元素入栈,点击“返回”按钮我们叫元素出栈,当然出栈前提是栈里要有元素,比如在浏览器里,如果已经返回到了最开始的页面,那就无法返回了。栈有一个重要的性质,就是先进后出,First In Last Out(FILO)。

什么是先进后出?在浏览器的例子里,先打开的页面的一定是到后面才会被返回到,后面打开的页面一定是先被返回到的。这个过程,我们可以用栈这种数据结构来模拟实现。

2.栈的性质:

  新元素插入栈顶(新打开的页面会加到栈顶)

  栈顶元素先出(最后打开的页面会先被返回到)

  栈为空不可以出栈(在最开始的页面不可以返回上一级)

3.入栈,出栈,访问栈顶元素等操作

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 template<class Type>class Stack{
 5   private:               
 6     Type *urls;                  //定义一个Type类型的指针变量,用来存储浏览过网页的地址
 7     int max_size,top_index;    //栈里最多可以有的元素个数和栈顶元素的位置
 8   public:
 9     Stack(int length_input){        //构造函数
10         urls=new Type[length_input];
11         max_size=length_input;
12         top_index=-1;               //栈顶元素初始化为-1
13     }
14     ~Stack(){                      //析构函数
15         delete[]urls;
16     }
17     bool push(const Type &element){     //入栈操作,为节省内存,在参数名前加一个引用,在参数类型前加一个const
18         if(top_index>=max_size-1){      //特殊情况,栈顶元素的位置已经大于栈的容量
19             return false;
20         }
21         top_index++;                   //普通情况,栈顶位置先加一,在把元素插入栈顶
22         urls[top_index]=element;
23         return true;
24     }
25    bool pop(){                        //出栈操作
26         if(top_index<0){               //栈为空,不能出栈
27             return false;
28         }
29         top_index--;                   //否则栈顶元素位置向前移动一位,表示出栈
30         return true;
31     }
32    Type top(){                   //访问栈顶元素
33         assert(top_index>=0);     //宏断言,判断栈是否为空,为空则结束程序
34         return urls[top_index];   //不为空则返回栈顶元素
35     }
36    bool empty(){              //判断栈是否为空
37         if(top_index<0){
38             return true;
39         }
40         else{
41             return false;
42         }
43     }
44 };
45    int main() {
46     int n,num;
47     cin>>n;
48     Stack<int> stack(n);
49     for(int i=1;i<=n;i++){
50         cin>>num;
51         stack.push(num);
52     }
53     while(!stack.empty()){           //实现数列的翻转
54         cout<<stack.top()<<" ";
55         stack.pop();
56     }
57     return 0;
58 }

4.栈的应用:表达式的求值

 1 #include<iostream>
 2 #include<string>
 3 #include<cassert>
 4 using namespace std;
 5 template<class Type> class Stack {
 6 private:
 7     Type *urls;
 8     int max_size, top_index;
 9 public:
10     Stack(int length_input) {
11         urls = new Type[length_input];
12         max_size = length_input;
13         top_index = -1;
14     }
15     ~Stack() {
16         delete[] urls;
17     }
18     bool push(const Type &element) {
19         if (top_index >= max_size - 1) {
20             return false;
21         }
22         top_index++;
23         urls[top_index] = element;
24         return true;
25     }
26     bool pop() {
27         if (top_index < 0) {
28             return false;
29         }
30         top_index--;
31         return true;
32     }
33     Type top() {
34         assert(top_index >= 0);
35         return urls[top_index];
36     }
37     bool empty() {
38         if (top_index < 0) {
39             return true;
40         } else {
41             return false;
42         }
43     }
44 };
45 bool precede(char a, char b) {                          //判断两个运算符a和b的优先级,如果a的优先级高则返回true
46     if (a == '*') {
47         return true;
48     } else {
49         return false;
50     }
51 }
52 int operate(char theta, int a, int b) {                  //根据运算符theta计算两个数a和b的值,并返回计算结果
53     if (theta == '+') {
54         return a + b;
55     } else {
56         return a * b;
57     }
58 }
59 void calc(Stack<int> &numbers, Stack<char> &operators) {                      //根据运算符栈顶的运算符,计算数字栈顶的两个数的结果,并把结果加入到数字栈里
60     int a=numbers.top();
61     numbers.pop();
62     int b=numbers.top();
63     numbers.pop();
64     numbers.push(operate(operators.top(),a,b));
65     operators.pop();
66 }
67 int main() {
68     int n;                                                                        //输入的表达式长度
69     cin>>n;
70     Stack<int> numbers(n);
71     Stack<char> operators(n);
72     string buffer;                                                                //输入的表达式
73     cin>>buffer;
74     int i=0;
75     while(i<n){
76         if(isdigit(buffer[i])){                                                  //是数字则化成int型后加入到数字栈
77             numbers.push(buffer[i]-'0');
78             i++;
79         }
80         else{                                                                      //是运算符的情况
81             if(operators.empty()||precede(buffer[i],operators.top())){             //运算符栈为空或当前运算符的优先级大于栈顶的符号的优先级
82                 operators.push(buffer[i]);
83                 i++;
84             }
85             else{
86                 calc(numbers,operators);
87             }
88         }
89         
90     }
91     while(!operators.empty()){
92         calc(numbers,operators);
93     }
94     cout<<numbers.top()<<endl;
95     return 0;
96 }

5.单调栈

       单调栈就是栈内元素单调递增或者单调递减的栈,这一点和单调队列很相似,但是单调栈只能在栈顶操作。

借用拿号排队的场景来说明下。现在有很多人在排队买蒜味可乐,每个人手里都拿着号,越靠前的人手里的号越小,但是号不一定是连续的。一个人拿了号后并没有去排队,而是跑去约会了。等他回来后,发现队伍已经排得很长了,他不能直接插入到队伍里,不然人家以为他是来插队的。他只能跑到队伍最后,挨个询问排队人手里的号,蒜头君认为号比他大的人都是“插队”的,蒜头君就会施魔法把这些人变消失,直到蒜头君找到号比他小的为止。

在上面这个场景里,大家排的队伍就像是单调栈,因为大家手里拿的号是单调递增的。蒜头君找自己位置的这个过程就是元素加入单调栈的过程。新加入的元素如果加到栈顶后,栈里的元素不再是单调递增了,那么我们就删除加入前的栈顶元素,就像施魔法把“插队”的人变消失一样。直到新元素加入后,栈依然是单调递增时,我们才把元素加进栈里。

给定一个包含若干个整数的数组,我们从第 1 个元素开始依次加入单调栈里,并且加入后更新单调栈。那么单调栈有这样的性质:对于单调递增的栈,如果此时栈顶元素为 b,加入新元素 a 后进行更新时,如果 a 大于 b,说明 a 在数组里不能再往左扩展了,也就是说,如果从 a 在数组中的位置开始往左边遍历,则 b 一定是第一个比 a 大的元素;如果 a 小于 b,说明在数组里,a 前面至少有一个元素不能扩展到 a 的位置,也就是对于这些元素来说,a 是其在数组右侧第一个比它小的元素。

单调栈的维护是 O(n) 级的时间复杂度,因为所有元素只会进入栈一次,并且出栈后再也不会进栈了。

      单调栈的性质:

  • 使用单调栈可以找到元素向左遍历第一个比他大的元素
  • 元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除
  • 单调栈里的元素具有单调性

     eg:我们来看看这样一道题:地上从左到右竖立着 n 块木板,从 1 到 n 依次编号,如下图所示。我们知道每块木板的高度,在第 n 块木板右侧竖立着一块高度无限大的木板,现对每块木板依次做如下的操作:对于第 i 块木板,我们从其右侧开始倒水,直到水的高度等于第 i 块木板的高度,倒入的水会淹没 ai 块木板(如果木板左右两侧水的高度大于等于木板高度即视为木板被淹没),求 n 次操作后,所有 ai 的和是多少。如图上所示,在第 4 块木板右侧倒水,可以淹没第 5 块和第 6 块一共 2 块木板,a4 = 2。

        

答案:

      我们来分析下,什么时候水的高度会等于第 i 块木板的高度 hi 呢,一定是水往右边蔓延遇到了一块高度大于等于 hi 的木板 j,ai 就等于木板 i 和木板 j 之间的木板数。于是,问题就变成了寻找在第 i 个数右边第一个比它大的数。可以暴力求解,从 1 循环到 n,对每块木板再往右循环一遍,这样的时间复杂度是 O(n2) 的。有没有高效一点的做法呢?我们回想下单调栈的性质,可以在某点左右扩展出一段连续区间,且该点在区间里始终保证是最值。是不是和这题很像呀,而且这道题只要看点右侧扩展出来的区间即可。那么,接下来我们就用单调栈来解这道题吧。

       

 1 #include<iostream>
 2 #include<cassert>
 3 using namespace std;
 4 class Node {
 5 public:
 6     int id, height;
 7 };
 8 template<class Type> class Stack {
 9 private:
10     Type *urls;
11     int max_size, top_index;
12 public:
13     Stack(int length_input) {
14         urls = new Type[length_input];
15         max_size = length_input;
16         top_index = -1;
17     }
18     ~Stack() {
19         delete[] urls;
20     }
21     bool push(const Type &element) {
22         if (top_index >= max_size - 1) {
23             return false;
24         }
25         top_index++;
26         urls[top_index] = element;
27         return true;
28     }
29     bool pop() {
30         if (top_index < 0) {
31             return false;
32         }
33         top_index--;
34         return true;
35     }
36     Type top() {
37         assert(top_index >= 0);
38         return urls[top_index];
39     }
40     bool empty() {
41         if (top_index < 0) {
42             return true;
43         } else {
44             return false;
45         }
46     }
47 };
48 int main() {
49     int n,ans=0;
50     cin>>n;
51     Stack<Node> stack(n);
52     Node temp;
53     for(int i=1;i<=n;i++){
54         cin>>temp.height;
55         temp.id=i;
56         while(!stack.empty()&&stack.top().height<=temp.height){
57             ans=ans+i-stack.top().id-1;
58             stack.pop();
59         }
60         stack.push(temp);
61     }
62     while(!stack.empty()){
63         ans=ans+n+1-stack.top().id-1;
64         stack.pop();
65     }
66     cout<<ans<<endl;
67     return 0;
68 }

 

posted @ 2016-07-14 22:05  绵绵思远道  阅读(440)  评论(0编辑  收藏  举报