栈的概念及其实现

1 前言

栈是一种特殊的线性表,栈仅能在线性表的一端进行操作,栈顶(Top)为允许操作的一端,栈底(Bottom)为不允许操作的一端,栈的特性就是后进先出(Last In First Out),只允许在线性表的一段进行操作

在这里插入图片描述
栈可以看成一个容器,所以栈的操作有创建栈,销毁栈,清空栈,进栈(pop),出栈(push),获取栈顶元素,获取栈的大小,对于栈有静态栈和动态栈,所以我们可以抽象出一个**栈父类,静态栈和动态栈继承于该父类,**我们用一片连续空间(原生数组)来容纳栈中元素,还需要设置一个标记变量top,该标记变量指向栈顶,用来标记栈空还是栈满,当栈中没有任何元素的话该top变量指向-1,当栈中装满元素则指向capacity-1的地方
在这里插入图片描述

2 StaticStack

对于StaticStack设计要点
1.使用原生数组作为栈的存储空间
2.使用类模板参数决定栈的最大容量

父类代码 : stack.h

#ifndef STACK_H
#define STACK_H
#include "object.h"
namespace CGSLib
{
  template <typename T>   //使用参数模板
  class Stack:public Object
  {
  public:
      virtual void push(const T& e) = 0;//入栈函数
      virtual void pop(void)        = 0;//出栈函数
      virtual T top()const          = 0;//获取栈顶的元素
      virtual void clear()          = 0;//消除栈
      virtual int size()const       = 0;//获取栈的大小
  };

}
#endif // STACK_H

子类代码: staticstack

#ifndef STATICSTACK_H
#define STATICSTACK_H
#include "Stack.h"
#include "Exception.h"
namespace CGSLib
{
  template <typename T,int N>    
  class StaticStack : public Stack<T>
 {
     protected:
            T m_space[N];//连续数组空间来存放
            int m_top;//指向栈顶
            int m_size;//表示栈内元素的大小
     public:
            /*默认构造函数,栈顶指向-1,元素为0*/
            StaticStack()
            {
              m_top = -1;
              m_size = 0;
            }
            /*入栈函数*/
            void push(const T& e)
            {
                if(m_size<N)
                {
                    m_space[m_top+1] = e;
                    m_size++;
                    m_top++;
                }
                else
                {
                    THROW_EXCEPTION(IndexOutOfBoundsException,"no memery to push");
                }
            }
            /*出栈函数*/
            void pop(void)
            {
                if(m_size>0)
                {
                  m_top--;
                  m_size--;
                }
                else
                {
                     THROW_EXCEPTION(InvalidOperationException ,"no element to pop");
                }
            }
            /*获取栈顶元素*/
            T top() const
            {
                return m_space[m_top];
            }
            /*清除函数*/
            void clear()
            {
               m_top = -1;
               m_size = 0;
            }
            /*返回存有的元素的个数*/
            int size() const
            {
               return m_size;
            }
            /*返回连续空间的大小*/
            int capacity()const
            {
               return   N;
            }
 };
}
#endif // STATICSTACK_H

测试函数 main.c


using namespace std;
using namespace CGSLib;
int main()
{
   StaticStack<int,5> Stack;
   for(int i=0;i<5;i++)
   {
     Stack.push(i);
   }
   while(Stack.size())
   {
       cout<<Stack.top()<<endl;
       Stack.pop();
   }
   return 0;
}

测试结果:

4
3
2
1
0

可以看出我们的实验成功了,满足了栈的先进后出的特性

2 LinkStack

在上面我们实现了静态栈,但是对于静态栈他是有缺陷的,当我们存储的元素为类类型时,StaticStack的对象在创建时,会多次调用元素类型的构造函数,影响效率,我们写个例子测试下

#include <iostream>
#include "StaticStack.h"

using namespace std;
using namespace CGSLib;
class Test : public Object
{
  public:
    Test()
    {
        cout<<"Test()"<<endl;
    }
    ~Test()
    {
        cout<<"~Test()"<<endl;
    }

};
int main()
{
   StaticStack<Test,5> Stack;
   cout<<Stack.size()<<endl;
   return 0;
}

测试结果:

Test()
Test()
Test()
Test()
Test()
0
~Test()
~Test()
~Test()
~Test()
~Test()

可以看出这个缺陷确实存在的,StaticStack在实现的时候使用了原生数组来实现,这样的话在创建栈对象的时候当然会去调用对应的构造函数,在函数返回时会调用析构函数,所以我们要改进,就要引入链式栈
链式栈的存储实现
链式栈的本质就是链表,定义top指针始终指向链表的首元素,当入栈时就将该元素变为首节点,并将top指针指向它,当出栈时将top指针指向的元素析构,top指针指向下一个节点
在这里插入图片描述
链式栈的设计要点
1.使用类模板,抽象父类Stack的直接子类
2.在内部组合使用LinkList类(该类我们在之前实现过),实现栈的链式存储
3.只在单链表成员对象的头部进行操作
LinkList 代码如下

