栈的C++实现及其应用

栈是一种先进后出的数据结构,是一种功能受限的线性表。因为这世间存在这后进先出的计算顺序,为简化计算的过程,栈得以应用。好比装水的桶,好比装子弹的弹夹。

栈的C++实现

本文主要是编程实现栈,使用的是顺序储存结构(动态数组)来实现栈,需要注意的是当内存空间不够用,即栈满的时候,应该将重新开辟新的空间(大小为源空间大小+STACK_INCREMENT),然后将数据复制到新空间,在重新释放旧空间。
下面给出头文件定义(Stack.h):

#define STACK_INIT_SIZE 100 ///the size of stack after initialized
#define STACK_INCREMENT 10  ///the increment of stack growth
class Stack{
private:
    int *base;///using array to store element
    int top;///use index(index of base) to find the top element of stack
    int stacksize;
    ///will be called when push a element to a full stack(top = stacksize)
    bool ReallocateMemory();
public:
    ///apply for memory and form a empty stack
    Stack();
    ///destroy the stack s and s won't exist anymore
    void DestroyStack();
    ///clear the stack s and s become a empty stack
    void ClearStack();
    ///get top element of stack while s is not empty otherwise return false
    bool GetTop(int &e);
    ///insert a element to the stack
    bool Push(int e);
    ///remove the top element and return it
    bool Pop(int &e);
    ///get stack size
    int GetSize();
};

不难看出,栈的函数比较少,可以很容易写出其函数定义(Stack.cpp):

#include "Stack.h"
///apply for memory and form a empty stack
Stack::Stack(){
    base = new int[STACK_INIT_SIZE];
    top = 0;
    stacksize = STACK_INIT_SIZE;
}
///will be called when push a element to a full stack(top = stacksize)
bool Stack::ReallocateMemory(){
    int * tmp = new int[stacksize+STACK_INCREMENT];
    for(int i = 0; i < top; ++i){
        tmp[i] = base[i];
    }
    delete [] base;
    base = tmp;
    stacksize +=STACK_INCREMENT;
    if(base!=nullptr)
        return true;
    return false;
}
///destroy the stack s and s won't exist anymore
void Stack::DestroyStack()
{
    delete [] base;
    base = nullptr;
    top = 0;
    stacksize = 0;
}
///clear the stack s and s become a empty stack
inline void Stack::ClearStack(){
    top = 0;
}
///get top element of stack while s is not empty otherwise return false
bool Stack::GetTop(int &e){
    if(top==0)
        return false;
    e=base[top-1];
    return true;
}
///insert a element to the stack
bool Stack::Push(int e){
    bool flag;
    if(top==stacksize)
        flag = ReallocateMemory();
    base[top++]=e;
    return flag;
}
///remove the top element and return it
bool Stack::Pop(int &e){
    if(top==0)
        return false;
    e = base[--top];
    return true;
}
///get stack size
inline int Stack::GetSize(){
    return top;
}

这里top是动态数组的下标,指向栈顶元素的下一个位置,当top=0时,表示栈空,当top=stacksize时,表示栈满。
测试
下面给出测试代码(main.cpp):

#include <iostream>
using namespace std;
#include "Stack.h"

int main()
{
    Stack s;
    int e;
    for(int i = 0;i<110;++i)
        s.Push(i);
    for(int i = 0;i<110;++i)
    {
        if(s.Pop(e))
            cout<<e<<' ';
    }
    cout<<endl;
    return 0;
}

上述代码的思路是依次向栈压入0/1/2/3/4…109,那么输出应该是109/108…0。
测试结果:
这里写图片描述

栈的应用

1、表达式求值
(1)我们知道,对于中缀表达式而言,不好判断计算顺序,于是波兰学者提出了后缀表达式的概念,而后缀表达式非常适合程序的处理。如果不清楚这个算法的话,可以参考博文:后缀表达式
(2)对算法有所了解以后,我们可以发现,要想计算表达式的值,需要先将中缀表达式转换成后缀表达式(步骤一),然后利用后缀表达式进行求值(步骤二);而将中缀表达式转换成后缀表达式的关键在于算符优先表的构建;上述的两个过程都涉及到栈的应用;
这里写图片描述
(3)步骤一编程实现:

