栈、队列的实现及对栈中时间复杂度的优化

来源:互联网 发布:mac lamp环境搭建 编辑:程序博客网 时间:2024/06/06 01:16

一、栈及其队列的介绍及其实现

             栈:栈又称为堆栈,是一种数据结构,是一种受限制的线性表只允许在一端进行插入和删除操作。人们把此端称为栈顶,栈顶的第一个元素被称为栈顶元素,相对地,把另一端称为栈底。向一个栈插入新元素又称为进栈或入栈,它是把该元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称为出栈或退栈,它是把栈顶元素删除掉,使其下面的相邻元素成为新的栈顶元素。栈的特性是后进先出(LIFO),栈的顺序存储结构需要使用一个数组和一个整型变量来实现,下面我将通过代码来展示如何实现栈的。
#include<iostream>#include<cassert>using namespace std;template<class T>class Stack{public:Stack();    :_a(NULL),_size(0),_capacity(0){}~Stack(){if(_size==0){delete[] _a;_a=NULL;_size=0;_capacity=0;}}void Push(const T& data);void Pop();T& Top();bool Empty();size_t Size();protected:T* _a;size_t _size;size_t _capacity;void Checkcapacity();};template<class T>void Stack<T>::Push(const T& data){Checkcapacity();_a[_size]=data;_size++;}template<class T>void Stack<T>::Pop(){assert(_size);--_size;}template<class T>T& Stack<T>::Top(){assert(_size);return _a[_size-1];}template<class T>bool Stack<T>::Empty(){if(_size==0)return true;elsereturn false;}template<class T>size_t Stack<T>::Size(){return (size_t)_size;}template<class T>void Stack<T>::Checkcapacity(){assert(_size);if(_size>=_capacity){size_t newCapacity=_capacity*2+3;T* tmp=new T[newCapacity];for(size_t i=0;i<_size;i++){tmp[i]=_a[i];}delete[] _a;  _a=tmp;_capacity=newCapacity;}}
    队列:队列简称队,也是一种受限的线性表。它只允许在表的一端进行插入,在表的另一端进行删除。我们把进行插入的一端称作队尾(rear),进行删除的一端称作队首(front)。向队列中插入新元素称为进队或入队,新元素进队后就成为新的队尾元素;从队列中删除元素称为离队或出队,元素离队后,其后继元素就成为队首元素。由于队列的插入和删除操作分别是在各自的一端进行的,每个元素必然按照进入的次序离队,所以又把队列称为先进先出表(FIFO)。下面我将给出实现队列的代码
template<class T>struct QueueNode{T _data;QueueNode<T>* _next;QueueNode(const T& x):_data(x),_next(NULL){}};template<class T>class Queue{typedefQueueNode<T> Node;public:Queue():_front(NULL),_tail(NULL),_size(0){}~Queue(){if(_front==NULL){delete _front;}else{while(_front->next){T* del=_front;_front=_front->next;delete del;}}_front=_tail=NULL;_size=0;}void Push(const T& data);void Pop();T& Front();T& Back();bool Empty();size_t Size();protected:T* _front;T* _tail;size_t _size;Node* buyNode(T data);};template<class T>void Queue<T>::Push(const T& data){Node* newNode=buyNode(data);if(_size==0){_front=_tail=newNode;}else{_tail->next=newNode;}_size++;}template<class T>void Queue<T>::Pop(){assert(_front);T* del=_front;_front=_front->next;delete _front;_szie--;}template<class T>T& Queue<T>::Front(){assert(_front);return _front;}template<class T>T& Queue<T>::Back(){assert(_tail);return _tail;}template<class T>bool Queue<T>::Empty(){if(_size==0)return true;elsereturn false;}template<class T>size_t Queue<T>::Size(){return _size;}template<class T>Node* Queue<T>::buyNode(T data){Node* tmp=new Node;tmp->_a=data;tmp->next=NULL;return tmp;}

    上面是简单对于栈、队列的一个介绍还有给出了我栈、队列实现的代码,主要是下面对于栈和队列进行相关的深入实现。

二、栈、队列面试题之-----实现一个栈其Push、Pop、Min的时间复杂度为O(1)

解题思路:
         我们知道栈的特性是“后进先出”的,所以栈的Push、Pop的时间复杂度就是O(1)。但是Min的时间复杂度就不是O(1)了,所以这个题的主要就是使得Min的时间复杂度为O(1)。要让Min的时间复杂度为O(1)的话,我们可以让一个数组或者栈来记录栈中前n项的最小值,用空间来换时间这样就可以让Min的时间复杂度为O(1)。
代码如下:

#include<iostream>using namespace std;template<class T>class Stack{public:Stack():_ptr(NULL):_Min(NULL):_size(0):_capacity(0){}~Stack(){delete[] _ptr;delete[] _Min;_ptr = NULL;}void Push(const T& data){CheckCapacity();_ptr[_size] = data;if(_size == 0){_Min[_size] = data;}else{if(data < _Min[_size - 1]){_Min[_size ] = data;}else{_Min[_size] = _Min[_size - 1];}}_size+=1;}void Pop(){assert(_size > 0);_size--;}size_t Size(){return (size_t)_size;}T& Top(){assert(_size >= 0);return _ptr[_size - 1];}T Min(){assert(_size >= 0);return _Min[_size -1];}bool Empty(){if(_size == 0)return true;elsereturn false;}protected:void CheckCapacity(){assert(_size >= 0);if(_capacity <= _size){int Newcapacity =_capacity * 2 + 3;T* tmp1 = new T[Newcapacity];T* tmp2 = new T[Newcapacity];for(int i = 0; i < _size ; i++){tmp1[i] = _ptr[i];}delete[] _ptr;_ptr = temp1;for(int i = 0; i < _size ; i++){tmp2[i] = _Min[i];}delete[] _Min;_Min = tmp2;_capacity = Newcapacity;m}}T* _ptr;T* _Min;int _size;int _capacity;};

