数据结构——栈

158 阅读5分钟

**栈(stack)是限定仅在表的一端进行插入和删除操作的线性表,允许插入和删除的一端称为栈顶(stack top),另一端称为栈底(stack bottom),不含任何元素的栈称为空栈,栈还具有后进先出(last in first out)**的特性。

顺序栈

栈的顺序存储结构称为顺序栈(sequential stack),顺序栈本质上是顺序表的简化,唯一需要确定的是用数组的哪一端表示栈底,通常把数组中下标为0的一端称为栈底,同时附设变量top指示栈顶元素在数组中的位置。

顺序栈的实现

const int StackSize = 10;        // 根据实际问题具体定义
template <typename DataType>
class SeqStack{
    public:
        SeqStack();                    // 构造函数,初始化一个空栈
        ~SeqStack(){}                  // 析构函数
        void Push(DataType x);         // 入栈操作,将元素x入栈
        DataType Pop();                // 出栈操作,将栈顶元素弹出
        DataType GetTop();             // 取栈顶元素(并不删除)
        bool Empty();                  // 判断栈是否为空
    private:
        DataType data[StackSize];        // 存放栈元素的数组
        int top;                         // 栈顶元素在数组中的下标
};

template <typename DataType>
SeqStack<DataType>::SeqStack(){
    top = -1;
}

template <typename DataType>
void SeqStack<DataType>::Push(DataType x){
    if (top == StackSize-1)
        throw "overflow";
    data[++top] = x;
}

template <typename DataType>
DataType SeqStack<DataType>::Pop(){
    if (top == -1)
        throw "underflow";
    return data[top--];
}

template <typename DataType>
DataType SeqStack<DataType>::GetTop(){
    if (top == -1)
        throw "no element in stack";
    return data[top];
}

template <typename DataType>
bool SeqStack<DataType>::Empty(){
    return top==-1?true:false;
}

链栈

栈的链接存储结构称为链栈(linked stack),通常用单链表表示,因为只能在栈顶执行插入和删除操作,显然以单链表的头部作为栈顶是最方便的,而且没有必要像单链表那样为了运算方便附加头结点。

链栈的实现

template <typename DataType>
struct Node{
    DataType data;
    Node<DataType> *next;
};

template <typename DataType>
class LinkStack{
    public:
        LinkStack();            // 构造函数,初始化一个空链栈
        ~LinkStack();           // 析构函数,释放链栈各结点的存储空间
        void Push(DataType x);  // 入栈操作,将元素x入栈
        DataType Pop();         // 出栈操作,将栈顶元素出栈
        DataType GetTop();      // 取栈顶元素(并不删除)
        bool Empty();           // 判空操作,判断链栈是否为空栈 
    private:
        Node<DataType> *top;    // 栈顶指针即链栈的头指针
};

template <typename DataType>
LinkStack<DataType>::LinkStack(){
    top = nullptr;
}

template <typename DataType>
LinkStack<DataType>::~LinkStack(){
    Node<DataType> *p = top;
    while (top != nullptr){
        top = top->next;
        delete p;
        p = top;
    }
}

template <typename DataType>
void LinkStack<DataType>::Push(DataType x){
    Node<DataType> *p = new Node<DataType>; p->data = x;
    p->next = top;
    top = p;
}

template <typename DataType>
DataType LinkStack<DataType>::Pop(){
    if (top == nullptr)
        throw "underflow";
    Node<DataType> *p = top;
    DataType x = p->data;
    top = top->next;
    delete p;

    return x;
}

template <typename DataType>
DataType LinkStack<DataType>::GetTop(){
    if (top == nullptr)
        throw "no element in stack";
    return top->data;
}

template <typename DataType>
bool LinkStack<DataType>::Empty(){
    return top==nullptr?true:false;
}

双栈共享空间

在一个程序中,如果同时使用具有相同数据类型的两个顺序栈,最直接的方法是为每个栈开辟一个数组空间,这样做的结果可能会出现一个栈的空间被占满而无法进行插入操作,同时另一个栈空间仍有大量剩余而没有得到利用的情况,从而造成存储空间的浪费。

可以充分利用顺序栈单向延展的特性,使得一个数组来存储两个栈,让一个栈的栈底位于该数组的始端,另一个栈的栈底位于该数组的末端,每个栈从各自的端点向中间延伸,其中,top1和top2分别为栈1和栈2的栈顶位置,StackSize为整个数组空间的大小,栈1的底位于下表为0的一端;栈2的底位于下标为StackSize-1的一端。

在双栈共享空间中,由于两栈相向增长,浪费的数组空间就会减少,同时发生上溢的概率也会减少。但是,只有当两个栈的空间需求有相反的关系时,这种方法才会奏效,也就是说,最好一个栈增加时另一个栈缩短。

双栈的实现

const int StackSize = 100;    // 根据具体问题定义
template <typename DataType>
class BothStack{
    public:
        BothStack();                    // 构造函数,将两个栈分别初始化
        ~BothStack(){}                  // 析构函数
        void Push(int i,DataType x);    // 入栈操作,将元素x压入栈i
        DataType Pop(int i);            // 出栈操作,对栈i执行出栈操作
        DataType GetTop(int i);         // 取栈i的栈顶元素
        bool Empty(int i);              // 判断栈i是否为空栈
    private:
        DataType data[StackSize];       // 存放两个栈的数组
        int top1,top2;                  // 两个栈的栈顶指针,分别为各自栈顶元素在数组中的下标
};

template <typename DataType>
BothStack<DataType>::BothStack(){
    top1 = -1;
    top2 = StackSize;
}

template <typename DataType>
void BothStack<DataType>::Push(int i,DataType x){
    if (top1 == top2-1)
        throw "overflow";
    if (i == 1)
        data[++top1] = x;
    if (i == 2)
        data[--top2] = x;
}

template <typename DataType>
DataType BothStack<DataType>::Pop(int i){
    if (i == 1){
        if (top1 == -1)
            throw "underflow";
        return data[top1--];
    }
    if (i == 2){
        if (top2 == StackSize)
            throw "underflow";
        return data[top2++];
    }
}

template <typename DataType>
DataType BothStack<DataType>::GetTop(int i){
    if (i == 1){
        if (top1 == -1)
            throw "stack 1 is empty";
        return data[top1];
    }
    if (i == 2){
        if (top2 == StackSize)
            throw "stack 2 is empty";
        return data[top2];
    }
}

template <typename DataType>
bool BothStack<DataType>::Empty(int i){
    if (i == 1)
        return top1==-1?true:false;
    if (i == 2)
        return top2==StackSize?true:false;
}

顺序栈和链栈的比较

顺序栈和链栈基本操作的时间复杂度均为O(1),因此唯一可以比较的是空间性能。初始时顺序栈必须确定一个固定的长度,所以有存储元素个数的限制和浪费空间的问题。而链栈没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。

作为一般规律,当栈的使用过程中元素个数变化较大时,应该采用链栈,反之,应该采用顺序栈。