///算符优先表,1表示>,0表示=,-1表示<,2表示不存在优先关系
int opTable[7][7]={
1,1,-1,-1,-1,1,1,
1,1,-1,-1,-1,1,1,
1,1,1,1,-1,1,1,
1,1,1,1,-1,1,1,
-1,-1,-1,-1,-1,0,2,
1,1,1,1,2,1,1,
-1,-1,-1,-1,-1,2,0
};
char op[7]={'+','-','*','/','(',')','#'};
int indexOf(char c){
    int j;
    for(j = 0;j<7;++j){
            if(op[j]==c)
                break;
    }
    return j;
}
bool convert(char *mExp,char *bExp){
/**<
1、建立符号栈*/
Stack opStack;

/**   2、顺序扫描中序表达式
a) 是数字, 直接输出
b) 是运算符*/
int n = strlen(mExp);
int k = 0;
for(int i=0;i<n;++i){
    char c = mExp[i];
    if(isdigit(c)){
        bExp[k++]=c;
    }
    else{
        int j = indexOf(c);
        ///不是合法算符
        if(j==7)
            return false;
        ///i : “(” 直接入栈
        if(c=='('){
            ///由于我的stack是int型,这里会有类型转换
            int x = (int)c;
            opStack.Push(x);
        }
        ///ii : “)” 将符号栈中的元素依次出栈并输出, 直到 “(“, “(“只出栈, 不输出
        else if(c==')'){
           char ch;
           int xx = ch;
           opStack.GetTop(xx);
           ch = (char)xx;
           while(ch!='('){
                opStack.Pop(xx);
                bExp[k++]=(char)xx;
                opStack.GetTop(xx);
                ch = (char)xx;
           }
           opStack.Pop(xx);
        }
        ///iii: 其他符号, 将符号栈中的元素依次出栈并输出,
        ///直到遇到比当前符号优先级更低的符号或者”(“。 将当前符号入栈。
        else{
            if(opStack.GetSize()!=0){
                char ch;
                int x = (int)ch;
                opStack.GetTop(x);
                ch = (char)x;
                int column = indexOf(ch);
                int tuplE = indexOf(c);
                while(opTable[column][tuplE]!=-1 && ch != '('){
                    bExp[k++]=ch;
                    opStack.Pop(x);
                    opStack.GetTop(x);
                    ch=(char)x;
                    column=indexOf(ch);
                }
            }
            opStack.Push(c);
        }
    }
}
///扫描完后, 将栈中剩余符号依次输出
while(opStack.GetSize()!=0){
    int x;
    opStack.Pop(x);
    bExp[k++]=char(x);
}
bExp[k]='\0';
return true;
}

需要注意的是,我这里用的栈是自己写的栈,栈的元素是整型,而处理表达式用的是字符型,在程序运行过程中存在类型转换。因此,这是容易出错的。比较好的做法是在编写栈的实现的时候使用泛型编程,使得栈的数据类型可变,但是对每一种数据类型的操作都是一样的。
(4)步骤二编程实现:

/** 建立一个栈S 。从左到右读表达式,
如果读到操作数就将它压入栈S中,如果
读到n元运算符(即需要参数个数为n的运算符)
则取出由栈顶向下的n项按操作符运算,
再将运算的结果代替原栈顶的n项,
压入栈S中。如果后缀表达式未读完,
则重复上面过程,最后输出栈顶的数值则为结束。
 */
int calculate(char *bExp){
    Stack s;
    int n = strlen(bExp);
    char c;
    int x,y,op1,op2;
    int result;
    for(int i=0;i<n;++i){
        c = bExp[i];
        x = (int)c;
        if(isdigit(c)){
            s.Push(x-'0');
        }
        else{
            if(c=='+'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1+op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
            if(c=='-'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1-op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
            if(c=='*'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1*op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
            if(c=='/'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1/op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
        }

    }
    s.Pop(x);
    return x;
}

(4)测试

int main()
{
    char mExp[100],bExp[100];
    cout<<"请输入中缀表达式:"<<endl;
    cin>>mExp;
    if(convert(mExp,bExp)){
         cout<<"转换得到的后缀表达式:"<<endl;
         for(int i=0;i<strlen(bExp);++i)
            cout<<bExp[i]<<' ';
        cout<<endl;
    }
    cout<<"根据后缀表达式计算得到值为:"<<calculate(bExp)<<endl;
    return 0;
}

运行结果:
这里写图片描述
2、利用两个栈实现一个队列
算法原理:我们知道队列是先进先出的线性表,而栈是先进后出的线性表。那么两次先进后出,就会变成先进先出,于是编程的关键在于保证两次先进后出的顺序,比如对于输入1,2,3,4来说,到达第一个栈后输出变成4/3/2/1;而到达第二个栈后输出就变成1/2/3/4了,这就实现了队列的功能。设s1是输入的第一个栈,s2是第二个栈,那么,当s2不为空时,不可以将s1的数据导入s2,否则将打破两次先进后出的顺序。

#include "Stack.h"
///利用两个栈实现一个队列的功能
class Queue{
private:
    Stack s1,s2;///s1是输入栈、s2是输出栈
public:
    Queue(){}
    bool push(int e){
        s1.Push(e);
    }
    bool pop(int &e){
        if(s2.GetSize()!=0){
            s2.Pop(e);
            return true;
        }
        else if(s1.GetSize()!=0){
            int x;
            while(s1.GetSize()!=0){
                s1.Pop(x);
                s2.Push(x);
            }
            s2.Pop(e);
            return true;
        }
        else{
            return false;
        }
    }
};

我这里的栈都是可以无限增长的,所以不用判断s1是否为满。否则需要判断s1是否为满。
测试:

Queue q;
    q.push(1);
    q.push(2);
    int e;
    q.pop(e);
    cout<<e<<' ';
    q.push(3);
    q.push(4);
    q.pop(e);
    cout<<e<<' ';
    q.pop(e);
    cout<<e<<' ';
    q.pop(e);
    cout<<e<<' ';

运行结果:
这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值