三、逆波兰表达式

1、逆波兰表达式的介绍:

       逆波兰表达式:逆波兰表达式又被称为后缀表达式,我们知道表达式一般由操作符和运算符组成的。在一般的算术表达式中一般会把运算符放在两个操作数中间,逆波兰表达式则是把运算符放在操作数的后面。有人会问为什么我们会用逆波兰表达式把运算符放在操作数中间就不好吗?其实对于计算机来说,计算机并不知道先进行乘除后进行加减,先处理括号里面的,再处理括号外面的。它会把你输入的数字变成逆波兰表达式这样就可以执行上面两个固定的规则。编译器在处理的时候按照从左向右的顺序来读取逆波兰表达式,遇到操作数就会直接压入堆栈,遇到运算符的话就会直接从堆栈中取两个操作数按照该运算符进行计算。这个过程就符合了计算机的原理,这样的话逆波兰表达式产生的机器码的效率就会高。

2、中序表达式转化成逆波兰表达式
     将一个中序表达式转化成逆波兰表达式其实非常简单的,只需要通过两个栈就能完成。下面我将给大家介绍一下如何将一个中序表达式转化成一个逆波兰表达式:
     1、首先我们要维护两个栈S1、S2,S1里面最后村的就是逆波兰表达式的结果,S2中存放的是运算符。首先我们要定义一下优先级的关系
     2、如果遇到的是一个数字的话,我们就直接将它入栈到S1中去
     3、如果遇到的是左括号的话,直接将左括号入栈到S2中
     4、如果遇到的是右括号的话,将栈S2中的运算符一次出栈加入到S中。直到遇到左括号,但左括号出栈并不加到S1中去
     5、如果遇到的是运算符,包括单目运算符和双目运算符按一下规则进行:
        1)如果栈S2为空的话,将运算符加到S2中去
        2)如果栈S2不为空的话,当前遍历的运算符的优先级大于等于栈顶运算符的优先级,那么直接入栈S2
        3)如果栈S2不为空的话,当前遍历的运算符的优先级小于栈顶运算符的优先级,则将栈顶运算符一直出栈加入到栈S1中去,直到栈为空或遇到一个运算符优先级小于等于当前遍历的运算符的优先级,此时将该运算符加到栈S2中去
     6、直到遍历完整个中序表达式,栈S2中依然存在着运算符将这些运算符依次加到栈S1中去,直到栈为空。

3、利用逆波兰表达式求值

           对于逆波兰表达式的求值我们用一个栈S3 就能算出来,我们会看到该栈最后存放的就是表达式的值,从左到右遍历S1操作S3。如过遇到的是数字的话,直接将数字压到S3中去;如果遇到的是单目运算符的话,取S3栈顶的一个元素进行单目运算操作后再压入栈中;遇到双目运算符的话就取S3栈顶的两个元素进行运算之后再压入栈中去。按照上面的规则,我们遍历完整个栈S1之后,栈S3中存的就是逆波兰表达式的值。
        下面我给出自己的代码实现:
#pragma once  #include<iostream>  #include<stack>  #include<cassert>  #include<cstring>  using namespace std;    enum Symbol  {      OP_NUM,      OP_SYMBOL,      ADD,      SUB,      SUL,      DIV,  };    struct Cell  {      char _symbol;   //操作符      int _value;    //操作数  };  //12 3 4 + * 6 - 8 2 / + = 82  //9 3 1 - 3 * + 10 2 / + =20  void testMinToLast()  {      stack<int> s;      Cell RPNArray[]=      {          {OP_NUM,9},          {OP_NUM,3},          {OP_NUM,1},          {OP_SYMBOL,SUB},          {OP_NUM,3},          {OP_SYMBOL,SUL},          {OP_SYMBOL,ADD},          {OP_NUM,10},          {OP_NUM,2},          {OP_SYMBOL,DIV},          {OP_SYMBOL,ADD},      };      size_t sz=sizeof(RPNArray)/sizeof(RPNArray[0]);      for(size_t i=0;i<sz;i++)      {          int right=0,left=0;          if(RPNArray[i]._symbol == 0)          {              s.push(RPNArray[i]._value);          }          else          {              switch(RPNArray[i]._value)              {              case ADD:                  right=s.top();                  s.pop();                  left=s.top();                  s.pop();                  s.push(left+right);                  break;              case SUB:                  right=s.top();                  s.pop();                  left=s.top();                  s.pop();                  s.push(left-right);                  break;              case SUL:                  right=s.top();                  s.pop();                  left=s.top();                  s.pop();                  s.push(left*right);                  break;              case DIV:                  right=s.top();                  s.pop();                  left=s.top();                  s.pop();                  s.push(left/right);                  break;              default:                  break;              }          }      }      cout<<s.top()<<endl;      s.pop();  }    int main()  {      testMinToLast();      system("pause");      return 0;  }  


0 0
原创粉丝点击