#ifndef LINKSTACK_H
#define LINKSTACK_H
#include "LinkList.h"
#include "Stack.h"
#include "Exception.h"
namespace CGSLib
{
  template <typename T>
  class LinkStack:public Stack<T>
  {
    protected:
    LinkList<T> m_list; //定义一个LinkList链表类对象
  public:
    void push(const T& e)
    {

      m_list.instert(e);//入栈,在链表的头部插入元素
    }
    void pop(void)
    {
        if(m_list.length()>0)
        {
          m_list.remove(0);//出栈,删除链表的头部元素,
        }
        else
        {
          THROW_EXCEPTION(InvalidOperationException ,"no element to pop");
        }
    }
    T top() const
    {
        if(m_list.length()>0)
        {
            return m_list.get(0);  //返回栈顶元素的值,在链表栈中栈顶元素总是首结点的值,也就是我们上面说的top一直指向首节点
        }
        else
        {
          THROW_EXCEPTION(InvalidOperationException ,"no element to top");
        }
    }
    void clear()
    {
       m_list.clear();//调用链表的清除函数
    }
    int size() const
    {
        return m_list.length();//返回链表的长度,也就是栈中的元素个数
    }

  };
}
#endif // LINKSTACK_H

测试代码如下

#include <iostream>
#include "LinkStack.h"

using namespace std;
using namespace CGSLib;
class Test : public Object
{
  public:
    Test()
    {
        cout<<"Test()"<<endl;
    }
    ~Test()
    {
        cout<<"~Test()"<<endl;
    }

};
int main()
{
   LinkStack<Test> Stack;
   cout<<Stack.size()<<endl;
   return 0;
}

测试结果:

0

可以看出这样我们在刚开始创建栈的时候不用到原生数组,也就不会调用到对象的构造函数,避免资源的消耗,链式栈的实现组合使用了单链表对象,在单链表的头部进行操作能够实现高效的入栈和出栈操作,栈非常适合于需要"就近匹配"的场合

3.栈的应用实践

符号匹配问题,在C语言中有一些成对匹配出现的符号,比如(),[],{},<>, ’ ’ , " " 等等,那么编译器是怎样去匹配这些符号的呢?算法思路如下
1.
从第一个字符开始扫描
当遇见普通字符时忽略
当遇见左符号时压入栈中
当遇见右符号时弹出栈顶符号,并进行匹配
2.
成功则所有字符扫描完毕,且栈为空
失败则匹配失败或所有字符扫描完毕但是栈非空

#include <iostream>
#include "LinkStack.h"

using namespace std;
using namespace CGSLib;
/*判断是否为左符号的函数*/
bool is_left(char c)
{
    return (c == '(')||(c == '{')||(c == '[')||(c == '<');

}
/*判断是否为右符号的函数*/
bool is_right(char c)
{
    return (c == ')')||(c == '}')||(c == ']')||(c == '>');

}
/*是否为' '' 的函数*/
bool is_quot(char c)
{
    return (c == '\'')||(c == '\""');
}
/*判断是否匹配*/
bool is_match(char l,char r)
{
    return ((l == '(')&&(r == ')'))||
           ((l == '<')&&(r == '>'))||
           ((l == '{')&&(r == '}'))||
           ((l == '[')&&(r == ']'))||
           ((l == '\'')&&(r == '\''))||
           ((l == '\""')&&(r == '\""'));
}
/*扫描函数*/
bool scan(const char* code)
{
  LinkStack<char> stack;  /*定义一个链表栈*/
  int i = 0;
  bool ret = true;
  code = (code == NULL)? " " : code;/*判空处理*/
  while(ret&&code[i] != '\0')
  {   
      /*如果为左符号则入栈*/
      if(is_left(code[i]))
      {
          stack.push(code[i]);
      }
      /*右符号处理函数*/
      else if(is_right(code[i]))
      {
          if(stack.size()>0 &&is_match(stack.top(),code[i]))/*如果匹配且链表长度大于0则首结点出栈*/
          {
              stack.pop();
          }
          else
          {
             ret = false;/*否则设置错误*/
          }
      }
      /*' '' 处理函数*/
      else if(is_quot(code[i]))
      {
          if((stack.size() == 0)||!is_match(stack.top(),code[i]))/*如果长度为0,则不匹配则该符号为左符号,入栈*/
          {
                  stack.push(code[i]);
          }
          else
          {
              stack.pop();/*否则匹配到则出栈*/
          }

      }
    i++;
  }
  return ret&&(stack.size()==0);

}
int main()
{

    cout<<scan("sasasasads[][][][][]")<<endl;
   return 0;
}

测试结果

1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值