编程基础 - 栈(Stack)

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/darkrabbit/article/details/89409458

编程基础 - 栈(Stack)

返回分类:全部文章 >> 基础知识

本文意指简明扼要的描述栈,并用C++从零到有的实现栈。

需要一定的数据结构与程序语言的基础,尤其是要了解什么是顺序表。



1 栈的简介(Introduction of Stack)

栈(Stack)又名堆栈。

  • 定义:栈作为一种数据结构,它是一种只能在一端进行插入和删除操作的特殊线性表

    • 允许插入和删除的一端称为栈顶(TOP)
    • 不允许插入和删除的一端称为栈底(BOTTOM)
    • 插入操作称为进栈或入栈(PUSH)
    • 删除操作称为退栈或出栈(POP)
  • 限制:只能在栈顶插入和删除

  • 存储结构:顺序结构或链式结构

  • 运算规则(特点):后进先出(Last-In/First-Out,LIFO)或先进后出(First-In/Last-Out,FILO)

  • 与一般线性表的区别:仅运算规则不同


2 栈的主要方法(Main Methods of Stack)

栈的主要操作包括:

  • 初始化(Initialize)

  • 进栈(Push):插入元素

  • 退栈(Pop):如果栈中有元素,删除栈顶元素;

  • 获取栈顶元素(GetTop):如果栈不为空,获取栈顶元素;

  • 清空栈(Clear):如果栈不为空,则删除所有元素;

  • 判断栈空(IsEmpty):栈空返回true,否则返回false

  • 判断栈满(IsFull):栈满返回true,否则返回false


3 栈的实现(C++ Code)

虽然在各种高级语言中,栈已经都被实现了:

  • 在C++中,使用栈需要加入#include <stack>

  • 在C#中,使用栈需要加入using System.Collection.Generic;

但在这里为了更好的理解它,我们将用C++自己实现栈

提示:以下代码编译器为VC++,属性__declspec(property…)代码在其它编译器可能不通用,如果用其它编译器可删除这一行,直接使用方法代替。

3.1 栈的抽象类(Abstract Class)

