首页 > 代码库 > 基本数据结构-栈的实现及其运用

基本数据结构-栈的实现及其运用

概述:数据结构是用来实现动态集合的方式。动态集合有两个要素,一是动态集合中的元素,二是动态集合上的操作如search(s,k):其中s为给定的集合,k为所要查询的关键字。Insert(s,k),delete,maximun,minimum,successor,predecessor等。

这里介绍几种简单的数据结构:栈,队列,链表,有根树。

一、栈

   栈有一定限制的表,元素的插入和删除只能在表头进行,栈虽然缺少鲁棒性,但是更有效,并且很容易应用,栈后进先出。基本的操作包括进栈PUSH,出栈pop,判空isempty,取栈顶元素peer,返回栈的大小getsize。下图PUSH,POP操作示意(基于数组)。

image

1.栈的链表实现

stack.h

#ifndef STACK_H#define STACK_H#include <iostream>template <class T>  class stack{public:    stack();    ~stack();    bool push(const T &element);    T pop();    T peer();    int getsize();    bool isempty();    struct  node    {        T data;        node * next;    };private:    int sizes;    node * top;};template <class T>  stack<T>::stack(){    top=NULL;    sizes=0;}template <class T>  stack<T>::~stack(){    node *temp;    while (top!=NULL)    {        temp=top;        top=top->next;        delete temp;    }    sizes=0;}template <class T>  bool stack<T>::push(const T &element){    node *temp=new node();    if (temp==false)    {        return false;    }     else    {        temp->data=http://www.mamicode.com/element;        temp->next=top;        top=temp;        ++sizes;        return true;    }}template<class T> T stack<T>::pop(){    node *q=top;    T element=top->data;    top=top->next;    delete q;    --sizes;    return element;}template <class T> T stack<T>::peer(){    return top->data;}template <class T> int stack<T>::getsize(){    return sizes;}template <class T> bool stack<T>::isempty(){    return top==NULL;}#endif

main.cpp

#include "stack.h"int main(){    stack<int> s;    s.push(5);    s.push(9);    s.push(10);    s.push(6);    s.push(8);    while(!s.isempty())    {        std::cout<<s.peer()<<std::endl;        s.pop();            std::cout<<s.getsize()<<std::endl;    }}

注意1.模板类成员函数的定义和声明是放在一起的,而不像写类一样把成员变量和函数声明放在stack.h,定义放在stack.cpp中,不然就会发生错误。原因见http://blog.csdn.net/pongba/article/details/19130。

主要是实现栈上的操作,不难,两个关键操作push和pop,push创建一个节点,将数据放入其中数据域,指针域指向当前top节点,将top指针指向新插入的节点。pop存储当前节点的值用于返回,将top指针指向下一节点,删除头节点。

一般来说用链表实现的栈大小可以无穷大,对应的基于数组的在一开始就要指定栈的大小。

2.栈的数组实现

stack_array.h

#ifndef STACK_ARRAY_H#define STACK_ARRAY_H#include <iostream>using namespace std;template <class T>  class stack{public:    stack(){sizes=6;top=-1;values=new T [sizes];};    stack(int size);    ~stack();    bool push(const T element);    T pop();    T peer();    bool isempty();    bool isfull();private:    int sizes;    int top;    T *values;};template <class T>  stack<T>::stack(int size){    sizes=size;    values=new T[sizes];    top=-1;}template <class T>  stack<T>::~stack(){    delete [] values;}template <class T>  bool stack<T>::push(const T element){    if (isfull())    {            cout<<"Error: the stack is full."<<endl;            return false;    }    else    {         values[++top]=element;         return true;    }    }template<class T> T stack<T>::pop(){    if (isempty())     {        cout << "Error: the stack is empty." << endl;        return -1;    }    else    return values[top--];}template <class T> T stack<T>::peer(){    return values[top];}template <class T> bool stack<T>::isempty(){    return top==-1;}template <class T> bool stack<T>::isfull(){    return top==(sizes-1);}#endif

main.c

#include "stack_array.h"const int sizes=500;int main(){    stack<double> s(sizes);    s.push(5.0);    s.push(9.2);    s.push(10.6);    s.push(6.7);    s.push(8.6);    while(!s.isempty())    {        std::cout<<s.peer()<<std::endl;        cout<<s.pop()<<endl;                }}

初始时基于数组的在一开始就要指定栈的大小,其push,pop实现简单,如之前图所示。

3.栈的应用--表达式的合法性和计算表达式的值

3.1.表达式合法性判断:

主要是判断一个表达式中{}[](),这些括号时候符合规则,实现方法有很多这里用栈实现,