首先,我们新建一个头文件起名为Stacks.h。它将包含如下内容:

  • 一些需要的常量(主要是顺序结构需要):

    • 栈的默认最大长度
    • 栈的默认长度增长率
  • 一个栈的模板抽象类:包含了栈的主要方法的虚函数。

  • 一些需要的包含库(#include)

我们同时为这些内容放入 命名空间Stacks(之后所有代码都在它之中) 中:

#pragma once

#include <stdexcept> // Build-in exceptions

namespace Stacks
{
    constexpr int DEFAULT_MAX_COUNT = 255; // Max Count of Sequential Structure
    constexpr int DEFAULT_INCREAMENT = 16; // Increament of Sequential Structure

    template<typename T>
    class AbstractStack
    {
        public: // Constructor
        virtual ~AbstractStack()
        {
        };

        public: // public Properties
        inline virtual int GetCount() = 0;
        __declspec(property(get = GetCount)) int count;

        public: // public Methods
        virtual void Push(T item) = 0;
        virtual T Pop() = 0;
        virtual T Top() = 0;
        virtual T Peek(); // in C#, `Top()` Method is called `Peek()`
        virtual void Clear() = 0;
        virtual bool Empty() = 0;
    };


    template<typename T>
    T AbstractStack<T>::Peek()
    {
        return Top();
    }

}
  • #include <stdexcept>:程序异常库,里面有许多常用的异常错误信息

  • DEFAULT_MAX_COUNT:顺序结构的默认长度;

  • DEFAULT_INCREAMENT:顺序结构的默认增长长度;

  • template<typename T> class AbstractStack:栈的模板抽象类(T为元素的类型)

    • int GetCount()int count:是一个属性方法,获取栈内元素个数
    • void Push(T item):入栈
    • T Pop():出栈
    • T Top()T Peek():获取栈顶元素()
    • void Clear():清空栈
    • bool Empty():栈是否为空

Tips
constexpr int可以替换成#define

  • #define DEFAULT_MAX_COUNT 255
  • #define DEFAULT_INCREAMENT 16

有了基类之后,我们将按照栈的结构来分别写代码。

3.2 顺序结构(Sequential Structure)

在顺序结构中,数组存储数据的主要类型。而我们在进行栈操作时,需要一个变量指向栈顶元素,从而顺序结构需要的变量:

  • 元素数组:存储元素

    • 数组初始化的长度
    • 当数组满时,需要增长的长度
  • 栈顶元素下标:标识当前栈顶元素的位置

而在方法上,除了继承下来的方法,我们需要增加一些顺序结构的必要方法:

  • 扩充栈:当栈满时,扩充栈的大小

  • 栈是否满

所以,我们建立一个新的头文件SequentialStack.h来写顺序结构,并将顺序栈命名为SequentialStack

即,我们的最终结构为:

#pragma once

#include "Stacks.h"

namespace Stacks
{
    template<typename T>
    class SequentialStack : virtual public AbstractStack<T>
    {
        public: // Constructor
        SequentialStack();
        virtual ~SequentialStack() override;

        protected: // protected Fields
        T* m_Items;         // 元素数组
        int m_TopIndex;     // 栈顶下标
        int m_MaxCount;     // 元素数组长度
        int m_Increament;   // 元素数组增长长度

        public: // public Properties
        inline virtual int GetCount() override;
        inline int GetMaxCount();
        inline int GetIncreament();
        inline void PutIncreament(int value);

        __declspec(property(get = GetCount)) int count;
        __declspec(property(get = GetMaxCount)) int maxCount;
        __declspec(property(get = GetIncreament, put = PutIncreament)) int increament;

        protected: // protected Methods
        virtual void Overflow(); // 扩充栈,每次扩充`m_Increament`的长度

        public: // public Methods
        virtual void Push(T item) override;
        virtual T Pop() override;
        virtual T Top() override;
        virtual void Clear() override;
        virtual bool Empty() override;
        virtual bool Full(); // 栈是否满
    };
}

3.2.1 初始化与销毁(Initialize and Destroy)

在构造函数,我们主要是进行数组的初始化:

    template<typename T>
    inline SequentialStack<T>::SequentialStack()
    {
        m_TopIndex = -1;
        m_MaxCount = DEFAULT_MAX_COUNT;
        m_Increament = DEFAULT_INCREAMENT;
        
        m_Items = new T[m_MaxCount];
        if (m_Items == 0) // == NULL
        {
            throw std::bad_alloc(); // 分配内存失败
        }
    }

类似的,在析构函数中,我们主要是对数组的销毁:

    template<typename T>
    inline SequentialStack<T>::~SequentialStack()
    {
        if (m_Items != 0)
        {
            delete[] m_Items;
        }
        m_TopIndex = 0;
        m_MaxCount = 0;
        m_Increament = 0;
    }

3.2.2 属性方法(Properties)

这里只是给内部字段对外的一个属性方法:

    template<typename T>
    inline int SequentialStack<T>::GetCount()
    {
        return m_TopIndex + 1;
    }

    template<typename T>
    inline int SequentialStack<T>::GetMaxCount()
    {
        return m_MaxCount;
    }

    template<typename T>
    inline int SequentialStack<T>::GetIncreament()
    {
        return m_Increament;
    }

    template<typename T>
    inline void SequentialStack<T>::PutIncreament(int value)
    {
        if (value < 1) // 栈的增长率不能小于1
        {
            value = 1;
        }
        m_Increament = value;
    }

3.2.3 主要方法(Main Methods)

我们逐个添加,要注意每个方法的前置条件

  • 入栈(PUSH):

        template<typename T>
        void SequentialStack<T>::Push(T item)
        {
            if (Full()) // 如果栈满
            {
                Overflow(); // 扩充栈
            }
            m_TopIndex++; // 栈顶位置+1(后移)
            m_Items[m_TopIndex] = item; // 赋值数据
        }
    
  • 出栈(POP):

        template<typename T>
        T SequentialStack<T>::Pop()
        {
            if (Empty()) // 如果栈空
            {
                throw std::underflow_error("no element."); // 溢出错误
            }
    
            T item = m_Items[m_TopIndex]; // 取出元素
            m_Items[m_TopIndex] = 0; // 将此位置设置为空
            m_TopIndex--; // 栈顶位置-1(前移)
            return item; // 返回取出的元素
        }
    
  • 获取栈顶元素(TOP):

        template<typename T>
        T SequentialStack<T>::Top()
        {
            if (Empty()) // 如果栈空
            {
                throw std::underflow_error("no element."); // 溢出错误
            }
            return m_Items[m_TopIndex]; // 返回栈顶元素
        }
    
  • 清空栈(CLEAR):

        template<typename T>
        void SequentialStack<T>::Clear()
        {
            while (!Empty()) // 如果栈非空
            {
                m_Items[m_TopIndex] = 0; // 将栈顶元素设置成空
                m_TopIndex--; // 栈顶位置-1(前移)
            }
        }
    
  • 判断栈是否空(EMPTY):由于我们有栈顶位置,只要栈顶位置为-1,即栈空

        template<typename T>
        bool SequentialStack<T>::Empty()
        {
            return m_TopIndex == -1;
        }
    
  • 判断栈是否满(FULL):如果栈顶位置达到了数组最后的位置(m_MaxCount - 1),即栈满

        template<typename T>
        bool SequentialStack<T>::Full()
        {
            return m_TopIndex + 1 == m_MaxCount;
        }
    
  • 扩充栈(OVERFLOW):当栈满时,我们为栈增加存储空间

        template<typename T>
        void SequentialStack<T>::Overflow()
        {
            int newSize = m_MaxCount + m_Increament; // 新的长度 = 旧长度 + 增长率
            T* items = new T[newSize]; // 用新的长度初始化新的元素数组
            if (items == 0)
            {
                throw std::bad_alloc(); // 分配内存失败
            }
    
            // 将旧数组中的元素复制到新数组
            for (int i = 0; i < m_MaxCount; i++)
            {
                items[i] = m_Items[i];
            }
    
            delete[] m_Items; // 删除旧数组
            m_Items = items; // 将新数组赋值
            m_MaxCount = newSize; // 更新长度
        }
    

3.3 双栈共享的顺序结构(Shared Sequential Structure)

这种顺序结构是指两个栈共享一个数组空间

这种结构我们并不过多的阐述,只是需要注意以下几点:

  • 栈顶标识应有两个int m_TopIndex[2]

    • 自下而上(数组从左到右),进栈时栈顶位置递增
    • 自上而下(数组从右到左),进栈时栈顶位置递减;
  • 判断条件要分别判断,或需要更改:

    • 栈满:两个栈顶下标相差1
    • 栈空:左栈空且右栈空(数组左右)
  • 扩充栈时,要注意复制右栈元素的下标和新数组元素下标的关系

如果是继承前面的类,可能需要添加一些方法(主要是区分左右栈)

        void PushRight(T item);
        T PopRight();
        T TopRight();
        void ClearLeft();
        void ClearRight();
        bool EmptyLeft();
        bool EmptyRight();
        // and so on

如果不继承,也可以重新编写,主要方法可以加入位置参数,例如:

void Push(T item, int position = 0); // 0表示左,1表示右

3.4 链式结构(Linked Structure)

在链式结构中,链表用于存储数据

我们首先建立一个头节点(HEAD),将它指向的第一个元素作为栈顶。

则我们进栈的方式:

  • 将新栈顶(New)链接到旧栈顶(Old)
  • 将头(Head)链接到新栈顶(New)

我们建立一个新的头文件LinkedStack.h来写链式结构,并将链式栈命名为LinkedStack

即,我们的链式结构最终为:

#pragma once

#include "Stacks.h"

namespace Stacks
{
    template<typename T>
    struct LinkedNode
    {
        T item;
        struct LinkedNode<T>* next;
    };

    template<typename T>
    class LinkedStack : virtual public AbstractStack<T>
    {
        public: // Constructor
        LinkedStack();
        virtual ~LinkedStack() override;

        protected: // protected Fields
        LinkedNode<T>* m_Head; // 头结点
        int m_Count;

        public: // public Properties
        inline virtual int GetCount() override;
        __declspec(property(get = GetCount)) int count;

        public: // public Methods
        virtual void Push(T item) override;
        virtual T Pop() override;
        virtual T Top() override;
        virtual void Clear() override;
        virtual bool Empty() override;
    };

    template<typename T>
    inline int LinkedStack<T>::GetCount()
    {
        return m_Count;
    }
}

3.4.1 初始化与销毁(Initialize and Destroy)

类似顺序结构,我们这里主要是初始化与销毁链表的头节点

    template<typename T>
    inline LinkedStack<T>::LinkedStack()
    {
        m_Head = new LinkedNode<T>(); // 初始化头结点
        m_Head->item = 0;
        m_Head->next = 0;

        m_Count = 0; // 初始化数量
    }

    template<typename T>
    inline LinkedStack<T>::~LinkedStack()
    {
        Clear();
        delete m_Head;
    }

3.4.2 主要方法(Main Methods)

我们依然逐个添加。

  • 入栈(PUSH):

        template<typename T>
        void LinkedStack<T>::Push(T item)
        {
            LinkedNode<T>* newTop = new LinkedNode<T>(); // 初始化新栈顶
            newTop->item = item;
            newTop->next = 0;
    
            LinkedNode<T>* oldTop = m_Head->next; // 获取当前栈顶指针
            m_Head->next = newTop; // 断开旧栈顶,链接新栈顶
            newTop->next = oldTop; // 链接新栈顶与旧栈顶
    
            m_Count++; // 数量+1
        }
    
  • 出栈(POP):

        template<typename T>
        T LinkedStack<T>::Pop()
        {
            if (Empty()) // 如果栈空
            {
                throw std::underflow_error("no element."); // 溢出错误
            }
    
            LinkedNode<T>* pop = m_Head->next; // 取出栈顶,作为旧栈顶
            m_Head->next = pop->next; // 链接新栈顶
            pop->next = 0; // 将旧栈顶链接断开
    
            T item = pop->item; // 取出栈顶数据
            delete pop; // 删除旧栈顶
            
            m_Count--; // 数量-1
            return item;
        }
    
  • 获取栈顶元素(TOP):

        template<typename T>
        T LinkedStack<T>::Top()
        {
            if (Empty()) // 如果栈空
            {
                throw std::underflow_error("no element."); // 溢出错误
            }
    
            return m_Head->next->item;
        }
    
  • 清空栈(CLEAR):

        template<typename T>
        void LinkedStack<T>::Clear()
        {
            LinkedNode<T>* top = m_Head->next; // 获取栈顶
            while (top != 0) // 如果栈顶有元素
            {
                m_Head->next = top->next; // 断开栈顶,并设置新栈顶
                top->next = 0; // 断开旧栈顶链接
                delete top; // 删除旧栈顶
                top = m_Head->next; // 循环获取栈顶
            }
    
            m_Count = 0; // 更新长度
        }
    
  • 判断栈是否空(EMPTY):

        template<typename T>
        bool LinkedStack<T>::Empty()
        {
            return m_Head->next == 0; // 或者 m_Count == 0
        }
    

3.5 测试栈(Stack Testing)

到目前为止,我们完成了三种栈的编写,我们来稍微测试一下它们。

创建一个主函数main.cpp,内容如下:

#include <iostream>

#include "SequentialStack.h"
#include "SharedSequentialStack.h"
#include "LinkedStack.h"

using namespace std;

int main()
{
    Stacks::SequentialStack<char> sStack;
    Stacks::SharedSequentialStack<char> ssStack(1000);
    Stacks::LinkedStack<char> lStack;

    cout << "为每个栈添加[a, n]字母(共享是左右都添加)(PUSH)" << endl;
    for (char ch = 'a'; ch <= 'n'; ch++)
    {
        sStack.Push(ch);
        ssStack.Push(ch);
        ssStack.PushRight(ch);
        lStack.Push(ch);
    }
    cout << "顺序栈数量:" << sStack.count << " / " << sStack.maxCount << endl;
    cout << "共享栈数量:" << ssStack.count << " / " << ssStack.maxCount << endl;
    cout << "链式栈数量:" << lStack.count << endl;
    cout << endl;

    cout << "出栈打印(POP):" << endl;
    // 打印结果应为:"nnnn mmmm .... aaaa" (以换行隔开)
    while (sStack.count > 0)
    {
        cout << sStack.Pop();
        cout << ssStack.Pop() << ssStack.PopRight();
        cout << lStack.Pop();
        cout << endl;
    }
    cout << "顺序栈数量:" << sStack.count << " / " << sStack.maxCount << endl;
    cout << "共享栈数量:" << ssStack.count << " / " << ssStack.maxCount << endl;
    cout << "链式栈数量:" << lStack.count << endl;
    cout << endl;

    for (int i = 0; i < 300; i++)
    {
        sStack.Push('a');
    }
    cout << "加入300个`a`后,顺序栈正确数量(OVERFLOW):默认255 + 3次 x 增长16 = 303" << endl;
    cout << "测试实际顺序栈数量:" << sStack.count << " / " << sStack.maxCount << endl;
    cout << endl;

    cout << "清除顺序栈(CLEAR)" << endl;
    sStack.Clear();
    cout << "顺序栈数量:" << sStack.count << " / " << sStack.maxCount << endl;
    cout << endl;

    system("pause");
    return 0;
}

测试结果如下:

为每个栈添加[a, n]字母(共享是左右都添加)(PUSH)
顺序栈数量:14 / 255
共享栈数量:28 / 1000
链式栈数量:14

出栈打印(POP):
nnnn
mmmm
llll
kkkk
jjjj
iiii
hhhh
gggg
ffff
eeee
dddd
cccc
bbbb
aaaa
顺序栈数量:0 / 255
共享栈数量:0 / 1000
链式栈数量:0

加入300个`a`后,顺序栈正确数量(OVERFLOW):默认255 + 3次 x 增长16 = 303
测试实际顺序栈数量:300 / 303

清除顺序栈(CLEAR)
顺序栈数量:0 / 303

请按任意键继续. . .


4 栈的应用实例 (Stack Examples)

以下常见应用,将在后续更新,更新后将变成链接。


猜你喜欢

转载自blog.csdn.net/darkrabbit/article/details/89409458