bool check(char *str){    stack<char> ch(sizes);    for (int i=0;i<strlen(str);i++)    {        if (str[i]==(||str[i]=={||str[i]==[)            ch.push(str[i]);        else if(!ch.isempty()&&((str[i]==)&&ch.pop()==()||(str[i]==}&&ch.pop()=={)||(str[i]==]&&ch.pop()==[)))            return true;        else          return false;    }}

3.2 计算表达式的值

中缀表达式转换成后缀表达式,计算后缀表达式的值

表达式形式介绍见http://blog.csdn.net/geekcoder/article/details/6829465

Infix notation : a + b(中缀)

Prefix notation : + a b(前缀)

Postfix notation: a b +(后缀)

为什么我们需要前后缀这种反自然的方式呢?用前后缀就能够在不需要括号的情况下处理复杂情况,同时也更适合于计算机处理,只需要对后缀表达式从左往右扫描一次就能够计算值了。所以一般计算中缀表达式的时候,分为两步进行,第一步转换成为后缀表达式,然后根据后缀表达式求值。后缀表达式不需要考虑运算符的优先级,因为在转换过程中已经考虑了。

怎么进行转换?先看一个人工转换的例子,然后推导计算机转换原则。

假设有一个中缀表达式a+b*c-(d+e)

1.首先将这个中缀表达式的所有运算加括号((a+(b*c))-(d+e))

2.然后将所有运算符放到括号后面,这样就变成了((a(bc)* )+ (de)+ )-

3.把所有括号去掉abc*+de+-,最后得出的结果就是后缀表达式

这样转换的原理何在?我们知道添加括号就是指定优先级,嵌套在里面的括号优先级更高,而顺序的括号优先级一致。一个括号相当于一个子表达式,把运算符放到括号后面是用来连接括号里的两个操作数(这就是为什么叫后缀表达式)。如何用计算机来转换呢?

1.首先表达式是以字符串的形式存储的,需要一个栈来存储运算符,扫描中缀表达式,遇到操作数输出到后缀表达式。

2.然后遇到运算符入栈,根据栈中运算符和将要输入栈的运算符优先级来决定输出哪个,如果将要入栈运算符优先级大于栈顶元素就入栈,反之若入栈的元素的优先级小于等于栈顶元素则先输出栈中元素后入栈因为栈是后进先出的,所以优先级高的要在后,同时要保证栈中运算优先级是递增的。对于括号而言,括号内的运算符要满足前面规则外,当括号结束时,括号里的运算符也要全部输出。

#include "stack_array.h"#include <string.h>const int sizes=500;
int priorty(char a, char b) 
{    if(b == ()        return 1;//左括号直接入栈    else if((b == * || b == /) &&(a == + || a == - || a == ())        return 1;//*、/优先级高于+、-、(,入栈    else if((b == + || b == -) && (a == ())        return 1;//+、-优先级高于(,入栈    else        return 0;}
/*中缀表达式转后缀表达式 中缀表达式之间无分割 后缀表达式操作数、操作符之间用空格分割,便于区分不同操作数*/void infix_to_suffix(char* infix, char* suffix) {    int i, k, j=0;   stack<char> s(sizes);//存储运算符的栈    for(i=0; infix[i]!=\0; i++)    {        if(infix[i] >= 0 && infix[i] <= 9)         {            suffix[j++] = infix[i];//操作数则直接输出        }         else         {            if(i != 0 && infix[i-1] >= 0 && infix[i-1] <= 9)            {                suffix[j++] =  ;//操作数后补充空格分割            }            if(infix[i] == ))            {                //遇到右括号则一直弹出直到左括号,但左括号不输出                while(s.peer() != ()                {                    suffix[j++] = s.pop();                    suffix[j++] =  ;                }                s.pop();            }             else if(s.isempty()|| priorty(s.peer(), infix[i]))             {                //栈为空或当前操作符的优先级高于栈顶操作符,当前操作符入栈                s.push(infix[i]);            }            else             {                //当前操作符优先级等于或低于栈顶操作符则弹出栈顶                while(!priorty(s.peer(), infix[i]))                 {                    suffix[j++] =s.pop();                    suffix[j++] =  ;                    if(s.isempty())                        break;                }                s.push(infix[i]) ;//当前操作符入栈            }        }    }    //补充空格分割    if(suffix[j-1] !=  )     {        suffix[j++] =  ;    }    //如果操作符栈不为空,弹出所有操作符    while(!s.isempty())     {        suffix[j++] = s.pop();        suffix[j++] =  ;    }    suffix[j] = \0;    cout<<suffix<<endl;}/*后缀表达式求值*/int suffix_value(char* suffix){    int i, j;    char op;    stack <int> si(sizes);    int top = 0, value = http://www.mamicode.com/0;    for(i=0; suffix[i] != \0; i++)    {        if(suffix[i] >= 0 && suffix[i] <= 9)         {            value = value*10 + suffix[i] - 0;        }         else if(suffix[i] ==  )         {            //操作数入栈            si.push(value) ;            value = 0;        }         else         {            //根据操作符,对栈顶两个操作数进行计算并得到结果            switch(suffix[i])            {            case +: value = http://www.mamicode.com/si.pop() + si.pop() ;break;            case -: value = http://www.mamicode.com/si.pop() - si.pop() ;break;            case *: value = http://www.mamicode.com/si.pop()  * si.pop() ;break;            case /: value = http://www.mamicode.com/si.pop()  / si.pop() ;break;            default: break;            }        }    }    return si.peer();}int main(){        int n;        char infix[sizes], suffix[sizes];//infix中缀表达式,suffix后缀表达式         cin>>infix;        infix_to_suffix(infix, suffix);            printf("%d\n", suffix_value(suffix));}

程序包括三个子函数,一个是指定优先级,二是转换,三是计算后缀表达式的值。

转换过程中要注意,每个操作数之间要加空格,以便后续计算值比如2345+到底是23+45还是2+345,没有空格后面无法区分,加上23 45 +就明了。程序主要是扫描中缀表达式,对于遇到的字符分情况处理。

计算后缀表达式值时,关键如何把字符的值转成数字,计算好一个子表达式值后要将其值入栈。

参考:http://www.cnblogs.com/zghaobac/p/3394705.html;

image

image

image

上图演示了如何把中缀转换成前缀的过程,stackVect:表示的是存储操作符的栈,而右边分别为中缀和输出的后缀表达式。